@@ -6,15 +6,12 @@ import (
66 "net/http/httptest"
77 "net/url"
88 "testing"
9- "time"
109
1110 "github.com/gofrs/uuid"
12- jwt "github.com/golang-jwt/jwt/v5"
1311 "github.com/stretchr/testify/require"
1412 "github.com/stretchr/testify/suite"
1513 "github.com/supabase/auth/internal/conf"
1614 "github.com/supabase/auth/internal/models"
17- "github.com/supabase/auth/internal/tokens"
1815)
1916
2017type ExternalTestSuite struct {
@@ -352,145 +349,62 @@ func setupGenericOAuthServer(ts *ExternalTestSuite, code string) *httptest.Serve
352349 return server
353350}
354351
355- // TestOAuthState_BackwardCompatibleJWT tests that the callback endpoint
356- // still accepts the legacy JWT state format for backward compatibility during migration.
357- func (ts * ExternalTestSuite ) TestOAuthState_BackwardCompatibleJWT () {
352+ // TestOAuthState_UUIDFormat tests that the callback endpoint processes UUID state correctly.
353+ func (ts * ExternalTestSuite ) TestOAuthState_UUIDFormat () {
358354 code := "authcode"
359355 server := setupGenericOAuthServer (ts , code )
360356 defer server .Close ()
361357
362- // Create a legacy JWT state token manually
363- claims := & ExternalProviderClaims {
364- AuthMicroserviceClaims : AuthMicroserviceClaims {
365- RegisteredClaims : jwt.RegisteredClaims {
366- ExpiresAt : jwt .NewNumericDate (time .Now ().Add (5 * time .Minute )),
367- IssuedAt : jwt .NewNumericDate (time .Now ()),
368- Issuer : ts .Config .JWT .Issuer ,
369- },
370- },
371- Provider : "github" ,
372- Referrer : "https://example.com/admin" ,
373- EmailOptional : false ,
374- }
358+ // Use the standard authorization flow which generates UUID state
359+ w := performAuthorizationRequest (ts , "github" , "" )
360+ ts .Require ().Equal (http .StatusFound , w .Code )
361+ u , err := url .Parse (w .Header ().Get ("Location" ))
362+ ts .Require ().NoError (err )
375363
376- jwtState , err := tokens .SignJWT (& ts .Config .JWT , claims )
377- require .NoError (ts .T (), err )
378- require .NotEmpty (ts .T (), jwtState )
364+ state := u .Query ().Get ("state" )
365+ ts .Require ().NotEmpty (state )
366+
367+ stateUUID , err := uuid .FromString (state )
368+ require .NoError (ts .T (), err , "state should be a valid UUID" )
369+ require .NotEqual (ts .T (), uuid .Nil , stateUUID )
379370
380371 testURL , err := url .Parse ("http://localhost/callback" )
381372 require .NoError (ts .T (), err )
382373 v := testURL .Query ()
383374 v .Set ("code" , code )
384- v .Set ("state" , jwtState )
375+ v .Set ("state" , state )
385376 testURL .RawQuery = v .Encode ()
386377
387378 req := httptest .NewRequest (http .MethodGet , testURL .String (), nil )
388- w : = httptest .NewRecorder ()
379+ w = httptest .NewRecorder ()
389380 ts .API .handler .ServeHTTP (w , req )
390381
391382 ts .Require ().Equal (http .StatusFound , w .Code )
392- u , err := url .Parse (w .Header ().Get ("Location" ))
393- ts .Require ().NoError (err , "redirect url parse failed" )
394- ts .Require ().Equal ("/admin" , u .Path )
395-
396- fragment , err := url .ParseQuery (u .Fragment )
383+ resultURL , err := url .Parse (w .Header ().Get ("Location" ))
397384 ts .Require ().NoError (err )
398- ts .NotEmpty (fragment .Get ("access_token" ), "should have access_token" )
399- ts .NotEmpty (fragment .Get ("refresh_token" ), "should have refresh_token" )
400385
401- user , err := models . FindUserByEmailAndAudience ( ts . API . db , "test@example.com" , ts . Config . JWT . Aud )
402- require . NoError ( ts .T (), err )
403- require . NotNil ( ts . T ( ), user )
386+ fragment , err := url . ParseQuery ( resultURL . Fragment )
387+ ts .Require (). NoError ( err )
388+ ts . NotEmpty ( fragment . Get ( "access_token" ), "UUID state should result in access_token" )
404389}
405390
406- // TestOAuthState_MigrationScenario tests that both UUID and JWT state formats
407- // can be processed during the migration period.
408- func (ts * ExternalTestSuite ) TestOAuthState_MigrationScenario () {
391+ // TestOAuthState_InvalidFormat tests that non-UUID state parameters are rejected.
392+ func (ts * ExternalTestSuite ) TestOAuthState_InvalidFormat () {
409393 code := "authcode"
410394 server := setupGenericOAuthServer (ts , code )
411395 defer server .Close ()
412396
413- ts .Run ("NewUUIDFormat" , func () {
414- // Use the standard authorization flow which now generates UUID state
415- w := performAuthorizationRequest (ts , "github" , "" )
416- ts .Require ().Equal (http .StatusFound , w .Code )
417- u , err := url .Parse (w .Header ().Get ("Location" ))
418- ts .Require ().NoError (err )
419-
420- state := u .Query ().Get ("state" )
421- ts .Require ().NotEmpty (state )
422-
423- // Verify state is a valid UUID
424- stateUUID , err := uuid .FromString (state )
425- require .NoError (ts .T (), err , "state should be a valid UUID" )
426- require .NotEqual (ts .T (), uuid .Nil , stateUUID )
427-
428- // Complete the callback
429- testURL , err := url .Parse ("http://localhost/callback" )
430- require .NoError (ts .T (), err )
431- v := testURL .Query ()
432- v .Set ("code" , code )
433- v .Set ("state" , state )
434- testURL .RawQuery = v .Encode ()
435-
436- req := httptest .NewRequest (http .MethodGet , testURL .String (), nil )
437- w = httptest .NewRecorder ()
438- ts .API .handler .ServeHTTP (w , req )
439-
440- ts .Require ().Equal (http .StatusFound , w .Code )
441- resultURL , err := url .Parse (w .Header ().Get ("Location" ))
442- ts .Require ().NoError (err )
443-
444- fragment , err := url .ParseQuery (resultURL .Fragment )
445- ts .Require ().NoError (err )
446- ts .NotEmpty (fragment .Get ("access_token" ), "UUID state should result in access_token" )
447- })
448-
449- // Clean up user for next test
450- user , _ := models .FindUserByEmailAndAudience (ts .API .db , "test@example.com" , ts .Config .JWT .Aud )
451- if user != nil {
452- require .NoError (ts .T (), ts .API .db .Destroy (user ))
453- }
454-
455- ts .Run ("LegacyJWTFormat" , func () {
456- // Create a legacy JWT state
457- claims := & ExternalProviderClaims {
458- AuthMicroserviceClaims : AuthMicroserviceClaims {
459- RegisteredClaims : jwt.RegisteredClaims {
460- ExpiresAt : jwt .NewNumericDate (time .Now ().Add (5 * time .Minute )),
461- IssuedAt : jwt .NewNumericDate (time .Now ()),
462- Issuer : ts .Config .JWT .Issuer ,
463- },
464- },
465- Provider : "github" ,
466- Referrer : "https://example.com/admin" ,
467- }
468-
469- jwtState , err := tokens .SignJWT (& ts .Config .JWT , claims )
470- require .NoError (ts .T (), err )
471-
472- // Verify state is NOT a UUID (it's a JWT)
473- _ , uuidErr := uuid .FromString (jwtState )
474- require .Error (ts .T (), uuidErr , "JWT state should not be parseable as UUID" )
475-
476- // Complete the callback with JWT state
477- testURL , err := url .Parse ("http://localhost/callback" )
478- require .NoError (ts .T (), err )
479- v := testURL .Query ()
480- v .Set ("code" , code )
481- v .Set ("state" , jwtState )
482- testURL .RawQuery = v .Encode ()
483-
484- req := httptest .NewRequest (http .MethodGet , testURL .String (), nil )
485- w := httptest .NewRecorder ()
486- ts .API .handler .ServeHTTP (w , req )
397+ testURL , err := url .Parse ("http://localhost/callback" )
398+ require .NoError (ts .T (), err )
399+ v := testURL .Query ()
400+ v .Set ("code" , code )
401+ v .Set ("state" , "not-a-valid-uuid" )
402+ testURL .RawQuery = v .Encode ()
487403
488- ts . Require (). Equal ( http .StatusFound , w . Code )
489- resultURL , err := url . Parse ( w . Header (). Get ( "Location" ) )
490- ts .Require (). NoError ( err )
404+ req := httptest . NewRequest ( http .MethodGet , testURL . String (), nil )
405+ w := httptest . NewRecorder ( )
406+ ts .API . handler . ServeHTTP ( w , req )
491407
492- fragment , err := url .ParseQuery (resultURL .Fragment )
493- ts .Require ().NoError (err )
494- ts .NotEmpty (fragment .Get ("access_token" ), "JWT state should also result in access_token" )
495- })
408+ // Should redirect to site URL with error since state is invalid
409+ ts .Require ().Equal (http .StatusSeeOther , w .Code )
496410}
0 commit comments