@@ -971,6 +971,90 @@ func TestE2EHooks(t *testing.T) {
971971 }
972972 })
973973 }
974+
975+ t .Run ("AMRStringArrayUnmarshalling" , func (t * testing.T ) {
976+ defer inst .HookRecorder .CustomizeAccessToken .ClearCalls ()
977+
978+ // Setup hook that returns amr as array of strings
979+ var claimsIn M
980+ hr := http .HandlerFunc (func (w http.ResponseWriter , r * http.Request ) {
981+ w .Header ().Add ("content-type" , "application/json" )
982+ w .WriteHeader (http .StatusOK )
983+
984+ err := json .NewDecoder (r .Body ).Decode (& claimsIn )
985+ require .NoError (t , err )
986+
987+ // Modify amr to be array of strings instead of objects
988+ claimsOut := copyMap (t , claimsIn )
989+ claimsOut ["claims" ].(M )["amr" ] = []string {"password" , "totp" }
990+
991+ err = json .NewEncoder (w ).Encode (claimsOut )
992+ require .NoError (t , err )
993+ })
994+
995+ inst .HookRecorder .CustomizeAccessToken .ClearCalls ()
996+ inst .HookRecorder .CustomizeAccessToken .SetHandler (hr )
997+
998+ // Get token with modified amr
999+ req := & api.PasswordGrantParams {
1000+ Email : string (currentUser .Email ),
1001+ Password : defaultPassword ,
1002+ }
1003+
1004+ res := new (api.AccessTokenResponse )
1005+ err := e2eapi .Do (ctx , http .MethodPost , inst .APIServer .URL + "/token?grant_type=password" , req , res )
1006+ require .NoError (t , err )
1007+ require .True (t , len (res .Token ) > 0 )
1008+
1009+ // Verify hook was called
1010+ {
1011+ calls := inst .HookRecorder .CustomizeAccessToken .GetCalls ()
1012+ require .Equal (t , 1 , len (calls ))
1013+ }
1014+
1015+ // Parse token to verify it can be unmarshalled
1016+ p := jwt .NewParser (jwt .WithValidMethods (globalCfg .JWT .ValidMethods ))
1017+ token , err := p .ParseWithClaims (
1018+ res .Token ,
1019+ & api.AccessTokenClaims {},
1020+ func (token * jwt.Token ) (any , error ) {
1021+ if kid , ok := token .Header ["kid" ]; ok {
1022+ if kidStr , ok := kid .(string ); ok {
1023+ return conf .FindPublicKeyByKid (kidStr , & globalCfg .JWT )
1024+ }
1025+ }
1026+ if alg , ok := token .Header ["alg" ]; ok {
1027+ if alg == jwt .SigningMethodHS256 .Name {
1028+ return []byte (globalCfg .JWT .Secret ), nil
1029+ }
1030+ }
1031+ return nil , fmt .Errorf ("missing kid" )
1032+ })
1033+ require .NoError (t , err , "Token should parse successfully even with string array amr" )
1034+
1035+ fmt .Println ("token hereee" , res .Token )
1036+ // Verify claims were unmarshalled correctly
1037+ claims , ok := token .Claims .(* api.AccessTokenClaims )
1038+ require .True (t , ok , "Claims should be AccessTokenClaims type" )
1039+ require .NotNil (t , claims .AuthenticationMethodReference , "AMR should not be nil" )
1040+ require .Len (t , claims .AuthenticationMethodReference , 2 , "AMR should have 2 entries" )
1041+ require .Equal (t , "password" , claims .AuthenticationMethodReference [0 ].Method )
1042+ require .Equal (t , "totp" , claims .AuthenticationMethodReference [1 ].Method )
1043+
1044+ // Call /user endpoint with the token to verify it works end-to-end
1045+ httpReq , err := http .NewRequestWithContext (ctx , http .MethodGet , "/user" , nil )
1046+ require .NoError (t , err )
1047+
1048+ httpRes , err := inst .DoAuth (httpReq , res .Token )
1049+ require .NoError (t , err , "Should be able to call /user endpoint with token containing string array amr" )
1050+ require .Equal (t , http .StatusOK , httpRes .StatusCode , "/user endpoint should return 200 OK" )
1051+
1052+ // Verify we got user data back
1053+ var userData models.User
1054+ err = json .NewDecoder (httpRes .Body ).Decode (& userData )
1055+ require .NoError (t , err , "Should be able to decode user response" )
1056+ require .Equal (t , currentUser .ID , userData .ID , "Should get the correct user" )
1057+ })
9741058 })
9751059
9761060 t .Run ("SendEmail" , func (t * testing.T ) {
0 commit comments