@cloudflare/workers-oauth-provider 0.0.0-a204c64 → 0.0.0-d18b865
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/README.md +9 -2
- package/dist/oauth-provider.d.ts +59 -1
- package/dist/oauth-provider.js +110 -59
- package/package.json +4 -3
package/README.md
CHANGED
|
@@ -38,7 +38,7 @@ export default new OAuthProvider({
|
|
|
38
38
|
// You can provide either an object with a fetch method (ExportedHandler)
|
|
39
39
|
// or a class extending WorkerEntrypoint.
|
|
40
40
|
apiHandler: ApiHandler, // Using a WorkerEntrypoint class
|
|
41
|
-
|
|
41
|
+
|
|
42
42
|
// For multi-handler setups, you can use apiHandlers instead of apiRoute+apiHandler.
|
|
43
43
|
// This allows you to use different handlers for different API routes.
|
|
44
44
|
// Note: You must use either apiRoute+apiHandler (single-handler) OR apiHandlers (multi-handler), not both.
|
|
@@ -87,7 +87,13 @@ export default new OAuthProvider({
|
|
|
87
87
|
// Note: Creating public clients via the OAuthHelpers.createClient() method
|
|
88
88
|
// is always allowed regardless of this setting.
|
|
89
89
|
// Defaults to false.
|
|
90
|
-
disallowPublicClientRegistration: false
|
|
90
|
+
disallowPublicClientRegistration: false,
|
|
91
|
+
|
|
92
|
+
// Optional: Time-to-live for refresh tokens in seconds.
|
|
93
|
+
// If not specified, refresh tokens do not expire.
|
|
94
|
+
// Set to 0 to disable refresh tokens (only access tokens will be issued).
|
|
95
|
+
// For example: 3600 = 1 hour, 86400 = 1 day, 2592000 = 30 days
|
|
96
|
+
refreshTokenTTL: 2592000 // 30 days
|
|
91
97
|
});
|
|
92
98
|
|
|
93
99
|
// The default handler object - the OAuthProvider will pass through HTTP requests to this object's fetch method
|
|
@@ -261,6 +267,7 @@ The callback can:
|
|
|
261
267
|
- Return only `accessTokenProps` to update just the current access token
|
|
262
268
|
- Return only `newProps` to update both the grant and access token (the access token inherits these props)
|
|
263
269
|
- Return `accessTokenTTL` to override the default TTL for this specific access token
|
|
270
|
+
- Return `refreshTokenTTL` to override the default TTL for this specific refresh token
|
|
264
271
|
- Return nothing to keep the original props unchanged
|
|
265
272
|
|
|
266
273
|
The `accessTokenTTL` override is particularly useful when the application is also an OAuth client to another service and wants to match its access token TTL to the upstream access token TTL. This helps prevent situations where the downstream token is still valid but the upstream token has expired.
|
package/dist/oauth-provider.d.ts
CHANGED
|
@@ -33,6 +33,13 @@ interface TokenExchangeCallbackResult {
|
|
|
33
33
|
* Value should be in seconds.
|
|
34
34
|
*/
|
|
35
35
|
accessTokenTTL?: number;
|
|
36
|
+
/**
|
|
37
|
+
* Override the default refresh token TTL (time-to-live) for this specific grant.
|
|
38
|
+
* Value should be in seconds.
|
|
39
|
+
* Note: This is only honored during authorization code exchange. If returned during
|
|
40
|
+
* refresh token exchange, it will be ignored.
|
|
41
|
+
*/
|
|
42
|
+
refreshTokenTTL?: number;
|
|
36
43
|
}
|
|
37
44
|
/**
|
|
38
45
|
* Options for token exchange callback functions
|
|
@@ -61,6 +68,33 @@ interface TokenExchangeCallbackOptions {
|
|
|
61
68
|
*/
|
|
62
69
|
props: any;
|
|
63
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
|
+
}
|
|
64
98
|
interface OAuthProviderOptions {
|
|
65
99
|
/**
|
|
66
100
|
* URL(s) for API routes. Requests with URLs starting with any of these prefixes
|
|
@@ -116,6 +150,12 @@ interface OAuthProviderOptions {
|
|
|
116
150
|
* Defaults to 1 hour (3600 seconds) if not specified.
|
|
117
151
|
*/
|
|
118
152
|
accessTokenTTL?: number;
|
|
153
|
+
/**
|
|
154
|
+
* Time-to-live for refresh tokens in seconds.
|
|
155
|
+
* If not specified, refresh tokens do not expire.
|
|
156
|
+
* For example: 3600 = 1 hour, 2592000 = 30 days
|
|
157
|
+
*/
|
|
158
|
+
refreshTokenTTL?: number;
|
|
119
159
|
/**
|
|
120
160
|
* List of scopes supported by this OAuth provider.
|
|
121
161
|
* If not provided, the 'scopes_supported' field will be omitted from the OAuth metadata.
|
|
@@ -144,6 +184,16 @@ interface OAuthProviderOptions {
|
|
|
144
184
|
* If the callback returns nothing or undefined for a props field, the original props will be used.
|
|
145
185
|
*/
|
|
146
186
|
tokenExchangeCallback?: (options: TokenExchangeCallbackOptions) => Promise<TokenExchangeCallbackResult | void> | TokenExchangeCallbackResult | void;
|
|
187
|
+
/**
|
|
188
|
+
* Optional callback function that is called when a provided token was not found in the internal KV.
|
|
189
|
+
* This allows authentication through external OAuth servers.
|
|
190
|
+
* For example, if a request includes an authenticated token from a different OAuth authentication server,
|
|
191
|
+
* the callback can be used to authenticate it and set the context props through it.
|
|
192
|
+
*
|
|
193
|
+
* The callback can optionally return props values that will passed-through to the apiHandlers.
|
|
194
|
+
* The callback can return `null` to signal resolution failure.
|
|
195
|
+
*/
|
|
196
|
+
resolveExternalToken?: (input: ResolveExternalTokenInput) => Promise<ResolveExternalTokenResult | null>;
|
|
147
197
|
/**
|
|
148
198
|
* Optional callback function that is called whenever the OAuthProvider returns an error response
|
|
149
199
|
* This allows the client to emit notifications or perform other actions when an error occurs.
|
|
@@ -381,6 +431,10 @@ interface Grant {
|
|
|
381
431
|
* Unix timestamp when the grant was created
|
|
382
432
|
*/
|
|
383
433
|
createdAt: number;
|
|
434
|
+
/**
|
|
435
|
+
* Unix timestamp when the grant expires (if TTL is configured)
|
|
436
|
+
*/
|
|
437
|
+
expiresAt?: number;
|
|
384
438
|
/**
|
|
385
439
|
* The hash of the current refresh token associated with this grant
|
|
386
440
|
*/
|
|
@@ -523,6 +577,10 @@ interface GrantSummary {
|
|
|
523
577
|
* Unix timestamp when the grant was created
|
|
524
578
|
*/
|
|
525
579
|
createdAt: number;
|
|
580
|
+
/**
|
|
581
|
+
* Unix timestamp when the grant expires (if TTL is configured)
|
|
582
|
+
*/
|
|
583
|
+
expiresAt?: number;
|
|
526
584
|
}
|
|
527
585
|
/**
|
|
528
586
|
* OAuth 2.0 Provider implementation for Cloudflare Workers
|
|
@@ -547,4 +605,4 @@ declare class OAuthProvider {
|
|
|
547
605
|
fetch(request: Request, env: any, ctx: ExecutionContext): Promise<Response>;
|
|
548
606
|
}
|
|
549
607
|
|
|
550
|
-
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 };
|
|
608
|
+
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
|
@@ -478,12 +478,10 @@ var OAuthProviderImpl = class {
|
|
|
478
478
|
}
|
|
479
479
|
}
|
|
480
480
|
const accessTokenSecret = generateRandomString(TOKEN_LENGTH);
|
|
481
|
-
const refreshTokenSecret = generateRandomString(TOKEN_LENGTH);
|
|
482
481
|
const accessToken = `${userId}:${grantId}:${accessTokenSecret}`;
|
|
483
|
-
const refreshToken = `${userId}:${grantId}:${refreshTokenSecret}`;
|
|
484
482
|
const accessTokenId = await generateTokenId(accessToken);
|
|
485
|
-
const refreshTokenId = await generateTokenId(refreshToken);
|
|
486
483
|
let accessTokenTTL = this.options.accessTokenTTL;
|
|
484
|
+
let refreshTokenTTL = this.options.refreshTokenTTL;
|
|
487
485
|
const encryptionKey = await unwrapKeyWithToken(code, grantData.authCodeWrappedKey);
|
|
488
486
|
let grantEncryptionKey = encryptionKey;
|
|
489
487
|
let accessTokenEncryptionKey = encryptionKey;
|
|
@@ -513,6 +511,9 @@ var OAuthProviderImpl = class {
|
|
|
513
511
|
if (callbackResult.accessTokenTTL !== void 0) {
|
|
514
512
|
accessTokenTTL = callbackResult.accessTokenTTL;
|
|
515
513
|
}
|
|
514
|
+
if ("refreshTokenTTL" in callbackResult) {
|
|
515
|
+
refreshTokenTTL = callbackResult.refreshTokenTTL;
|
|
516
|
+
}
|
|
516
517
|
}
|
|
517
518
|
const grantResult = await encryptProps(grantProps);
|
|
518
519
|
grantData.encryptedProps = grantResult.encryptedData;
|
|
@@ -528,17 +529,26 @@ var OAuthProviderImpl = class {
|
|
|
528
529
|
}
|
|
529
530
|
const now = Math.floor(Date.now() / 1e3);
|
|
530
531
|
const accessTokenExpiresAt = now + accessTokenTTL;
|
|
532
|
+
const useRefreshToken = refreshTokenTTL !== 0;
|
|
531
533
|
const accessTokenWrappedKey = await wrapKeyWithToken(accessToken, accessTokenEncryptionKey);
|
|
532
|
-
const refreshTokenWrappedKey = await wrapKeyWithToken(refreshToken, grantEncryptionKey);
|
|
533
534
|
delete grantData.authCodeId;
|
|
534
535
|
delete grantData.codeChallenge;
|
|
535
536
|
delete grantData.codeChallengeMethod;
|
|
536
537
|
delete grantData.authCodeWrappedKey;
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
538
|
+
let refreshToken;
|
|
539
|
+
if (useRefreshToken) {
|
|
540
|
+
const refreshTokenSecret = generateRandomString(TOKEN_LENGTH);
|
|
541
|
+
refreshToken = `${userId}:${grantId}:${refreshTokenSecret}`;
|
|
542
|
+
const refreshTokenId = await generateTokenId(refreshToken);
|
|
543
|
+
const refreshTokenWrappedKey = await wrapKeyWithToken(refreshToken, grantEncryptionKey);
|
|
544
|
+
const expiresAt = refreshTokenTTL !== void 0 ? now + refreshTokenTTL : void 0;
|
|
545
|
+
grantData.refreshTokenId = refreshTokenId;
|
|
546
|
+
grantData.refreshTokenWrappedKey = refreshTokenWrappedKey;
|
|
547
|
+
grantData.previousRefreshTokenId = void 0;
|
|
548
|
+
grantData.previousRefreshTokenWrappedKey = void 0;
|
|
549
|
+
grantData.expiresAt = expiresAt;
|
|
550
|
+
}
|
|
551
|
+
await this.saveGrantWithTTL(env, grantKey, grantData, now);
|
|
542
552
|
const accessTokenData = {
|
|
543
553
|
id: accessTokenId,
|
|
544
554
|
grantId,
|
|
@@ -555,18 +565,18 @@ var OAuthProviderImpl = class {
|
|
|
555
565
|
await env.OAUTH_KV.put(`token:${userId}:${grantId}:${accessTokenId}`, JSON.stringify(accessTokenData), {
|
|
556
566
|
expirationTtl: accessTokenTTL
|
|
557
567
|
});
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
}
|
|
569
|
-
);
|
|
568
|
+
const tokenResponse = {
|
|
569
|
+
access_token: accessToken,
|
|
570
|
+
token_type: "bearer",
|
|
571
|
+
expires_in: accessTokenTTL,
|
|
572
|
+
scope: grantData.scope.join(" ")
|
|
573
|
+
};
|
|
574
|
+
if (refreshToken) {
|
|
575
|
+
tokenResponse.refresh_token = refreshToken;
|
|
576
|
+
}
|
|
577
|
+
return new Response(JSON.stringify(tokenResponse), {
|
|
578
|
+
headers: { "Content-Type": "application/json" }
|
|
579
|
+
});
|
|
570
580
|
}
|
|
571
581
|
/**
|
|
572
582
|
* Handles the refresh token grant type
|
|
@@ -600,12 +610,15 @@ var OAuthProviderImpl = class {
|
|
|
600
610
|
if (grantData.clientId !== clientInfo.clientId) {
|
|
601
611
|
return this.createErrorResponse("invalid_grant", "Client ID mismatch");
|
|
602
612
|
}
|
|
613
|
+
if (grantData.expiresAt !== void 0) {
|
|
614
|
+
const now2 = Math.floor(Date.now() / 1e3);
|
|
615
|
+
if (now2 >= grantData.expiresAt) {
|
|
616
|
+
return this.createErrorResponse("invalid_grant", "Refresh token has expired");
|
|
617
|
+
}
|
|
618
|
+
}
|
|
603
619
|
const accessTokenSecret = generateRandomString(TOKEN_LENGTH);
|
|
604
620
|
const newAccessToken = `${userId}:${grantId}:${accessTokenSecret}`;
|
|
605
621
|
const accessTokenId = await generateTokenId(newAccessToken);
|
|
606
|
-
const refreshTokenSecret = generateRandomString(TOKEN_LENGTH);
|
|
607
|
-
const newRefreshToken = `${userId}:${grantId}:${refreshTokenSecret}`;
|
|
608
|
-
const newRefreshTokenId = await generateTokenId(newRefreshToken);
|
|
609
622
|
let accessTokenTTL = this.options.accessTokenTTL;
|
|
610
623
|
let wrappedKeyToUse;
|
|
611
624
|
if (isCurrentToken) {
|
|
@@ -617,6 +630,7 @@ var OAuthProviderImpl = class {
|
|
|
617
630
|
let grantEncryptionKey = encryptionKey;
|
|
618
631
|
let accessTokenEncryptionKey = encryptionKey;
|
|
619
632
|
let encryptedAccessTokenProps = grantData.encryptedProps;
|
|
633
|
+
let grantPropsChanged = false;
|
|
620
634
|
if (this.options.tokenExchangeCallback) {
|
|
621
635
|
const decryptedProps = await decryptProps(encryptionKey, grantData.encryptedProps);
|
|
622
636
|
let grantProps = decryptedProps;
|
|
@@ -629,7 +643,6 @@ var OAuthProviderImpl = class {
|
|
|
629
643
|
props: decryptedProps
|
|
630
644
|
};
|
|
631
645
|
const callbackResult = await Promise.resolve(this.options.tokenExchangeCallback(callbackOptions));
|
|
632
|
-
let grantPropsChanged = false;
|
|
633
646
|
if (callbackResult) {
|
|
634
647
|
if (callbackResult.newProps) {
|
|
635
648
|
grantProps = callbackResult.newProps;
|
|
@@ -644,6 +657,12 @@ var OAuthProviderImpl = class {
|
|
|
644
657
|
if (callbackResult.accessTokenTTL !== void 0) {
|
|
645
658
|
accessTokenTTL = callbackResult.accessTokenTTL;
|
|
646
659
|
}
|
|
660
|
+
if ("refreshTokenTTL" in callbackResult) {
|
|
661
|
+
return this.createErrorResponse(
|
|
662
|
+
"invalid_request",
|
|
663
|
+
"refreshTokenTTL cannot be changed during refresh token exchange"
|
|
664
|
+
);
|
|
665
|
+
}
|
|
647
666
|
}
|
|
648
667
|
if (grantPropsChanged) {
|
|
649
668
|
const grantResult = await encryptProps(grantProps);
|
|
@@ -665,14 +684,23 @@ var OAuthProviderImpl = class {
|
|
|
665
684
|
}
|
|
666
685
|
}
|
|
667
686
|
const now = Math.floor(Date.now() / 1e3);
|
|
687
|
+
if (grantData.expiresAt !== void 0) {
|
|
688
|
+
const remainingRefreshTokenLifetime = grantData.expiresAt - now;
|
|
689
|
+
if (remainingRefreshTokenLifetime > 0) {
|
|
690
|
+
accessTokenTTL = Math.min(accessTokenTTL, remainingRefreshTokenLifetime);
|
|
691
|
+
}
|
|
692
|
+
}
|
|
668
693
|
const accessTokenExpiresAt = now + accessTokenTTL;
|
|
669
694
|
const accessTokenWrappedKey = await wrapKeyWithToken(newAccessToken, accessTokenEncryptionKey);
|
|
695
|
+
const refreshTokenSecret = generateRandomString(TOKEN_LENGTH);
|
|
696
|
+
const newRefreshToken = `${userId}:${grantId}:${refreshTokenSecret}`;
|
|
697
|
+
const newRefreshTokenId = await generateTokenId(newRefreshToken);
|
|
670
698
|
const newRefreshTokenWrappedKey = await wrapKeyWithToken(newRefreshToken, grantEncryptionKey);
|
|
671
699
|
grantData.previousRefreshTokenId = providedTokenHash;
|
|
672
700
|
grantData.previousRefreshTokenWrappedKey = wrappedKeyToUse;
|
|
673
701
|
grantData.refreshTokenId = newRefreshTokenId;
|
|
674
702
|
grantData.refreshTokenWrappedKey = newRefreshTokenWrappedKey;
|
|
675
|
-
await
|
|
703
|
+
await this.saveGrantWithTTL(env, grantKey, grantData, now);
|
|
676
704
|
const accessTokenData = {
|
|
677
705
|
id: accessTokenId,
|
|
678
706
|
grantId,
|
|
@@ -689,18 +717,16 @@ var OAuthProviderImpl = class {
|
|
|
689
717
|
await env.OAUTH_KV.put(`token:${userId}:${grantId}:${accessTokenId}`, JSON.stringify(accessTokenData), {
|
|
690
718
|
expirationTtl: accessTokenTTL
|
|
691
719
|
});
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
{
|
|
701
|
-
|
|
702
|
-
}
|
|
703
|
-
);
|
|
720
|
+
const tokenResponse = {
|
|
721
|
+
access_token: newAccessToken,
|
|
722
|
+
token_type: "bearer",
|
|
723
|
+
expires_in: accessTokenTTL,
|
|
724
|
+
refresh_token: newRefreshToken,
|
|
725
|
+
scope: grantData.scope.join(" ")
|
|
726
|
+
};
|
|
727
|
+
return new Response(JSON.stringify(tokenResponse), {
|
|
728
|
+
headers: { "Content-Type": "application/json" }
|
|
729
|
+
});
|
|
704
730
|
}
|
|
705
731
|
/**
|
|
706
732
|
* Handles OAuth 2.0 token revocation requests (RFC 7009)
|
|
@@ -736,7 +762,7 @@ var OAuthProviderImpl = class {
|
|
|
736
762
|
} else if (isRefreshToken) {
|
|
737
763
|
await this.createOAuthHelpers(env).revokeGrant(grantId, userId);
|
|
738
764
|
}
|
|
739
|
-
return new Response("", { status:
|
|
765
|
+
return new Response("", { status: 200 });
|
|
740
766
|
}
|
|
741
767
|
/**
|
|
742
768
|
* Revokes a specific access token without affecting the refresh token
|
|
@@ -914,30 +940,40 @@ var OAuthProviderImpl = class {
|
|
|
914
940
|
});
|
|
915
941
|
}
|
|
916
942
|
const accessToken = authHeader.substring(7);
|
|
917
|
-
const
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
if (!tokenData) {
|
|
943
|
+
const parts = accessToken.split(":");
|
|
944
|
+
const isPossiblyInternalFormat = parts.length === 3;
|
|
945
|
+
let tokenData = null;
|
|
946
|
+
let userId = "";
|
|
947
|
+
let grantId = "";
|
|
948
|
+
if (isPossiblyInternalFormat) {
|
|
949
|
+
[userId, grantId] = parts;
|
|
950
|
+
const id = await generateTokenId(accessToken);
|
|
951
|
+
tokenData = await env.OAUTH_KV.get(`token:${userId}:${grantId}:${id}`, { type: "json" });
|
|
952
|
+
}
|
|
953
|
+
if (!tokenData && !this.options.resolveExternalToken) {
|
|
928
954
|
return this.createErrorResponse("invalid_token", "Invalid access token", 401, {
|
|
929
955
|
"WWW-Authenticate": 'Bearer realm="OAuth", error="invalid_token"'
|
|
930
956
|
});
|
|
931
957
|
}
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
"
|
|
936
|
-
|
|
958
|
+
if (tokenData) {
|
|
959
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
960
|
+
if (tokenData.expiresAt < now) {
|
|
961
|
+
return this.createErrorResponse("invalid_token", "Access token expired", 401, {
|
|
962
|
+
"WWW-Authenticate": 'Bearer realm="OAuth", error="invalid_token"'
|
|
963
|
+
});
|
|
964
|
+
}
|
|
965
|
+
const encryptionKey = await unwrapKeyWithToken(accessToken, tokenData.wrappedEncryptionKey);
|
|
966
|
+
const decryptedProps = await decryptProps(encryptionKey, tokenData.grant.encryptedProps);
|
|
967
|
+
ctx.props = decryptedProps;
|
|
968
|
+
} else if (this.options.resolveExternalToken) {
|
|
969
|
+
const ext = await this.options.resolveExternalToken({ token: accessToken, request, env });
|
|
970
|
+
if (!ext) {
|
|
971
|
+
return this.createErrorResponse("invalid_token", "Invalid access token", 401, {
|
|
972
|
+
"WWW-Authenticate": 'Bearer realm="OAuth", error="invalid_token"'
|
|
973
|
+
});
|
|
974
|
+
}
|
|
975
|
+
ctx.props = ext.props;
|
|
937
976
|
}
|
|
938
|
-
const encryptionKey = await unwrapKeyWithToken(accessToken, tokenData.wrappedEncryptionKey);
|
|
939
|
-
const decryptedProps = await decryptProps(encryptionKey, tokenData.grant.encryptedProps);
|
|
940
|
-
ctx.props = decryptedProps;
|
|
941
977
|
if (!env.OAUTH_PROVIDER) {
|
|
942
978
|
env.OAUTH_PROVIDER = this.createOAuthHelpers(env);
|
|
943
979
|
}
|
|
@@ -962,6 +998,17 @@ var OAuthProviderImpl = class {
|
|
|
962
998
|
createOAuthHelpers(env) {
|
|
963
999
|
return new OAuthHelpersImpl(env, this);
|
|
964
1000
|
}
|
|
1001
|
+
/**
|
|
1002
|
+
* Saves a grant to KV with appropriate TTL based on expiration
|
|
1003
|
+
* @param env - The environment bindings
|
|
1004
|
+
* @param grantKey - The KV key for the grant
|
|
1005
|
+
* @param grantData - The grant data to save
|
|
1006
|
+
* @param now - Current timestamp in seconds
|
|
1007
|
+
*/
|
|
1008
|
+
async saveGrantWithTTL(env, grantKey, grantData, now) {
|
|
1009
|
+
const kvOptions = grantData.expiresAt !== void 0 ? { expiration: grantData.expiresAt } : {};
|
|
1010
|
+
await env.OAUTH_KV.put(grantKey, JSON.stringify(grantData), kvOptions);
|
|
1011
|
+
}
|
|
965
1012
|
/**
|
|
966
1013
|
* Fetches client information from KV storage
|
|
967
1014
|
* This method is not private because `OAuthHelpers` needs to call it. Note that since
|
|
@@ -1174,6 +1221,9 @@ var OAuthHelpersImpl = class {
|
|
|
1174
1221
|
const state = url.searchParams.get("state") || "";
|
|
1175
1222
|
const codeChallenge = url.searchParams.get("code_challenge") || void 0;
|
|
1176
1223
|
const codeChallengeMethod = url.searchParams.get("code_challenge_method") || "plain";
|
|
1224
|
+
if (!redirectUri.startsWith("http://") && !redirectUri.startsWith("https://")) {
|
|
1225
|
+
throw new Error("Invalid redirect URI");
|
|
1226
|
+
}
|
|
1177
1227
|
if (responseType === "token" && !this.provider.options.allowImplicitFlow) {
|
|
1178
1228
|
throw new Error("The implicit grant flow is not enabled for this provider");
|
|
1179
1229
|
}
|
|
@@ -1441,7 +1491,8 @@ var OAuthHelpersImpl = class {
|
|
|
1441
1491
|
userId: grantData.userId,
|
|
1442
1492
|
scope: grantData.scope,
|
|
1443
1493
|
metadata: grantData.metadata,
|
|
1444
|
-
createdAt: grantData.createdAt
|
|
1494
|
+
createdAt: grantData.createdAt,
|
|
1495
|
+
expiresAt: grantData.expiresAt
|
|
1445
1496
|
};
|
|
1446
1497
|
grantSummaries.push(summary);
|
|
1447
1498
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cloudflare/workers-oauth-provider",
|
|
3
|
-
"version": "0.0.0-
|
|
3
|
+
"version": "0.0.0-d18b865",
|
|
4
4
|
"description": "OAuth provider for Cloudflare Workers",
|
|
5
5
|
"main": "dist/oauth-provider.js",
|
|
6
6
|
"types": "dist/oauth-provider.d.ts",
|
|
@@ -26,11 +26,12 @@
|
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
28
|
"@changesets/changelog-github": "^0.5.1",
|
|
29
|
-
"@changesets/cli": "^2.29.
|
|
29
|
+
"@changesets/cli": "^2.29.7",
|
|
30
30
|
"@cloudflare/workers-types": "^4.20250807.0",
|
|
31
|
+
"pkg-pr-new": "^0.0.59",
|
|
31
32
|
"prettier": "^3.6.2",
|
|
32
33
|
"tsup": "^8.5.0",
|
|
33
|
-
"tsx": "^4.20.
|
|
34
|
+
"tsx": "^4.20.5",
|
|
34
35
|
"typescript": "^5.9.2",
|
|
35
36
|
"vitest": "^3.2.4"
|
|
36
37
|
},
|