Skip to content

Commit 5caee88

Browse files
committed
fix: ensure no oidc code reuse
1 parent b5770ef commit 5caee88

2 files changed

Lines changed: 46 additions & 2 deletions

File tree

internal/controller/oidc_controller.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -327,13 +327,28 @@ func (controller *OIDCController) Token(c *gin.Context) {
327327
entry, ok := controller.oidc.GetCodeEntry(controller.oidc.Hash(req.Code), client.ClientID)
328328

329329
if !ok {
330+
// ensure no code reuse
331+
usedCodeSub, ok := controller.oidc.IsCodeUsed(controller.oidc.Hash(req.Code))
332+
333+
if ok {
334+
controller.log.App.Warn().Msg("Code reuse detected")
335+
controller.oidc.DeleteSessionBySub(c, usedCodeSub)
336+
c.JSON(400, gin.H{
337+
"error": "invalid_grant",
338+
})
339+
return
340+
}
341+
330342
controller.log.App.Warn().Msg("Code not found")
331343
c.JSON(400, gin.H{
332344
"error": "invalid_grant",
333345
})
334346
return
335347
}
336348

349+
// mark code as used to prevent reuse
350+
controller.oidc.MarkCodeAsUsed(controller.oidc.Hash(req.Code), entry.Userinfo.Sub)
351+
337352
if entry.RedirectURI != req.RedirectURI {
338353
controller.log.App.Warn().Msg("Redirect URI does not match")
339354
c.JSON(400, gin.H{

internal/service/oidc_service.go

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,10 @@ type AuthorizeCodeEntry struct {
126126
Userinfo UserinfoResponse
127127
}
128128

129+
type UsedCodeEntry struct {
130+
Sub string
131+
}
132+
129133
type OIDCService struct {
130134
log *logger.Logger
131135
config model.Config
@@ -138,7 +142,8 @@ type OIDCService struct {
138142
issuer string
139143

140144
caches struct {
141-
code *CacheStore[AuthorizeCodeEntry]
145+
code *CacheStore[AuthorizeCodeEntry]
146+
usedCode *CacheStore[UsedCodeEntry]
142147
}
143148
}
144149

@@ -305,7 +310,9 @@ func NewOIDCService(
305310

306311
// Create caches
307312
codeCash := NewCacheStore[AuthorizeCodeEntry](256)
313+
usedCode := NewCacheStore[UsedCodeEntry](256)
308314
service.caches.code = codeCash
315+
service.caches.usedCode = usedCode
309316

310317
// Start cache cleanup routine
311318
dg.Go(func(ctx context.Context) {
@@ -316,6 +323,7 @@ func NewOIDCService(
316323
select {
317324
case <-ticker.C:
318325
service.caches.code.Sweep()
326+
service.caches.usedCode.Sweep()
319327
case <-ctx.Done():
320328
return
321329
}
@@ -406,7 +414,7 @@ func (service *OIDCService) CreateCode(req AuthorizeRequest, userContext model.U
406414
}
407415

408416
// Store the code in the cache
409-
service.caches.code.Set(entry.CodeHash, entry, 10*time.Minute)
417+
service.caches.code.Set(entry.CodeHash, entry, 1*time.Minute)
410418

411419
return code
412420
}
@@ -817,3 +825,24 @@ func (service *OIDCService) hashAndEncodePKCE(codeVerifier string) string {
817825
func (service *OIDCService) CreateSub(userContext model.UserContext, clientId string) string {
818826
return utils.GenerateUUID(fmt.Sprintf("%s:%s", userContext.GetUsername(), clientId))
819827
}
828+
829+
func (service *OIDCService) IsCodeUsed(codeHash string) (string, bool) {
830+
entry, ok := service.caches.usedCode.Get(codeHash)
831+
832+
if !ok {
833+
return "", false
834+
}
835+
836+
return entry.Sub, true
837+
}
838+
839+
func (service *OIDCService) MarkCodeAsUsed(codeHash string, sub string) {
840+
entry := UsedCodeEntry{
841+
Sub: sub,
842+
}
843+
service.caches.usedCode.Set(codeHash, entry, 2*time.Minute)
844+
}
845+
846+
func (service *OIDCService) DeleteSessionBySub(ctx context.Context, sub string) error {
847+
return service.queries.DeleteOIDCSessionBySub(ctx, sub)
848+
}

0 commit comments

Comments
 (0)