@depup/better-auth__core 1.6.23-depup.2
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/LICENSE.md +20 -0
- package/README.md +32 -0
- package/changes.json +14 -0
- package/dist/api/index.d.mts +281 -0
- package/dist/api/index.mjs +92 -0
- package/dist/async_hooks/index.d.mts +6 -0
- package/dist/async_hooks/index.mjs +20 -0
- package/dist/async_hooks/pure.index.d.mts +6 -0
- package/dist/async_hooks/pure.index.mjs +33 -0
- package/dist/context/endpoint-context.d.mts +18 -0
- package/dist/context/endpoint-context.mjs +29 -0
- package/dist/context/global.d.mts +6 -0
- package/dist/context/global.mjs +36 -0
- package/dist/context/index.d.mts +5 -0
- package/dist/context/index.mjs +5 -0
- package/dist/context/request-state.d.mts +27 -0
- package/dist/context/request-state.mjs +47 -0
- package/dist/context/transaction.d.mts +27 -0
- package/dist/context/transaction.mjs +96 -0
- package/dist/db/adapter/factory.d.mts +17 -0
- package/dist/db/adapter/factory.mjs +905 -0
- package/dist/db/adapter/get-default-field-name.d.mts +18 -0
- package/dist/db/adapter/get-default-field-name.mjs +36 -0
- package/dist/db/adapter/get-default-model-name.d.mts +12 -0
- package/dist/db/adapter/get-default-model-name.mjs +30 -0
- package/dist/db/adapter/get-field-attributes.d.mts +26 -0
- package/dist/db/adapter/get-field-attributes.mjs +37 -0
- package/dist/db/adapter/get-field-name.d.mts +18 -0
- package/dist/db/adapter/get-field-name.mjs +31 -0
- package/dist/db/adapter/get-id-field.d.mts +36 -0
- package/dist/db/adapter/get-id-field.mjs +64 -0
- package/dist/db/adapter/get-model-name.d.mts +12 -0
- package/dist/db/adapter/get-model-name.mjs +21 -0
- package/dist/db/adapter/index.d.mts +615 -0
- package/dist/db/adapter/index.mjs +24 -0
- package/dist/db/adapter/types.d.mts +105 -0
- package/dist/db/adapter/utils.d.mts +7 -0
- package/dist/db/adapter/utils.mjs +37 -0
- package/dist/db/get-tables.d.mts +6 -0
- package/dist/db/get-tables.mjs +266 -0
- package/dist/db/index.d.mts +10 -0
- package/dist/db/index.mjs +8 -0
- package/dist/db/plugin.d.mts +12 -0
- package/dist/db/schema/account.d.mts +28 -0
- package/dist/db/schema/account.mjs +17 -0
- package/dist/db/schema/rate-limit.d.mts +18 -0
- package/dist/db/schema/rate-limit.mjs +9 -0
- package/dist/db/schema/session.d.mts +23 -0
- package/dist/db/schema/session.mjs +12 -0
- package/dist/db/schema/shared.d.mts +10 -0
- package/dist/db/schema/shared.mjs +9 -0
- package/dist/db/schema/user.d.mts +22 -0
- package/dist/db/schema/user.mjs +11 -0
- package/dist/db/schema/verification.d.mts +21 -0
- package/dist/db/schema/verification.mjs +10 -0
- package/dist/db/type.d.mts +194 -0
- package/dist/env/color-depth.d.mts +4 -0
- package/dist/env/color-depth.mjs +86 -0
- package/dist/env/env-impl.d.mts +32 -0
- package/dist/env/env-impl.mjs +81 -0
- package/dist/env/index.d.mts +4 -0
- package/dist/env/index.mjs +4 -0
- package/dist/env/logger.d.mts +48 -0
- package/dist/env/logger.mjs +79 -0
- package/dist/error/codes.d.mts +69 -0
- package/dist/error/codes.mjs +55 -0
- package/dist/error/index.d.mts +26 -0
- package/dist/error/index.mjs +27 -0
- package/dist/index.d.mts +9 -0
- package/dist/index.mjs +1 -0
- package/dist/instrumentation/api.mjs +12 -0
- package/dist/instrumentation/attributes.d.mts +11 -0
- package/dist/instrumentation/attributes.mjs +10 -0
- package/dist/instrumentation/index.d.mts +3 -0
- package/dist/instrumentation/index.mjs +3 -0
- package/dist/instrumentation/noop.mjs +42 -0
- package/dist/instrumentation/pure.index.d.mts +7 -0
- package/dist/instrumentation/pure.index.mjs +7 -0
- package/dist/instrumentation/tracer.d.mts +13 -0
- package/dist/instrumentation/tracer.mjs +53 -0
- package/dist/oauth2/client-credentials-token.d.mts +56 -0
- package/dist/oauth2/client-credentials-token.mjs +64 -0
- package/dist/oauth2/create-authorization-url.d.mts +44 -0
- package/dist/oauth2/create-authorization-url.mjs +41 -0
- package/dist/oauth2/index.d.mts +8 -0
- package/dist/oauth2/index.mjs +7 -0
- package/dist/oauth2/oauth-provider.d.mts +192 -0
- package/dist/oauth2/refresh-access-token.d.mts +54 -0
- package/dist/oauth2/refresh-access-token.mjs +73 -0
- package/dist/oauth2/reject-redirects.mjs +60 -0
- package/dist/oauth2/utils.d.mts +24 -0
- package/dist/oauth2/utils.mjs +48 -0
- package/dist/oauth2/validate-authorization-code.d.mts +85 -0
- package/dist/oauth2/validate-authorization-code.mjs +86 -0
- package/dist/oauth2/verify.d.mts +65 -0
- package/dist/oauth2/verify.mjs +202 -0
- package/dist/social-providers/apple.d.mts +133 -0
- package/dist/social-providers/apple.mjs +117 -0
- package/dist/social-providers/atlassian.d.mts +70 -0
- package/dist/social-providers/atlassian.mjs +80 -0
- package/dist/social-providers/cognito.d.mts +85 -0
- package/dist/social-providers/cognito.mjs +162 -0
- package/dist/social-providers/discord.d.mts +124 -0
- package/dist/social-providers/discord.mjs +62 -0
- package/dist/social-providers/dropbox.d.mts +69 -0
- package/dist/social-providers/dropbox.mjs +72 -0
- package/dist/social-providers/facebook.d.mts +79 -0
- package/dist/social-providers/facebook.mjs +157 -0
- package/dist/social-providers/figma.d.mts +61 -0
- package/dist/social-providers/figma.mjs +83 -0
- package/dist/social-providers/github.d.mts +102 -0
- package/dist/social-providers/github.mjs +92 -0
- package/dist/social-providers/gitlab.d.mts +123 -0
- package/dist/social-providers/gitlab.mjs +79 -0
- package/dist/social-providers/google.d.mts +123 -0
- package/dist/social-providers/google.mjs +137 -0
- package/dist/social-providers/huggingface.d.mts +83 -0
- package/dist/social-providers/huggingface.mjs +73 -0
- package/dist/social-providers/index.d.mts +1834 -0
- package/dist/social-providers/index.mjs +78 -0
- package/dist/social-providers/kakao.d.mts +161 -0
- package/dist/social-providers/kakao.mjs +70 -0
- package/dist/social-providers/kick.d.mts +73 -0
- package/dist/social-providers/kick.mjs +68 -0
- package/dist/social-providers/line.d.mts +105 -0
- package/dist/social-providers/line.mjs +110 -0
- package/dist/social-providers/linear.d.mts +68 -0
- package/dist/social-providers/linear.mjs +85 -0
- package/dist/social-providers/linkedin.d.mts +67 -0
- package/dist/social-providers/linkedin.mjs +73 -0
- package/dist/social-providers/microsoft-entra-id.d.mts +174 -0
- package/dist/social-providers/microsoft-entra-id.mjs +152 -0
- package/dist/social-providers/naver.d.mts +92 -0
- package/dist/social-providers/naver.mjs +65 -0
- package/dist/social-providers/notion.d.mts +64 -0
- package/dist/social-providers/notion.mjs +72 -0
- package/dist/social-providers/paybin.d.mts +71 -0
- package/dist/social-providers/paybin.mjs +81 -0
- package/dist/social-providers/paypal.d.mts +131 -0
- package/dist/social-providers/paypal.mjs +188 -0
- package/dist/social-providers/polar.d.mts +74 -0
- package/dist/social-providers/polar.mjs +71 -0
- package/dist/social-providers/railway.d.mts +65 -0
- package/dist/social-providers/railway.mjs +74 -0
- package/dist/social-providers/reddit.d.mts +62 -0
- package/dist/social-providers/reddit.mjs +81 -0
- package/dist/social-providers/roblox.d.mts +70 -0
- package/dist/social-providers/roblox.mjs +57 -0
- package/dist/social-providers/salesforce.d.mts +79 -0
- package/dist/social-providers/salesforce.mjs +87 -0
- package/dist/social-providers/slack.d.mts +83 -0
- package/dist/social-providers/slack.mjs +66 -0
- package/dist/social-providers/spotify.d.mts +63 -0
- package/dist/social-providers/spotify.mjs +69 -0
- package/dist/social-providers/tiktok.d.mts +168 -0
- package/dist/social-providers/tiktok.mjs +60 -0
- package/dist/social-providers/twitch.d.mts +79 -0
- package/dist/social-providers/twitch.mjs +75 -0
- package/dist/social-providers/twitter.d.mts +126 -0
- package/dist/social-providers/twitter.mjs +85 -0
- package/dist/social-providers/vercel.d.mts +62 -0
- package/dist/social-providers/vercel.mjs +58 -0
- package/dist/social-providers/vk.d.mts +70 -0
- package/dist/social-providers/vk.mjs +81 -0
- package/dist/social-providers/wechat.d.mts +113 -0
- package/dist/social-providers/wechat.mjs +81 -0
- package/dist/social-providers/zoom.d.mts +163 -0
- package/dist/social-providers/zoom.mjs +69 -0
- package/dist/types/context.d.mts +319 -0
- package/dist/types/cookie.d.mts +15 -0
- package/dist/types/helper.d.mts +10 -0
- package/dist/types/index.d.mts +9 -0
- package/dist/types/init-options.d.mts +1430 -0
- package/dist/types/plugin-client.d.mts +123 -0
- package/dist/types/plugin.d.mts +124 -0
- package/dist/types/secret.d.mts +11 -0
- package/dist/utils/async.d.mts +22 -0
- package/dist/utils/async.mjs +32 -0
- package/dist/utils/db.d.mts +9 -0
- package/dist/utils/db.mjs +15 -0
- package/dist/utils/deprecate.d.mts +9 -0
- package/dist/utils/deprecate.mjs +16 -0
- package/dist/utils/error-codes.d.mts +13 -0
- package/dist/utils/error-codes.mjs +10 -0
- package/dist/utils/fetch-metadata.d.mts +4 -0
- package/dist/utils/fetch-metadata.mjs +6 -0
- package/dist/utils/host.d.mts +147 -0
- package/dist/utils/host.mjs +295 -0
- package/dist/utils/id.d.mts +4 -0
- package/dist/utils/id.mjs +7 -0
- package/dist/utils/ip.d.mts +77 -0
- package/dist/utils/ip.mjs +230 -0
- package/dist/utils/is-api-error.d.mts +6 -0
- package/dist/utils/is-api-error.mjs +8 -0
- package/dist/utils/json.d.mts +4 -0
- package/dist/utils/json.mjs +41 -0
- package/dist/utils/redirect-uri.d.mts +20 -0
- package/dist/utils/redirect-uri.mjs +48 -0
- package/dist/utils/string.d.mts +8 -0
- package/dist/utils/string.mjs +25 -0
- package/dist/utils/url.d.mts +37 -0
- package/dist/utils/url.mjs +61 -0
- package/package.json +216 -0
- package/src/api/index.ts +185 -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.ts +91 -0
- package/src/context/transaction.ts +169 -0
- package/src/db/adapter/factory.ts +1740 -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 +150 -0
- package/src/db/adapter/get-model-name.ts +36 -0
- package/src/db/adapter/index.ts +656 -0
- package/src/db/adapter/types.ts +134 -0
- package/src/db/adapter/utils.ts +61 -0
- package/src/db/get-tables.ts +304 -0
- package/src/db/index.ts +43 -0
- package/src/db/plugin.ts +11 -0
- package/src/db/schema/account.ts +47 -0
- package/src/db/schema/rate-limit.ts +36 -0
- package/src/db/schema/session.ts +29 -0
- package/src/db/schema/shared.ts +7 -0
- package/src/db/schema/user.ts +31 -0
- package/src/db/schema/verification.ts +28 -0
- package/src/db/type.ts +360 -0
- package/src/env/color-depth.ts +172 -0
- package/src/env/env-impl.ts +123 -0
- package/src/env/index.ts +23 -0
- package/src/env/logger.ts +145 -0
- package/src/error/codes.ts +72 -0
- package/src/error/index.ts +44 -0
- package/src/index.ts +1 -0
- package/src/instrumentation/api.ts +17 -0
- package/src/instrumentation/attributes.ts +22 -0
- package/src/instrumentation/index.ts +2 -0
- package/src/instrumentation/noop.ts +74 -0
- package/src/instrumentation/pure.index.ts +31 -0
- package/src/instrumentation/tracer.ts +95 -0
- package/src/oauth2/client-credentials-token.ts +126 -0
- package/src/oauth2/create-authorization-url.ts +89 -0
- package/src/oauth2/index.ts +34 -0
- package/src/oauth2/oauth-provider.ts +222 -0
- package/src/oauth2/refresh-access-token.ts +157 -0
- package/src/oauth2/reject-redirects.ts +70 -0
- package/src/oauth2/utils.ts +70 -0
- package/src/oauth2/validate-authorization-code.ts +190 -0
- package/src/oauth2/verify.ts +408 -0
- package/src/social-providers/apple.ts +249 -0
- package/src/social-providers/atlassian.ts +133 -0
- package/src/social-providers/cognito.ts +281 -0
- package/src/social-providers/discord.ts +170 -0
- package/src/social-providers/dropbox.ts +112 -0
- package/src/social-providers/facebook.ts +288 -0
- package/src/social-providers/figma.ts +118 -0
- package/src/social-providers/github.ts +184 -0
- package/src/social-providers/gitlab.ts +155 -0
- package/src/social-providers/google.ts +263 -0
- package/src/social-providers/huggingface.ts +119 -0
- package/src/social-providers/index.ts +132 -0
- package/src/social-providers/kakao.ts +179 -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 +391 -0
- package/src/social-providers/naver.ts +113 -0
- package/src/social-providers/notion.ts +108 -0
- package/src/social-providers/paybin.ts +118 -0
- package/src/social-providers/paypal.ts +371 -0
- package/src/social-providers/polar.ts +111 -0
- package/src/social-providers/railway.ts +100 -0
- package/src/social-providers/reddit.ts +126 -0
- package/src/social-providers/roblox.ts +112 -0
- package/src/social-providers/salesforce.ts +159 -0
- package/src/social-providers/slack.ts +112 -0
- package/src/social-providers/spotify.ts +94 -0
- package/src/social-providers/tiktok.ts +211 -0
- package/src/social-providers/twitch.ts +112 -0
- package/src/social-providers/twitter.ts +199 -0
- package/src/social-providers/vercel.ts +87 -0
- package/src/social-providers/vk.ts +125 -0
- package/src/social-providers/wechat.ts +220 -0
- package/src/social-providers/zoom.ts +230 -0
- package/src/types/context.ts +460 -0
- package/src/types/cookie.ts +10 -0
- package/src/types/helper.ts +27 -0
- package/src/types/index.ts +40 -0
- package/src/types/init-options.ts +1679 -0
- package/src/types/plugin-client.ts +143 -0
- package/src/types/plugin.ts +163 -0
- package/src/types/secret.ts +8 -0
- package/src/utils/async.ts +53 -0
- package/src/utils/db.ts +20 -0
- package/src/utils/deprecate.ts +21 -0
- package/src/utils/error-codes.ts +68 -0
- package/src/utils/fetch-metadata.ts +3 -0
- package/src/utils/host.ts +416 -0
- package/src/utils/id.ts +5 -0
- package/src/utils/ip.ts +395 -0
- package/src/utils/is-api-error.ts +10 -0
- package/src/utils/json.ts +56 -0
- package/src/utils/redirect-uri.ts +54 -0
- package/src/utils/string.ts +40 -0
- package/src/utils/url.ts +77 -0
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { betterFetch } from "@better-fetch/fetch";
|
|
2
|
+
import type { OAuthProvider, ProviderOptions } from "../oauth2";
|
|
3
|
+
import {
|
|
4
|
+
createAuthorizationURL,
|
|
5
|
+
refreshAccessToken,
|
|
6
|
+
validateAuthorizationCode,
|
|
7
|
+
} from "../oauth2";
|
|
8
|
+
|
|
9
|
+
export interface NotionProfile {
|
|
10
|
+
object: "user";
|
|
11
|
+
id: string;
|
|
12
|
+
type: "person" | "bot";
|
|
13
|
+
name?: string | undefined;
|
|
14
|
+
avatar_url?: string | undefined;
|
|
15
|
+
person?:
|
|
16
|
+
| {
|
|
17
|
+
email?: string;
|
|
18
|
+
}
|
|
19
|
+
| undefined;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface NotionOptions extends ProviderOptions<NotionProfile> {
|
|
23
|
+
clientId: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export const notion = (options: NotionOptions) => {
|
|
27
|
+
const tokenEndpoint = "https://api.notion.com/v1/oauth/token";
|
|
28
|
+
return {
|
|
29
|
+
id: "notion",
|
|
30
|
+
name: "Notion",
|
|
31
|
+
createAuthorizationURL({ state, scopes, loginHint, redirectURI }) {
|
|
32
|
+
const _scopes: string[] = options.disableDefaultScope ? [] : [];
|
|
33
|
+
if (options.scope) _scopes.push(...options.scope);
|
|
34
|
+
if (scopes) _scopes.push(...scopes);
|
|
35
|
+
return createAuthorizationURL({
|
|
36
|
+
id: "notion",
|
|
37
|
+
options,
|
|
38
|
+
authorizationEndpoint: "https://api.notion.com/v1/oauth/authorize",
|
|
39
|
+
scopes: _scopes,
|
|
40
|
+
state,
|
|
41
|
+
redirectURI,
|
|
42
|
+
loginHint,
|
|
43
|
+
additionalParams: {
|
|
44
|
+
owner: "user",
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
},
|
|
48
|
+
validateAuthorizationCode: async ({ code, redirectURI }) => {
|
|
49
|
+
return validateAuthorizationCode({
|
|
50
|
+
code,
|
|
51
|
+
redirectURI,
|
|
52
|
+
options,
|
|
53
|
+
tokenEndpoint,
|
|
54
|
+
authentication: "basic",
|
|
55
|
+
});
|
|
56
|
+
},
|
|
57
|
+
refreshAccessToken: options.refreshAccessToken
|
|
58
|
+
? options.refreshAccessToken
|
|
59
|
+
: async (refreshToken) => {
|
|
60
|
+
return refreshAccessToken({
|
|
61
|
+
refreshToken,
|
|
62
|
+
options: {
|
|
63
|
+
clientId: options.clientId,
|
|
64
|
+
clientKey: options.clientKey,
|
|
65
|
+
clientSecret: options.clientSecret,
|
|
66
|
+
},
|
|
67
|
+
tokenEndpoint,
|
|
68
|
+
});
|
|
69
|
+
},
|
|
70
|
+
async getUserInfo(token) {
|
|
71
|
+
if (options.getUserInfo) {
|
|
72
|
+
return options.getUserInfo(token);
|
|
73
|
+
}
|
|
74
|
+
const { data: profile, error } = await betterFetch<{
|
|
75
|
+
bot: {
|
|
76
|
+
owner: {
|
|
77
|
+
user: NotionProfile;
|
|
78
|
+
};
|
|
79
|
+
};
|
|
80
|
+
}>("https://api.notion.com/v1/users/me", {
|
|
81
|
+
headers: {
|
|
82
|
+
Authorization: `Bearer ${token.accessToken}`,
|
|
83
|
+
"Notion-Version": "2022-06-28",
|
|
84
|
+
},
|
|
85
|
+
});
|
|
86
|
+
if (error || !profile) {
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
const userProfile = profile.bot?.owner?.user;
|
|
90
|
+
if (!userProfile) {
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
const userMap = await options.mapProfileToUser?.(userProfile);
|
|
94
|
+
return {
|
|
95
|
+
user: {
|
|
96
|
+
id: userProfile.id,
|
|
97
|
+
name: userProfile.name || "",
|
|
98
|
+
email: userProfile.person?.email || null,
|
|
99
|
+
image: userProfile.avatar_url,
|
|
100
|
+
emailVerified: false,
|
|
101
|
+
...userMap,
|
|
102
|
+
},
|
|
103
|
+
data: userProfile,
|
|
104
|
+
};
|
|
105
|
+
},
|
|
106
|
+
options,
|
|
107
|
+
} satisfies OAuthProvider<NotionProfile>;
|
|
108
|
+
};
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { decodeJwt } from "jose";
|
|
2
|
+
import { logger } from "../env";
|
|
3
|
+
import { BetterAuthError } from "../error";
|
|
4
|
+
import type { OAuthProvider, ProviderOptions } from "../oauth2";
|
|
5
|
+
import {
|
|
6
|
+
createAuthorizationURL,
|
|
7
|
+
refreshAccessToken,
|
|
8
|
+
validateAuthorizationCode,
|
|
9
|
+
} from "../oauth2";
|
|
10
|
+
|
|
11
|
+
export interface PaybinProfile {
|
|
12
|
+
sub: string;
|
|
13
|
+
email: string;
|
|
14
|
+
email_verified?: boolean | undefined;
|
|
15
|
+
name?: string | undefined;
|
|
16
|
+
preferred_username?: string | undefined;
|
|
17
|
+
picture?: string | undefined;
|
|
18
|
+
given_name?: string | undefined;
|
|
19
|
+
family_name?: string | undefined;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface PaybinOptions extends ProviderOptions<PaybinProfile> {
|
|
23
|
+
clientId: string;
|
|
24
|
+
/**
|
|
25
|
+
* The issuer URL of your Paybin OAuth server
|
|
26
|
+
* @default "https://idp.paybin.io"
|
|
27
|
+
*/
|
|
28
|
+
issuer?: string | undefined;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export const paybin = (options: PaybinOptions) => {
|
|
32
|
+
const issuer = options.issuer || "https://idp.paybin.io";
|
|
33
|
+
const authorizationEndpoint = `${issuer}/oauth2/authorize`;
|
|
34
|
+
const tokenEndpoint = `${issuer}/oauth2/token`;
|
|
35
|
+
|
|
36
|
+
return {
|
|
37
|
+
id: "paybin",
|
|
38
|
+
name: "Paybin",
|
|
39
|
+
async createAuthorizationURL({
|
|
40
|
+
state,
|
|
41
|
+
scopes,
|
|
42
|
+
codeVerifier,
|
|
43
|
+
redirectURI,
|
|
44
|
+
loginHint,
|
|
45
|
+
}) {
|
|
46
|
+
if (!options.clientId || !options.clientSecret) {
|
|
47
|
+
logger.error(
|
|
48
|
+
"Client Id and Client Secret is required for Paybin. Make sure to provide them in the options.",
|
|
49
|
+
);
|
|
50
|
+
throw new BetterAuthError("CLIENT_ID_AND_SECRET_REQUIRED");
|
|
51
|
+
}
|
|
52
|
+
if (!codeVerifier) {
|
|
53
|
+
throw new BetterAuthError("codeVerifier is required for Paybin");
|
|
54
|
+
}
|
|
55
|
+
const _scopes = options.disableDefaultScope
|
|
56
|
+
? []
|
|
57
|
+
: ["openid", "email", "profile"];
|
|
58
|
+
if (options.scope) _scopes.push(...options.scope);
|
|
59
|
+
if (scopes) _scopes.push(...scopes);
|
|
60
|
+
const url = await createAuthorizationURL({
|
|
61
|
+
id: "paybin",
|
|
62
|
+
options,
|
|
63
|
+
authorizationEndpoint,
|
|
64
|
+
scopes: _scopes,
|
|
65
|
+
state,
|
|
66
|
+
codeVerifier,
|
|
67
|
+
redirectURI,
|
|
68
|
+
prompt: options.prompt,
|
|
69
|
+
loginHint,
|
|
70
|
+
});
|
|
71
|
+
return url;
|
|
72
|
+
},
|
|
73
|
+
validateAuthorizationCode: async ({ code, codeVerifier, redirectURI }) => {
|
|
74
|
+
return validateAuthorizationCode({
|
|
75
|
+
code,
|
|
76
|
+
codeVerifier,
|
|
77
|
+
redirectURI,
|
|
78
|
+
options,
|
|
79
|
+
tokenEndpoint,
|
|
80
|
+
});
|
|
81
|
+
},
|
|
82
|
+
refreshAccessToken: options.refreshAccessToken
|
|
83
|
+
? options.refreshAccessToken
|
|
84
|
+
: async (refreshToken) => {
|
|
85
|
+
return refreshAccessToken({
|
|
86
|
+
refreshToken,
|
|
87
|
+
options: {
|
|
88
|
+
clientId: options.clientId,
|
|
89
|
+
clientKey: options.clientKey,
|
|
90
|
+
clientSecret: options.clientSecret,
|
|
91
|
+
},
|
|
92
|
+
tokenEndpoint,
|
|
93
|
+
});
|
|
94
|
+
},
|
|
95
|
+
async getUserInfo(token) {
|
|
96
|
+
if (options.getUserInfo) {
|
|
97
|
+
return options.getUserInfo(token);
|
|
98
|
+
}
|
|
99
|
+
if (!token.idToken) {
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
const user = decodeJwt(token.idToken) as PaybinProfile;
|
|
103
|
+
const userMap = await options.mapProfileToUser?.(user);
|
|
104
|
+
return {
|
|
105
|
+
user: {
|
|
106
|
+
id: user.sub,
|
|
107
|
+
name: user.name || user.preferred_username || "",
|
|
108
|
+
email: user.email,
|
|
109
|
+
image: user.picture,
|
|
110
|
+
emailVerified: user.email_verified || false,
|
|
111
|
+
...userMap,
|
|
112
|
+
},
|
|
113
|
+
data: user,
|
|
114
|
+
};
|
|
115
|
+
},
|
|
116
|
+
options,
|
|
117
|
+
} satisfies OAuthProvider<PaybinProfile>;
|
|
118
|
+
};
|
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
import { base64 } from "@better-auth/utils/base64";
|
|
2
|
+
import { betterFetch } from "@better-fetch/fetch";
|
|
3
|
+
import { decodeJwt, decodeProtectedHeader, importJWK, jwtVerify } from "jose";
|
|
4
|
+
import { logger } from "../env";
|
|
5
|
+
import { APIError, BetterAuthError } from "../error";
|
|
6
|
+
import type { OAuthProvider, ProviderOptions } from "../oauth2";
|
|
7
|
+
import { createAuthorizationURL } from "../oauth2";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* ID token signing algorithms advertised by PayPal's OpenID configuration.
|
|
11
|
+
* Anything outside this allowlist is rejected so each token is only ever
|
|
12
|
+
* verified with the algorithm it was issued for.
|
|
13
|
+
*
|
|
14
|
+
* @see https://www.paypal.com/.well-known/openid-configuration
|
|
15
|
+
*/
|
|
16
|
+
const PAYPAL_ID_TOKEN_ALGORITHMS = ["RS256", "HS256"] as const;
|
|
17
|
+
|
|
18
|
+
export interface PayPalProfile {
|
|
19
|
+
sub?: string | undefined;
|
|
20
|
+
user_id: string;
|
|
21
|
+
name: string;
|
|
22
|
+
given_name: string;
|
|
23
|
+
family_name: string;
|
|
24
|
+
middle_name?: string | undefined;
|
|
25
|
+
picture?: string | undefined;
|
|
26
|
+
email: string;
|
|
27
|
+
email_verified: boolean;
|
|
28
|
+
gender?: string | undefined;
|
|
29
|
+
birthdate?: string | undefined;
|
|
30
|
+
zoneinfo?: string | undefined;
|
|
31
|
+
locale?: string | undefined;
|
|
32
|
+
phone_number?: string | undefined;
|
|
33
|
+
address?:
|
|
34
|
+
| {
|
|
35
|
+
street_address?: string;
|
|
36
|
+
locality?: string;
|
|
37
|
+
region?: string;
|
|
38
|
+
postal_code?: string;
|
|
39
|
+
country?: string;
|
|
40
|
+
}
|
|
41
|
+
| undefined;
|
|
42
|
+
verified_account?: boolean | undefined;
|
|
43
|
+
account_type?: string | undefined;
|
|
44
|
+
age_range?: string | undefined;
|
|
45
|
+
payer_id?: string | undefined;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface PayPalTokenResponse {
|
|
49
|
+
scope?: string | undefined;
|
|
50
|
+
access_token: string;
|
|
51
|
+
refresh_token?: string | undefined;
|
|
52
|
+
token_type: "Bearer";
|
|
53
|
+
id_token?: string | undefined;
|
|
54
|
+
expires_in: number;
|
|
55
|
+
nonce?: string | undefined;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface PayPalOptions extends ProviderOptions<PayPalProfile> {
|
|
59
|
+
clientId: string;
|
|
60
|
+
/**
|
|
61
|
+
* PayPal environment - 'sandbox' for testing, 'live' for production
|
|
62
|
+
* @default 'sandbox'
|
|
63
|
+
*/
|
|
64
|
+
environment?: ("sandbox" | "live") | undefined;
|
|
65
|
+
/**
|
|
66
|
+
* Whether to request shipping address information
|
|
67
|
+
* @default false
|
|
68
|
+
*/
|
|
69
|
+
requestShippingAddress?: boolean | undefined;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export const paypal = (options: PayPalOptions) => {
|
|
73
|
+
const environment = options.environment || "sandbox";
|
|
74
|
+
const isSandbox = environment === "sandbox";
|
|
75
|
+
|
|
76
|
+
const authorizationEndpoint = isSandbox
|
|
77
|
+
? "https://www.sandbox.paypal.com/signin/authorize"
|
|
78
|
+
: "https://www.paypal.com/signin/authorize";
|
|
79
|
+
|
|
80
|
+
const tokenEndpoint = isSandbox
|
|
81
|
+
? "https://api-m.sandbox.paypal.com/v1/oauth2/token"
|
|
82
|
+
: "https://api-m.paypal.com/v1/oauth2/token";
|
|
83
|
+
|
|
84
|
+
const userInfoEndpoint = isSandbox
|
|
85
|
+
? "https://api-m.sandbox.paypal.com/v1/identity/oauth2/userinfo"
|
|
86
|
+
: "https://api-m.paypal.com/v1/identity/oauth2/userinfo";
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Issuer and JWKS endpoints used to cryptographically verify ID tokens.
|
|
90
|
+
*
|
|
91
|
+
* @see https://www.paypal.com/.well-known/openid-configuration
|
|
92
|
+
*/
|
|
93
|
+
const issuer = isSandbox
|
|
94
|
+
? "https://www.sandbox.paypal.com"
|
|
95
|
+
: "https://www.paypal.com";
|
|
96
|
+
|
|
97
|
+
const jwksEndpoint = isSandbox
|
|
98
|
+
? "https://api.sandbox.paypal.com/v1/oauth2/certs"
|
|
99
|
+
: "https://api.paypal.com/v1/oauth2/certs";
|
|
100
|
+
|
|
101
|
+
return {
|
|
102
|
+
id: "paypal",
|
|
103
|
+
name: "PayPal",
|
|
104
|
+
async createAuthorizationURL({ state, codeVerifier, redirectURI }) {
|
|
105
|
+
if (!options.clientId || !options.clientSecret) {
|
|
106
|
+
logger.error(
|
|
107
|
+
"Client Id and Client Secret is required for PayPal. Make sure to provide them in the options.",
|
|
108
|
+
);
|
|
109
|
+
throw new BetterAuthError("CLIENT_ID_AND_SECRET_REQUIRED");
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Log in with PayPal doesn't use traditional OAuth2 scopes
|
|
114
|
+
* Instead, permissions are configured in the PayPal Developer Dashboard
|
|
115
|
+
* We don't pass any scopes to avoid "invalid scope" errors
|
|
116
|
+
**/
|
|
117
|
+
|
|
118
|
+
const _scopes: string[] = [];
|
|
119
|
+
|
|
120
|
+
const url = await createAuthorizationURL({
|
|
121
|
+
id: "paypal",
|
|
122
|
+
options,
|
|
123
|
+
authorizationEndpoint,
|
|
124
|
+
scopes: _scopes,
|
|
125
|
+
state,
|
|
126
|
+
codeVerifier,
|
|
127
|
+
redirectURI,
|
|
128
|
+
prompt: options.prompt,
|
|
129
|
+
});
|
|
130
|
+
return url;
|
|
131
|
+
},
|
|
132
|
+
|
|
133
|
+
validateAuthorizationCode: async ({ code, redirectURI }) => {
|
|
134
|
+
/**
|
|
135
|
+
* PayPal requires Basic Auth for token exchange
|
|
136
|
+
**/
|
|
137
|
+
|
|
138
|
+
const credentials = base64.encode(
|
|
139
|
+
`${options.clientId}:${options.clientSecret}`,
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
try {
|
|
143
|
+
const response = await betterFetch(tokenEndpoint, {
|
|
144
|
+
method: "POST",
|
|
145
|
+
headers: {
|
|
146
|
+
Authorization: `Basic ${credentials}`,
|
|
147
|
+
Accept: "application/json",
|
|
148
|
+
"Accept-Language": "en_US",
|
|
149
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
150
|
+
},
|
|
151
|
+
body: new URLSearchParams({
|
|
152
|
+
grant_type: "authorization_code",
|
|
153
|
+
code: code,
|
|
154
|
+
redirect_uri: redirectURI,
|
|
155
|
+
}).toString(),
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
if (!response.data) {
|
|
159
|
+
throw new BetterAuthError("FAILED_TO_GET_ACCESS_TOKEN");
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const data = response.data as PayPalTokenResponse;
|
|
163
|
+
|
|
164
|
+
const result = {
|
|
165
|
+
accessToken: data.access_token,
|
|
166
|
+
refreshToken: data.refresh_token,
|
|
167
|
+
accessTokenExpiresAt: data.expires_in
|
|
168
|
+
? new Date(Date.now() + data.expires_in * 1000)
|
|
169
|
+
: undefined,
|
|
170
|
+
idToken: data.id_token,
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
return result;
|
|
174
|
+
} catch (error) {
|
|
175
|
+
logger.error("PayPal token exchange failed:", error);
|
|
176
|
+
throw new BetterAuthError("FAILED_TO_GET_ACCESS_TOKEN");
|
|
177
|
+
}
|
|
178
|
+
},
|
|
179
|
+
|
|
180
|
+
refreshAccessToken: options.refreshAccessToken
|
|
181
|
+
? options.refreshAccessToken
|
|
182
|
+
: async (refreshToken) => {
|
|
183
|
+
const credentials = base64.encode(
|
|
184
|
+
`${options.clientId}:${options.clientSecret}`,
|
|
185
|
+
);
|
|
186
|
+
|
|
187
|
+
try {
|
|
188
|
+
const response = await betterFetch(tokenEndpoint, {
|
|
189
|
+
method: "POST",
|
|
190
|
+
headers: {
|
|
191
|
+
Authorization: `Basic ${credentials}`,
|
|
192
|
+
Accept: "application/json",
|
|
193
|
+
"Accept-Language": "en_US",
|
|
194
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
195
|
+
},
|
|
196
|
+
body: new URLSearchParams({
|
|
197
|
+
grant_type: "refresh_token",
|
|
198
|
+
refresh_token: refreshToken,
|
|
199
|
+
}).toString(),
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
if (!response.data) {
|
|
203
|
+
throw new BetterAuthError("FAILED_TO_REFRESH_ACCESS_TOKEN");
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const data = response.data as any;
|
|
207
|
+
return {
|
|
208
|
+
accessToken: data.access_token,
|
|
209
|
+
refreshToken: data.refresh_token,
|
|
210
|
+
accessTokenExpiresAt: data.expires_in
|
|
211
|
+
? new Date(Date.now() + data.expires_in * 1000)
|
|
212
|
+
: undefined,
|
|
213
|
+
};
|
|
214
|
+
} catch (error) {
|
|
215
|
+
logger.error("PayPal token refresh failed:", error);
|
|
216
|
+
throw new BetterAuthError("FAILED_TO_REFRESH_ACCESS_TOKEN");
|
|
217
|
+
}
|
|
218
|
+
},
|
|
219
|
+
|
|
220
|
+
async verifyIdToken(token, nonce) {
|
|
221
|
+
if (options.disableIdTokenSignIn) {
|
|
222
|
+
return false;
|
|
223
|
+
}
|
|
224
|
+
if (options.verifyIdToken) {
|
|
225
|
+
return options.verifyIdToken(token, nonce);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Cryptographically verify the ID token. Decoding alone is not enough:
|
|
229
|
+
// the signature, issuer, audience and expiration must all be checked
|
|
230
|
+
// before the token's claims can be relied on as proof of identity.
|
|
231
|
+
// See https://www.paypal.com/.well-known/openid-configuration
|
|
232
|
+
|
|
233
|
+
try {
|
|
234
|
+
const { kid, alg: jwtAlg } = decodeProtectedHeader(token);
|
|
235
|
+
if (!jwtAlg) return false;
|
|
236
|
+
if (
|
|
237
|
+
!PAYPAL_ID_TOKEN_ALGORITHMS.includes(
|
|
238
|
+
jwtAlg as (typeof PAYPAL_ID_TOKEN_ALGORITHMS)[number],
|
|
239
|
+
)
|
|
240
|
+
) {
|
|
241
|
+
return false;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// PayPal can sign ID tokens either asymmetrically (RS256, verified
|
|
245
|
+
// against the published JWKS) or symmetrically (HS256, verified with
|
|
246
|
+
// the client secret). Selecting the key by algorithm keeps the two
|
|
247
|
+
// paths separate so each algorithm is only verified with its
|
|
248
|
+
// corresponding key type.
|
|
249
|
+
const key =
|
|
250
|
+
jwtAlg === "HS256"
|
|
251
|
+
? new TextEncoder().encode(options.clientSecret)
|
|
252
|
+
: kid
|
|
253
|
+
? await getPayPalPublicKey(kid, jwksEndpoint)
|
|
254
|
+
: undefined;
|
|
255
|
+
if (!key) return false;
|
|
256
|
+
|
|
257
|
+
const { payload: jwtClaims } = await jwtVerify(token, key, {
|
|
258
|
+
algorithms: [jwtAlg],
|
|
259
|
+
issuer,
|
|
260
|
+
audience: options.clientId,
|
|
261
|
+
maxTokenAge: "1h",
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
if (nonce && jwtClaims.nonce !== nonce) {
|
|
265
|
+
return false;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
return true;
|
|
269
|
+
} catch (error) {
|
|
270
|
+
logger.error("Failed to verify PayPal ID token:", error);
|
|
271
|
+
return false;
|
|
272
|
+
}
|
|
273
|
+
},
|
|
274
|
+
|
|
275
|
+
async getUserInfo(token) {
|
|
276
|
+
if (options.getUserInfo) {
|
|
277
|
+
return options.getUserInfo(token);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
if (!token.accessToken) {
|
|
281
|
+
logger.error("Access token is required to fetch PayPal user info");
|
|
282
|
+
return null;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
try {
|
|
286
|
+
const response = await betterFetch<PayPalProfile>(
|
|
287
|
+
`${userInfoEndpoint}?schema=paypalv1.1`,
|
|
288
|
+
{
|
|
289
|
+
headers: {
|
|
290
|
+
Authorization: `Bearer ${token.accessToken}`,
|
|
291
|
+
Accept: "application/json",
|
|
292
|
+
},
|
|
293
|
+
},
|
|
294
|
+
);
|
|
295
|
+
|
|
296
|
+
if (!response.data) {
|
|
297
|
+
logger.error("Failed to fetch user info from PayPal");
|
|
298
|
+
return null;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
const userInfo = response.data;
|
|
302
|
+
if (token.idToken) {
|
|
303
|
+
let idTokenSubject: string | undefined;
|
|
304
|
+
try {
|
|
305
|
+
idTokenSubject = decodeJwt(token.idToken).sub;
|
|
306
|
+
} catch (error) {
|
|
307
|
+
logger.error("Failed to decode PayPal ID token:", error);
|
|
308
|
+
return null;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// OIDC binds UserInfo to the ID Token with `sub`. Keep `user_id`
|
|
312
|
+
// as the account id below for existing PayPal account mappings.
|
|
313
|
+
const userInfoSubject = userInfo.sub ?? userInfo.user_id;
|
|
314
|
+
if (!idTokenSubject || userInfoSubject !== idTokenSubject) {
|
|
315
|
+
logger.error(
|
|
316
|
+
"PayPal user info subject does not match ID token subject",
|
|
317
|
+
);
|
|
318
|
+
return null;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
const userMap = await options.mapProfileToUser?.(userInfo);
|
|
323
|
+
|
|
324
|
+
const result = {
|
|
325
|
+
user: {
|
|
326
|
+
id: userInfo.user_id,
|
|
327
|
+
name: userInfo.name,
|
|
328
|
+
email: userInfo.email,
|
|
329
|
+
image: userInfo.picture,
|
|
330
|
+
emailVerified: userInfo.email_verified,
|
|
331
|
+
...userMap,
|
|
332
|
+
},
|
|
333
|
+
data: userInfo,
|
|
334
|
+
};
|
|
335
|
+
|
|
336
|
+
return result;
|
|
337
|
+
} catch (error) {
|
|
338
|
+
logger.error("Failed to fetch user info from PayPal:", error);
|
|
339
|
+
return null;
|
|
340
|
+
}
|
|
341
|
+
},
|
|
342
|
+
|
|
343
|
+
options,
|
|
344
|
+
} satisfies OAuthProvider<PayPalProfile>;
|
|
345
|
+
};
|
|
346
|
+
|
|
347
|
+
export const getPayPalPublicKey = async (kid: string, jwksUri: string) => {
|
|
348
|
+
const { data } = await betterFetch<{
|
|
349
|
+
keys: Array<{
|
|
350
|
+
kid: string;
|
|
351
|
+
alg: string;
|
|
352
|
+
kty: string;
|
|
353
|
+
use: string;
|
|
354
|
+
n: string;
|
|
355
|
+
e: string;
|
|
356
|
+
}>;
|
|
357
|
+
}>(jwksUri);
|
|
358
|
+
|
|
359
|
+
if (!data?.keys) {
|
|
360
|
+
throw new APIError("BAD_REQUEST", {
|
|
361
|
+
message: "Keys not found",
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
const jwk = data.keys.find((key) => key.kid === kid);
|
|
366
|
+
if (!jwk) {
|
|
367
|
+
throw new Error(`JWK with kid ${kid} not found`);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
return await importJWK(jwk, jwk.alg);
|
|
371
|
+
};
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { betterFetch } from "@better-fetch/fetch";
|
|
2
|
+
import type { OAuthProvider, ProviderOptions } from "../oauth2";
|
|
3
|
+
import {
|
|
4
|
+
createAuthorizationURL,
|
|
5
|
+
refreshAccessToken,
|
|
6
|
+
validateAuthorizationCode,
|
|
7
|
+
} from "../oauth2";
|
|
8
|
+
|
|
9
|
+
export interface PolarProfile {
|
|
10
|
+
id: string;
|
|
11
|
+
email: string;
|
|
12
|
+
username: string;
|
|
13
|
+
avatar_url: string;
|
|
14
|
+
github_username?: string | undefined;
|
|
15
|
+
account_id?: string | undefined;
|
|
16
|
+
public_name?: string | undefined;
|
|
17
|
+
email_verified?: boolean | undefined;
|
|
18
|
+
profile_settings?:
|
|
19
|
+
| {
|
|
20
|
+
profile_settings_enabled?: boolean;
|
|
21
|
+
profile_settings_public_name?: string;
|
|
22
|
+
profile_settings_public_avatar?: string;
|
|
23
|
+
profile_settings_public_bio?: string;
|
|
24
|
+
profile_settings_public_location?: string;
|
|
25
|
+
profile_settings_public_website?: string;
|
|
26
|
+
profile_settings_public_twitter?: string;
|
|
27
|
+
profile_settings_public_github?: string;
|
|
28
|
+
profile_settings_public_email?: string;
|
|
29
|
+
}
|
|
30
|
+
| undefined;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface PolarOptions extends ProviderOptions<PolarProfile> {}
|
|
34
|
+
|
|
35
|
+
export const polar = (options: PolarOptions) => {
|
|
36
|
+
const tokenEndpoint = "https://api.polar.sh/v1/oauth2/token";
|
|
37
|
+
return {
|
|
38
|
+
id: "polar",
|
|
39
|
+
name: "Polar",
|
|
40
|
+
createAuthorizationURL({ state, scopes, codeVerifier, redirectURI }) {
|
|
41
|
+
const _scopes = options.disableDefaultScope
|
|
42
|
+
? []
|
|
43
|
+
: ["openid", "profile", "email"];
|
|
44
|
+
if (options.scope) _scopes.push(...options.scope);
|
|
45
|
+
if (scopes) _scopes.push(...scopes);
|
|
46
|
+
return createAuthorizationURL({
|
|
47
|
+
id: "polar",
|
|
48
|
+
options,
|
|
49
|
+
authorizationEndpoint: "https://polar.sh/oauth2/authorize",
|
|
50
|
+
scopes: _scopes,
|
|
51
|
+
state,
|
|
52
|
+
codeVerifier,
|
|
53
|
+
redirectURI,
|
|
54
|
+
prompt: options.prompt,
|
|
55
|
+
});
|
|
56
|
+
},
|
|
57
|
+
validateAuthorizationCode: async ({ code, codeVerifier, redirectURI }) => {
|
|
58
|
+
return validateAuthorizationCode({
|
|
59
|
+
code,
|
|
60
|
+
codeVerifier,
|
|
61
|
+
redirectURI,
|
|
62
|
+
options,
|
|
63
|
+
tokenEndpoint,
|
|
64
|
+
});
|
|
65
|
+
},
|
|
66
|
+
refreshAccessToken: options.refreshAccessToken
|
|
67
|
+
? options.refreshAccessToken
|
|
68
|
+
: async (refreshToken) => {
|
|
69
|
+
return refreshAccessToken({
|
|
70
|
+
refreshToken,
|
|
71
|
+
options: {
|
|
72
|
+
clientId: options.clientId,
|
|
73
|
+
clientKey: options.clientKey,
|
|
74
|
+
clientSecret: options.clientSecret,
|
|
75
|
+
},
|
|
76
|
+
tokenEndpoint,
|
|
77
|
+
});
|
|
78
|
+
},
|
|
79
|
+
async getUserInfo(token) {
|
|
80
|
+
if (options.getUserInfo) {
|
|
81
|
+
return options.getUserInfo(token);
|
|
82
|
+
}
|
|
83
|
+
const { data: profile, error } = await betterFetch<PolarProfile>(
|
|
84
|
+
"https://api.polar.sh/v1/oauth2/userinfo",
|
|
85
|
+
{
|
|
86
|
+
headers: {
|
|
87
|
+
Authorization: `Bearer ${token.accessToken}`,
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
);
|
|
91
|
+
if (error) {
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
const userMap = await options.mapProfileToUser?.(profile);
|
|
95
|
+
// Polar may provide email_verified claim, but it's not guaranteed.
|
|
96
|
+
// We check for it first, then default to false for security consistency.
|
|
97
|
+
return {
|
|
98
|
+
user: {
|
|
99
|
+
id: profile.id,
|
|
100
|
+
name: profile.public_name || profile.username || "",
|
|
101
|
+
email: profile.email,
|
|
102
|
+
image: profile.avatar_url,
|
|
103
|
+
emailVerified: profile.email_verified ?? false,
|
|
104
|
+
...userMap,
|
|
105
|
+
},
|
|
106
|
+
data: profile,
|
|
107
|
+
};
|
|
108
|
+
},
|
|
109
|
+
options,
|
|
110
|
+
} satisfies OAuthProvider<PolarProfile>;
|
|
111
|
+
};
|