@@ -1210,7 +1210,9 @@ describe('OAuthProvider', () => {
1210
1210
expect ( data . success ) . toBe ( true ) ;
1211
1211
expect ( data . user ) . toEqual ( { userId : 'test-user-123' , username : 'TestUser' } ) ;
1212
1212
} ) ;
1213
+ } ) ;
1213
1214
1215
+ describe ( 'CORS Support' , ( ) => {
1214
1216
it ( 'should handle CORS preflight for API requests' , async ( ) => {
1215
1217
const preflightRequest = createMockRequest ( 'https://example.com/api/test' , 'OPTIONS' , {
1216
1218
Origin : 'https://client.example.com' ,
@@ -1225,6 +1227,215 @@ describe('OAuthProvider', () => {
1225
1227
expect ( preflightResponse . headers . get ( 'Access-Control-Allow-Methods' ) ) . toBe ( '*' ) ;
1226
1228
expect ( preflightResponse . headers . get ( 'Access-Control-Allow-Headers' ) ) . toContain ( 'Authorization' ) ;
1227
1229
} ) ;
1230
+
1231
+ it ( 'should add CORS headers to OAuth metadata discovery endpoint' , async ( ) => {
1232
+ const request = createMockRequest ( 'https://example.com/.well-known/oauth-authorization-server' , 'GET' , {
1233
+ Origin : 'https://client.example.com' ,
1234
+ } ) ;
1235
+
1236
+ const response = await oauthProvider . fetch ( request , mockEnv , mockCtx ) ;
1237
+
1238
+ expect ( response . status ) . toBe ( 200 ) ;
1239
+ expect ( response . headers . get ( 'Access-Control-Allow-Origin' ) ) . toBe ( 'https://client.example.com' ) ;
1240
+ expect ( response . headers . get ( 'Access-Control-Allow-Methods' ) ) . toBe ( '*' ) ;
1241
+ expect ( response . headers . get ( 'Access-Control-Allow-Headers' ) ) . toBe ( 'Authorization, *' ) ;
1242
+ expect ( response . headers . get ( 'Access-Control-Max-Age' ) ) . toBe ( '86400' ) ;
1243
+ } ) ;
1244
+
1245
+ it ( 'should handle OPTIONS preflight for metadata discovery endpoint' , async ( ) => {
1246
+ const preflightRequest = createMockRequest ( 'https://example.com/.well-known/oauth-authorization-server' , 'OPTIONS' , {
1247
+ Origin : 'https://spa.example.com' ,
1248
+ 'Access-Control-Request-Method' : 'GET' ,
1249
+ } ) ;
1250
+
1251
+ const response = await oauthProvider . fetch ( preflightRequest , mockEnv , mockCtx ) ;
1252
+
1253
+ expect ( response . status ) . toBe ( 204 ) ;
1254
+ expect ( response . headers . get ( 'Access-Control-Allow-Origin' ) ) . toBe ( 'https://spa.example.com' ) ;
1255
+ expect ( response . headers . get ( 'Access-Control-Allow-Methods' ) ) . toBe ( '*' ) ;
1256
+ expect ( response . headers . get ( 'Access-Control-Allow-Headers' ) ) . toBe ( 'Authorization, *' ) ;
1257
+ expect ( response . headers . get ( 'Access-Control-Max-Age' ) ) . toBe ( '86400' ) ;
1258
+ expect ( response . headers . get ( 'Content-Length' ) ) . toBe ( '0' ) ;
1259
+ } ) ;
1260
+
1261
+ it ( 'should add CORS headers to token endpoint responses' , async ( ) => {
1262
+ // First create a client and get auth code
1263
+ const clientData = {
1264
+ redirect_uris : [ 'https://client.example.com/callback' ] ,
1265
+ client_name : 'CORS Test Client' ,
1266
+ token_endpoint_auth_method : 'client_secret_basic' ,
1267
+ } ;
1268
+
1269
+ const registerRequest = createMockRequest (
1270
+ 'https://example.com/oauth/register' ,
1271
+ 'POST' ,
1272
+ { 'Content-Type' : 'application/json' } ,
1273
+ JSON . stringify ( clientData )
1274
+ ) ;
1275
+
1276
+ const registerResponse = await oauthProvider . fetch ( registerRequest , mockEnv , mockCtx ) ;
1277
+ const client = await registerResponse . json < any > ( ) ;
1278
+ const clientId = client . client_id ;
1279
+ const clientSecret = client . client_secret ;
1280
+ const redirectUri = 'https://client.example.com/callback' ;
1281
+
1282
+ const authRequest = createMockRequest (
1283
+ `https://example.com/authorize?response_type=code&client_id=${ clientId } ` +
1284
+ `&redirect_uri=${ encodeURIComponent ( redirectUri ) } ` +
1285
+ `&scope=read%20write&state=xyz123`
1286
+ ) ;
1287
+
1288
+ const authResponse = await oauthProvider . fetch ( authRequest , mockEnv , mockCtx ) ;
1289
+ const location = authResponse . headers . get ( 'Location' ) ! ;
1290
+ const code = new URL ( location ) . searchParams . get ( 'code' ) ! ;
1291
+
1292
+ // Now test token exchange with CORS
1293
+ const params = new URLSearchParams ( ) ;
1294
+ params . append ( 'grant_type' , 'authorization_code' ) ;
1295
+ params . append ( 'code' , code ) ;
1296
+ params . append ( 'redirect_uri' , redirectUri ) ;
1297
+ params . append ( 'client_id' , clientId ) ;
1298
+ params . append ( 'client_secret' , clientSecret ) ;
1299
+
1300
+ const tokenRequest = createMockRequest (
1301
+ 'https://example.com/oauth/token' ,
1302
+ 'POST' ,
1303
+ {
1304
+ 'Content-Type' : 'application/x-www-form-urlencoded' ,
1305
+ 'Origin' : 'https://webapp.example.com'
1306
+ } ,
1307
+ params . toString ( )
1308
+ ) ;
1309
+
1310
+ const tokenResponse = await oauthProvider . fetch ( tokenRequest , mockEnv , mockCtx ) ;
1311
+
1312
+ expect ( tokenResponse . status ) . toBe ( 200 ) ;
1313
+ expect ( tokenResponse . headers . get ( 'Access-Control-Allow-Origin' ) ) . toBe ( 'https://webapp.example.com' ) ;
1314
+ expect ( tokenResponse . headers . get ( 'Access-Control-Allow-Methods' ) ) . toBe ( '*' ) ;
1315
+ expect ( tokenResponse . headers . get ( 'Access-Control-Allow-Headers' ) ) . toBe ( 'Authorization, *' ) ;
1316
+ expect ( tokenResponse . headers . get ( 'Access-Control-Max-Age' ) ) . toBe ( '86400' ) ;
1317
+ } ) ;
1318
+
1319
+ it ( 'should handle OPTIONS preflight for token endpoint' , async ( ) => {
1320
+ const preflightRequest = createMockRequest ( 'https://example.com/oauth/token' , 'OPTIONS' , {
1321
+ Origin : 'https://mobile.example.com' ,
1322
+ 'Access-Control-Request-Method' : 'POST' ,
1323
+ 'Access-Control-Request-Headers' : 'Content-Type' ,
1324
+ } ) ;
1325
+
1326
+ const response = await oauthProvider . fetch ( preflightRequest , mockEnv , mockCtx ) ;
1327
+
1328
+ expect ( response . status ) . toBe ( 204 ) ;
1329
+ expect ( response . headers . get ( 'Access-Control-Allow-Origin' ) ) . toBe ( 'https://mobile.example.com' ) ;
1330
+ expect ( response . headers . get ( 'Access-Control-Allow-Methods' ) ) . toBe ( '*' ) ;
1331
+ expect ( response . headers . get ( 'Access-Control-Allow-Headers' ) ) . toBe ( 'Authorization, *' ) ;
1332
+ expect ( response . headers . get ( 'Access-Control-Max-Age' ) ) . toBe ( '86400' ) ;
1333
+ } ) ;
1334
+
1335
+ it ( 'should add CORS headers to client registration endpoint' , async ( ) => {
1336
+ const clientData = {
1337
+ redirect_uris : [ 'https://newapp.example.com/callback' ] ,
1338
+ client_name : 'New CORS Test Client' ,
1339
+ token_endpoint_auth_method : 'client_secret_basic' ,
1340
+ } ;
1341
+
1342
+ const request = createMockRequest (
1343
+ 'https://example.com/oauth/register' ,
1344
+ 'POST' ,
1345
+ {
1346
+ 'Content-Type' : 'application/json' ,
1347
+ 'Origin' : 'https://admin.example.com'
1348
+ } ,
1349
+ JSON . stringify ( clientData )
1350
+ ) ;
1351
+
1352
+ const response = await oauthProvider . fetch ( request , mockEnv , mockCtx ) ;
1353
+
1354
+ expect ( response . status ) . toBe ( 201 ) ;
1355
+ expect ( response . headers . get ( 'Access-Control-Allow-Origin' ) ) . toBe ( 'https://admin.example.com' ) ;
1356
+ expect ( response . headers . get ( 'Access-Control-Allow-Methods' ) ) . toBe ( '*' ) ;
1357
+ expect ( response . headers . get ( 'Access-Control-Allow-Headers' ) ) . toBe ( 'Authorization, *' ) ;
1358
+ } ) ;
1359
+
1360
+ it ( 'should handle OPTIONS preflight for client registration endpoint' , async ( ) => {
1361
+ const preflightRequest = createMockRequest ( 'https://example.com/oauth/register' , 'OPTIONS' , {
1362
+ Origin : 'https://dashboard.example.com' ,
1363
+ 'Access-Control-Request-Method' : 'POST' ,
1364
+ 'Access-Control-Request-Headers' : 'Content-Type, Authorization' ,
1365
+ } ) ;
1366
+
1367
+ const response = await oauthProvider . fetch ( preflightRequest , mockEnv , mockCtx ) ;
1368
+
1369
+ expect ( response . status ) . toBe ( 204 ) ;
1370
+ expect ( response . headers . get ( 'Access-Control-Allow-Origin' ) ) . toBe ( 'https://dashboard.example.com' ) ;
1371
+ expect ( response . headers . get ( 'Access-Control-Allow-Methods' ) ) . toBe ( '*' ) ;
1372
+ expect ( response . headers . get ( 'Access-Control-Allow-Headers' ) ) . toBe ( 'Authorization, *' ) ;
1373
+ } ) ;
1374
+
1375
+ it ( 'should not add CORS headers when no Origin header is present' , async ( ) => {
1376
+ const request = createMockRequest ( 'https://example.com/.well-known/oauth-authorization-server' ) ;
1377
+ const response = await oauthProvider . fetch ( request , mockEnv , mockCtx ) ;
1378
+
1379
+ expect ( response . status ) . toBe ( 200 ) ;
1380
+ expect ( response . headers . get ( 'Access-Control-Allow-Origin' ) ) . toBeNull ( ) ;
1381
+ expect ( response . headers . get ( 'Access-Control-Allow-Methods' ) ) . toBeNull ( ) ;
1382
+ expect ( response . headers . get ( 'Access-Control-Allow-Headers' ) ) . toBeNull ( ) ;
1383
+ } ) ;
1384
+
1385
+ it ( 'should add CORS headers to API error responses' , async ( ) => {
1386
+ const apiRequest = createMockRequest ( 'https://example.com/api/test' , 'GET' , {
1387
+ Origin : 'https://client.example.com' ,
1388
+ // No Authorization header - should get 401 error with CORS headers
1389
+ } ) ;
1390
+
1391
+ const response = await oauthProvider . fetch ( apiRequest , mockEnv , mockCtx ) ;
1392
+
1393
+ expect ( response . status ) . toBe ( 401 ) ;
1394
+ expect ( response . headers . get ( 'Access-Control-Allow-Origin' ) ) . toBe ( 'https://client.example.com' ) ;
1395
+ expect ( response . headers . get ( 'Access-Control-Allow-Methods' ) ) . toBe ( '*' ) ;
1396
+ expect ( response . headers . get ( 'Access-Control-Allow-Headers' ) ) . toBe ( 'Authorization, *' ) ;
1397
+
1398
+ const error = await response . json < any > ( ) ;
1399
+ expect ( error . error ) . toBe ( 'invalid_token' ) ;
1400
+ } ) ;
1401
+
1402
+ it ( 'should add CORS headers to token endpoint error responses' , async ( ) => {
1403
+ const params = new URLSearchParams ( ) ;
1404
+ params . append ( 'grant_type' , 'authorization_code' ) ;
1405
+ params . append ( 'code' , 'invalid-code' ) ;
1406
+ params . append ( 'client_id' , 'invalid-client' ) ;
1407
+
1408
+ const tokenRequest = createMockRequest (
1409
+ 'https://example.com/oauth/token' ,
1410
+ 'POST' ,
1411
+ {
1412
+ 'Content-Type' : 'application/x-www-form-urlencoded' ,
1413
+ 'Origin' : 'https://evil.example.com'
1414
+ } ,
1415
+ params . toString ( )
1416
+ ) ;
1417
+
1418
+ const response = await oauthProvider . fetch ( tokenRequest , mockEnv , mockCtx ) ;
1419
+
1420
+ expect ( response . status ) . toBe ( 401 ) ; // Should be an error
1421
+ expect ( response . headers . get ( 'Access-Control-Allow-Origin' ) ) . toBe ( 'https://evil.example.com' ) ;
1422
+ expect ( response . headers . get ( 'Access-Control-Allow-Methods' ) ) . toBe ( '*' ) ;
1423
+ expect ( response . headers . get ( 'Access-Control-Allow-Headers' ) ) . toBe ( 'Authorization, *' ) ;
1424
+ } ) ;
1425
+
1426
+ it ( 'should not add CORS headers to default handler responses' , async ( ) => {
1427
+ const defaultRequest = createMockRequest ( 'https://example.com/some-other-route' , 'GET' , {
1428
+ Origin : 'https://client.example.com' ,
1429
+ } ) ;
1430
+
1431
+ const response = await oauthProvider . fetch ( defaultRequest , mockEnv , mockCtx ) ;
1432
+
1433
+ expect ( response . status ) . toBe ( 200 ) ;
1434
+ // CORS headers should NOT be added to default handler responses
1435
+ expect ( response . headers . get ( 'Access-Control-Allow-Origin' ) ) . toBeNull ( ) ;
1436
+ expect ( response . headers . get ( 'Access-Control-Allow-Methods' ) ) . toBeNull ( ) ;
1437
+ expect ( response . headers . get ( 'Access-Control-Allow-Headers' ) ) . toBeNull ( ) ;
1438
+ } ) ;
1228
1439
} ) ;
1229
1440
1230
1441
describe ( 'Token Exchange Callback' , ( ) => {
0 commit comments