@@ -1106,5 +1106,211 @@ describe("OAuth Authorization", () => {
1106
1106
// Should use the PRM's resource value, not the full requested URL
1107
1107
expect ( authUrl . searchParams . get ( "resource" ) ) . toBe ( "https://api.example.com/" ) ;
1108
1108
} ) ;
1109
+
1110
+ describe ( "delegateAuthorization" , ( ) => {
1111
+ const validMetadata = {
1112
+ issuer : "https://auth.example.com" ,
1113
+ authorization_endpoint : "https://auth.example.com/authorize" ,
1114
+ token_endpoint : "https://auth.example.com/token" ,
1115
+ registration_endpoint : "https://auth.example.com/register" ,
1116
+ response_types_supported : [ "code" ] ,
1117
+ code_challenge_methods_supported : [ "S256" ] ,
1118
+ } ;
1119
+
1120
+ const validClientInfo = {
1121
+ client_id : "client123" ,
1122
+ client_secret : "secret123" ,
1123
+ redirect_uris : [ "http://localhost:3000/callback" ] ,
1124
+ client_name : "Test Client" ,
1125
+ } ;
1126
+
1127
+ const validTokens = {
1128
+ access_token : "access123" ,
1129
+ token_type : "Bearer" ,
1130
+ expires_in : 3600 ,
1131
+ refresh_token : "refresh123" ,
1132
+ } ;
1133
+
1134
+ // Setup shared mock function for all tests
1135
+ beforeEach ( ( ) => {
1136
+ // Reset mockFetch implementation
1137
+ mockFetch . mockReset ( ) ;
1138
+
1139
+ // Set up the mockFetch to respond to all necessary API calls
1140
+ mockFetch . mockImplementation ( ( url ) => {
1141
+ const urlString = url . toString ( ) ;
1142
+
1143
+ if ( urlString . includes ( "/.well-known/oauth-protected-resource" ) ) {
1144
+ return Promise . resolve ( {
1145
+ ok : false ,
1146
+ status : 404
1147
+ } ) ;
1148
+ } else if ( urlString . includes ( "/.well-known/oauth-authorization-server" ) ) {
1149
+ return Promise . resolve ( {
1150
+ ok : true ,
1151
+ status : 200 ,
1152
+ json : async ( ) => validMetadata
1153
+ } ) ;
1154
+ } else if ( urlString . includes ( "/token" ) ) {
1155
+ return Promise . resolve ( {
1156
+ ok : true ,
1157
+ status : 200 ,
1158
+ json : async ( ) => validTokens
1159
+ } ) ;
1160
+ }
1161
+
1162
+ return Promise . reject ( new Error ( `Unexpected fetch call: ${ urlString } ` ) ) ;
1163
+ } ) ;
1164
+ } ) ;
1165
+
1166
+ it ( "should use delegateAuthorization when implemented and return AUTHORIZED" , async ( ) => {
1167
+ const mockProvider : OAuthClientProvider = {
1168
+ redirectUrl : "http://localhost:3000/callback" ,
1169
+ clientMetadata : {
1170
+ redirect_uris : [ "http://localhost:3000/callback" ] ,
1171
+ client_name : "Test Client"
1172
+ } ,
1173
+ clientInformation : ( ) => validClientInfo ,
1174
+ tokens : ( ) => validTokens ,
1175
+ saveTokens : jest . fn ( ) ,
1176
+ redirectToAuthorization : jest . fn ( ) ,
1177
+ saveCodeVerifier : jest . fn ( ) ,
1178
+ codeVerifier : ( ) => "test_verifier" ,
1179
+ delegateAuthorization : jest . fn ( ) . mockResolvedValue ( "AUTHORIZED" )
1180
+ } ;
1181
+
1182
+ const result = await auth ( mockProvider , { serverUrl : "https://auth.example.com" } ) ;
1183
+
1184
+ expect ( result ) . toBe ( "AUTHORIZED" ) ;
1185
+ expect ( mockProvider . delegateAuthorization ) . toHaveBeenCalledWith (
1186
+ "https://auth.example.com" ,
1187
+ {
1188
+ metadata : expect . objectContaining ( validMetadata ) ,
1189
+ resource : expect . any ( URL )
1190
+ }
1191
+ ) ;
1192
+ expect ( mockProvider . redirectToAuthorization ) . not . toHaveBeenCalled ( ) ;
1193
+ } ) ;
1194
+
1195
+ it ( "should fall back to standard flow when delegateAuthorization returns undefined" , async ( ) => {
1196
+ const mockProvider : OAuthClientProvider = {
1197
+ redirectUrl : "http://localhost:3000/callback" ,
1198
+ clientMetadata : {
1199
+ redirect_uris : [ "http://localhost:3000/callback" ] ,
1200
+ client_name : "Test Client"
1201
+ } ,
1202
+ clientInformation : ( ) => validClientInfo ,
1203
+ tokens : ( ) => validTokens ,
1204
+ saveTokens : jest . fn ( ) ,
1205
+ redirectToAuthorization : jest . fn ( ) ,
1206
+ saveCodeVerifier : jest . fn ( ) ,
1207
+ codeVerifier : ( ) => "test_verifier" ,
1208
+ delegateAuthorization : jest . fn ( ) . mockResolvedValue ( undefined )
1209
+ } ;
1210
+
1211
+ const result = await auth ( mockProvider , { serverUrl : "https://auth.example.com" } ) ;
1212
+
1213
+ expect ( result ) . toBe ( "AUTHORIZED" ) ;
1214
+ expect ( mockProvider . delegateAuthorization ) . toHaveBeenCalled ( ) ;
1215
+ expect ( mockProvider . saveTokens ) . toHaveBeenCalled ( ) ;
1216
+ } ) ;
1217
+
1218
+ it ( "should not call delegateAuthorization when processing authorizationCode" , async ( ) => {
1219
+ const mockProvider : OAuthClientProvider = {
1220
+ redirectUrl : "http://localhost:3000/callback" ,
1221
+ clientMetadata : {
1222
+ redirect_uris : [ "http://localhost:3000/callback" ] ,
1223
+ client_name : "Test Client"
1224
+ } ,
1225
+ clientInformation : ( ) => validClientInfo ,
1226
+ tokens : jest . fn ( ) ,
1227
+ saveTokens : jest . fn ( ) ,
1228
+ redirectToAuthorization : jest . fn ( ) ,
1229
+ saveCodeVerifier : jest . fn ( ) ,
1230
+ codeVerifier : ( ) => "test_verifier" ,
1231
+ delegateAuthorization : jest . fn ( )
1232
+ } ;
1233
+
1234
+ await auth ( mockProvider , {
1235
+ serverUrl : "https://auth.example.com" ,
1236
+ authorizationCode : "code123"
1237
+ } ) ;
1238
+
1239
+ expect ( mockProvider . delegateAuthorization ) . not . toHaveBeenCalled ( ) ;
1240
+ expect ( mockProvider . saveTokens ) . toHaveBeenCalled ( ) ;
1241
+ } ) ;
1242
+
1243
+ it ( "should propagate errors from delegateAuthorization" , async ( ) => {
1244
+ const mockProvider : OAuthClientProvider = {
1245
+ redirectUrl : "http://localhost:3000/callback" ,
1246
+ clientMetadata : {
1247
+ redirect_uris : [ "http://localhost:3000/callback" ] ,
1248
+ client_name : "Test Client"
1249
+ } ,
1250
+ clientInformation : ( ) => validClientInfo ,
1251
+ tokens : jest . fn ( ) ,
1252
+ saveTokens : jest . fn ( ) ,
1253
+ redirectToAuthorization : jest . fn ( ) ,
1254
+ saveCodeVerifier : jest . fn ( ) ,
1255
+ codeVerifier : ( ) => "test_verifier" ,
1256
+ delegateAuthorization : jest . fn ( ) . mockRejectedValue ( new Error ( "Delegation failed" ) )
1257
+ } ;
1258
+
1259
+ await expect ( auth ( mockProvider , { serverUrl : "https://auth.example.com" } ) )
1260
+ . rejects . toThrow ( "Delegation failed" ) ;
1261
+ } ) ;
1262
+
1263
+ it ( "should pass both resource and metadata to delegateAuthorization when available" , async ( ) => {
1264
+ // Mock resource metadata to be returned by the fetch
1265
+ mockFetch . mockImplementation ( ( url ) => {
1266
+ const urlString = url . toString ( ) ;
1267
+
1268
+ if ( urlString . includes ( "/.well-known/oauth-protected-resource" ) ) {
1269
+ return Promise . resolve ( {
1270
+ ok : true ,
1271
+ status : 200 ,
1272
+ json : async ( ) => ( {
1273
+ resource : "https://api.example.com/" ,
1274
+ authorization_servers : [ "https://auth.example.com" ]
1275
+ } )
1276
+ } ) ;
1277
+ } else if ( urlString . includes ( "/.well-known/oauth-authorization-server" ) ) {
1278
+ return Promise . resolve ( {
1279
+ ok : true ,
1280
+ status : 200 ,
1281
+ json : async ( ) => validMetadata
1282
+ } ) ;
1283
+ }
1284
+
1285
+ return Promise . reject ( new Error ( `Unexpected fetch call: ${ urlString } ` ) ) ;
1286
+ } ) ;
1287
+
1288
+ const mockProvider : OAuthClientProvider = {
1289
+ redirectUrl : "http://localhost:3000/callback" ,
1290
+ clientMetadata : {
1291
+ redirect_uris : [ "http://localhost:3000/callback" ] ,
1292
+ client_name : "Test Client"
1293
+ } ,
1294
+ clientInformation : ( ) => validClientInfo ,
1295
+ tokens : jest . fn ( ) ,
1296
+ saveTokens : jest . fn ( ) ,
1297
+ redirectToAuthorization : jest . fn ( ) ,
1298
+ saveCodeVerifier : jest . fn ( ) ,
1299
+ codeVerifier : ( ) => "test_verifier" ,
1300
+ delegateAuthorization : jest . fn ( ) . mockResolvedValue ( "AUTHORIZED" )
1301
+ } ;
1302
+
1303
+ const result = await auth ( mockProvider , { serverUrl : "https://api.example.com" } ) ;
1304
+
1305
+ expect ( result ) . toBe ( "AUTHORIZED" ) ;
1306
+ expect ( mockProvider . delegateAuthorization ) . toHaveBeenCalledWith (
1307
+ "https://auth.example.com" ,
1308
+ {
1309
+ resource : new URL ( "https://api.example.com/" ) ,
1310
+ metadata : expect . objectContaining ( validMetadata )
1311
+ }
1312
+ ) ;
1313
+ } ) ;
1314
+ } ) ;
1109
1315
} ) ;
1110
1316
} ) ;
0 commit comments