@cloudflare/workers-oauth-provider 0.0.0-aa007fc → 0.0.0-be89144
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/oauth-provider.d.ts +58 -1
- package/dist/oauth-provider.js +188 -26
- package/package.json +1 -1
package/dist/oauth-provider.d.ts
CHANGED
|
@@ -68,6 +68,39 @@ interface TokenExchangeCallbackOptions {
|
|
|
68
68
|
*/
|
|
69
69
|
props: any;
|
|
70
70
|
}
|
|
71
|
+
/**
|
|
72
|
+
* Input parameters for the resolveExternalToken callback function
|
|
73
|
+
*/
|
|
74
|
+
interface ResolveExternalTokenInput {
|
|
75
|
+
/**
|
|
76
|
+
* The token string that was provided in the Authorization header
|
|
77
|
+
*/
|
|
78
|
+
token: string;
|
|
79
|
+
/**
|
|
80
|
+
* The original HTTP request
|
|
81
|
+
*/
|
|
82
|
+
request: Request;
|
|
83
|
+
/**
|
|
84
|
+
* Cloudflare Worker environment variables
|
|
85
|
+
*/
|
|
86
|
+
env: any;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Result returned from the resolveExternalToken callback function
|
|
90
|
+
*/
|
|
91
|
+
interface ResolveExternalTokenResult {
|
|
92
|
+
/**
|
|
93
|
+
* Application-specific properties that will be passed to the API handlers
|
|
94
|
+
* These properties are set in the execution context (ctx.props) when the external token is validated
|
|
95
|
+
*/
|
|
96
|
+
props: any;
|
|
97
|
+
/**
|
|
98
|
+
* Audience claim from the external token (RFC 7519 Section 4.1.3)
|
|
99
|
+
* If provided, will be validated against the resource server identity
|
|
100
|
+
*
|
|
101
|
+
*/
|
|
102
|
+
audience?: string | string[];
|
|
103
|
+
}
|
|
71
104
|
interface OAuthProviderOptions {
|
|
72
105
|
/**
|
|
73
106
|
* URL(s) for API routes. Requests with URLs starting with any of these prefixes
|
|
@@ -157,6 +190,16 @@ interface OAuthProviderOptions {
|
|
|
157
190
|
* If the callback returns nothing or undefined for a props field, the original props will be used.
|
|
158
191
|
*/
|
|
159
192
|
tokenExchangeCallback?: (options: TokenExchangeCallbackOptions) => Promise<TokenExchangeCallbackResult | void> | TokenExchangeCallbackResult | void;
|
|
193
|
+
/**
|
|
194
|
+
* Optional callback function that is called when a provided token was not found in the internal KV.
|
|
195
|
+
* This allows authentication through external OAuth servers.
|
|
196
|
+
* For example, if a request includes an authenticated token from a different OAuth authentication server,
|
|
197
|
+
* the callback can be used to authenticate it and set the context props through it.
|
|
198
|
+
*
|
|
199
|
+
* The callback can optionally return props values that will passed-through to the apiHandlers.
|
|
200
|
+
* The callback can return `null` to signal resolution failure.
|
|
201
|
+
*/
|
|
202
|
+
resolveExternalToken?: (input: ResolveExternalTokenInput) => Promise<ResolveExternalTokenResult | null>;
|
|
160
203
|
/**
|
|
161
204
|
* Optional callback function that is called whenever the OAuthProvider returns an error response
|
|
162
205
|
* This allows the client to emit notifications or perform other actions when an error occurs.
|
|
@@ -267,6 +310,10 @@ interface AuthRequest {
|
|
|
267
310
|
* PKCE code challenge method (plain or S256)
|
|
268
311
|
*/
|
|
269
312
|
codeChallengeMethod?: string;
|
|
313
|
+
/**
|
|
314
|
+
* Resource parameter indicating target resource(s) (RFC 8707)
|
|
315
|
+
*/
|
|
316
|
+
resource?: string | string[];
|
|
270
317
|
}
|
|
271
318
|
/**
|
|
272
319
|
* OAuth client registration information
|
|
@@ -435,6 +482,11 @@ interface Grant {
|
|
|
435
482
|
* Only present during the authorization code exchange process
|
|
436
483
|
*/
|
|
437
484
|
codeChallengeMethod?: string;
|
|
485
|
+
/**
|
|
486
|
+
* Resource parameter from authorization request (RFC 8707 Section 2.1)
|
|
487
|
+
* Indicates the protected resource(s) for which access is requested
|
|
488
|
+
*/
|
|
489
|
+
resource?: string | string[];
|
|
438
490
|
}
|
|
439
491
|
/**
|
|
440
492
|
* Token record stored in KV
|
|
@@ -463,6 +515,11 @@ interface Token {
|
|
|
463
515
|
* Unix timestamp when the token expires
|
|
464
516
|
*/
|
|
465
517
|
expiresAt: number;
|
|
518
|
+
/**
|
|
519
|
+
* Intended audience for this token (RFC 7519 Section 4.1.3)
|
|
520
|
+
* Can be a single string or array of strings
|
|
521
|
+
*/
|
|
522
|
+
audience?: string | string[];
|
|
466
523
|
/**
|
|
467
524
|
* The encryption key for props, wrapped with this token
|
|
468
525
|
*/
|
|
@@ -568,4 +625,4 @@ declare class OAuthProvider {
|
|
|
568
625
|
fetch(request: Request, env: any, ctx: ExecutionContext): Promise<Response>;
|
|
569
626
|
}
|
|
570
627
|
|
|
571
|
-
export { type AuthRequest, type ClientInfo, type CompleteAuthorizationOptions, type Grant, type GrantSummary, type ListOptions, type ListResult, type OAuthHelpers, OAuthProvider, type OAuthProviderOptions, type Token, type TokenExchangeCallbackOptions, type TokenExchangeCallbackResult, OAuthProvider as default };
|
|
628
|
+
export { type AuthRequest, type ClientInfo, type CompleteAuthorizationOptions, type Grant, type GrantSummary, type ListOptions, type ListResult, type OAuthHelpers, OAuthProvider, type OAuthProviderOptions, type ResolveExternalTokenInput, type ResolveExternalTokenResult, type Token, type TokenExchangeCallbackOptions, type TokenExchangeCallbackResult, OAuthProvider as default };
|
package/dist/oauth-provider.js
CHANGED
|
@@ -231,7 +231,8 @@ var OAuthProviderImpl = class {
|
|
|
231
231
|
}
|
|
232
232
|
const formData = await request.formData();
|
|
233
233
|
for (const [key, value] of formData.entries()) {
|
|
234
|
-
|
|
234
|
+
const allValues = formData.getAll(key);
|
|
235
|
+
body[key] = allValues.length > 1 ? allValues : value;
|
|
235
236
|
}
|
|
236
237
|
const authHeader = request.headers.get("Authorization");
|
|
237
238
|
let clientId = "";
|
|
@@ -549,12 +550,32 @@ var OAuthProviderImpl = class {
|
|
|
549
550
|
grantData.expiresAt = expiresAt;
|
|
550
551
|
}
|
|
551
552
|
await this.saveGrantWithTTL(env, grantKey, grantData, now);
|
|
553
|
+
if (body.resource && grantData.resource) {
|
|
554
|
+
const requestedResources = Array.isArray(body.resource) ? body.resource : [body.resource];
|
|
555
|
+
const grantedResources = Array.isArray(grantData.resource) ? grantData.resource : [grantData.resource];
|
|
556
|
+
for (const requested of requestedResources) {
|
|
557
|
+
if (!grantedResources.includes(requested)) {
|
|
558
|
+
return this.createErrorResponse(
|
|
559
|
+
"invalid_target",
|
|
560
|
+
"Requested resource was not included in the authorization request"
|
|
561
|
+
);
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
const audience = parseResourceParameter(body.resource || grantData.resource);
|
|
566
|
+
if ((body.resource || grantData.resource) && !audience) {
|
|
567
|
+
return this.createErrorResponse(
|
|
568
|
+
"invalid_target",
|
|
569
|
+
"The resource parameter must be a valid absolute URI without a fragment"
|
|
570
|
+
);
|
|
571
|
+
}
|
|
552
572
|
const accessTokenData = {
|
|
553
573
|
id: accessTokenId,
|
|
554
574
|
grantId,
|
|
555
575
|
userId,
|
|
556
576
|
createdAt: now,
|
|
557
577
|
expiresAt: accessTokenExpiresAt,
|
|
578
|
+
audience,
|
|
558
579
|
wrappedEncryptionKey: accessTokenWrappedKey,
|
|
559
580
|
grant: {
|
|
560
581
|
clientId: grantData.clientId,
|
|
@@ -574,6 +595,9 @@ var OAuthProviderImpl = class {
|
|
|
574
595
|
if (refreshToken) {
|
|
575
596
|
tokenResponse.refresh_token = refreshToken;
|
|
576
597
|
}
|
|
598
|
+
if (audience) {
|
|
599
|
+
tokenResponse.resource = audience;
|
|
600
|
+
}
|
|
577
601
|
return new Response(JSON.stringify(tokenResponse), {
|
|
578
602
|
headers: { "Content-Type": "application/json" }
|
|
579
603
|
});
|
|
@@ -701,12 +725,32 @@ var OAuthProviderImpl = class {
|
|
|
701
725
|
grantData.refreshTokenId = newRefreshTokenId;
|
|
702
726
|
grantData.refreshTokenWrappedKey = newRefreshTokenWrappedKey;
|
|
703
727
|
await this.saveGrantWithTTL(env, grantKey, grantData, now);
|
|
728
|
+
if (body.resource && grantData.resource) {
|
|
729
|
+
const requestedResources = Array.isArray(body.resource) ? body.resource : [body.resource];
|
|
730
|
+
const grantedResources = Array.isArray(grantData.resource) ? grantData.resource : [grantData.resource];
|
|
731
|
+
for (const requested of requestedResources) {
|
|
732
|
+
if (!grantedResources.includes(requested)) {
|
|
733
|
+
return this.createErrorResponse(
|
|
734
|
+
"invalid_target",
|
|
735
|
+
"Requested resource was not included in the authorization request"
|
|
736
|
+
);
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
const audience = parseResourceParameter(body.resource || grantData.resource);
|
|
741
|
+
if ((body.resource || grantData.resource) && !audience) {
|
|
742
|
+
return this.createErrorResponse(
|
|
743
|
+
"invalid_target",
|
|
744
|
+
"The resource parameter must be a valid absolute URI without a fragment"
|
|
745
|
+
);
|
|
746
|
+
}
|
|
704
747
|
const accessTokenData = {
|
|
705
748
|
id: accessTokenId,
|
|
706
749
|
grantId,
|
|
707
750
|
userId,
|
|
708
751
|
createdAt: now,
|
|
709
752
|
expiresAt: accessTokenExpiresAt,
|
|
753
|
+
audience,
|
|
710
754
|
wrappedEncryptionKey: accessTokenWrappedKey,
|
|
711
755
|
grant: {
|
|
712
756
|
clientId: grantData.clientId,
|
|
@@ -724,6 +768,9 @@ var OAuthProviderImpl = class {
|
|
|
724
768
|
refresh_token: newRefreshToken,
|
|
725
769
|
scope: grantData.scope.join(" ")
|
|
726
770
|
};
|
|
771
|
+
if (audience) {
|
|
772
|
+
tokenResponse.resource = audience;
|
|
773
|
+
}
|
|
727
774
|
return new Response(JSON.stringify(tokenResponse), {
|
|
728
775
|
headers: { "Content-Type": "application/json" }
|
|
729
776
|
});
|
|
@@ -876,6 +923,9 @@ var OAuthProviderImpl = class {
|
|
|
876
923
|
if (!redirectUris || redirectUris.length === 0) {
|
|
877
924
|
throw new Error("At least one redirect URI is required");
|
|
878
925
|
}
|
|
926
|
+
for (const uri of redirectUris) {
|
|
927
|
+
validateRedirectUriScheme(uri);
|
|
928
|
+
}
|
|
879
929
|
clientInfo = {
|
|
880
930
|
clientId,
|
|
881
931
|
redirectUris,
|
|
@@ -940,30 +990,62 @@ var OAuthProviderImpl = class {
|
|
|
940
990
|
});
|
|
941
991
|
}
|
|
942
992
|
const accessToken = authHeader.substring(7);
|
|
943
|
-
const
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
if (!tokenData) {
|
|
993
|
+
const parts = accessToken.split(":");
|
|
994
|
+
const isPossiblyInternalFormat = parts.length === 3;
|
|
995
|
+
let tokenData = null;
|
|
996
|
+
let userId = "";
|
|
997
|
+
let grantId = "";
|
|
998
|
+
if (isPossiblyInternalFormat) {
|
|
999
|
+
[userId, grantId] = parts;
|
|
1000
|
+
const id = await generateTokenId(accessToken);
|
|
1001
|
+
tokenData = await env.OAUTH_KV.get(`token:${userId}:${grantId}:${id}`, { type: "json" });
|
|
1002
|
+
}
|
|
1003
|
+
if (!tokenData && !this.options.resolveExternalToken) {
|
|
954
1004
|
return this.createErrorResponse("invalid_token", "Invalid access token", 401, {
|
|
955
1005
|
"WWW-Authenticate": 'Bearer realm="OAuth", error="invalid_token"'
|
|
956
1006
|
});
|
|
957
1007
|
}
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
"
|
|
962
|
-
|
|
1008
|
+
if (tokenData) {
|
|
1009
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
1010
|
+
if (tokenData.expiresAt < now) {
|
|
1011
|
+
return this.createErrorResponse("invalid_token", "Access token expired", 401, {
|
|
1012
|
+
"WWW-Authenticate": 'Bearer realm="OAuth", error="invalid_token"'
|
|
1013
|
+
});
|
|
1014
|
+
}
|
|
1015
|
+
if (tokenData.audience) {
|
|
1016
|
+
const requestUrl = new URL(request.url);
|
|
1017
|
+
const resourceServer = `${requestUrl.protocol}//${requestUrl.host}`;
|
|
1018
|
+
const audiences = Array.isArray(tokenData.audience) ? tokenData.audience : [tokenData.audience];
|
|
1019
|
+
const matches = audiences.some((aud) => audienceMatches(resourceServer, aud));
|
|
1020
|
+
if (!matches) {
|
|
1021
|
+
return this.createErrorResponse("invalid_token", "Token audience does not match resource server", 401, {
|
|
1022
|
+
"WWW-Authenticate": 'Bearer realm="OAuth", error="invalid_token", error_description="Invalid audience"'
|
|
1023
|
+
});
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
const encryptionKey = await unwrapKeyWithToken(accessToken, tokenData.wrappedEncryptionKey);
|
|
1027
|
+
const decryptedProps = await decryptProps(encryptionKey, tokenData.grant.encryptedProps);
|
|
1028
|
+
ctx.props = decryptedProps;
|
|
1029
|
+
} else if (this.options.resolveExternalToken) {
|
|
1030
|
+
const ext = await this.options.resolveExternalToken({ token: accessToken, request, env });
|
|
1031
|
+
if (!ext) {
|
|
1032
|
+
return this.createErrorResponse("invalid_token", "Invalid access token", 401, {
|
|
1033
|
+
"WWW-Authenticate": 'Bearer realm="OAuth", error="invalid_token"'
|
|
1034
|
+
});
|
|
1035
|
+
}
|
|
1036
|
+
if (ext.audience) {
|
|
1037
|
+
const requestUrl = new URL(request.url);
|
|
1038
|
+
const resourceServer = `${requestUrl.protocol}//${requestUrl.host}`;
|
|
1039
|
+
const audiences = Array.isArray(ext.audience) ? ext.audience : [ext.audience];
|
|
1040
|
+
const matches = audiences.some((aud) => audienceMatches(resourceServer, aud));
|
|
1041
|
+
if (!matches) {
|
|
1042
|
+
return this.createErrorResponse("invalid_token", "Token audience does not match resource server", 401, {
|
|
1043
|
+
"WWW-Authenticate": 'Bearer realm="OAuth", error="invalid_token", error_description="Invalid audience"'
|
|
1044
|
+
});
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
ctx.props = ext.props;
|
|
963
1048
|
}
|
|
964
|
-
const encryptionKey = await unwrapKeyWithToken(accessToken, tokenData.wrappedEncryptionKey);
|
|
965
|
-
const decryptedProps = await decryptProps(encryptionKey, tokenData.grant.encryptedProps);
|
|
966
|
-
ctx.props = decryptedProps;
|
|
967
1049
|
if (!env.OAUTH_PROVIDER) {
|
|
968
1050
|
env.OAUTH_PROVIDER = this.createOAuthHelpers(env);
|
|
969
1051
|
}
|
|
@@ -1038,11 +1120,46 @@ var OAuthProviderImpl = class {
|
|
|
1038
1120
|
};
|
|
1039
1121
|
var DEFAULT_ACCESS_TOKEN_TTL = 60 * 60;
|
|
1040
1122
|
var TOKEN_LENGTH = 32;
|
|
1123
|
+
function validateResourceUri(uri) {
|
|
1124
|
+
if (!uri || typeof uri !== "string") {
|
|
1125
|
+
return false;
|
|
1126
|
+
}
|
|
1127
|
+
try {
|
|
1128
|
+
const parsed = new URL(uri);
|
|
1129
|
+
if (!parsed.protocol) {
|
|
1130
|
+
return false;
|
|
1131
|
+
}
|
|
1132
|
+
if (parsed.hash) {
|
|
1133
|
+
return false;
|
|
1134
|
+
}
|
|
1135
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
1136
|
+
return false;
|
|
1137
|
+
}
|
|
1138
|
+
return true;
|
|
1139
|
+
} catch {
|
|
1140
|
+
return false;
|
|
1141
|
+
}
|
|
1142
|
+
}
|
|
1143
|
+
function audienceMatches(resourceServerUrl, audienceValue) {
|
|
1144
|
+
return resourceServerUrl === audienceValue;
|
|
1145
|
+
}
|
|
1146
|
+
function parseResourceParameter(value) {
|
|
1147
|
+
if (!value) {
|
|
1148
|
+
return void 0;
|
|
1149
|
+
}
|
|
1150
|
+
const uris = Array.isArray(value) ? value : [value];
|
|
1151
|
+
for (const uri of uris) {
|
|
1152
|
+
if (typeof uri !== "string" || !validateResourceUri(uri)) {
|
|
1153
|
+
return void 0;
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
return value;
|
|
1157
|
+
}
|
|
1041
1158
|
async function hashSecret(secret) {
|
|
1042
1159
|
return generateTokenId(secret);
|
|
1043
1160
|
}
|
|
1044
1161
|
function generateRandomString(length) {
|
|
1045
|
-
const characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
|
1162
|
+
const characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
|
|
1046
1163
|
let result = "";
|
|
1047
1164
|
const values = new Uint8Array(length);
|
|
1048
1165
|
crypto.getRandomValues(values);
|
|
@@ -1059,6 +1176,26 @@ async function generateTokenId(token) {
|
|
|
1059
1176
|
const hashHex = hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
1060
1177
|
return hashHex;
|
|
1061
1178
|
}
|
|
1179
|
+
function validateRedirectUriScheme(redirectUri) {
|
|
1180
|
+
const dangerousSchemes = ["javascript:", "data:", "vbscript:", "file:", "mailto:", "blob:"];
|
|
1181
|
+
const normalized = redirectUri.trim();
|
|
1182
|
+
for (let i = 0; i < normalized.length; i++) {
|
|
1183
|
+
const code = normalized.charCodeAt(i);
|
|
1184
|
+
if (code >= 0 && code <= 31 || code >= 127 && code <= 159) {
|
|
1185
|
+
throw new Error("Invalid redirect URI");
|
|
1186
|
+
}
|
|
1187
|
+
}
|
|
1188
|
+
const colonIndex = normalized.indexOf(":");
|
|
1189
|
+
if (colonIndex === -1) {
|
|
1190
|
+
throw new Error("Invalid redirect URI");
|
|
1191
|
+
}
|
|
1192
|
+
const scheme = normalized.substring(0, colonIndex + 1).toLowerCase();
|
|
1193
|
+
for (const dangerousScheme of dangerousSchemes) {
|
|
1194
|
+
if (scheme === dangerousScheme) {
|
|
1195
|
+
throw new Error("Invalid redirect URI");
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1062
1199
|
function base64UrlEncode(str) {
|
|
1063
1200
|
return btoa(str).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
|
|
1064
1201
|
}
|
|
@@ -1211,8 +1348,12 @@ var OAuthHelpersImpl = class {
|
|
|
1211
1348
|
const state = url.searchParams.get("state") || "";
|
|
1212
1349
|
const codeChallenge = url.searchParams.get("code_challenge") || void 0;
|
|
1213
1350
|
const codeChallengeMethod = url.searchParams.get("code_challenge_method") || "plain";
|
|
1214
|
-
|
|
1215
|
-
|
|
1351
|
+
const resourceParams = url.searchParams.getAll("resource");
|
|
1352
|
+
const resourceParam = resourceParams.length > 0 ? resourceParams.length === 1 ? resourceParams[0] : resourceParams : void 0;
|
|
1353
|
+
validateRedirectUriScheme(redirectUri);
|
|
1354
|
+
const resource = parseResourceParameter(resourceParam);
|
|
1355
|
+
if (resourceParam && !resource) {
|
|
1356
|
+
throw new Error("The resource parameter must be a valid absolute URI without a fragment");
|
|
1216
1357
|
}
|
|
1217
1358
|
if (responseType === "token" && !this.provider.options.allowImplicitFlow) {
|
|
1218
1359
|
throw new Error("The implicit grant flow is not enabled for this provider");
|
|
@@ -1237,7 +1378,8 @@ var OAuthHelpersImpl = class {
|
|
|
1237
1378
|
scope,
|
|
1238
1379
|
state,
|
|
1239
1380
|
codeChallenge,
|
|
1240
|
-
codeChallengeMethod
|
|
1381
|
+
codeChallengeMethod,
|
|
1382
|
+
resource
|
|
1241
1383
|
};
|
|
1242
1384
|
}
|
|
1243
1385
|
/**
|
|
@@ -1256,6 +1398,16 @@ var OAuthHelpersImpl = class {
|
|
|
1256
1398
|
* @returns A Promise resolving to an object containing the redirect URL
|
|
1257
1399
|
*/
|
|
1258
1400
|
async completeAuthorization(options) {
|
|
1401
|
+
const { clientId, redirectUri } = options.request;
|
|
1402
|
+
if (!clientId || !redirectUri) {
|
|
1403
|
+
throw new Error("Client ID and Redirect URI are required in the authorization request.");
|
|
1404
|
+
}
|
|
1405
|
+
const clientInfo = await this.lookupClient(clientId);
|
|
1406
|
+
if (!clientInfo || !clientInfo.redirectUris.includes(redirectUri)) {
|
|
1407
|
+
throw new Error(
|
|
1408
|
+
"Invalid redirect URI. The redirect URI provided does not match any registered URI for this client."
|
|
1409
|
+
);
|
|
1410
|
+
}
|
|
1259
1411
|
const grantId = generateRandomString(16);
|
|
1260
1412
|
const { encryptedData, key: encryptionKey } = await encryptProps(options.props);
|
|
1261
1413
|
const now = Math.floor(Date.now() / 1e3);
|
|
@@ -1266,6 +1418,10 @@ var OAuthHelpersImpl = class {
|
|
|
1266
1418
|
const accessTokenTTL = this.provider.options.accessTokenTTL || DEFAULT_ACCESS_TOKEN_TTL;
|
|
1267
1419
|
const accessTokenExpiresAt = now + accessTokenTTL;
|
|
1268
1420
|
const accessTokenWrappedKey = await wrapKeyWithToken(accessToken, encryptionKey);
|
|
1421
|
+
const audience = parseResourceParameter(options.request.resource);
|
|
1422
|
+
if (options.request.resource && !audience) {
|
|
1423
|
+
throw new Error("The resource parameter must be a valid absolute URI without a fragment");
|
|
1424
|
+
}
|
|
1269
1425
|
const grant = {
|
|
1270
1426
|
id: grantId,
|
|
1271
1427
|
clientId: options.request.clientId,
|
|
@@ -1273,7 +1429,8 @@ var OAuthHelpersImpl = class {
|
|
|
1273
1429
|
scope: options.scope,
|
|
1274
1430
|
metadata: options.metadata,
|
|
1275
1431
|
encryptedProps: encryptedData,
|
|
1276
|
-
createdAt: now
|
|
1432
|
+
createdAt: now,
|
|
1433
|
+
resource: options.request.resource
|
|
1277
1434
|
};
|
|
1278
1435
|
const grantKey = `grant:${options.userId}:${grantId}`;
|
|
1279
1436
|
await this.env.OAUTH_KV.put(grantKey, JSON.stringify(grant));
|
|
@@ -1283,6 +1440,7 @@ var OAuthHelpersImpl = class {
|
|
|
1283
1440
|
userId: options.userId,
|
|
1284
1441
|
createdAt: now,
|
|
1285
1442
|
expiresAt: accessTokenExpiresAt,
|
|
1443
|
+
audience,
|
|
1286
1444
|
wrappedEncryptionKey: accessTokenWrappedKey,
|
|
1287
1445
|
grant: {
|
|
1288
1446
|
clientId: options.request.clientId,
|
|
@@ -1325,7 +1483,8 @@ var OAuthHelpersImpl = class {
|
|
|
1325
1483
|
// Store the wrapped key
|
|
1326
1484
|
// Store PKCE parameters if provided
|
|
1327
1485
|
codeChallenge: options.request.codeChallenge,
|
|
1328
|
-
codeChallengeMethod: options.request.codeChallengeMethod
|
|
1486
|
+
codeChallengeMethod: options.request.codeChallengeMethod,
|
|
1487
|
+
resource: options.request.resource
|
|
1329
1488
|
};
|
|
1330
1489
|
const grantKey = `grant:${options.userId}:${grantId}`;
|
|
1331
1490
|
const codeExpiresIn = 600;
|
|
@@ -1362,6 +1521,9 @@ var OAuthHelpersImpl = class {
|
|
|
1362
1521
|
registrationDate: Math.floor(Date.now() / 1e3),
|
|
1363
1522
|
tokenEndpointAuthMethod
|
|
1364
1523
|
};
|
|
1524
|
+
for (const uri of newClient.redirectUris) {
|
|
1525
|
+
validateRedirectUriScheme(uri);
|
|
1526
|
+
}
|
|
1365
1527
|
let clientSecret;
|
|
1366
1528
|
if (!isPublicClient) {
|
|
1367
1529
|
clientSecret = generateRandomString(32);
|
package/package.json
CHANGED