@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,170 @@
|
|
|
1
|
+
import { betterFetch } from "@better-fetch/fetch";
|
|
2
|
+
import type { OAuthProvider, ProviderOptions } from "../oauth2";
|
|
3
|
+
import { refreshAccessToken, validateAuthorizationCode } from "../oauth2";
|
|
4
|
+
export interface DiscordProfile extends Record<string, any> {
|
|
5
|
+
/** the user's id (i.e. the numerical snowflake) */
|
|
6
|
+
id: string;
|
|
7
|
+
/** the user's username, not unique across the platform */
|
|
8
|
+
username: string;
|
|
9
|
+
/** the user's Discord-tag */
|
|
10
|
+
discriminator: string;
|
|
11
|
+
/** the user's display name, if it is set */
|
|
12
|
+
global_name: string | null;
|
|
13
|
+
/**
|
|
14
|
+
* the user's avatar hash:
|
|
15
|
+
* https://discord.com/developers/docs/reference#image-formatting
|
|
16
|
+
*/
|
|
17
|
+
avatar: string | null;
|
|
18
|
+
/** whether the user belongs to an OAuth2 application */
|
|
19
|
+
bot?: boolean | undefined;
|
|
20
|
+
/**
|
|
21
|
+
* whether the user is an Official Discord System user (part of the urgent
|
|
22
|
+
* message system)
|
|
23
|
+
*/
|
|
24
|
+
system?: boolean | undefined;
|
|
25
|
+
/** whether the user has two factor enabled on their account */
|
|
26
|
+
mfa_enabled: boolean;
|
|
27
|
+
/**
|
|
28
|
+
* the user's banner hash:
|
|
29
|
+
* https://discord.com/developers/docs/reference#image-formatting
|
|
30
|
+
*/
|
|
31
|
+
banner: string | null;
|
|
32
|
+
|
|
33
|
+
/** the user's banner color encoded as an integer representation of hexadecimal color code */
|
|
34
|
+
accent_color: number | null;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* the user's chosen language option:
|
|
38
|
+
* https://discord.com/developers/docs/reference#locales
|
|
39
|
+
*/
|
|
40
|
+
locale: string;
|
|
41
|
+
/** whether the email on this account has been verified */
|
|
42
|
+
verified: boolean;
|
|
43
|
+
/** the user's email */
|
|
44
|
+
email?: string | null;
|
|
45
|
+
/**
|
|
46
|
+
* the flags on a user's account:
|
|
47
|
+
* https://discord.com/developers/docs/resources/user#user-object-user-flags
|
|
48
|
+
*/
|
|
49
|
+
flags: number;
|
|
50
|
+
/**
|
|
51
|
+
* the type of Nitro subscription on a user's account:
|
|
52
|
+
* https://discord.com/developers/docs/resources/user#user-object-premium-types
|
|
53
|
+
*/
|
|
54
|
+
premium_type: number;
|
|
55
|
+
/**
|
|
56
|
+
* the public flags on a user's account:
|
|
57
|
+
* https://discord.com/developers/docs/resources/user#user-object-user-flags
|
|
58
|
+
*/
|
|
59
|
+
public_flags: number;
|
|
60
|
+
/** undocumented field; corresponds to the user's custom nickname */
|
|
61
|
+
display_name: string | null;
|
|
62
|
+
/**
|
|
63
|
+
* undocumented field; corresponds to the Discord feature where you can e.g.
|
|
64
|
+
* put your avatar inside of an ice cube
|
|
65
|
+
*/
|
|
66
|
+
avatar_decoration: string | null;
|
|
67
|
+
/**
|
|
68
|
+
* undocumented field; corresponds to the premium feature where you can
|
|
69
|
+
* select a custom banner color
|
|
70
|
+
*/
|
|
71
|
+
banner_color: string | null;
|
|
72
|
+
/** undocumented field; the CDN URL of their profile picture */
|
|
73
|
+
image_url: string;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export interface DiscordOptions extends ProviderOptions<DiscordProfile> {
|
|
77
|
+
clientId: string;
|
|
78
|
+
prompt?: ("none" | "consent") | undefined;
|
|
79
|
+
permissions?: number | undefined;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export const discord = (options: DiscordOptions) => {
|
|
83
|
+
const tokenEndpoint = "https://discord.com/api/oauth2/token";
|
|
84
|
+
return {
|
|
85
|
+
id: "discord",
|
|
86
|
+
name: "Discord",
|
|
87
|
+
createAuthorizationURL({ state, scopes, redirectURI }) {
|
|
88
|
+
const _scopes = options.disableDefaultScope ? [] : ["identify", "email"];
|
|
89
|
+
if (scopes) _scopes.push(...scopes);
|
|
90
|
+
if (options.scope) _scopes.push(...options.scope);
|
|
91
|
+
const hasBotScope = _scopes.includes("bot");
|
|
92
|
+
const permissionsParam =
|
|
93
|
+
hasBotScope && options.permissions !== undefined
|
|
94
|
+
? `&permissions=${options.permissions}`
|
|
95
|
+
: "";
|
|
96
|
+
return new URL(
|
|
97
|
+
`https://discord.com/api/oauth2/authorize?scope=${_scopes.join(
|
|
98
|
+
"+",
|
|
99
|
+
)}&response_type=code&client_id=${
|
|
100
|
+
options.clientId
|
|
101
|
+
}&redirect_uri=${encodeURIComponent(
|
|
102
|
+
options.redirectURI || redirectURI,
|
|
103
|
+
)}&state=${state}&prompt=${
|
|
104
|
+
options.prompt || "none"
|
|
105
|
+
}${permissionsParam}`,
|
|
106
|
+
);
|
|
107
|
+
},
|
|
108
|
+
validateAuthorizationCode: async ({ code, redirectURI }) => {
|
|
109
|
+
return validateAuthorizationCode({
|
|
110
|
+
code,
|
|
111
|
+
redirectURI,
|
|
112
|
+
options,
|
|
113
|
+
tokenEndpoint,
|
|
114
|
+
});
|
|
115
|
+
},
|
|
116
|
+
refreshAccessToken: options.refreshAccessToken
|
|
117
|
+
? options.refreshAccessToken
|
|
118
|
+
: async (refreshToken) => {
|
|
119
|
+
return refreshAccessToken({
|
|
120
|
+
refreshToken,
|
|
121
|
+
options: {
|
|
122
|
+
clientId: options.clientId,
|
|
123
|
+
clientKey: options.clientKey,
|
|
124
|
+
clientSecret: options.clientSecret,
|
|
125
|
+
},
|
|
126
|
+
tokenEndpoint,
|
|
127
|
+
});
|
|
128
|
+
},
|
|
129
|
+
async getUserInfo(token) {
|
|
130
|
+
if (options.getUserInfo) {
|
|
131
|
+
return options.getUserInfo(token);
|
|
132
|
+
}
|
|
133
|
+
const { data: profile, error } = await betterFetch<DiscordProfile>(
|
|
134
|
+
"https://discord.com/api/users/@me",
|
|
135
|
+
{
|
|
136
|
+
headers: {
|
|
137
|
+
authorization: `Bearer ${token.accessToken}`,
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
if (error) {
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
if (profile.avatar === null) {
|
|
146
|
+
const defaultAvatarNumber =
|
|
147
|
+
profile.discriminator === "0"
|
|
148
|
+
? Number(BigInt(profile.id) >> BigInt(22)) % 6
|
|
149
|
+
: parseInt(profile.discriminator) % 5;
|
|
150
|
+
profile.image_url = `https://cdn.discordapp.com/embed/avatars/${defaultAvatarNumber}.png`;
|
|
151
|
+
} else {
|
|
152
|
+
const format = profile.avatar.startsWith("a_") ? "gif" : "png";
|
|
153
|
+
profile.image_url = `https://cdn.discordapp.com/avatars/${profile.id}/${profile.avatar}.${format}`;
|
|
154
|
+
}
|
|
155
|
+
const userMap = await options.mapProfileToUser?.(profile);
|
|
156
|
+
return {
|
|
157
|
+
user: {
|
|
158
|
+
id: profile.id,
|
|
159
|
+
name: profile.global_name || profile.username || "",
|
|
160
|
+
email: profile.email,
|
|
161
|
+
emailVerified: profile.verified,
|
|
162
|
+
image: profile.image_url,
|
|
163
|
+
...userMap,
|
|
164
|
+
},
|
|
165
|
+
data: profile,
|
|
166
|
+
};
|
|
167
|
+
},
|
|
168
|
+
options,
|
|
169
|
+
} satisfies OAuthProvider<DiscordProfile>;
|
|
170
|
+
};
|
|
@@ -0,0 +1,112 @@
|
|
|
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 DropboxProfile {
|
|
10
|
+
account_id: string;
|
|
11
|
+
name: {
|
|
12
|
+
given_name: string;
|
|
13
|
+
surname: string;
|
|
14
|
+
familiar_name: string;
|
|
15
|
+
display_name: string;
|
|
16
|
+
abbreviated_name: string;
|
|
17
|
+
};
|
|
18
|
+
email: string;
|
|
19
|
+
email_verified: boolean;
|
|
20
|
+
profile_photo_url: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface DropboxOptions extends ProviderOptions<DropboxProfile> {
|
|
24
|
+
clientId: string;
|
|
25
|
+
accessType?: ("offline" | "online" | "legacy") | undefined;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export const dropbox = (options: DropboxOptions) => {
|
|
29
|
+
const tokenEndpoint = "https://api.dropboxapi.com/oauth2/token";
|
|
30
|
+
|
|
31
|
+
return {
|
|
32
|
+
id: "dropbox",
|
|
33
|
+
name: "Dropbox",
|
|
34
|
+
createAuthorizationURL: async ({
|
|
35
|
+
state,
|
|
36
|
+
scopes,
|
|
37
|
+
codeVerifier,
|
|
38
|
+
redirectURI,
|
|
39
|
+
}) => {
|
|
40
|
+
const _scopes = options.disableDefaultScope ? [] : ["account_info.read"];
|
|
41
|
+
if (options.scope) _scopes.push(...options.scope);
|
|
42
|
+
if (scopes) _scopes.push(...scopes);
|
|
43
|
+
const additionalParams: Record<string, string> = {};
|
|
44
|
+
if (options.accessType) {
|
|
45
|
+
additionalParams.token_access_type = options.accessType;
|
|
46
|
+
}
|
|
47
|
+
return await createAuthorizationURL({
|
|
48
|
+
id: "dropbox",
|
|
49
|
+
options,
|
|
50
|
+
authorizationEndpoint: "https://www.dropbox.com/oauth2/authorize",
|
|
51
|
+
scopes: _scopes,
|
|
52
|
+
state,
|
|
53
|
+
redirectURI,
|
|
54
|
+
codeVerifier,
|
|
55
|
+
additionalParams,
|
|
56
|
+
});
|
|
57
|
+
},
|
|
58
|
+
validateAuthorizationCode: async ({ code, codeVerifier, redirectURI }) => {
|
|
59
|
+
return await validateAuthorizationCode({
|
|
60
|
+
code,
|
|
61
|
+
codeVerifier,
|
|
62
|
+
redirectURI,
|
|
63
|
+
options,
|
|
64
|
+
tokenEndpoint,
|
|
65
|
+
});
|
|
66
|
+
},
|
|
67
|
+
refreshAccessToken: options.refreshAccessToken
|
|
68
|
+
? options.refreshAccessToken
|
|
69
|
+
: async (refreshToken) => {
|
|
70
|
+
return refreshAccessToken({
|
|
71
|
+
refreshToken,
|
|
72
|
+
options: {
|
|
73
|
+
clientId: options.clientId,
|
|
74
|
+
clientKey: options.clientKey,
|
|
75
|
+
clientSecret: options.clientSecret,
|
|
76
|
+
},
|
|
77
|
+
tokenEndpoint,
|
|
78
|
+
});
|
|
79
|
+
},
|
|
80
|
+
async getUserInfo(token) {
|
|
81
|
+
if (options.getUserInfo) {
|
|
82
|
+
return options.getUserInfo(token);
|
|
83
|
+
}
|
|
84
|
+
const { data: profile, error } = await betterFetch<DropboxProfile>(
|
|
85
|
+
"https://api.dropboxapi.com/2/users/get_current_account",
|
|
86
|
+
{
|
|
87
|
+
method: "POST",
|
|
88
|
+
headers: {
|
|
89
|
+
Authorization: `Bearer ${token.accessToken}`,
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
if (error) {
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
const userMap = await options.mapProfileToUser?.(profile);
|
|
98
|
+
return {
|
|
99
|
+
user: {
|
|
100
|
+
id: profile.account_id,
|
|
101
|
+
name: profile.name?.display_name,
|
|
102
|
+
email: profile.email,
|
|
103
|
+
emailVerified: profile.email_verified || false,
|
|
104
|
+
image: profile.profile_photo_url,
|
|
105
|
+
...userMap,
|
|
106
|
+
},
|
|
107
|
+
data: profile,
|
|
108
|
+
};
|
|
109
|
+
},
|
|
110
|
+
options,
|
|
111
|
+
} satisfies OAuthProvider<DropboxProfile>;
|
|
112
|
+
};
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
import { betterFetch } from "@better-fetch/fetch";
|
|
2
|
+
import { createRemoteJWKSet, decodeJwt, jwtVerify } from "jose";
|
|
3
|
+
import { logger } from "../env";
|
|
4
|
+
import { BetterAuthError } from "../error";
|
|
5
|
+
import type { OAuthProvider, ProviderOptions } from "../oauth2";
|
|
6
|
+
import {
|
|
7
|
+
createAuthorizationURL,
|
|
8
|
+
getPrimaryClientId,
|
|
9
|
+
refreshAccessToken,
|
|
10
|
+
validateAuthorizationCode,
|
|
11
|
+
} from "../oauth2";
|
|
12
|
+
export interface FacebookProfile {
|
|
13
|
+
id: string;
|
|
14
|
+
name: string;
|
|
15
|
+
email?: string;
|
|
16
|
+
email_verified?: boolean;
|
|
17
|
+
picture: {
|
|
18
|
+
data: {
|
|
19
|
+
height: number;
|
|
20
|
+
is_silhouette: boolean;
|
|
21
|
+
url: string;
|
|
22
|
+
width: number;
|
|
23
|
+
};
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface FacebookDebugTokenData {
|
|
28
|
+
app_id?: string;
|
|
29
|
+
is_valid?: boolean;
|
|
30
|
+
user_id?: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Validate an opaque Facebook access token against the configured app.
|
|
35
|
+
*
|
|
36
|
+
* Facebook access tokens are not audience-bound at the Graph `/me` endpoint: a
|
|
37
|
+
* token minted for any Facebook app returns that app's profile. Without this
|
|
38
|
+
* check, a token issued to an unrelated app could be presented to this
|
|
39
|
+
* app's direct sign-in path and accepted as proof of identity. We call the
|
|
40
|
+
* `debug_token` endpoint and require the token to be valid, bound to one of the
|
|
41
|
+
* configured client ids, and tied to a user.
|
|
42
|
+
*
|
|
43
|
+
* @see https://developers.facebook.com/docs/facebook-login/guides/access-tokens/debugging
|
|
44
|
+
*
|
|
45
|
+
* @returns the inspected token's `user_id` when the token is valid and bound to
|
|
46
|
+
* the configured app, otherwise `null`.
|
|
47
|
+
*/
|
|
48
|
+
async function verifyFacebookAccessToken(
|
|
49
|
+
accessToken: string,
|
|
50
|
+
options: FacebookOptions,
|
|
51
|
+
): Promise<string | null> {
|
|
52
|
+
const primaryClientId = getPrimaryClientId(options.clientId);
|
|
53
|
+
if (!primaryClientId || !options.clientSecret) {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
const clientIds = Array.isArray(options.clientId)
|
|
57
|
+
? options.clientId
|
|
58
|
+
: [options.clientId];
|
|
59
|
+
const appAccessToken = `${primaryClientId}|${options.clientSecret}`;
|
|
60
|
+
const { data, error } = await betterFetch<{ data?: FacebookDebugTokenData }>(
|
|
61
|
+
"https://graph.facebook.com/debug_token",
|
|
62
|
+
{
|
|
63
|
+
query: {
|
|
64
|
+
input_token: accessToken,
|
|
65
|
+
access_token: appAccessToken,
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
);
|
|
69
|
+
if (error || !data?.data) {
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
const { is_valid, app_id, user_id } = data.data;
|
|
73
|
+
if (is_valid !== true || !app_id || !clientIds.includes(app_id) || !user_id) {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
return user_id;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export interface FacebookOptions extends ProviderOptions<FacebookProfile> {
|
|
80
|
+
clientId: string | string[];
|
|
81
|
+
/**
|
|
82
|
+
* Extend list of fields to retrieve from the Facebook user profile.
|
|
83
|
+
*
|
|
84
|
+
* @default ["id", "name", "email", "picture"]
|
|
85
|
+
*/
|
|
86
|
+
fields?: string[] | undefined;
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* The config id to use when undergoing oauth
|
|
90
|
+
*/
|
|
91
|
+
configId?: string | undefined;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export const facebook = (options: FacebookOptions) => {
|
|
95
|
+
return {
|
|
96
|
+
id: "facebook",
|
|
97
|
+
name: "Facebook",
|
|
98
|
+
async createAuthorizationURL({ state, scopes, redirectURI, loginHint }) {
|
|
99
|
+
if (!getPrimaryClientId(options.clientId) || !options.clientSecret) {
|
|
100
|
+
logger.error(
|
|
101
|
+
"Client ID and client secret are required for Facebook. Make sure to provide them in the options.",
|
|
102
|
+
);
|
|
103
|
+
throw new BetterAuthError("CLIENT_ID_AND_SECRET_REQUIRED");
|
|
104
|
+
}
|
|
105
|
+
const _scopes = options.disableDefaultScope
|
|
106
|
+
? []
|
|
107
|
+
: ["email", "public_profile"];
|
|
108
|
+
if (options.scope) _scopes.push(...options.scope);
|
|
109
|
+
if (scopes) _scopes.push(...scopes);
|
|
110
|
+
return await createAuthorizationURL({
|
|
111
|
+
id: "facebook",
|
|
112
|
+
options,
|
|
113
|
+
authorizationEndpoint: "https://www.facebook.com/v24.0/dialog/oauth",
|
|
114
|
+
scopes: _scopes,
|
|
115
|
+
state,
|
|
116
|
+
redirectURI,
|
|
117
|
+
loginHint,
|
|
118
|
+
additionalParams: options.configId
|
|
119
|
+
? {
|
|
120
|
+
config_id: options.configId,
|
|
121
|
+
}
|
|
122
|
+
: {},
|
|
123
|
+
});
|
|
124
|
+
},
|
|
125
|
+
validateAuthorizationCode: async ({ code, redirectURI }) => {
|
|
126
|
+
return validateAuthorizationCode({
|
|
127
|
+
code,
|
|
128
|
+
redirectURI,
|
|
129
|
+
options,
|
|
130
|
+
tokenEndpoint: "https://graph.facebook.com/v24.0/oauth/access_token",
|
|
131
|
+
});
|
|
132
|
+
},
|
|
133
|
+
async verifyIdToken(token, nonce) {
|
|
134
|
+
if (options.disableIdTokenSignIn) {
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (options.verifyIdToken) {
|
|
139
|
+
return options.verifyIdToken(token, nonce);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/* limited login */
|
|
143
|
+
// check is limited token
|
|
144
|
+
if (token.split(".").length === 3) {
|
|
145
|
+
try {
|
|
146
|
+
const { payload: jwtClaims } = await jwtVerify(
|
|
147
|
+
token,
|
|
148
|
+
createRemoteJWKSet(
|
|
149
|
+
// https://developers.facebook.com/docs/facebook-login/limited-login/token/#jwks
|
|
150
|
+
new URL(
|
|
151
|
+
"https://limited.facebook.com/.well-known/oauth/openid/jwks/",
|
|
152
|
+
),
|
|
153
|
+
),
|
|
154
|
+
{
|
|
155
|
+
algorithms: ["RS256"],
|
|
156
|
+
audience: options.clientId,
|
|
157
|
+
issuer: "https://www.facebook.com",
|
|
158
|
+
},
|
|
159
|
+
);
|
|
160
|
+
|
|
161
|
+
if (nonce && jwtClaims.nonce !== nonce) {
|
|
162
|
+
return false;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return !!jwtClaims;
|
|
166
|
+
} catch {
|
|
167
|
+
return false;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/* access_token */
|
|
172
|
+
// An opaque access token carries no app binding of its own, so it
|
|
173
|
+
// must be validated against the configured app before it can be
|
|
174
|
+
// trusted as proof of identity.
|
|
175
|
+
return (await verifyFacebookAccessToken(token, options)) !== null;
|
|
176
|
+
},
|
|
177
|
+
refreshAccessToken: options.refreshAccessToken
|
|
178
|
+
? options.refreshAccessToken
|
|
179
|
+
: async (refreshToken) => {
|
|
180
|
+
return refreshAccessToken({
|
|
181
|
+
refreshToken,
|
|
182
|
+
options: {
|
|
183
|
+
clientId: options.clientId,
|
|
184
|
+
clientKey: options.clientKey,
|
|
185
|
+
clientSecret: options.clientSecret,
|
|
186
|
+
},
|
|
187
|
+
tokenEndpoint:
|
|
188
|
+
"https://graph.facebook.com/v24.0/oauth/access_token",
|
|
189
|
+
});
|
|
190
|
+
},
|
|
191
|
+
async getUserInfo(token) {
|
|
192
|
+
if (options.getUserInfo) {
|
|
193
|
+
return options.getUserInfo(token);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (token.idToken && token.idToken.split(".").length === 3) {
|
|
197
|
+
const profile = decodeJwt(token.idToken) as {
|
|
198
|
+
sub: string;
|
|
199
|
+
email: string;
|
|
200
|
+
name: string;
|
|
201
|
+
picture: string;
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
const user = {
|
|
205
|
+
id: profile.sub,
|
|
206
|
+
name: profile.name,
|
|
207
|
+
email: profile.email,
|
|
208
|
+
picture: {
|
|
209
|
+
data: {
|
|
210
|
+
url: profile.picture,
|
|
211
|
+
height: 100,
|
|
212
|
+
width: 100,
|
|
213
|
+
is_silhouette: false,
|
|
214
|
+
},
|
|
215
|
+
},
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
// https://developers.facebook.com/docs/facebook-login/limited-login/permissions
|
|
219
|
+
// Facebook ID token does not include email_verified claim.
|
|
220
|
+
// We default to false for security consistency.
|
|
221
|
+
const userMap = await options.mapProfileToUser?.({
|
|
222
|
+
...user,
|
|
223
|
+
email_verified: false,
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
return {
|
|
227
|
+
user: {
|
|
228
|
+
...user,
|
|
229
|
+
emailVerified: false,
|
|
230
|
+
...userMap,
|
|
231
|
+
},
|
|
232
|
+
data: profile,
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// The profile is fetched with `accessToken`, which is the credential
|
|
237
|
+
// that actually proves identity here — and a separate request field
|
|
238
|
+
// from the `idToken`/token validated by `verifyIdToken`. Since an
|
|
239
|
+
// opaque token is not app-bound at `/me`, validate this exact token
|
|
240
|
+
// against the configured app before trusting the profile it returns.
|
|
241
|
+
const accessToken = token.accessToken;
|
|
242
|
+
if (!accessToken) {
|
|
243
|
+
return null;
|
|
244
|
+
}
|
|
245
|
+
const tokenUserId = await verifyFacebookAccessToken(accessToken, options);
|
|
246
|
+
if (!tokenUserId) {
|
|
247
|
+
return null;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const fields = [
|
|
251
|
+
"id",
|
|
252
|
+
"name",
|
|
253
|
+
"email",
|
|
254
|
+
"picture",
|
|
255
|
+
...(options?.fields || []),
|
|
256
|
+
];
|
|
257
|
+
const { data: profile, error } = await betterFetch<FacebookProfile>(
|
|
258
|
+
"https://graph.facebook.com/me?fields=" + fields.join(","),
|
|
259
|
+
{
|
|
260
|
+
auth: {
|
|
261
|
+
type: "Bearer",
|
|
262
|
+
token: accessToken,
|
|
263
|
+
},
|
|
264
|
+
},
|
|
265
|
+
);
|
|
266
|
+
if (error) {
|
|
267
|
+
return null;
|
|
268
|
+
}
|
|
269
|
+
// Bind the validated token to the profile it returned.
|
|
270
|
+
if (profile.id !== tokenUserId) {
|
|
271
|
+
return null;
|
|
272
|
+
}
|
|
273
|
+
const userMap = await options.mapProfileToUser?.(profile);
|
|
274
|
+
return {
|
|
275
|
+
user: {
|
|
276
|
+
id: profile.id,
|
|
277
|
+
name: profile.name,
|
|
278
|
+
email: profile.email,
|
|
279
|
+
image: profile.picture.data.url,
|
|
280
|
+
emailVerified: profile.email_verified ?? false,
|
|
281
|
+
...userMap,
|
|
282
|
+
},
|
|
283
|
+
data: profile,
|
|
284
|
+
};
|
|
285
|
+
},
|
|
286
|
+
options,
|
|
287
|
+
} satisfies OAuthProvider<FacebookProfile>;
|
|
288
|
+
};
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { betterFetch } from "@better-fetch/fetch";
|
|
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 FigmaProfile {
|
|
12
|
+
id: string;
|
|
13
|
+
email: string;
|
|
14
|
+
handle: string;
|
|
15
|
+
img_url: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface FigmaOptions extends ProviderOptions<FigmaProfile> {
|
|
19
|
+
clientId: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export const figma = (options: FigmaOptions) => {
|
|
23
|
+
const tokenEndpoint = "https://api.figma.com/v1/oauth/token";
|
|
24
|
+
return {
|
|
25
|
+
id: "figma",
|
|
26
|
+
name: "Figma",
|
|
27
|
+
async createAuthorizationURL({ state, scopes, codeVerifier, redirectURI }) {
|
|
28
|
+
if (!options.clientId || !options.clientSecret) {
|
|
29
|
+
logger.error(
|
|
30
|
+
"Client Id and Client Secret are required for Figma. Make sure to provide them in the options.",
|
|
31
|
+
);
|
|
32
|
+
throw new BetterAuthError("CLIENT_ID_AND_SECRET_REQUIRED");
|
|
33
|
+
}
|
|
34
|
+
if (!codeVerifier) {
|
|
35
|
+
throw new BetterAuthError("codeVerifier is required for Figma");
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const _scopes = options.disableDefaultScope ? [] : ["current_user:read"];
|
|
39
|
+
if (options.scope) _scopes.push(...options.scope);
|
|
40
|
+
if (scopes) _scopes.push(...scopes);
|
|
41
|
+
|
|
42
|
+
const url = await createAuthorizationURL({
|
|
43
|
+
id: "figma",
|
|
44
|
+
options,
|
|
45
|
+
authorizationEndpoint: "https://www.figma.com/oauth",
|
|
46
|
+
scopes: _scopes,
|
|
47
|
+
state,
|
|
48
|
+
codeVerifier,
|
|
49
|
+
redirectURI,
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
return url;
|
|
53
|
+
},
|
|
54
|
+
validateAuthorizationCode: async ({ code, codeVerifier, redirectURI }) => {
|
|
55
|
+
return validateAuthorizationCode({
|
|
56
|
+
code,
|
|
57
|
+
codeVerifier,
|
|
58
|
+
redirectURI,
|
|
59
|
+
options,
|
|
60
|
+
tokenEndpoint,
|
|
61
|
+
authentication: "basic",
|
|
62
|
+
});
|
|
63
|
+
},
|
|
64
|
+
refreshAccessToken: options.refreshAccessToken
|
|
65
|
+
? options.refreshAccessToken
|
|
66
|
+
: async (refreshToken) => {
|
|
67
|
+
return refreshAccessToken({
|
|
68
|
+
refreshToken,
|
|
69
|
+
options: {
|
|
70
|
+
clientId: options.clientId,
|
|
71
|
+
clientKey: options.clientKey,
|
|
72
|
+
clientSecret: options.clientSecret,
|
|
73
|
+
},
|
|
74
|
+
tokenEndpoint,
|
|
75
|
+
authentication: "basic",
|
|
76
|
+
});
|
|
77
|
+
},
|
|
78
|
+
async getUserInfo(token) {
|
|
79
|
+
if (options.getUserInfo) {
|
|
80
|
+
return options.getUserInfo(token);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
try {
|
|
84
|
+
const { data: profile } = await betterFetch<FigmaProfile>(
|
|
85
|
+
"https://api.figma.com/v1/me",
|
|
86
|
+
{
|
|
87
|
+
headers: {
|
|
88
|
+
Authorization: `Bearer ${token.accessToken}`,
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
if (!profile) {
|
|
94
|
+
logger.error("Failed to fetch user from Figma");
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const userMap = await options.mapProfileToUser?.(profile);
|
|
99
|
+
|
|
100
|
+
return {
|
|
101
|
+
user: {
|
|
102
|
+
id: profile.id,
|
|
103
|
+
name: profile.handle,
|
|
104
|
+
email: profile.email,
|
|
105
|
+
image: profile.img_url,
|
|
106
|
+
emailVerified: false,
|
|
107
|
+
...userMap,
|
|
108
|
+
},
|
|
109
|
+
data: profile,
|
|
110
|
+
};
|
|
111
|
+
} catch (error) {
|
|
112
|
+
logger.error("Failed to fetch user info from Figma:", error);
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
},
|
|
116
|
+
options,
|
|
117
|
+
} satisfies OAuthProvider<FigmaProfile>;
|
|
118
|
+
};
|