fix: ensure no oidc code reuse

This commit is contained in:
Stavros
2026-06-01 12:22:49 +03:00
parent b5770ef305
commit 5caee887de
2 changed files with 46 additions and 2 deletions
+15
View File
@@ -327,6 +327,18 @@ func (controller *OIDCController) Token(c *gin.Context) {
entry, ok := controller.oidc.GetCodeEntry(controller.oidc.Hash(req.Code), client.ClientID)
if !ok {
// ensure no code reuse
usedCodeSub, ok := controller.oidc.IsCodeUsed(controller.oidc.Hash(req.Code))
if ok {
controller.log.App.Warn().Msg("Code reuse detected")
controller.oidc.DeleteSessionBySub(c, usedCodeSub)
c.JSON(400, gin.H{
"error": "invalid_grant",
})
return
}
controller.log.App.Warn().Msg("Code not found")
c.JSON(400, gin.H{
"error": "invalid_grant",
@@ -334,6 +346,9 @@ func (controller *OIDCController) Token(c *gin.Context) {
return
}
// mark code as used to prevent reuse
controller.oidc.MarkCodeAsUsed(controller.oidc.Hash(req.Code), entry.Userinfo.Sub)
if entry.RedirectURI != req.RedirectURI {
controller.log.App.Warn().Msg("Redirect URI does not match")
c.JSON(400, gin.H{
+31 -2
View File
@@ -126,6 +126,10 @@ type AuthorizeCodeEntry struct {
Userinfo UserinfoResponse
}
type UsedCodeEntry struct {
Sub string
}
type OIDCService struct {
log *logger.Logger
config model.Config
@@ -138,7 +142,8 @@ type OIDCService struct {
issuer string
caches struct {
code *CacheStore[AuthorizeCodeEntry]
code *CacheStore[AuthorizeCodeEntry]
usedCode *CacheStore[UsedCodeEntry]
}
}
@@ -305,7 +310,9 @@ func NewOIDCService(
// Create caches
codeCash := NewCacheStore[AuthorizeCodeEntry](256)
usedCode := NewCacheStore[UsedCodeEntry](256)
service.caches.code = codeCash
service.caches.usedCode = usedCode
// Start cache cleanup routine
dg.Go(func(ctx context.Context) {
@@ -316,6 +323,7 @@ func NewOIDCService(
select {
case <-ticker.C:
service.caches.code.Sweep()
service.caches.usedCode.Sweep()
case <-ctx.Done():
return
}
@@ -406,7 +414,7 @@ func (service *OIDCService) CreateCode(req AuthorizeRequest, userContext model.U
}
// Store the code in the cache
service.caches.code.Set(entry.CodeHash, entry, 10*time.Minute)
service.caches.code.Set(entry.CodeHash, entry, 1*time.Minute)
return code
}
@@ -817,3 +825,24 @@ func (service *OIDCService) hashAndEncodePKCE(codeVerifier string) string {
func (service *OIDCService) CreateSub(userContext model.UserContext, clientId string) string {
return utils.GenerateUUID(fmt.Sprintf("%s:%s", userContext.GetUsername(), clientId))
}
func (service *OIDCService) IsCodeUsed(codeHash string) (string, bool) {
entry, ok := service.caches.usedCode.Get(codeHash)
if !ok {
return "", false
}
return entry.Sub, true
}
func (service *OIDCService) MarkCodeAsUsed(codeHash string, sub string) {
entry := UsedCodeEntry{
Sub: sub,
}
service.caches.usedCode.Set(codeHash, entry, 2*time.Minute)
}
func (service *OIDCService) DeleteSessionBySub(ctx context.Context, sub string) error {
return service.queries.DeleteOIDCSessionBySub(ctx, sub)
}