@hammadj/better-auth-core 1.5.0-beta.9
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/.turbo/turbo-build.log +266 -0
- package/.turbo/turbo-test.log +2 -0
- package/LICENSE.md +20 -0
- package/dist/api/index.d.mts +181 -0
- package/dist/api/index.mjs +34 -0
- package/dist/api/index.mjs.map +1 -0
- package/dist/async_hooks/index.d.mts +7 -0
- package/dist/async_hooks/index.mjs +22 -0
- package/dist/async_hooks/index.mjs.map +1 -0
- package/dist/async_hooks/pure.index.d.mts +7 -0
- package/dist/async_hooks/pure.index.mjs +35 -0
- package/dist/async_hooks/pure.index.mjs.map +1 -0
- package/dist/context/endpoint-context.d.mts +19 -0
- package/dist/context/endpoint-context.mjs +32 -0
- package/dist/context/endpoint-context.mjs.map +1 -0
- package/dist/context/global.d.mts +7 -0
- package/dist/context/global.mjs +38 -0
- package/dist/context/global.mjs.map +1 -0
- package/dist/context/index.d.mts +5 -0
- package/dist/context/index.mjs +6 -0
- package/dist/context/request-state.d.mts +26 -0
- package/dist/context/request-state.mjs +50 -0
- package/dist/context/request-state.mjs.map +1 -0
- package/dist/context/transaction.d.mts +25 -0
- package/dist/context/transaction.mjs +96 -0
- package/dist/context/transaction.mjs.map +1 -0
- package/dist/db/adapter/factory.d.mts +28 -0
- package/dist/db/adapter/factory.mjs +716 -0
- package/dist/db/adapter/factory.mjs.map +1 -0
- package/dist/db/adapter/get-default-field-name.d.mts +19 -0
- package/dist/db/adapter/get-default-field-name.mjs +39 -0
- package/dist/db/adapter/get-default-field-name.mjs.map +1 -0
- package/dist/db/adapter/get-default-model-name.d.mts +13 -0
- package/dist/db/adapter/get-default-model-name.mjs +33 -0
- package/dist/db/adapter/get-default-model-name.mjs.map +1 -0
- package/dist/db/adapter/get-field-attributes.d.mts +30 -0
- package/dist/db/adapter/get-field-attributes.mjs +40 -0
- package/dist/db/adapter/get-field-attributes.mjs.map +1 -0
- package/dist/db/adapter/get-field-name.d.mts +19 -0
- package/dist/db/adapter/get-field-name.mjs +34 -0
- package/dist/db/adapter/get-field-name.mjs.map +1 -0
- package/dist/db/adapter/get-id-field.d.mts +40 -0
- package/dist/db/adapter/get-id-field.mjs +68 -0
- package/dist/db/adapter/get-id-field.mjs.map +1 -0
- package/dist/db/adapter/get-model-name.d.mts +13 -0
- package/dist/db/adapter/get-model-name.mjs +24 -0
- package/dist/db/adapter/get-model-name.mjs.map +1 -0
- package/dist/db/adapter/index.d.mts +515 -0
- package/dist/db/adapter/index.mjs +10 -0
- package/dist/db/adapter/types.d.mts +140 -0
- package/dist/db/adapter/utils.d.mts +8 -0
- package/dist/db/adapter/utils.mjs +39 -0
- package/dist/db/adapter/utils.mjs.map +1 -0
- package/dist/db/get-tables.d.mts +9 -0
- package/dist/db/get-tables.mjs +267 -0
- package/dist/db/get-tables.mjs.map +1 -0
- package/dist/db/index.d.mts +10 -0
- package/dist/db/index.mjs +9 -0
- package/dist/db/plugin.d.mts +13 -0
- package/dist/db/schema/account.d.mts +27 -0
- package/dist/db/schema/account.mjs +20 -0
- package/dist/db/schema/account.mjs.map +1 -0
- package/dist/db/schema/rate-limit.d.mts +15 -0
- package/dist/db/schema/rate-limit.mjs +12 -0
- package/dist/db/schema/rate-limit.mjs.map +1 -0
- package/dist/db/schema/session.d.mts +22 -0
- package/dist/db/schema/session.mjs +15 -0
- package/dist/db/schema/session.mjs.map +1 -0
- package/dist/db/schema/shared.d.mts +11 -0
- package/dist/db/schema/shared.mjs +12 -0
- package/dist/db/schema/shared.mjs.map +1 -0
- package/dist/db/schema/user.d.mts +21 -0
- package/dist/db/schema/user.mjs +14 -0
- package/dist/db/schema/user.mjs.map +1 -0
- package/dist/db/schema/verification.d.mts +20 -0
- package/dist/db/schema/verification.mjs +13 -0
- package/dist/db/schema/verification.mjs.map +1 -0
- package/dist/db/type.d.mts +147 -0
- package/dist/env/color-depth.d.mts +5 -0
- package/dist/env/color-depth.mjs +89 -0
- package/dist/env/color-depth.mjs.map +1 -0
- package/dist/env/env-impl.d.mts +33 -0
- package/dist/env/env-impl.mjs +83 -0
- package/dist/env/env-impl.mjs.map +1 -0
- package/dist/env/index.d.mts +4 -0
- package/dist/env/index.mjs +5 -0
- package/dist/env/logger.d.mts +49 -0
- package/dist/env/logger.mjs +82 -0
- package/dist/env/logger.mjs.map +1 -0
- package/dist/error/codes.d.mts +199 -0
- package/dist/error/codes.mjs +57 -0
- package/dist/error/codes.mjs.map +1 -0
- package/dist/error/index.d.mts +20 -0
- package/dist/error/index.mjs +30 -0
- package/dist/error/index.mjs.map +1 -0
- package/dist/index.d.mts +8 -0
- package/dist/index.mjs +1 -0
- package/dist/oauth2/client-credentials-token.d.mts +37 -0
- package/dist/oauth2/client-credentials-token.mjs +55 -0
- package/dist/oauth2/client-credentials-token.mjs.map +1 -0
- package/dist/oauth2/create-authorization-url.d.mts +46 -0
- package/dist/oauth2/create-authorization-url.mjs +43 -0
- package/dist/oauth2/create-authorization-url.mjs.map +1 -0
- package/dist/oauth2/index.d.mts +8 -0
- package/dist/oauth2/index.mjs +8 -0
- package/dist/oauth2/oauth-provider.d.mts +195 -0
- package/dist/oauth2/refresh-access-token.d.mts +36 -0
- package/dist/oauth2/refresh-access-token.mjs +59 -0
- package/dist/oauth2/refresh-access-token.mjs.map +1 -0
- package/dist/oauth2/utils.d.mts +8 -0
- package/dist/oauth2/utils.mjs +28 -0
- package/dist/oauth2/utils.mjs.map +1 -0
- package/dist/oauth2/validate-authorization-code.d.mts +56 -0
- package/dist/oauth2/validate-authorization-code.mjs +72 -0
- package/dist/oauth2/validate-authorization-code.mjs.map +1 -0
- package/dist/oauth2/verify.d.mts +43 -0
- package/dist/oauth2/verify.mjs +96 -0
- package/dist/oauth2/verify.mjs.map +1 -0
- package/dist/social-providers/apple.d.mts +120 -0
- package/dist/social-providers/apple.mjs +105 -0
- package/dist/social-providers/apple.mjs.map +1 -0
- package/dist/social-providers/atlassian.d.mts +73 -0
- package/dist/social-providers/atlassian.mjs +84 -0
- package/dist/social-providers/atlassian.mjs.map +1 -0
- package/dist/social-providers/cognito.d.mts +88 -0
- package/dist/social-providers/cognito.mjs +166 -0
- package/dist/social-providers/cognito.mjs.map +1 -0
- package/dist/social-providers/discord.d.mts +127 -0
- package/dist/social-providers/discord.mjs +65 -0
- package/dist/social-providers/discord.mjs.map +1 -0
- package/dist/social-providers/dropbox.d.mts +72 -0
- package/dist/social-providers/dropbox.mjs +76 -0
- package/dist/social-providers/dropbox.mjs.map +1 -0
- package/dist/social-providers/facebook.d.mts +82 -0
- package/dist/social-providers/facebook.mjs +121 -0
- package/dist/social-providers/facebook.mjs.map +1 -0
- package/dist/social-providers/figma.d.mts +64 -0
- package/dist/social-providers/figma.mjs +87 -0
- package/dist/social-providers/figma.mjs.map +1 -0
- package/dist/social-providers/github.d.mts +105 -0
- package/dist/social-providers/github.mjs +97 -0
- package/dist/social-providers/github.mjs.map +1 -0
- package/dist/social-providers/gitlab.d.mts +126 -0
- package/dist/social-providers/gitlab.mjs +83 -0
- package/dist/social-providers/gitlab.mjs.map +1 -0
- package/dist/social-providers/google.d.mts +100 -0
- package/dist/social-providers/google.mjs +109 -0
- package/dist/social-providers/google.mjs.map +1 -0
- package/dist/social-providers/huggingface.d.mts +86 -0
- package/dist/social-providers/huggingface.mjs +76 -0
- package/dist/social-providers/huggingface.mjs.map +1 -0
- package/dist/social-providers/index.d.mts +1725 -0
- package/dist/social-providers/index.mjs +77 -0
- package/dist/social-providers/index.mjs.map +1 -0
- package/dist/social-providers/kakao.d.mts +164 -0
- package/dist/social-providers/kakao.mjs +73 -0
- package/dist/social-providers/kakao.mjs.map +1 -0
- package/dist/social-providers/kick.d.mts +76 -0
- package/dist/social-providers/kick.mjs +72 -0
- package/dist/social-providers/kick.mjs.map +1 -0
- package/dist/social-providers/line.d.mts +108 -0
- package/dist/social-providers/line.mjs +114 -0
- package/dist/social-providers/line.mjs.map +1 -0
- package/dist/social-providers/linear.d.mts +71 -0
- package/dist/social-providers/linear.mjs +89 -0
- package/dist/social-providers/linear.mjs.map +1 -0
- package/dist/social-providers/linkedin.d.mts +70 -0
- package/dist/social-providers/linkedin.mjs +77 -0
- package/dist/social-providers/linkedin.mjs.map +1 -0
- package/dist/social-providers/microsoft-entra-id.d.mts +175 -0
- package/dist/social-providers/microsoft-entra-id.mjs +107 -0
- package/dist/social-providers/microsoft-entra-id.mjs.map +1 -0
- package/dist/social-providers/naver.d.mts +95 -0
- package/dist/social-providers/naver.mjs +68 -0
- package/dist/social-providers/naver.mjs.map +1 -0
- package/dist/social-providers/notion.d.mts +67 -0
- package/dist/social-providers/notion.mjs +76 -0
- package/dist/social-providers/notion.mjs.map +1 -0
- package/dist/social-providers/paybin.d.mts +74 -0
- package/dist/social-providers/paybin.mjs +86 -0
- package/dist/social-providers/paybin.mjs.map +1 -0
- package/dist/social-providers/paypal.d.mts +132 -0
- package/dist/social-providers/paypal.mjs +145 -0
- package/dist/social-providers/paypal.mjs.map +1 -0
- package/dist/social-providers/polar.d.mts +77 -0
- package/dist/social-providers/polar.mjs +74 -0
- package/dist/social-providers/polar.mjs.map +1 -0
- package/dist/social-providers/reddit.d.mts +65 -0
- package/dist/social-providers/reddit.mjs +84 -0
- package/dist/social-providers/reddit.mjs.map +1 -0
- package/dist/social-providers/roblox.d.mts +73 -0
- package/dist/social-providers/roblox.mjs +60 -0
- package/dist/social-providers/roblox.mjs.map +1 -0
- package/dist/social-providers/salesforce.d.mts +82 -0
- package/dist/social-providers/salesforce.mjs +92 -0
- package/dist/social-providers/salesforce.mjs.map +1 -0
- package/dist/social-providers/slack.d.mts +86 -0
- package/dist/social-providers/slack.mjs +69 -0
- package/dist/social-providers/slack.mjs.map +1 -0
- package/dist/social-providers/spotify.d.mts +66 -0
- package/dist/social-providers/spotify.mjs +72 -0
- package/dist/social-providers/spotify.mjs.map +1 -0
- package/dist/social-providers/tiktok.d.mts +171 -0
- package/dist/social-providers/tiktok.mjs +63 -0
- package/dist/social-providers/tiktok.mjs.map +1 -0
- package/dist/social-providers/twitch.d.mts +82 -0
- package/dist/social-providers/twitch.mjs +79 -0
- package/dist/social-providers/twitch.mjs.map +1 -0
- package/dist/social-providers/twitter.d.mts +129 -0
- package/dist/social-providers/twitter.mjs +88 -0
- package/dist/social-providers/twitter.mjs.map +1 -0
- package/dist/social-providers/vercel.d.mts +65 -0
- package/dist/social-providers/vercel.mjs +62 -0
- package/dist/social-providers/vercel.mjs.map +1 -0
- package/dist/social-providers/vk.d.mts +73 -0
- package/dist/social-providers/vk.mjs +84 -0
- package/dist/social-providers/vk.mjs.map +1 -0
- package/dist/social-providers/zoom.d.mts +173 -0
- package/dist/social-providers/zoom.mjs +73 -0
- package/dist/social-providers/zoom.mjs.map +1 -0
- package/dist/types/context.d.mts +267 -0
- package/dist/types/cookie.d.mts +16 -0
- package/dist/types/helper.d.mts +10 -0
- package/dist/types/index.d.mts +8 -0
- package/dist/types/init-options.d.mts +1314 -0
- package/dist/types/plugin-client.d.mts +112 -0
- package/dist/types/plugin.d.mts +125 -0
- package/dist/utils/db.d.mts +12 -0
- package/dist/utils/db.mjs +17 -0
- package/dist/utils/db.mjs.map +1 -0
- package/dist/utils/deprecate.d.mts +10 -0
- package/dist/utils/deprecate.mjs +18 -0
- package/dist/utils/deprecate.mjs.map +1 -0
- package/dist/utils/error-codes.d.mts +13 -0
- package/dist/utils/error-codes.mjs +12 -0
- package/dist/utils/error-codes.mjs.map +1 -0
- package/dist/utils/id.d.mts +5 -0
- package/dist/utils/id.mjs +10 -0
- package/dist/utils/id.mjs.map +1 -0
- package/dist/utils/ip.d.mts +55 -0
- package/dist/utils/ip.mjs +119 -0
- package/dist/utils/ip.mjs.map +1 -0
- package/dist/utils/json.d.mts +5 -0
- package/dist/utils/json.mjs +26 -0
- package/dist/utils/json.mjs.map +1 -0
- package/dist/utils/string.d.mts +5 -0
- package/dist/utils/string.mjs +8 -0
- package/dist/utils/string.mjs.map +1 -0
- package/dist/utils/url.d.mts +21 -0
- package/dist/utils/url.mjs +33 -0
- package/dist/utils/url.mjs.map +1 -0
- package/package.json +147 -0
- package/src/api/index.ts +106 -0
- package/src/async_hooks/index.ts +40 -0
- package/src/async_hooks/pure.index.ts +46 -0
- package/src/context/endpoint-context.ts +50 -0
- package/src/context/global.ts +57 -0
- package/src/context/index.ts +23 -0
- package/src/context/request-state.test.ts +94 -0
- package/src/context/request-state.ts +91 -0
- package/src/context/transaction.ts +136 -0
- package/src/db/adapter/factory.ts +1362 -0
- package/src/db/adapter/get-default-field-name.ts +59 -0
- package/src/db/adapter/get-default-model-name.ts +51 -0
- package/src/db/adapter/get-field-attributes.ts +62 -0
- package/src/db/adapter/get-field-name.ts +43 -0
- package/src/db/adapter/get-id-field.ts +141 -0
- package/src/db/adapter/get-model-name.ts +36 -0
- package/src/db/adapter/index.ts +554 -0
- package/src/db/adapter/types.ts +171 -0
- package/src/db/adapter/utils.ts +61 -0
- package/src/db/get-tables.ts +296 -0
- package/src/db/index.ts +18 -0
- package/src/db/plugin.ts +11 -0
- package/src/db/schema/account.ts +34 -0
- package/src/db/schema/rate-limit.ts +21 -0
- package/src/db/schema/session.ts +17 -0
- package/src/db/schema/shared.ts +7 -0
- package/src/db/schema/user.ts +16 -0
- package/src/db/schema/verification.ts +15 -0
- package/src/db/test/get-tables.test.ts +116 -0
- package/src/db/type.ts +180 -0
- package/src/env/color-depth.ts +172 -0
- package/src/env/env-impl.ts +124 -0
- package/src/env/index.ts +23 -0
- package/src/env/logger.test.ts +34 -0
- package/src/env/logger.ts +145 -0
- package/src/error/codes.ts +58 -0
- package/src/error/index.ts +35 -0
- package/src/index.ts +1 -0
- package/src/oauth2/client-credentials-token.ts +102 -0
- package/src/oauth2/create-authorization-url.ts +87 -0
- package/src/oauth2/index.ts +26 -0
- package/src/oauth2/oauth-provider.ts +222 -0
- package/src/oauth2/refresh-access-token.ts +124 -0
- package/src/oauth2/utils.ts +38 -0
- package/src/oauth2/validate-authorization-code.ts +149 -0
- package/src/oauth2/validate-token.test.ts +174 -0
- package/src/oauth2/verify.ts +221 -0
- package/src/social-providers/apple.ts +223 -0
- package/src/social-providers/atlassian.ts +132 -0
- package/src/social-providers/cognito.ts +279 -0
- package/src/social-providers/discord.ts +169 -0
- package/src/social-providers/dropbox.ts +112 -0
- package/src/social-providers/facebook.ts +206 -0
- package/src/social-providers/figma.ts +117 -0
- package/src/social-providers/github.ts +184 -0
- package/src/social-providers/gitlab.ts +155 -0
- package/src/social-providers/google.ts +199 -0
- package/src/social-providers/huggingface.ts +118 -0
- package/src/social-providers/index.ts +127 -0
- package/src/social-providers/kakao.ts +178 -0
- package/src/social-providers/kick.ts +109 -0
- package/src/social-providers/line.ts +169 -0
- package/src/social-providers/linear.ts +121 -0
- package/src/social-providers/linkedin.ts +110 -0
- package/src/social-providers/microsoft-entra-id.ts +259 -0
- package/src/social-providers/naver.ts +112 -0
- package/src/social-providers/notion.ts +108 -0
- package/src/social-providers/paybin.ts +122 -0
- package/src/social-providers/paypal.ts +263 -0
- package/src/social-providers/polar.ts +110 -0
- package/src/social-providers/reddit.ts +122 -0
- package/src/social-providers/roblox.ts +111 -0
- package/src/social-providers/salesforce.ts +159 -0
- package/src/social-providers/slack.ts +111 -0
- package/src/social-providers/spotify.ts +93 -0
- package/src/social-providers/tiktok.ts +209 -0
- package/src/social-providers/twitch.ts +111 -0
- package/src/social-providers/twitter.ts +198 -0
- package/src/social-providers/vercel.ts +87 -0
- package/src/social-providers/vk.ts +124 -0
- package/src/social-providers/zoom.ts +238 -0
- package/src/types/context.ts +396 -0
- package/src/types/cookie.ts +10 -0
- package/src/types/helper.ts +26 -0
- package/src/types/index.ts +32 -0
- package/src/types/init-options.ts +1529 -0
- package/src/types/plugin-client.ts +127 -0
- package/src/types/plugin.ts +157 -0
- package/src/utils/db.ts +20 -0
- package/src/utils/deprecate.test.ts +72 -0
- package/src/utils/deprecate.ts +21 -0
- package/src/utils/error-codes.ts +65 -0
- package/src/utils/id.ts +5 -0
- package/src/utils/ip.test.ts +255 -0
- package/src/utils/ip.ts +211 -0
- package/src/utils/json.ts +25 -0
- package/src/utils/string.ts +3 -0
- package/src/utils/url.ts +43 -0
- package/tsconfig.json +7 -0
- package/tsdown.config.ts +35 -0
- package/vitest.config.ts +3 -0
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { base64 } from "@better-auth/utils/base64";
|
|
2
|
+
import { betterFetch } from "@better-fetch/fetch";
|
|
3
|
+
import type { JWK } from "jose";
|
|
4
|
+
import { decodeProtectedHeader, importJWK, jwtVerify } from "jose";
|
|
5
|
+
import type { ProviderOptions } from "./index";
|
|
6
|
+
import { getOAuth2Tokens } from "./index";
|
|
7
|
+
|
|
8
|
+
export function createAuthorizationCodeRequest({
|
|
9
|
+
code,
|
|
10
|
+
codeVerifier,
|
|
11
|
+
redirectURI,
|
|
12
|
+
options,
|
|
13
|
+
authentication,
|
|
14
|
+
deviceId,
|
|
15
|
+
headers,
|
|
16
|
+
additionalParams = {},
|
|
17
|
+
resource,
|
|
18
|
+
}: {
|
|
19
|
+
code: string;
|
|
20
|
+
redirectURI: string;
|
|
21
|
+
options: Partial<ProviderOptions>;
|
|
22
|
+
codeVerifier?: string | undefined;
|
|
23
|
+
deviceId?: string | undefined;
|
|
24
|
+
authentication?: ("basic" | "post") | undefined;
|
|
25
|
+
headers?: Record<string, string> | undefined;
|
|
26
|
+
additionalParams?: Record<string, string> | undefined;
|
|
27
|
+
resource?: (string | string[]) | undefined;
|
|
28
|
+
}) {
|
|
29
|
+
const body = new URLSearchParams();
|
|
30
|
+
const requestHeaders: Record<string, any> = {
|
|
31
|
+
"content-type": "application/x-www-form-urlencoded",
|
|
32
|
+
accept: "application/json",
|
|
33
|
+
...headers,
|
|
34
|
+
};
|
|
35
|
+
body.set("grant_type", "authorization_code");
|
|
36
|
+
body.set("code", code);
|
|
37
|
+
codeVerifier && body.set("code_verifier", codeVerifier);
|
|
38
|
+
options.clientKey && body.set("client_key", options.clientKey);
|
|
39
|
+
deviceId && body.set("device_id", deviceId);
|
|
40
|
+
body.set("redirect_uri", options.redirectURI || redirectURI);
|
|
41
|
+
if (resource) {
|
|
42
|
+
if (typeof resource === "string") {
|
|
43
|
+
body.append("resource", resource);
|
|
44
|
+
} else {
|
|
45
|
+
for (const _resource of resource) {
|
|
46
|
+
body.append("resource", _resource);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
// Use standard Base64 encoding for HTTP Basic Auth (OAuth2 spec, RFC 7617)
|
|
51
|
+
// Fixes compatibility with providers like Notion, Twitter, etc.
|
|
52
|
+
if (authentication === "basic") {
|
|
53
|
+
const primaryClientId = Array.isArray(options.clientId)
|
|
54
|
+
? options.clientId[0]
|
|
55
|
+
: options.clientId;
|
|
56
|
+
const encodedCredentials = base64.encode(
|
|
57
|
+
`${primaryClientId}:${options.clientSecret ?? ""}`,
|
|
58
|
+
);
|
|
59
|
+
requestHeaders["authorization"] = `Basic ${encodedCredentials}`;
|
|
60
|
+
} else {
|
|
61
|
+
const primaryClientId = Array.isArray(options.clientId)
|
|
62
|
+
? options.clientId[0]
|
|
63
|
+
: options.clientId;
|
|
64
|
+
body.set("client_id", primaryClientId);
|
|
65
|
+
if (options.clientSecret) {
|
|
66
|
+
body.set("client_secret", options.clientSecret);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
for (const [key, value] of Object.entries(additionalParams)) {
|
|
71
|
+
if (!body.has(key)) body.append(key, value);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
body,
|
|
76
|
+
headers: requestHeaders,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export async function validateAuthorizationCode({
|
|
81
|
+
code,
|
|
82
|
+
codeVerifier,
|
|
83
|
+
redirectURI,
|
|
84
|
+
options,
|
|
85
|
+
tokenEndpoint,
|
|
86
|
+
authentication,
|
|
87
|
+
deviceId,
|
|
88
|
+
headers,
|
|
89
|
+
additionalParams = {},
|
|
90
|
+
resource,
|
|
91
|
+
}: {
|
|
92
|
+
code: string;
|
|
93
|
+
redirectURI: string;
|
|
94
|
+
options: Partial<ProviderOptions>;
|
|
95
|
+
codeVerifier?: string | undefined;
|
|
96
|
+
deviceId?: string | undefined;
|
|
97
|
+
tokenEndpoint: string;
|
|
98
|
+
authentication?: ("basic" | "post") | undefined;
|
|
99
|
+
headers?: Record<string, string> | undefined;
|
|
100
|
+
additionalParams?: Record<string, string> | undefined;
|
|
101
|
+
resource?: (string | string[]) | undefined;
|
|
102
|
+
}) {
|
|
103
|
+
const { body, headers: requestHeaders } = createAuthorizationCodeRequest({
|
|
104
|
+
code,
|
|
105
|
+
codeVerifier,
|
|
106
|
+
redirectURI,
|
|
107
|
+
options,
|
|
108
|
+
authentication,
|
|
109
|
+
deviceId,
|
|
110
|
+
headers,
|
|
111
|
+
additionalParams,
|
|
112
|
+
resource,
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
const { data, error } = await betterFetch<object>(tokenEndpoint, {
|
|
116
|
+
method: "POST",
|
|
117
|
+
body: body,
|
|
118
|
+
headers: requestHeaders,
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
if (error) {
|
|
122
|
+
throw error;
|
|
123
|
+
}
|
|
124
|
+
const tokens = getOAuth2Tokens(data);
|
|
125
|
+
return tokens;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export async function validateToken(token: string, jwksEndpoint: string) {
|
|
129
|
+
const { data, error } = await betterFetch<{
|
|
130
|
+
keys: JWK[];
|
|
131
|
+
}>(jwksEndpoint, {
|
|
132
|
+
method: "GET",
|
|
133
|
+
headers: {
|
|
134
|
+
accept: "application/json",
|
|
135
|
+
},
|
|
136
|
+
});
|
|
137
|
+
if (error) {
|
|
138
|
+
throw error;
|
|
139
|
+
}
|
|
140
|
+
const keys = data["keys"];
|
|
141
|
+
const header = decodeProtectedHeader(token);
|
|
142
|
+
const key = keys.find((k) => k.kid === header.kid);
|
|
143
|
+
if (!key) {
|
|
144
|
+
throw new Error("Key not found");
|
|
145
|
+
}
|
|
146
|
+
const cryptoKey = await importJWK(key, header.alg);
|
|
147
|
+
const verified = await jwtVerify(token, cryptoKey);
|
|
148
|
+
return verified;
|
|
149
|
+
}
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { exportJWK, generateKeyPair, SignJWT } from "jose";
|
|
2
|
+
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
3
|
+
import { validateToken } from "./validate-authorization-code";
|
|
4
|
+
|
|
5
|
+
vi.mock("@better-fetch/fetch", () => ({
|
|
6
|
+
betterFetch: vi.fn(),
|
|
7
|
+
}));
|
|
8
|
+
|
|
9
|
+
import { betterFetch } from "@better-fetch/fetch";
|
|
10
|
+
|
|
11
|
+
const mockedBetterFetch = vi.mocked(betterFetch);
|
|
12
|
+
|
|
13
|
+
describe("validateToken", () => {
|
|
14
|
+
beforeEach(() => {
|
|
15
|
+
vi.clearAllMocks();
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
async function createTestJWKS(alg: string, crv?: string) {
|
|
19
|
+
const { publicKey, privateKey } = await generateKeyPair(alg, {
|
|
20
|
+
crv,
|
|
21
|
+
extractable: true,
|
|
22
|
+
});
|
|
23
|
+
const publicJWK = await exportJWK(publicKey);
|
|
24
|
+
const privateJWK = await exportJWK(privateKey);
|
|
25
|
+
const kid = `test-key-${Date.now()}`;
|
|
26
|
+
publicJWK.kid = kid;
|
|
27
|
+
privateJWK.kid = kid;
|
|
28
|
+
return { publicJWK, privateJWK, kid, publicKey, privateKey };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async function createSignedToken(
|
|
32
|
+
privateKey: CryptoKey,
|
|
33
|
+
alg: string,
|
|
34
|
+
kid: string,
|
|
35
|
+
payload: Record<string, unknown> = {},
|
|
36
|
+
) {
|
|
37
|
+
return await new SignJWT({
|
|
38
|
+
sub: "user-123",
|
|
39
|
+
email: "test@example.com",
|
|
40
|
+
iss: "https://example.com",
|
|
41
|
+
aud: "test-client",
|
|
42
|
+
...payload,
|
|
43
|
+
})
|
|
44
|
+
.setProtectedHeader({ alg, kid })
|
|
45
|
+
.setIssuedAt()
|
|
46
|
+
.setExpirationTime("1h")
|
|
47
|
+
.sign(privateKey);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
it("should verify RS256 signed token", async () => {
|
|
51
|
+
const { publicJWK, privateKey, kid } = await createTestJWKS("RS256");
|
|
52
|
+
const token = await createSignedToken(privateKey, "RS256", kid);
|
|
53
|
+
|
|
54
|
+
mockedBetterFetch.mockResolvedValueOnce({
|
|
55
|
+
data: { keys: [publicJWK] },
|
|
56
|
+
error: null,
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
const result = await validateToken(
|
|
60
|
+
token,
|
|
61
|
+
"https://example.com/.well-known/jwks",
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
expect(result).toBeDefined();
|
|
65
|
+
expect(result.payload.sub).toBe("user-123");
|
|
66
|
+
expect(result.payload.email).toBe("test@example.com");
|
|
67
|
+
expect(mockedBetterFetch).toHaveBeenCalledWith(
|
|
68
|
+
"https://example.com/.well-known/jwks",
|
|
69
|
+
expect.objectContaining({ method: "GET" }),
|
|
70
|
+
);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it("should verify ES256 signed token", async () => {
|
|
74
|
+
const { publicJWK, privateKey, kid } = await createTestJWKS("ES256");
|
|
75
|
+
const token = await createSignedToken(privateKey, "ES256", kid);
|
|
76
|
+
|
|
77
|
+
mockedBetterFetch.mockResolvedValueOnce({
|
|
78
|
+
data: { keys: [publicJWK] },
|
|
79
|
+
error: null,
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
const result = await validateToken(
|
|
83
|
+
token,
|
|
84
|
+
"https://example.com/.well-known/jwks",
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
expect(result).toBeDefined();
|
|
88
|
+
expect(result.payload.sub).toBe("user-123");
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it("should verify EdDSA (Ed25519) signed token", async () => {
|
|
92
|
+
const { publicJWK, privateKey, kid } = await createTestJWKS(
|
|
93
|
+
"EdDSA",
|
|
94
|
+
"Ed25519",
|
|
95
|
+
);
|
|
96
|
+
const token = await createSignedToken(privateKey, "EdDSA", kid);
|
|
97
|
+
|
|
98
|
+
mockedBetterFetch.mockResolvedValueOnce({
|
|
99
|
+
data: { keys: [publicJWK] },
|
|
100
|
+
error: null,
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
const result = await validateToken(
|
|
104
|
+
token,
|
|
105
|
+
"https://example.com/.well-known/jwks",
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
expect(result).toBeDefined();
|
|
109
|
+
expect(result.payload.sub).toBe("user-123");
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it("should throw 'Key not found' when kid doesn't match", async () => {
|
|
113
|
+
const { publicJWK, privateKey } = await createTestJWKS("RS256");
|
|
114
|
+
publicJWK.kid = "different-kid";
|
|
115
|
+
const token = await createSignedToken(privateKey, "RS256", "original-kid");
|
|
116
|
+
|
|
117
|
+
mockedBetterFetch.mockResolvedValueOnce({
|
|
118
|
+
data: { keys: [publicJWK] },
|
|
119
|
+
error: null,
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
await expect(
|
|
123
|
+
validateToken(token, "https://example.com/.well-known/jwks"),
|
|
124
|
+
).rejects.toThrow("Key not found");
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it("should find correct key when multiple keys exist", async () => {
|
|
128
|
+
const key1 = await createTestJWKS("RS256");
|
|
129
|
+
const key2 = await createTestJWKS("RS256");
|
|
130
|
+
const key3 = await createTestJWKS("ES256");
|
|
131
|
+
const token = await createSignedToken(key2.privateKey, "RS256", key2.kid);
|
|
132
|
+
|
|
133
|
+
mockedBetterFetch.mockResolvedValueOnce({
|
|
134
|
+
data: { keys: [key1.publicJWK, key2.publicJWK, key3.publicJWK] },
|
|
135
|
+
error: null,
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
const result = await validateToken(
|
|
139
|
+
token,
|
|
140
|
+
"https://example.com/.well-known/jwks",
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
expect(result).toBeDefined();
|
|
144
|
+
expect(result.payload.sub).toBe("user-123");
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it("should throw when JWKS returns empty keys array", async () => {
|
|
148
|
+
const { privateKey, kid } = await createTestJWKS("RS256");
|
|
149
|
+
const token = await createSignedToken(privateKey, "RS256", kid);
|
|
150
|
+
|
|
151
|
+
mockedBetterFetch.mockResolvedValueOnce({
|
|
152
|
+
data: { keys: [] },
|
|
153
|
+
error: null,
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
await expect(
|
|
157
|
+
validateToken(token, "https://example.com/.well-known/jwks"),
|
|
158
|
+
).rejects.toThrow("Key not found");
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it("should throw when JWKS fetch fails", async () => {
|
|
162
|
+
const { privateKey, kid } = await createTestJWKS("RS256");
|
|
163
|
+
const token = await createSignedToken(privateKey, "RS256", kid);
|
|
164
|
+
|
|
165
|
+
mockedBetterFetch.mockResolvedValueOnce({
|
|
166
|
+
data: null,
|
|
167
|
+
error: { status: 500, statusText: "Internal Server Error" },
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
await expect(
|
|
171
|
+
validateToken(token, "https://example.com/.well-known/jwks"),
|
|
172
|
+
).rejects.toBeDefined();
|
|
173
|
+
});
|
|
174
|
+
});
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import { betterFetch } from "@better-fetch/fetch";
|
|
2
|
+
import { APIError } from "better-call";
|
|
3
|
+
import type {
|
|
4
|
+
JSONWebKeySet,
|
|
5
|
+
JWTPayload,
|
|
6
|
+
JWTVerifyOptions,
|
|
7
|
+
ProtectedHeaderParameters,
|
|
8
|
+
} from "jose";
|
|
9
|
+
import {
|
|
10
|
+
createLocalJWKSet,
|
|
11
|
+
decodeProtectedHeader,
|
|
12
|
+
jwtVerify,
|
|
13
|
+
UnsecuredJWT,
|
|
14
|
+
} from "jose";
|
|
15
|
+
import { logger } from "../env";
|
|
16
|
+
|
|
17
|
+
/** Last fetched jwks used locally in getJwks @internal */
|
|
18
|
+
let jwks: JSONWebKeySet | undefined;
|
|
19
|
+
|
|
20
|
+
export interface VerifyAccessTokenRemote {
|
|
21
|
+
/** Full url of the introspect endpoint. Should end with `/oauth2/introspect` */
|
|
22
|
+
introspectUrl: string;
|
|
23
|
+
/** Client Secret */
|
|
24
|
+
clientId: string;
|
|
25
|
+
/** Client Secret */
|
|
26
|
+
clientSecret: string;
|
|
27
|
+
/**
|
|
28
|
+
* Forces remote verification of a token.
|
|
29
|
+
* This ensures attached session (if applicable)
|
|
30
|
+
* is also still active.
|
|
31
|
+
*/
|
|
32
|
+
force?: boolean;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Performs local verification of an access token for your APIs.
|
|
37
|
+
*
|
|
38
|
+
* Can also be configured for remote verification.
|
|
39
|
+
*/
|
|
40
|
+
export async function verifyJwsAccessToken(
|
|
41
|
+
token: string,
|
|
42
|
+
opts: {
|
|
43
|
+
/** Jwks url or promise of a Jwks */
|
|
44
|
+
jwksFetch: string | (() => Promise<JSONWebKeySet | undefined>);
|
|
45
|
+
/** Verify options */
|
|
46
|
+
verifyOptions: JWTVerifyOptions &
|
|
47
|
+
Required<Pick<JWTVerifyOptions, "audience" | "issuer">>;
|
|
48
|
+
},
|
|
49
|
+
) {
|
|
50
|
+
try {
|
|
51
|
+
const jwks = await getJwks(token, opts);
|
|
52
|
+
const jwt = await jwtVerify<JWTPayload>(
|
|
53
|
+
token,
|
|
54
|
+
createLocalJWKSet(jwks),
|
|
55
|
+
opts.verifyOptions,
|
|
56
|
+
);
|
|
57
|
+
// Return the JWT payload in introspection format
|
|
58
|
+
// https://datatracker.ietf.org/doc/html/rfc7662#section-2.2
|
|
59
|
+
if (jwt.payload.azp) {
|
|
60
|
+
jwt.payload.client_id = jwt.payload.azp;
|
|
61
|
+
}
|
|
62
|
+
return jwt.payload;
|
|
63
|
+
} catch (error) {
|
|
64
|
+
if (error instanceof Error) throw error;
|
|
65
|
+
throw new Error(error as unknown as string);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export async function getJwks(
|
|
70
|
+
token: string,
|
|
71
|
+
opts: {
|
|
72
|
+
/** Jwks url or promise of a Jwks */
|
|
73
|
+
jwksFetch: string | (() => Promise<JSONWebKeySet | undefined>);
|
|
74
|
+
},
|
|
75
|
+
) {
|
|
76
|
+
// Attempt to decode the token and find a matching kid in jwks
|
|
77
|
+
let jwtHeaders: ProtectedHeaderParameters | undefined;
|
|
78
|
+
try {
|
|
79
|
+
jwtHeaders = decodeProtectedHeader(token);
|
|
80
|
+
} catch (error) {
|
|
81
|
+
if (error instanceof Error) throw error;
|
|
82
|
+
throw new Error(error as unknown as string);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (!jwtHeaders.kid) throw new Error("Missing jwt kid");
|
|
86
|
+
|
|
87
|
+
// Fetch jwks if not set or has a different kid than the one stored
|
|
88
|
+
if (!jwks || !jwks.keys.find((jwk) => jwk.kid === jwtHeaders.kid)) {
|
|
89
|
+
jwks =
|
|
90
|
+
typeof opts.jwksFetch === "string"
|
|
91
|
+
? await betterFetch<JSONWebKeySet>(opts.jwksFetch, {
|
|
92
|
+
headers: {
|
|
93
|
+
Accept: "application/json",
|
|
94
|
+
},
|
|
95
|
+
}).then(async (res) => {
|
|
96
|
+
if (res.error)
|
|
97
|
+
throw new Error(
|
|
98
|
+
`Jwks failed: ${res.error.message ?? res.error.statusText}`,
|
|
99
|
+
);
|
|
100
|
+
return res.data;
|
|
101
|
+
})
|
|
102
|
+
: await opts.jwksFetch();
|
|
103
|
+
if (!jwks) throw new Error("No jwks found");
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return jwks;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Performs local verification of an access token for your API.
|
|
111
|
+
*
|
|
112
|
+
* Can also be configured for remote verification.
|
|
113
|
+
*/
|
|
114
|
+
export async function verifyAccessToken(
|
|
115
|
+
token: string,
|
|
116
|
+
opts: {
|
|
117
|
+
/** Verify options */
|
|
118
|
+
verifyOptions: JWTVerifyOptions &
|
|
119
|
+
Required<Pick<JWTVerifyOptions, "audience" | "issuer">>;
|
|
120
|
+
/** Scopes to additionally verify. Token must include all but not exact. */
|
|
121
|
+
scopes?: string[];
|
|
122
|
+
/** Required to verify access token locally */
|
|
123
|
+
jwksUrl?: string;
|
|
124
|
+
/** If provided, can verify a token remotely */
|
|
125
|
+
remoteVerify?: VerifyAccessTokenRemote;
|
|
126
|
+
},
|
|
127
|
+
) {
|
|
128
|
+
let payload: JWTPayload | undefined;
|
|
129
|
+
// Locally verify
|
|
130
|
+
if (opts.jwksUrl && !opts?.remoteVerify?.force) {
|
|
131
|
+
try {
|
|
132
|
+
payload = await verifyJwsAccessToken(token, {
|
|
133
|
+
jwksFetch: opts.jwksUrl,
|
|
134
|
+
verifyOptions: opts.verifyOptions,
|
|
135
|
+
});
|
|
136
|
+
} catch (error) {
|
|
137
|
+
if (error instanceof Error) {
|
|
138
|
+
if (error.name === "TypeError" || error.name === "JWSInvalid") {
|
|
139
|
+
// likely an opaque token (continue)
|
|
140
|
+
} else if (error.name === "JWTExpired") {
|
|
141
|
+
throw new APIError("UNAUTHORIZED", {
|
|
142
|
+
message: "token expired",
|
|
143
|
+
});
|
|
144
|
+
} else if (error.name === "JWTInvalid") {
|
|
145
|
+
throw new APIError("UNAUTHORIZED", {
|
|
146
|
+
message: "token invalid",
|
|
147
|
+
});
|
|
148
|
+
} else {
|
|
149
|
+
throw error;
|
|
150
|
+
}
|
|
151
|
+
} else {
|
|
152
|
+
throw new Error(error as unknown as string);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Remote verify
|
|
158
|
+
if (opts?.remoteVerify) {
|
|
159
|
+
const { data: introspect, error: introspectError } = await betterFetch<
|
|
160
|
+
JWTPayload & {
|
|
161
|
+
active: boolean;
|
|
162
|
+
}
|
|
163
|
+
>(opts.remoteVerify.introspectUrl, {
|
|
164
|
+
method: "POST",
|
|
165
|
+
headers: {
|
|
166
|
+
Accept: "application/json",
|
|
167
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
168
|
+
},
|
|
169
|
+
body: new URLSearchParams({
|
|
170
|
+
client_id: opts.remoteVerify.clientId,
|
|
171
|
+
client_secret: opts.remoteVerify.clientSecret,
|
|
172
|
+
token,
|
|
173
|
+
token_type_hint: "access_token",
|
|
174
|
+
}).toString(),
|
|
175
|
+
});
|
|
176
|
+
if (introspectError)
|
|
177
|
+
logger.error(
|
|
178
|
+
`Introspection failed: ${introspectError.message ?? introspectError.statusText}`,
|
|
179
|
+
);
|
|
180
|
+
if (!introspect)
|
|
181
|
+
throw new APIError("INTERNAL_SERVER_ERROR", {
|
|
182
|
+
message: "introspection failed",
|
|
183
|
+
});
|
|
184
|
+
if (!introspect.active)
|
|
185
|
+
throw new APIError("UNAUTHORIZED", {
|
|
186
|
+
message: "token inactive",
|
|
187
|
+
});
|
|
188
|
+
// Verifies payload using verify options (token valid through introspect)
|
|
189
|
+
try {
|
|
190
|
+
const unsecuredJwt = new UnsecuredJWT(introspect).encode();
|
|
191
|
+
const { audience: _audience, ...verifyOptions } = opts.verifyOptions;
|
|
192
|
+
const verify = introspect.aud
|
|
193
|
+
? UnsecuredJWT.decode(unsecuredJwt, opts.verifyOptions)
|
|
194
|
+
: UnsecuredJWT.decode(unsecuredJwt, verifyOptions);
|
|
195
|
+
payload = verify.payload;
|
|
196
|
+
} catch (error) {
|
|
197
|
+
throw new Error(error as unknown as string);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (!payload)
|
|
202
|
+
throw new APIError("UNAUTHORIZED", {
|
|
203
|
+
message: `no token payload`,
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
// Check scopes if provided
|
|
207
|
+
if (opts.scopes) {
|
|
208
|
+
const validScopes = new Set(
|
|
209
|
+
(payload.scope as string | undefined)?.split(" "),
|
|
210
|
+
);
|
|
211
|
+
for (const sc of opts.scopes) {
|
|
212
|
+
if (!validScopes.has(sc)) {
|
|
213
|
+
throw new APIError("FORBIDDEN", {
|
|
214
|
+
message: `invalid scope ${sc}`,
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return payload;
|
|
221
|
+
}
|