11package controller
22
33import (
4+ "encoding/json"
45 "errors"
56 "fmt"
67 "net/http"
@@ -12,7 +13,6 @@ import (
1213
1314 "github.com/tinyauthapp/tinyauth/internal/model"
1415 "github.com/tinyauthapp/tinyauth/internal/service"
15- "github.com/tinyauthapp/tinyauth/internal/utils"
1616 "github.com/tinyauthapp/tinyauth/internal/utils/logger"
1717)
1818
@@ -169,7 +169,7 @@ func (controller *OIDCController) Authorize(c *gin.Context) {
169169 return
170170 }
171171
172- client , ok := controller .oidc .GetClient (req .ClientID )
172+ _ , ok := controller .oidc .GetClient (req .ClientID )
173173
174174 if ! ok {
175175 controller .authorizeError (c , authorizeErrorParams {
@@ -203,9 +203,8 @@ func (controller *OIDCController) Authorize(c *gin.Context) {
203203 return
204204 }
205205
206- // WARNING: Since Tinyauth is stateless, we cannot have a sub that never changes. We will just create a uuid out of the username and client name which remains stable, but if username or client name changes then sub changes too.
207- sub := utils .GenerateUUID (fmt .Sprintf ("%s:%s" , userContext .GetUsername (), client .ID ))
208- code := utils .GenerateString (32 )
206+ // Create the sub to find and delete old sessions
207+ sub := controller .oidc .CreateSub (* userContext , req .ClientID )
209208
210209 // Before storing the code, delete old session
211210 err = controller .oidc .DeleteOldSession (c , sub )
@@ -221,37 +220,8 @@ func (controller *OIDCController) Authorize(c *gin.Context) {
221220 return
222221 }
223222
224- err = controller .oidc .StoreCode (c , sub , code , req )
225-
226- if err != nil {
227- controller .authorizeError (c , authorizeErrorParams {
228- err : err ,
229- reason : "Failed to store code" ,
230- reasonPublic : "Failed to store code" ,
231- callback : req .RedirectURI ,
232- callbackError : "server_error" ,
233- state : req .State ,
234- })
235- return
236- }
237-
238- // We also need a snapshot of the user that authorized this (skip if no openid scope)
239- if slices .Contains (strings .Fields (req .Scope ), "openid" ) {
240- err = controller .oidc .StoreUserinfo (c , sub , * userContext , req )
241-
242- if err != nil {
243- controller .log .App .Error ().Err (err ).Msg ("Failed to store user info" )
244- controller .authorizeError (c , authorizeErrorParams {
245- err : err ,
246- reason : "Failed to store user info" ,
247- reasonPublic : "Failed to store user info" ,
248- callback : req .RedirectURI ,
249- callbackError : "server_error" ,
250- state : req .State ,
251- })
252- return
253- }
254- }
223+ // Create the authorization code
224+ code := controller .oidc .CreateCode (req , * userContext )
255225
256226 queries , err := query .Values (AuthorizeCallback {
257227 Code : code ,
@@ -354,39 +324,34 @@ func (controller *OIDCController) Token(c *gin.Context) {
354324
355325 switch req .GrantType {
356326 case "authorization_code" :
357- entry , err := controller .oidc .GetCodeEntry (c , controller .oidc .Hash (req .Code ), client .ClientID )
358- if err != nil {
359- if err := controller .oidc .DeleteTokenByCodeHash (c , controller .oidc .Hash (req .Code )); err != nil {
360- controller .log .App .Error ().Err (err ).Msg ("Failed to revoke tokens for replayed code" )
361- }
362- if errors .Is (err , service .ErrCodeNotFound ) {
363- controller .log .App .Warn ().Msg ("Code not found" )
364- c .JSON (400 , gin.H {
365- "error" : "invalid_grant" ,
366- })
367- return
368- }
369- if errors .Is (err , service .ErrCodeExpired ) {
370- controller .log .App .Warn ().Msg ("Code expired" )
327+ entry , ok := controller .oidc .GetCodeEntry (controller .oidc .Hash (req .Code ), client .ClientID )
328+
329+ 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+ err := controller .oidc .DeleteSessionBySub (c , usedCodeSub )
336+ if err != nil {
337+ controller .log .App .Error ().Err (err ).Msg ("Failed to delete session for reused code" )
338+ }
371339 c .JSON (400 , gin.H {
372340 "error" : "invalid_grant" ,
373341 })
374342 return
375343 }
376- if errors .Is (err , service .ErrInvalidClient ) {
377- controller .log .App .Warn ().Msg ("Code does not belong to client" )
378- c .JSON (400 , gin.H {
379- "error" : "invalid_client" ,
380- })
381- return
382- }
383- controller .log .App .Error ().Err (err ).Msg ("Failed to get code entry" )
344+
345+ controller .log .App .Warn ().Msg ("Code not found" )
384346 c .JSON (400 , gin.H {
385- "error" : "server_error " ,
347+ "error" : "invalid_grant " ,
386348 })
387349 return
388350 }
389351
352+ // mark code as used to prevent reuse
353+ controller .oidc .MarkCodeAsUsed (controller .oidc .Hash (req .Code ), entry .Userinfo .Sub )
354+
390355 if entry .RedirectURI != req .RedirectURI {
391356 controller .log .App .Warn ().Msg ("Redirect URI does not match" )
392357 c .JSON (400 , gin.H {
@@ -395,7 +360,7 @@ func (controller *OIDCController) Token(c *gin.Context) {
395360 return
396361 }
397362
398- ok : = controller .oidc .ValidatePKCE (entry .CodeChallenge , req .CodeVerifier )
363+ ok = controller .oidc .ValidatePKCE (entry .CodeChallenge , req .CodeVerifier )
399364
400365 if ! ok {
401366 controller .log .App .Warn ().Msg ("PKCE validation failed" )
@@ -405,7 +370,7 @@ func (controller *OIDCController) Token(c *gin.Context) {
405370 return
406371 }
407372
408- tokenRes , err := controller .oidc .GenerateAccessToken (c , client , entry )
373+ tokenRes , err := controller .oidc .GenerateAccessToken (c , client , * entry )
409374
410375 if err != nil {
411376 controller .log .App .Error ().Err (err ).Msg ("Failed to generate access token" )
@@ -415,7 +380,7 @@ func (controller *OIDCController) Token(c *gin.Context) {
415380 return
416381 }
417382
418- tokenResponse = tokenRes
383+ tokenResponse = * tokenRes
419384 case "refresh_token" :
420385 tokenRes , err := controller .oidc .RefreshAccessToken (c , req .RefreshToken , creds .ClientID )
421386
@@ -443,7 +408,7 @@ func (controller *OIDCController) Token(c *gin.Context) {
443408 return
444409 }
445410
446- tokenResponse = tokenRes
411+ tokenResponse = * tokenRes
447412 }
448413
449414 c .Header ("cache-control" , "no-store" )
@@ -507,7 +472,7 @@ func (controller *OIDCController) Userinfo(c *gin.Context) {
507472 return
508473 }
509474
510- entry , err := controller .oidc .GetAccessToken (c , controller .oidc .Hash (token ))
475+ entry , err := controller .oidc .GetSessionByToken (c , controller .oidc .Hash (token ))
511476
512477 if err != nil {
513478 if errors .Is (err , service .ErrTokenNotFound ) {
@@ -526,15 +491,17 @@ func (controller *OIDCController) Userinfo(c *gin.Context) {
526491 }
527492
528493 // If we don't have the openid scope, return an error
529- if ! slices .Contains (strings .Split (entry .Scope , ", " ), "openid" ) {
530- controller .log .App .Warn ().Msg ("OIDC userinfo accessed with token missing openid scope" )
494+ if ! slices .Contains (strings .Split (entry .Scope , " " ), "openid" ) {
495+ controller .log .App .Warn ().Msg ("OIDC userinfo accessed with missing openid scope" )
531496 c .JSON (401 , gin.H {
532497 "error" : "invalid_scope" ,
533498 })
534499 return
535500 }
536501
537- user , err := controller .oidc .GetUserinfo (c , entry .Sub )
502+ var userinfo service.UserinfoResponse
503+
504+ err = json .Unmarshal ([]byte (entry .UserinfoJson ), & userinfo )
538505
539506 if err != nil {
540507 controller .log .App .Error ().Err (err ).Msg ("Failed to get user info" )
@@ -544,7 +511,7 @@ func (controller *OIDCController) Userinfo(c *gin.Context) {
544511 return
545512 }
546513
547- c .JSON (200 , controller .oidc .CompileUserinfo (user , entry .Scope ))
514+ c .JSON (200 , controller .oidc .CompileUserinfo (userinfo , entry .Scope ))
548515}
549516
550517func (controller * OIDCController ) authorizeError (c * gin.Context , params authorizeErrorParams ) {
0 commit comments