@@ -1244,5 +1244,211 @@ describe("OAuth Authorization", () => {
1244
1244
// Should use the PRM's resource value, not the full requested URL
1245
1245
expect ( authUrl . searchParams . get ( "resource" ) ) . toBe ( "https://api.example.com/" ) ;
1246
1246
} ) ;
1247
+
1248
+ describe ( "delegateAuthorization" , ( ) => {
1249
+ const validMetadata = {
1250
+ issuer : "https://auth.example.com" ,
1251
+ authorization_endpoint : "https://auth.example.com/authorize" ,
1252
+ token_endpoint : "https://auth.example.com/token" ,
1253
+ registration_endpoint : "https://auth.example.com/register" ,
1254
+ response_types_supported : [ "code" ] ,
1255
+ code_challenge_methods_supported : [ "S256" ] ,
1256
+ } ;
1257
+
1258
+ const validClientInfo = {
1259
+ client_id : "client123" ,
1260
+ client_secret : "secret123" ,
1261
+ redirect_uris : [ "http://localhost:3000/callback" ] ,
1262
+ client_name : "Test Client" ,
1263
+ } ;
1264
+
1265
+ const validTokens = {
1266
+ access_token : "access123" ,
1267
+ token_type : "Bearer" ,
1268
+ expires_in : 3600 ,
1269
+ refresh_token : "refresh123" ,
1270
+ } ;
1271
+
1272
+ // Setup shared mock function for all tests
1273
+ beforeEach ( ( ) => {
1274
+ // Reset mockFetch implementation
1275
+ mockFetch . mockReset ( ) ;
1276
+
1277
+ // Set up the mockFetch to respond to all necessary API calls
1278
+ mockFetch . mockImplementation ( ( url ) => {
1279
+ const urlString = url . toString ( ) ;
1280
+
1281
+ if ( urlString . includes ( "/.well-known/oauth-protected-resource" ) ) {
1282
+ return Promise . resolve ( {
1283
+ ok : false ,
1284
+ status : 404
1285
+ } ) ;
1286
+ } else if ( urlString . includes ( "/.well-known/oauth-authorization-server" ) ) {
1287
+ return Promise . resolve ( {
1288
+ ok : true ,
1289
+ status : 200 ,
1290
+ json : async ( ) => validMetadata
1291
+ } ) ;
1292
+ } else if ( urlString . includes ( "/token" ) ) {
1293
+ return Promise . resolve ( {
1294
+ ok : true ,
1295
+ status : 200 ,
1296
+ json : async ( ) => validTokens
1297
+ } ) ;
1298
+ }
1299
+
1300
+ return Promise . reject ( new Error ( `Unexpected fetch call: ${ urlString } ` ) ) ;
1301
+ } ) ;
1302
+ } ) ;
1303
+
1304
+ it ( "should use delegateAuthorization when implemented and return AUTHORIZED" , async ( ) => {
1305
+ const mockProvider : OAuthClientProvider = {
1306
+ redirectUrl : "http://localhost:3000/callback" ,
1307
+ clientMetadata : {
1308
+ redirect_uris : [ "http://localhost:3000/callback" ] ,
1309
+ client_name : "Test Client"
1310
+ } ,
1311
+ clientInformation : ( ) => validClientInfo ,
1312
+ tokens : ( ) => validTokens ,
1313
+ saveTokens : jest . fn ( ) ,
1314
+ redirectToAuthorization : jest . fn ( ) ,
1315
+ saveCodeVerifier : jest . fn ( ) ,
1316
+ codeVerifier : ( ) => "test_verifier" ,
1317
+ delegateAuthorization : jest . fn ( ) . mockResolvedValue ( "AUTHORIZED" )
1318
+ } ;
1319
+
1320
+ const result = await auth ( mockProvider , { serverUrl : "https://auth.example.com" } ) ;
1321
+
1322
+ expect ( result ) . toBe ( "AUTHORIZED" ) ;
1323
+ expect ( mockProvider . delegateAuthorization ) . toHaveBeenCalledWith (
1324
+ "https://auth.example.com" ,
1325
+ {
1326
+ metadata : expect . objectContaining ( validMetadata ) ,
1327
+ resource : expect . any ( URL )
1328
+ }
1329
+ ) ;
1330
+ expect ( mockProvider . redirectToAuthorization ) . not . toHaveBeenCalled ( ) ;
1331
+ } ) ;
1332
+
1333
+ it ( "should fall back to standard flow when delegateAuthorization returns undefined" , async ( ) => {
1334
+ const mockProvider : OAuthClientProvider = {
1335
+ redirectUrl : "http://localhost:3000/callback" ,
1336
+ clientMetadata : {
1337
+ redirect_uris : [ "http://localhost:3000/callback" ] ,
1338
+ client_name : "Test Client"
1339
+ } ,
1340
+ clientInformation : ( ) => validClientInfo ,
1341
+ tokens : ( ) => validTokens ,
1342
+ saveTokens : jest . fn ( ) ,
1343
+ redirectToAuthorization : jest . fn ( ) ,
1344
+ saveCodeVerifier : jest . fn ( ) ,
1345
+ codeVerifier : ( ) => "test_verifier" ,
1346
+ delegateAuthorization : jest . fn ( ) . mockResolvedValue ( undefined )
1347
+ } ;
1348
+
1349
+ const result = await auth ( mockProvider , { serverUrl : "https://auth.example.com" } ) ;
1350
+
1351
+ expect ( result ) . toBe ( "AUTHORIZED" ) ;
1352
+ expect ( mockProvider . delegateAuthorization ) . toHaveBeenCalled ( ) ;
1353
+ expect ( mockProvider . saveTokens ) . toHaveBeenCalled ( ) ;
1354
+ } ) ;
1355
+
1356
+ it ( "should not call delegateAuthorization when processing authorizationCode" , async ( ) => {
1357
+ const mockProvider : OAuthClientProvider = {
1358
+ redirectUrl : "http://localhost:3000/callback" ,
1359
+ clientMetadata : {
1360
+ redirect_uris : [ "http://localhost:3000/callback" ] ,
1361
+ client_name : "Test Client"
1362
+ } ,
1363
+ clientInformation : ( ) => validClientInfo ,
1364
+ tokens : jest . fn ( ) ,
1365
+ saveTokens : jest . fn ( ) ,
1366
+ redirectToAuthorization : jest . fn ( ) ,
1367
+ saveCodeVerifier : jest . fn ( ) ,
1368
+ codeVerifier : ( ) => "test_verifier" ,
1369
+ delegateAuthorization : jest . fn ( )
1370
+ } ;
1371
+
1372
+ await auth ( mockProvider , {
1373
+ serverUrl : "https://auth.example.com" ,
1374
+ authorizationCode : "code123"
1375
+ } ) ;
1376
+
1377
+ expect ( mockProvider . delegateAuthorization ) . not . toHaveBeenCalled ( ) ;
1378
+ expect ( mockProvider . saveTokens ) . toHaveBeenCalled ( ) ;
1379
+ } ) ;
1380
+
1381
+ it ( "should propagate errors from delegateAuthorization" , async ( ) => {
1382
+ const mockProvider : OAuthClientProvider = {
1383
+ redirectUrl : "http://localhost:3000/callback" ,
1384
+ clientMetadata : {
1385
+ redirect_uris : [ "http://localhost:3000/callback" ] ,
1386
+ client_name : "Test Client"
1387
+ } ,
1388
+ clientInformation : ( ) => validClientInfo ,
1389
+ tokens : jest . fn ( ) ,
1390
+ saveTokens : jest . fn ( ) ,
1391
+ redirectToAuthorization : jest . fn ( ) ,
1392
+ saveCodeVerifier : jest . fn ( ) ,
1393
+ codeVerifier : ( ) => "test_verifier" ,
1394
+ delegateAuthorization : jest . fn ( ) . mockRejectedValue ( new Error ( "Delegation failed" ) )
1395
+ } ;
1396
+
1397
+ await expect ( auth ( mockProvider , { serverUrl : "https://auth.example.com" } ) )
1398
+ . rejects . toThrow ( "Delegation failed" ) ;
1399
+ } ) ;
1400
+
1401
+ it ( "should pass both resource and metadata to delegateAuthorization when available" , async ( ) => {
1402
+ // Mock resource metadata to be returned by the fetch
1403
+ mockFetch . mockImplementation ( ( url ) => {
1404
+ const urlString = url . toString ( ) ;
1405
+
1406
+ if ( urlString . includes ( "/.well-known/oauth-protected-resource" ) ) {
1407
+ return Promise . resolve ( {
1408
+ ok : true ,
1409
+ status : 200 ,
1410
+ json : async ( ) => ( {
1411
+ resource : "https://api.example.com/" ,
1412
+ authorization_servers : [ "https://auth.example.com" ]
1413
+ } )
1414
+ } ) ;
1415
+ } else if ( urlString . includes ( "/.well-known/oauth-authorization-server" ) ) {
1416
+ return Promise . resolve ( {
1417
+ ok : true ,
1418
+ status : 200 ,
1419
+ json : async ( ) => validMetadata
1420
+ } ) ;
1421
+ }
1422
+
1423
+ return Promise . reject ( new Error ( `Unexpected fetch call: ${ urlString } ` ) ) ;
1424
+ } ) ;
1425
+
1426
+ const mockProvider : OAuthClientProvider = {
1427
+ redirectUrl : "http://localhost:3000/callback" ,
1428
+ clientMetadata : {
1429
+ redirect_uris : [ "http://localhost:3000/callback" ] ,
1430
+ client_name : "Test Client"
1431
+ } ,
1432
+ clientInformation : ( ) => validClientInfo ,
1433
+ tokens : jest . fn ( ) ,
1434
+ saveTokens : jest . fn ( ) ,
1435
+ redirectToAuthorization : jest . fn ( ) ,
1436
+ saveCodeVerifier : jest . fn ( ) ,
1437
+ codeVerifier : ( ) => "test_verifier" ,
1438
+ delegateAuthorization : jest . fn ( ) . mockResolvedValue ( "AUTHORIZED" )
1439
+ } ;
1440
+
1441
+ const result = await auth ( mockProvider , { serverUrl : "https://api.example.com" } ) ;
1442
+
1443
+ expect ( result ) . toBe ( "AUTHORIZED" ) ;
1444
+ expect ( mockProvider . delegateAuthorization ) . toHaveBeenCalledWith (
1445
+ "https://auth.example.com" ,
1446
+ {
1447
+ resource : new URL ( "https://api.example.com/" ) ,
1448
+ metadata : expect . objectContaining ( validMetadata )
1449
+ }
1450
+ ) ;
1451
+ } ) ;
1452
+ } ) ;
1247
1453
} ) ;
1248
1454
} ) ;
0 commit comments