@@ -1106,5 +1106,211 @@ describe("OAuth Authorization", () => {
11061106 // Should use the PRM's resource value, not the full requested URL
11071107 expect ( authUrl . searchParams . get ( "resource" ) ) . toBe ( "https://api.example.com/" ) ;
11081108 } ) ;
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+ } ) ;
11091315 } ) ;
11101316} ) ;
0 commit comments