@cloudflare/workers-oauth-provider 0.0.2 → 0.0.3
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 +42 -5
- package/dist/oauth-provider.d.ts +12 -0
- package/dist/oauth-provider.js +60 -49
- package/package.json +3 -1
package/README.md
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
This is a TypeScript library that implements the provider side of the OAuth 2.1 protocol with PKCE support. The library is intended to be used on Cloudflare Workers.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Beta
|
|
6
6
|
|
|
7
|
-
As of March, 2025, this library is very new
|
|
7
|
+
As of March, 2025, this library is very new, prerelease software. The API is still subject to change.
|
|
8
8
|
|
|
9
9
|
## Benefits of this library
|
|
10
10
|
|
|
@@ -262,11 +262,34 @@ The `accessTokenTTL` override is particularly useful when the application is als
|
|
|
262
262
|
|
|
263
263
|
The `props` values are end-to-end encrypted, so they can safely contain sensitive information.
|
|
264
264
|
|
|
265
|
-
##
|
|
265
|
+
## Custom Error Responses
|
|
266
266
|
|
|
267
|
-
|
|
267
|
+
By using the `onError` option, you can emit notifications or take other actions when an error response was to be emitted:
|
|
268
268
|
|
|
269
|
-
|
|
269
|
+
```ts
|
|
270
|
+
new OAuthProvider({
|
|
271
|
+
// ... other options ...
|
|
272
|
+
onError({ code, description, status, headers }) {
|
|
273
|
+
Sentry.captureMessage(/* ... */)
|
|
274
|
+
}
|
|
275
|
+
})
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
By returning a `Response` you can also override what the OAuthProvider returns to your users:
|
|
279
|
+
|
|
280
|
+
```ts
|
|
281
|
+
new OAuthProvider({
|
|
282
|
+
// ... other options ...
|
|
283
|
+
onError({ code, description, status, headers }) {
|
|
284
|
+
if (code === 'unsupported_grant_type') {
|
|
285
|
+
return new Response('...', { status, headers })
|
|
286
|
+
}
|
|
287
|
+
// returning undefined (i.e. void) uses the default Response generation
|
|
288
|
+
}
|
|
289
|
+
})
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
By default, the `onError` callback is set to ``({ status, code, description }) => console.warn(`OAuth error response: ${status} ${code} - ${description}`)``.
|
|
270
293
|
|
|
271
294
|
## Implementation Notes
|
|
272
295
|
|
|
@@ -286,3 +309,17 @@ OAuth 2.1 requires that refresh tokens are either "cryptographically bound" to t
|
|
|
286
309
|
This requirement is seemingly fundamentally flawed as it assumes that every refresh request will complete with no errors. In the real world, a transient network error, machine failure, or software fault could mean that the client fails to store the new refresh token after a refresh request. In this case, the client would be permanently unable to make any further requests, as the only token it has is no longer valid.
|
|
287
310
|
|
|
288
311
|
This library implements a compromise: At any particular time, a grant may have two valid refresh tokens. When the client uses one of them, the other one is invalidated, and a new one is generated and returned. Thus, if the client correctly uses the new refresh token each time, then older refresh tokens are continuously invalidated. But if a transient failure prevents the client from updating its token, it can always retry the request with the token it used previously.
|
|
312
|
+
|
|
313
|
+
## Written using Claude
|
|
314
|
+
|
|
315
|
+
This library (including the schema documentation) was largely written with the help of [Claude](https://claude.ai), the AI model by Anthropic. Claude's output was thoroughly reviewed by Cloudflare engineers with careful attention paid to security and compliance with standards. Many improvements were made on the initial output, mostly again by prompting Claude (and reviewing the results). Check out the commit history to see how Claude was prompted and what code it produced.
|
|
316
|
+
|
|
317
|
+
**"NOOOOOOOO!!!! You can't just use an LLM to write an auth library!"**
|
|
318
|
+
|
|
319
|
+
"haha gpus go brrr"
|
|
320
|
+
|
|
321
|
+
In all seriousness, two months ago (January 2025), I ([@kentonv](https://github.com/kentonv)) would have agreed. I was an AI skeptic. I thoughts LLMs were glorified Markov chain generators that didn't actually understand code and couldn't produce anything novel. I started this project on a lark, fully expecting the AI to produce terrible code for me to laugh at. And then, uh... the code actually looked pretty good. Not perfect, but I just told the AI to fix things, and it did. I was shocked.
|
|
322
|
+
|
|
323
|
+
To emphasize, **this is not "vibe coded"**. Every line was thoroughly reviewed and cross-referenced with relevant RFCs, by security experts with previous experience with those RFCs. I was *trying* to validate my skepticism. I ended up proving myself wrong.
|
|
324
|
+
|
|
325
|
+
Again, please check out the commit history -- especially early commits -- to understand how this went.
|
package/dist/oauth-provider.d.ts
CHANGED
|
@@ -128,6 +128,18 @@ interface OAuthProviderOptions {
|
|
|
128
128
|
* If the callback returns nothing or undefined for a props field, the original props will be used.
|
|
129
129
|
*/
|
|
130
130
|
tokenExchangeCallback?: (options: TokenExchangeCallbackOptions) => Promise<TokenExchangeCallbackResult | void> | TokenExchangeCallbackResult | void;
|
|
131
|
+
/**
|
|
132
|
+
* Optional callback function that is called whenever the OAuthProvider returns an error response
|
|
133
|
+
* This allows the client to emit notifications or perform other actions when an error occurs.
|
|
134
|
+
*
|
|
135
|
+
* If the function returns a Response, that will be used in place of the OAuthProvider's default one.
|
|
136
|
+
*/
|
|
137
|
+
onError?: (error: {
|
|
138
|
+
code: string;
|
|
139
|
+
description: string;
|
|
140
|
+
status: number;
|
|
141
|
+
headers: Record<string, string>;
|
|
142
|
+
}) => Response | void;
|
|
131
143
|
}
|
|
132
144
|
/**
|
|
133
145
|
* Helper methods for OAuth operations provided to handler functions
|
package/dist/oauth-provider.js
CHANGED
|
@@ -52,8 +52,9 @@ var OAuthProviderImpl = class {
|
|
|
52
52
|
this.validateEndpoint(options.clientRegistrationEndpoint, "clientRegistrationEndpoint");
|
|
53
53
|
}
|
|
54
54
|
this.options = {
|
|
55
|
-
|
|
56
|
-
|
|
55
|
+
accessTokenTTL: DEFAULT_ACCESS_TOKEN_TTL,
|
|
56
|
+
onError: ({ status, code, description }) => console.warn(`OAuth error response: ${status} ${code} - ${description}`),
|
|
57
|
+
...options
|
|
57
58
|
};
|
|
58
59
|
}
|
|
59
60
|
/**
|
|
@@ -298,12 +299,12 @@ var OAuthProviderImpl = class {
|
|
|
298
299
|
*/
|
|
299
300
|
async handleTokenRequest(request, env) {
|
|
300
301
|
if (request.method !== "POST") {
|
|
301
|
-
return createErrorResponse("invalid_request", "Method not allowed", 405);
|
|
302
|
+
return this.createErrorResponse("invalid_request", "Method not allowed", 405);
|
|
302
303
|
}
|
|
303
304
|
let contentType = request.headers.get("Content-Type") || "";
|
|
304
305
|
let body = {};
|
|
305
306
|
if (!contentType.includes("application/x-www-form-urlencoded")) {
|
|
306
|
-
return createErrorResponse("invalid_request", "Content-Type must be application/x-www-form-urlencoded", 400);
|
|
307
|
+
return this.createErrorResponse("invalid_request", "Content-Type must be application/x-www-form-urlencoded", 400);
|
|
307
308
|
}
|
|
308
309
|
const formData = await request.formData();
|
|
309
310
|
for (const [key, value] of formData.entries()) {
|
|
@@ -322,19 +323,19 @@ var OAuthProviderImpl = class {
|
|
|
322
323
|
clientSecret = body.client_secret || "";
|
|
323
324
|
}
|
|
324
325
|
if (!clientId) {
|
|
325
|
-
return createErrorResponse("invalid_client", "Client ID is required", 401);
|
|
326
|
+
return this.createErrorResponse("invalid_client", "Client ID is required", 401);
|
|
326
327
|
}
|
|
327
328
|
const clientInfo = await this.getClient(env, clientId);
|
|
328
329
|
if (!clientInfo) {
|
|
329
|
-
return createErrorResponse("invalid_client", "Client not found", 401);
|
|
330
|
+
return this.createErrorResponse("invalid_client", "Client not found", 401);
|
|
330
331
|
}
|
|
331
332
|
const isPublicClient = clientInfo.tokenEndpointAuthMethod === "none";
|
|
332
333
|
if (!isPublicClient) {
|
|
333
334
|
if (!clientSecret) {
|
|
334
|
-
return createErrorResponse("invalid_client", "Client authentication failed: missing client_secret", 401);
|
|
335
|
+
return this.createErrorResponse("invalid_client", "Client authentication failed: missing client_secret", 401);
|
|
335
336
|
}
|
|
336
337
|
if (!clientInfo.clientSecret) {
|
|
337
|
-
return createErrorResponse(
|
|
338
|
+
return this.createErrorResponse(
|
|
338
339
|
"invalid_client",
|
|
339
340
|
"Client authentication failed: client has no registered secret",
|
|
340
341
|
401
|
|
@@ -342,7 +343,7 @@ var OAuthProviderImpl = class {
|
|
|
342
343
|
}
|
|
343
344
|
const providedSecretHash = await hashSecret(clientSecret);
|
|
344
345
|
if (providedSecretHash !== clientInfo.clientSecret) {
|
|
345
|
-
return createErrorResponse("invalid_client", "Client authentication failed: invalid client_secret", 401);
|
|
346
|
+
return this.createErrorResponse("invalid_client", "Client authentication failed: invalid client_secret", 401);
|
|
346
347
|
}
|
|
347
348
|
}
|
|
348
349
|
const grantType = body.grant_type;
|
|
@@ -351,7 +352,7 @@ var OAuthProviderImpl = class {
|
|
|
351
352
|
} else if (grantType === "refresh_token") {
|
|
352
353
|
return this.handleRefreshTokenGrant(body, clientInfo, env);
|
|
353
354
|
} else {
|
|
354
|
-
return createErrorResponse("unsupported_grant_type", "Grant type not supported");
|
|
355
|
+
return this.createErrorResponse("unsupported_grant_type", "Grant type not supported");
|
|
355
356
|
}
|
|
356
357
|
}
|
|
357
358
|
/**
|
|
@@ -367,38 +368,38 @@ var OAuthProviderImpl = class {
|
|
|
367
368
|
const redirectUri = body.redirect_uri;
|
|
368
369
|
const codeVerifier = body.code_verifier;
|
|
369
370
|
if (!code) {
|
|
370
|
-
return createErrorResponse("invalid_request", "Authorization code is required");
|
|
371
|
+
return this.createErrorResponse("invalid_request", "Authorization code is required");
|
|
371
372
|
}
|
|
372
373
|
const codeParts = code.split(":");
|
|
373
374
|
if (codeParts.length !== 3) {
|
|
374
|
-
return createErrorResponse("invalid_grant", "Invalid authorization code format");
|
|
375
|
+
return this.createErrorResponse("invalid_grant", "Invalid authorization code format");
|
|
375
376
|
}
|
|
376
377
|
const [userId, grantId, _] = codeParts;
|
|
377
378
|
const grantKey = `grant:${userId}:${grantId}`;
|
|
378
379
|
const grantData = await env.OAUTH_KV.get(grantKey, { type: "json" });
|
|
379
380
|
if (!grantData) {
|
|
380
|
-
return createErrorResponse("invalid_grant", "Grant not found or authorization code expired");
|
|
381
|
+
return this.createErrorResponse("invalid_grant", "Grant not found or authorization code expired");
|
|
381
382
|
}
|
|
382
383
|
if (!grantData.authCodeId) {
|
|
383
|
-
return createErrorResponse("invalid_grant", "Authorization code already used");
|
|
384
|
+
return this.createErrorResponse("invalid_grant", "Authorization code already used");
|
|
384
385
|
}
|
|
385
386
|
const codeHash = await hashSecret(code);
|
|
386
387
|
if (codeHash !== grantData.authCodeId) {
|
|
387
|
-
return createErrorResponse("invalid_grant", "Invalid authorization code");
|
|
388
|
+
return this.createErrorResponse("invalid_grant", "Invalid authorization code");
|
|
388
389
|
}
|
|
389
390
|
if (grantData.clientId !== clientInfo.clientId) {
|
|
390
|
-
return createErrorResponse("invalid_grant", "Client ID mismatch");
|
|
391
|
+
return this.createErrorResponse("invalid_grant", "Client ID mismatch");
|
|
391
392
|
}
|
|
392
393
|
const isPkceEnabled = !!grantData.codeChallenge;
|
|
393
394
|
if (!redirectUri && !isPkceEnabled) {
|
|
394
|
-
return createErrorResponse("invalid_request", "redirect_uri is required when not using PKCE");
|
|
395
|
+
return this.createErrorResponse("invalid_request", "redirect_uri is required when not using PKCE");
|
|
395
396
|
}
|
|
396
397
|
if (redirectUri && !clientInfo.redirectUris.includes(redirectUri)) {
|
|
397
|
-
return createErrorResponse("invalid_grant", "Invalid redirect URI");
|
|
398
|
+
return this.createErrorResponse("invalid_grant", "Invalid redirect URI");
|
|
398
399
|
}
|
|
399
400
|
if (isPkceEnabled) {
|
|
400
401
|
if (!codeVerifier) {
|
|
401
|
-
return createErrorResponse("invalid_request", "code_verifier is required for PKCE");
|
|
402
|
+
return this.createErrorResponse("invalid_request", "code_verifier is required for PKCE");
|
|
402
403
|
}
|
|
403
404
|
let calculatedChallenge;
|
|
404
405
|
if (grantData.codeChallengeMethod === "S256") {
|
|
@@ -411,7 +412,7 @@ var OAuthProviderImpl = class {
|
|
|
411
412
|
calculatedChallenge = codeVerifier;
|
|
412
413
|
}
|
|
413
414
|
if (calculatedChallenge !== grantData.codeChallenge) {
|
|
414
|
-
return createErrorResponse("invalid_grant", "Invalid PKCE code_verifier");
|
|
415
|
+
return this.createErrorResponse("invalid_grant", "Invalid PKCE code_verifier");
|
|
415
416
|
}
|
|
416
417
|
}
|
|
417
418
|
const accessTokenSecret = generateRandomString(TOKEN_LENGTH);
|
|
@@ -516,26 +517,26 @@ var OAuthProviderImpl = class {
|
|
|
516
517
|
async handleRefreshTokenGrant(body, clientInfo, env) {
|
|
517
518
|
const refreshToken = body.refresh_token;
|
|
518
519
|
if (!refreshToken) {
|
|
519
|
-
return createErrorResponse("invalid_request", "Refresh token is required");
|
|
520
|
+
return this.createErrorResponse("invalid_request", "Refresh token is required");
|
|
520
521
|
}
|
|
521
522
|
const tokenParts = refreshToken.split(":");
|
|
522
523
|
if (tokenParts.length !== 3) {
|
|
523
|
-
return createErrorResponse("invalid_grant", "Invalid token format");
|
|
524
|
+
return this.createErrorResponse("invalid_grant", "Invalid token format");
|
|
524
525
|
}
|
|
525
526
|
const [userId, grantId, _] = tokenParts;
|
|
526
527
|
const providedTokenHash = await generateTokenId(refreshToken);
|
|
527
528
|
const grantKey = `grant:${userId}:${grantId}`;
|
|
528
529
|
const grantData = await env.OAUTH_KV.get(grantKey, { type: "json" });
|
|
529
530
|
if (!grantData) {
|
|
530
|
-
return createErrorResponse("invalid_grant", "Grant not found");
|
|
531
|
+
return this.createErrorResponse("invalid_grant", "Grant not found");
|
|
531
532
|
}
|
|
532
533
|
const isCurrentToken = grantData.refreshTokenId === providedTokenHash;
|
|
533
534
|
const isPreviousToken = grantData.previousRefreshTokenId === providedTokenHash;
|
|
534
535
|
if (!isCurrentToken && !isPreviousToken) {
|
|
535
|
-
return createErrorResponse("invalid_grant", "Invalid refresh token");
|
|
536
|
+
return this.createErrorResponse("invalid_grant", "Invalid refresh token");
|
|
536
537
|
}
|
|
537
538
|
if (grantData.clientId !== clientInfo.clientId) {
|
|
538
|
-
return createErrorResponse("invalid_grant", "Client ID mismatch");
|
|
539
|
+
return this.createErrorResponse("invalid_grant", "Client ID mismatch");
|
|
539
540
|
}
|
|
540
541
|
const accessTokenSecret = generateRandomString(TOKEN_LENGTH);
|
|
541
542
|
const newAccessToken = `${userId}:${grantId}:${accessTokenSecret}`;
|
|
@@ -647,24 +648,24 @@ var OAuthProviderImpl = class {
|
|
|
647
648
|
*/
|
|
648
649
|
async handleClientRegistration(request, env) {
|
|
649
650
|
if (!this.options.clientRegistrationEndpoint) {
|
|
650
|
-
return createErrorResponse("not_implemented", "Client registration is not enabled", 501);
|
|
651
|
+
return this.createErrorResponse("not_implemented", "Client registration is not enabled", 501);
|
|
651
652
|
}
|
|
652
653
|
if (request.method !== "POST") {
|
|
653
|
-
return createErrorResponse("invalid_request", "Method not allowed", 405);
|
|
654
|
+
return this.createErrorResponse("invalid_request", "Method not allowed", 405);
|
|
654
655
|
}
|
|
655
656
|
const contentLength = parseInt(request.headers.get("Content-Length") || "0", 10);
|
|
656
657
|
if (contentLength > 1048576) {
|
|
657
|
-
return createErrorResponse("invalid_request", "Request payload too large, must be under 1 MiB", 413);
|
|
658
|
+
return this.createErrorResponse("invalid_request", "Request payload too large, must be under 1 MiB", 413);
|
|
658
659
|
}
|
|
659
660
|
let clientMetadata;
|
|
660
661
|
try {
|
|
661
662
|
const text = await request.text();
|
|
662
663
|
if (text.length > 1048576) {
|
|
663
|
-
return createErrorResponse("invalid_request", "Request payload too large, must be under 1 MiB", 413);
|
|
664
|
+
return this.createErrorResponse("invalid_request", "Request payload too large, must be under 1 MiB", 413);
|
|
664
665
|
}
|
|
665
666
|
clientMetadata = JSON.parse(text);
|
|
666
667
|
} catch (error) {
|
|
667
|
-
return createErrorResponse("invalid_request", "Invalid JSON payload", 400);
|
|
668
|
+
return this.createErrorResponse("invalid_request", "Invalid JSON payload", 400);
|
|
668
669
|
}
|
|
669
670
|
const validateStringField = (field) => {
|
|
670
671
|
if (field === void 0) {
|
|
@@ -692,7 +693,7 @@ var OAuthProviderImpl = class {
|
|
|
692
693
|
const authMethod = validateStringField(clientMetadata.token_endpoint_auth_method) || "client_secret_basic";
|
|
693
694
|
const isPublicClient = authMethod === "none";
|
|
694
695
|
if (isPublicClient && this.options.disallowPublicClientRegistration) {
|
|
695
|
-
return createErrorResponse("invalid_client_metadata", "Public client registration is not allowed");
|
|
696
|
+
return this.createErrorResponse("invalid_client_metadata", "Public client registration is not allowed");
|
|
696
697
|
}
|
|
697
698
|
const clientId = generateRandomString(16);
|
|
698
699
|
let clientSecret;
|
|
@@ -726,7 +727,7 @@ var OAuthProviderImpl = class {
|
|
|
726
727
|
clientInfo.clientSecret = hashedSecret;
|
|
727
728
|
}
|
|
728
729
|
} catch (error) {
|
|
729
|
-
return createErrorResponse(
|
|
730
|
+
return this.createErrorResponse(
|
|
730
731
|
"invalid_client_metadata",
|
|
731
732
|
error instanceof Error ? error.message : "Invalid client metadata"
|
|
732
733
|
);
|
|
@@ -766,14 +767,14 @@ var OAuthProviderImpl = class {
|
|
|
766
767
|
async handleApiRequest(request, env, ctx) {
|
|
767
768
|
const authHeader = request.headers.get("Authorization");
|
|
768
769
|
if (!authHeader || !authHeader.startsWith("Bearer ")) {
|
|
769
|
-
return createErrorResponse("invalid_token", "Missing or invalid access token", 401, {
|
|
770
|
+
return this.createErrorResponse("invalid_token", "Missing or invalid access token", 401, {
|
|
770
771
|
"WWW-Authenticate": 'Bearer realm="OAuth", error="invalid_token", error_description="Missing or invalid access token"'
|
|
771
772
|
});
|
|
772
773
|
}
|
|
773
774
|
const accessToken = authHeader.substring(7);
|
|
774
775
|
const tokenParts = accessToken.split(":");
|
|
775
776
|
if (tokenParts.length !== 3) {
|
|
776
|
-
return createErrorResponse("invalid_token", "Invalid token format", 401, {
|
|
777
|
+
return this.createErrorResponse("invalid_token", "Invalid token format", 401, {
|
|
777
778
|
"WWW-Authenticate": 'Bearer realm="OAuth", error="invalid_token"'
|
|
778
779
|
});
|
|
779
780
|
}
|
|
@@ -782,13 +783,13 @@ var OAuthProviderImpl = class {
|
|
|
782
783
|
const tokenKey = `token:${userId}:${grantId}:${accessTokenId}`;
|
|
783
784
|
const tokenData = await env.OAUTH_KV.get(tokenKey, { type: "json" });
|
|
784
785
|
if (!tokenData) {
|
|
785
|
-
return createErrorResponse("invalid_token", "Invalid access token", 401, {
|
|
786
|
+
return this.createErrorResponse("invalid_token", "Invalid access token", 401, {
|
|
786
787
|
"WWW-Authenticate": 'Bearer realm="OAuth", error="invalid_token"'
|
|
787
788
|
});
|
|
788
789
|
}
|
|
789
790
|
const now = Math.floor(Date.now() / 1e3);
|
|
790
791
|
if (tokenData.expiresAt < now) {
|
|
791
|
-
return createErrorResponse("invalid_token", "Access token expired", 401, {
|
|
792
|
+
return this.createErrorResponse("invalid_token", "Access token expired", 401, {
|
|
792
793
|
"WWW-Authenticate": 'Bearer realm="OAuth", error="invalid_token"'
|
|
793
794
|
});
|
|
794
795
|
}
|
|
@@ -827,22 +828,32 @@ var OAuthProviderImpl = class {
|
|
|
827
828
|
const clientKey = `client:${clientId}`;
|
|
828
829
|
return env.OAUTH_KV.get(clientKey, { type: "json" });
|
|
829
830
|
}
|
|
831
|
+
/**
|
|
832
|
+
* Helper function to create OAuth error responses
|
|
833
|
+
* @param code - OAuth error code (e.g., 'invalid_request', 'invalid_token')
|
|
834
|
+
* @param description - Human-readable error description
|
|
835
|
+
* @param status - HTTP status code (default: 400)
|
|
836
|
+
* @param headers - Additional headers to include
|
|
837
|
+
* @returns A Response object with the error
|
|
838
|
+
*/
|
|
839
|
+
createErrorResponse(code, description, status = 400, headers = {}) {
|
|
840
|
+
const customErrorResponse = this.options.onError?.({ code, description, status, headers });
|
|
841
|
+
if (customErrorResponse) return customErrorResponse;
|
|
842
|
+
const body = JSON.stringify({
|
|
843
|
+
error: code,
|
|
844
|
+
error_description: description
|
|
845
|
+
});
|
|
846
|
+
return new Response(body, {
|
|
847
|
+
status,
|
|
848
|
+
headers: {
|
|
849
|
+
"Content-Type": "application/json",
|
|
850
|
+
...headers
|
|
851
|
+
}
|
|
852
|
+
});
|
|
853
|
+
}
|
|
830
854
|
};
|
|
831
855
|
var DEFAULT_ACCESS_TOKEN_TTL = 60 * 60;
|
|
832
856
|
var TOKEN_LENGTH = 32;
|
|
833
|
-
function createErrorResponse(code, description, status = 400, headers = {}) {
|
|
834
|
-
const body = JSON.stringify({
|
|
835
|
-
error: code,
|
|
836
|
-
error_description: description
|
|
837
|
-
});
|
|
838
|
-
return new Response(body, {
|
|
839
|
-
status,
|
|
840
|
-
headers: {
|
|
841
|
-
"Content-Type": "application/json",
|
|
842
|
-
...headers
|
|
843
|
-
}
|
|
844
|
-
});
|
|
845
|
-
}
|
|
846
857
|
async function hashSecret(secret) {
|
|
847
858
|
return generateTokenId(secret);
|
|
848
859
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cloudflare/workers-oauth-provider",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.3",
|
|
4
4
|
"description": "OAuth provider for Cloudflare Workers",
|
|
5
5
|
"main": "dist/oauth-provider.js",
|
|
6
6
|
"types": "dist/oauth-provider.d.ts",
|
|
@@ -15,7 +15,9 @@
|
|
|
15
15
|
},
|
|
16
16
|
"scripts": {
|
|
17
17
|
"build": "tsup",
|
|
18
|
+
"build:watch": "tsup --watch",
|
|
18
19
|
"test": "vitest run",
|
|
20
|
+
"test:watch": "vitest",
|
|
19
21
|
"prepublishOnly": "npm run build",
|
|
20
22
|
"prettier": "prettier -w ."
|
|
21
23
|
},
|