@@ -25,7 +25,6 @@ import type {
25
25
JsonApiResourceWithPath ,
26
26
PathAlias ,
27
27
PreviewOptions ,
28
- GetResourcePreviewUrlOptions ,
29
28
JsonApiWithCacheOptions ,
30
29
JsonApiCreateResourceBody ,
31
30
JsonApiUpdateResourceBody ,
@@ -105,6 +104,8 @@ export class DrupalClient {
105
104
106
105
private accessToken ?: DrupalClientOptions [ "accessToken" ]
107
106
107
+ private accessTokenScope ?: DrupalClientOptions [ "accessTokenScope" ]
108
+
108
109
private tokenExpiresOn ?: number
109
110
110
111
private withAuth ?: DrupalClientOptions [ "withAuth" ]
@@ -538,15 +539,17 @@ export class DrupalClient {
538
539
) : Promise < T > {
539
540
const type = typeof input === "string" ? input : input . jsonapi . resourceName
540
541
541
- const previewData = context . previewData as { resourceVersion ?: string }
542
+ const previewData = context . previewData as {
543
+ resourceVersion ?: string
544
+ }
542
545
543
546
options = {
544
547
// Add support for revisions for node by default.
545
548
// TODO: Make this required before stable?
546
549
isVersionable : / ^ n o d e - - / . test ( type ) ,
547
550
deserialize : true ,
548
551
pathPrefix : "/" ,
549
- withAuth : this . withAuth ,
552
+ withAuth : this . getAuthFromContextAndOptions ( context , options ) ,
550
553
params : { } ,
551
554
...options ,
552
555
}
@@ -556,7 +559,7 @@ export class DrupalClient {
556
559
isVersionable : options . isVersionable ,
557
560
locale : context . locale ,
558
561
defaultLocale : context . defaultLocale ,
559
- withAuth : context . preview || options ?. withAuth ,
562
+ withAuth : options ?. withAuth ,
560
563
params : {
561
564
resourceVersion : previewData ?. resourceVersion ,
562
565
...options ?. params ,
@@ -757,7 +760,6 @@ export class DrupalClient {
757
760
JsonApiWithAuthOptions
758
761
) : Promise < T > {
759
762
options = {
760
- withAuth : this . withAuth ,
761
763
deserialize : true ,
762
764
...options ,
763
765
}
@@ -766,7 +768,7 @@ export class DrupalClient {
766
768
...options ,
767
769
locale : context . locale ,
768
770
defaultLocale : context . defaultLocale ,
769
- withAuth : context . preview || options . withAuth ,
771
+ withAuth : this . getAuthFromContextAndOptions ( context , options ) ,
770
772
} )
771
773
}
772
774
@@ -928,18 +930,15 @@ export class DrupalClient {
928
930
) : Promise < DrupalTranslatedPath > {
929
931
options = {
930
932
pathPrefix : "/" ,
931
- withAuth : this . withAuth ,
932
933
...options ,
933
934
}
934
935
const path = this . getPathFromContext ( context , {
935
936
pathPrefix : options . pathPrefix ,
936
937
} )
937
938
938
- const response = await this . translatePath ( path , {
939
- withAuth : context . preview || options . withAuth ,
939
+ return await this . translatePath ( path , {
940
+ withAuth : this . getAuthFromContextAndOptions ( context , options ) ,
940
941
} )
941
-
942
- return response
943
942
}
944
943
945
944
getPathFromContext (
@@ -1045,84 +1044,57 @@ export class DrupalClient {
1045
1044
response ?: NextApiResponse ,
1046
1045
options ?: PreviewOptions
1047
1046
) {
1048
- const { slug, resourceVersion, secret, locale, defaultLocale } =
1049
- request . query
1050
-
1051
- if ( secret !== this . previewSecret ) {
1052
- return response
1053
- . status ( 401 )
1054
- . json ( options ?. errorMessages . secret || "Invalid preview secret." )
1055
- }
1047
+ const { slug, resourceVersion, plugin } = request . query
1056
1048
1057
- if ( ! slug ) {
1058
- return response
1059
- . status ( 401 )
1060
- . end ( options ?. errorMessages . slug || "Invalid slug." )
1061
- }
1062
-
1063
- let _options : GetResourcePreviewUrlOptions = {
1064
- isVersionable : ! ! resourceVersion ,
1065
- }
1049
+ try {
1050
+ // Always clear preview data to handle different scopes.
1051
+ response . clearPreviewData ( )
1052
+
1053
+ // Validate the preview url.
1054
+ const validateUrl = this . buildUrl ( "/next/preview-url" )
1055
+ const result = await this . fetch ( validateUrl . toString ( ) , {
1056
+ method : "POST" ,
1057
+ headers : {
1058
+ "Content-Type" : "application/json" ,
1059
+ } ,
1060
+ body : JSON . stringify ( request . query ) ,
1061
+ } )
1066
1062
1067
- if ( locale && defaultLocale ) {
1068
- // Fix for und locale.
1069
- const _locale = locale === "und" ? defaultLocale : locale
1063
+ if ( ! result . ok ) {
1064
+ response . statusCode = result . status
1070
1065
1071
- _options = {
1072
- ..._options ,
1073
- locale : _locale as string ,
1074
- defaultLocale : defaultLocale as string ,
1066
+ return response . json ( await result . json ( ) )
1075
1067
}
1076
- }
1077
1068
1078
- const entity = await this . getResourceByPath ( slug as string , {
1079
- withAuth : true ,
1080
- ..._options ,
1081
- } )
1082
-
1083
- const missingEntityErrorMessage = `The entity with slug ${ slug } coud not be found. If the entity exists on your Drupal site, make sure the proper permissions are configured so that Next.js can access it.`
1084
- const missingPathAliasErrorMessage = `The path alias is missing for entity with slug ${ slug } .`
1085
-
1086
- if ( ! entity ) {
1087
- this . throwError ( new Error ( missingEntityErrorMessage ) )
1069
+ const validationPayload = await result . json ( )
1088
1070
1089
- return response . status ( 404 ) . end ( missingEntityErrorMessage )
1090
- }
1091
-
1092
- if ( ! entity ?. path ?. alias ) {
1093
- this . throwError ( new Error ( missingPathAliasErrorMessage ) )
1094
-
1095
- return response . status ( 404 ) . end ( missingPathAliasErrorMessage )
1096
- }
1097
-
1098
- const url = entity . default_langcode
1099
- ? entity ?. path . alias
1100
- : `/${ entity . path . langcode } ${ entity . path . alias } `
1071
+ response . setPreviewData ( {
1072
+ resourceVersion,
1073
+ plugin,
1074
+ ...validationPayload ,
1075
+ } )
1101
1076
1102
- if ( ! url ) {
1103
- return response
1104
- . status ( 404 )
1105
- . end ( options ?. errorMessages . slug || "Invalid slug" )
1106
- }
1077
+ // Fix issue with cookie.
1078
+ // See https://github.com/vercel/next.js/discussions/32238.
1079
+ // See https://github.com/vercel/next.js/blob/d895a50abbc8f91726daa2d7ebc22c58f58aabbb/packages/next/server/api-utils/node.ts#L504.
1080
+ if ( this . forceIframeSameSiteCookie ) {
1081
+ const previous = response . getHeader ( "Set-Cookie" ) as string [ ]
1082
+ previous . forEach ( ( cookie , index ) => {
1083
+ previous [ index ] = cookie . replace (
1084
+ "SameSite=Lax" ,
1085
+ "SameSite=None;Secure"
1086
+ )
1087
+ } )
1088
+ response . setHeader ( `Set-Cookie` , previous )
1089
+ }
1107
1090
1108
- response . setPreviewData ( {
1109
- resourceVersion,
1110
- } )
1091
+ // We can safely redirect to the slug since this has been validated on the server.
1092
+ response . writeHead ( 307 , { Location : slug } )
1111
1093
1112
- // Fix issue with cookie.
1113
- // See https://github.com/vercel/next.js/discussions/32238.
1114
- // See https://github.com/vercel/next.js/blob/d895a50abbc8f91726daa2d7ebc22c58f58aabbb/packages/next/server/api-utils/node.ts#L504.
1115
- if ( this . forceIframeSameSiteCookie ) {
1116
- const previous = response . getHeader ( "Set-Cookie" ) as string [ ]
1117
- previous . forEach ( ( cookie , index ) => {
1118
- previous [ index ] = cookie . replace ( "SameSite=Lax" , "SameSite=None;Secure" )
1119
- } )
1120
- response . setHeader ( `Set-Cookie` , previous )
1094
+ return response . end ( )
1095
+ } catch ( error ) {
1096
+ return response . status ( 422 ) . end ( )
1121
1097
}
1122
-
1123
- response . writeHead ( 307 , { Location : url } )
1124
-
1125
- return response . end ( )
1126
1098
}
1127
1099
1128
1100
async getMenu < T = DrupalMenuLinkContent > (
@@ -1320,12 +1292,10 @@ export class DrupalClient {
1320
1292
return url
1321
1293
}
1322
1294
1323
- async getAccessToken ( opts ?: {
1324
- clientId : string
1325
- clientSecret : string
1326
- url ?: string
1327
- } ) : Promise < AccessToken > {
1328
- if ( this . accessToken ) {
1295
+ async getAccessToken (
1296
+ opts ?: DrupalClientAuthClientIdSecret
1297
+ ) : Promise < AccessToken > {
1298
+ if ( this . accessToken && this . accessTokenScope === opts ?. scope ) {
1329
1299
return this . accessToken
1330
1300
}
1331
1301
@@ -1350,7 +1320,11 @@ export class DrupalClient {
1350
1320
const clientSecret = opts ?. clientSecret || this . _auth . clientSecret
1351
1321
const url = this . buildUrl ( opts ?. url || this . _auth . url || DEFAULT_AUTH_URL )
1352
1322
1353
- if ( this . _token && Date . now ( ) < this . tokenExpiresOn ) {
1323
+ if (
1324
+ this . accessTokenScope === opts ?. scope &&
1325
+ this . _token &&
1326
+ Date . now ( ) < this . tokenExpiresOn
1327
+ ) {
1354
1328
this . _debug ( `Using existing access token.` )
1355
1329
return this . _token
1356
1330
}
@@ -1359,13 +1333,21 @@ export class DrupalClient {
1359
1333
1360
1334
const basic = Buffer . from ( `${ clientId } :${ clientSecret } ` ) . toString ( "base64" )
1361
1335
1336
+ let body = `grant_type=client_credentials`
1337
+
1338
+ if ( opts ?. scope ) {
1339
+ body = `${ body } &scope=${ opts . scope } `
1340
+
1341
+ this . _debug ( `Using scope: ${ opts . scope } ` )
1342
+ }
1343
+
1362
1344
const response = await fetch ( url . toString ( ) , {
1363
1345
method : "POST" ,
1364
1346
headers : {
1365
1347
Authorization : `Basic ${ basic } ` ,
1366
1348
"Content-Type" : "application/x-www-form-urlencoded" ,
1367
1349
} ,
1368
- body : `grant_type=client_credentials` ,
1350
+ body,
1369
1351
} )
1370
1352
1371
1353
if ( ! response ?. ok ) {
@@ -1378,6 +1360,8 @@ export class DrupalClient {
1378
1360
1379
1361
this . token = result
1380
1362
1363
+ this . accessTokenScope = opts ?. scope
1364
+
1381
1365
return result
1382
1366
}
1383
1367
@@ -1443,4 +1427,48 @@ export class DrupalClient {
1443
1427
throw new JsonApiErrors ( errors , response . status )
1444
1428
}
1445
1429
}
1430
+
1431
+ private getAuthFromContextAndOptions (
1432
+ context : GetStaticPropsContext ,
1433
+ options : JsonApiWithAuthOptions
1434
+ ) {
1435
+ // If not in preview or withAuth is provided, use that.
1436
+ if ( ! context . preview ) {
1437
+ // If we have provided an auth, use that.
1438
+ if ( typeof options ?. withAuth !== "undefined" ) {
1439
+ return options . withAuth
1440
+ }
1441
+
1442
+ // Otherwise we fallback to the global auth.
1443
+ return this . withAuth
1444
+ }
1445
+
1446
+ // If no plugin is provided, return.
1447
+ const plugin = context . previewData ?. [ "plugin" ]
1448
+ if ( ! plugin ) {
1449
+ return null
1450
+ }
1451
+
1452
+ let withAuth = this . _auth
1453
+
1454
+ if ( plugin === "simple_oauth" ) {
1455
+ // If we are using a client id and secret auth, pass the scope.
1456
+ if ( isClientIdSecretAuth ( withAuth ) && context . previewData ?. [ "scope" ] ) {
1457
+ withAuth = {
1458
+ ...withAuth ,
1459
+ scope : context . previewData ?. [ "scope" ] ,
1460
+ }
1461
+ }
1462
+ }
1463
+
1464
+ if ( plugin === "jwt" ) {
1465
+ const accessToken = context . previewData ?. [ "access_token" ]
1466
+
1467
+ if ( accessToken ) {
1468
+ return `Bearer ${ accessToken } `
1469
+ }
1470
+ }
1471
+
1472
+ return withAuth
1473
+ }
1446
1474
}
0 commit comments