@better-auth/core 1.4.12-beta.2 → 1.4.13
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 +172 -35
- package/dist/api/index.d.mts +178 -1
- package/dist/api/index.mjs +2 -1
- package/dist/context/endpoint-context.d.mts +19 -0
- package/dist/context/endpoint-context.mjs +31 -0
- package/dist/context/global.d.mts +7 -0
- package/dist/context/global.mjs +37 -0
- package/dist/context/index.d.mts +5 -53
- package/dist/context/index.mjs +5 -2
- package/dist/context/request-state.d.mts +27 -0
- package/dist/context/request-state.mjs +49 -0
- package/dist/context/transaction.d.mts +16 -0
- package/dist/context/transaction.mjs +52 -0
- package/dist/db/adapter/factory.d.mts +27 -0
- package/dist/db/adapter/factory.mjs +738 -0
- package/dist/db/adapter/get-default-field-name.d.mts +18 -0
- package/dist/db/adapter/get-default-field-name.mjs +38 -0
- package/dist/db/adapter/get-default-model-name.d.mts +12 -0
- package/dist/db/adapter/get-default-model-name.mjs +32 -0
- package/dist/db/adapter/get-field-attributes.d.mts +29 -0
- package/dist/db/adapter/get-field-attributes.mjs +39 -0
- package/dist/db/adapter/get-field-name.d.mts +18 -0
- package/dist/db/adapter/get-field-name.mjs +33 -0
- package/dist/db/adapter/get-id-field.d.mts +39 -0
- package/dist/db/adapter/get-id-field.mjs +68 -0
- package/dist/db/adapter/get-model-name.d.mts +12 -0
- package/dist/db/adapter/get-model-name.mjs +23 -0
- package/dist/db/adapter/index.d.mts +513 -1
- package/dist/db/adapter/index.mjs +8 -970
- package/dist/db/adapter/types.d.mts +139 -0
- package/dist/db/adapter/utils.d.mts +7 -0
- package/dist/db/adapter/utils.mjs +38 -0
- package/dist/db/get-tables.d.mts +8 -0
- package/dist/{get-tables-CMc_Emww.mjs → db/get-tables.mjs} +1 -1
- package/dist/db/index.d.mts +10 -2
- package/dist/db/index.mjs +7 -60
- package/dist/db/plugin.d.mts +12 -0
- package/dist/db/schema/account.d.mts +26 -0
- package/dist/db/schema/account.mjs +19 -0
- package/dist/db/schema/rate-limit.d.mts +14 -0
- package/dist/db/schema/rate-limit.mjs +11 -0
- package/dist/db/schema/session.d.mts +21 -0
- package/dist/db/schema/session.mjs +14 -0
- package/dist/db/schema/shared.d.mts +10 -0
- package/dist/db/schema/shared.mjs +11 -0
- package/dist/db/schema/user.d.mts +20 -0
- package/dist/db/schema/user.mjs +13 -0
- package/dist/db/schema/verification.d.mts +19 -0
- package/dist/db/schema/verification.mjs +12 -0
- package/dist/db/type.d.mts +143 -0
- package/dist/env/color-depth.d.mts +4 -0
- package/dist/env/color-depth.mjs +88 -0
- package/dist/env/env-impl.d.mts +32 -0
- package/dist/env/env-impl.mjs +82 -0
- package/dist/env/index.d.mts +4 -2
- package/dist/env/index.mjs +3 -1
- package/dist/{index-BRBu0-5h.d.mts → env/logger.d.mts} +1 -35
- package/dist/env/logger.mjs +81 -0
- package/dist/error/codes.d.mts +48 -0
- package/dist/{error-DP1xOn7P.mjs → error/codes.mjs} +3 -14
- package/dist/error/index.d.mts +5 -48
- package/dist/error/index.mjs +12 -3
- package/dist/index.d.mts +8 -2
- package/dist/oauth2/client-credentials-token.d.mts +36 -0
- package/dist/oauth2/client-credentials-token.mjs +54 -0
- package/dist/oauth2/create-authorization-url.d.mts +45 -0
- package/dist/oauth2/create-authorization-url.mjs +42 -0
- package/dist/oauth2/index.d.mts +8 -2
- package/dist/oauth2/index.mjs +6 -2
- package/dist/oauth2/oauth-provider.d.mts +194 -0
- package/dist/oauth2/refresh-access-token.d.mts +36 -0
- package/dist/oauth2/refresh-access-token.mjs +58 -0
- package/dist/oauth2/utils.d.mts +7 -0
- package/dist/oauth2/utils.mjs +27 -0
- package/dist/oauth2/validate-authorization-code.d.mts +55 -0
- package/dist/oauth2/validate-authorization-code.mjs +71 -0
- package/dist/oauth2/verify.d.mts +49 -0
- package/dist/oauth2/verify.mjs +95 -0
- package/dist/social-providers/apple.d.mts +119 -0
- package/dist/social-providers/apple.mjs +102 -0
- package/dist/social-providers/atlassian.d.mts +72 -0
- package/dist/social-providers/atlassian.mjs +83 -0
- package/dist/social-providers/cognito.d.mts +87 -0
- package/dist/social-providers/cognito.mjs +166 -0
- package/dist/social-providers/discord.d.mts +126 -0
- package/dist/social-providers/discord.mjs +64 -0
- package/dist/social-providers/dropbox.d.mts +71 -0
- package/dist/social-providers/dropbox.mjs +75 -0
- package/dist/social-providers/facebook.d.mts +81 -0
- package/dist/social-providers/facebook.mjs +120 -0
- package/dist/social-providers/figma.d.mts +63 -0
- package/dist/social-providers/figma.mjs +84 -0
- package/dist/social-providers/github.d.mts +104 -0
- package/dist/social-providers/github.mjs +80 -0
- package/dist/social-providers/gitlab.d.mts +125 -0
- package/dist/social-providers/gitlab.mjs +82 -0
- package/dist/social-providers/google.d.mts +99 -0
- package/dist/social-providers/google.mjs +109 -0
- package/dist/social-providers/huggingface.d.mts +85 -0
- package/dist/social-providers/huggingface.mjs +75 -0
- package/dist/social-providers/index.d.mts +1723 -1
- package/dist/social-providers/index.mjs +33 -2570
- package/dist/social-providers/kakao.d.mts +163 -0
- package/dist/social-providers/kakao.mjs +72 -0
- package/dist/social-providers/kick.d.mts +75 -0
- package/dist/social-providers/kick.mjs +71 -0
- package/dist/social-providers/line.d.mts +107 -0
- package/dist/social-providers/line.mjs +113 -0
- package/dist/social-providers/linear.d.mts +70 -0
- package/dist/social-providers/linear.mjs +88 -0
- package/dist/social-providers/linkedin.d.mts +69 -0
- package/dist/social-providers/linkedin.mjs +76 -0
- package/dist/social-providers/microsoft-entra-id.d.mts +174 -0
- package/dist/social-providers/microsoft-entra-id.mjs +106 -0
- package/dist/social-providers/naver.d.mts +104 -0
- package/dist/social-providers/naver.mjs +67 -0
- package/dist/social-providers/notion.d.mts +66 -0
- package/dist/social-providers/notion.mjs +75 -0
- package/dist/social-providers/paybin.d.mts +73 -0
- package/dist/social-providers/paybin.mjs +85 -0
- package/dist/social-providers/paypal.d.mts +131 -0
- package/dist/social-providers/paypal.mjs +144 -0
- package/dist/social-providers/polar.d.mts +76 -0
- package/dist/social-providers/polar.mjs +73 -0
- package/dist/social-providers/reddit.d.mts +64 -0
- package/dist/social-providers/reddit.mjs +83 -0
- package/dist/social-providers/roblox.d.mts +72 -0
- package/dist/social-providers/roblox.mjs +59 -0
- package/dist/social-providers/salesforce.d.mts +81 -0
- package/dist/social-providers/salesforce.mjs +91 -0
- package/dist/social-providers/slack.d.mts +85 -0
- package/dist/social-providers/slack.mjs +68 -0
- package/dist/social-providers/spotify.d.mts +65 -0
- package/dist/social-providers/spotify.mjs +71 -0
- package/dist/social-providers/tiktok.d.mts +171 -0
- package/dist/social-providers/tiktok.mjs +62 -0
- package/dist/social-providers/twitch.d.mts +81 -0
- package/dist/social-providers/twitch.mjs +78 -0
- package/dist/social-providers/twitter.d.mts +140 -0
- package/dist/social-providers/twitter.mjs +87 -0
- package/dist/social-providers/vercel.d.mts +64 -0
- package/dist/social-providers/vercel.mjs +61 -0
- package/dist/social-providers/vk.d.mts +72 -0
- package/dist/social-providers/vk.mjs +83 -0
- package/dist/social-providers/zoom.d.mts +173 -0
- package/dist/social-providers/zoom.mjs +72 -0
- package/dist/types/context.d.mts +215 -0
- package/dist/types/cookie.d.mts +15 -0
- package/dist/types/helper.d.mts +8 -0
- package/dist/types/index.d.mts +8 -0
- package/dist/types/init-options.d.mts +1266 -0
- package/dist/types/plugin-client.d.mts +103 -0
- package/dist/types/plugin.d.mts +121 -0
- package/dist/utils/deprecate.d.mts +10 -0
- package/dist/utils/deprecate.mjs +17 -0
- package/dist/utils/error-codes.d.mts +9 -0
- package/dist/utils/error-codes.mjs +7 -0
- package/dist/utils/id.d.mts +4 -0
- package/dist/utils/id.mjs +9 -0
- package/dist/utils/index.d.mts +5 -26
- package/dist/utils/index.mjs +5 -2
- package/dist/utils/json.d.mts +4 -0
- package/dist/utils/json.mjs +25 -0
- package/dist/utils/string.d.mts +4 -0
- package/dist/utils/string.mjs +7 -0
- package/package.json +1 -1
- package/src/context/endpoint-context.ts +7 -15
- package/src/context/global.ts +57 -0
- package/src/context/index.ts +1 -0
- package/src/context/request-state.ts +7 -12
- package/src/context/transaction.ts +7 -16
- package/src/db/adapter/factory.ts +13 -13
- package/src/db/adapter/get-default-model-name.ts +1 -1
- package/src/db/adapter/get-id-field.ts +2 -2
- package/src/error/index.ts +2 -3
- package/src/social-providers/gitlab.ts +1 -1
- package/src/types/context.ts +137 -131
- package/src/types/cookie.ts +6 -4
- package/src/types/index.ts +2 -1
- package/tsdown.config.ts +9 -0
- package/dist/context-BGZ8V6DD.mjs +0 -126
- package/dist/env-DbssmzoK.mjs +0 -245
- package/dist/index-zgYuzZ7O.d.mts +0 -8020
- package/dist/oauth2-COJkghlT.mjs +0 -326
- package/dist/utils-U2L7n92V.mjs +0 -59
|
@@ -1,2575 +1,38 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import "
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
1
|
+
import { apple, getApplePublicKey } from "./apple.mjs";
|
|
2
|
+
import { atlassian } from "./atlassian.mjs";
|
|
3
|
+
import { cognito, getCognitoPublicKey } from "./cognito.mjs";
|
|
4
|
+
import { discord } from "./discord.mjs";
|
|
5
|
+
import { dropbox } from "./dropbox.mjs";
|
|
6
|
+
import { facebook } from "./facebook.mjs";
|
|
7
|
+
import { figma } from "./figma.mjs";
|
|
8
|
+
import { github } from "./github.mjs";
|
|
9
|
+
import { gitlab } from "./gitlab.mjs";
|
|
10
|
+
import { getGooglePublicKey, google } from "./google.mjs";
|
|
11
|
+
import { huggingface } from "./huggingface.mjs";
|
|
12
|
+
import { kakao } from "./kakao.mjs";
|
|
13
|
+
import { kick } from "./kick.mjs";
|
|
14
|
+
import { line } from "./line.mjs";
|
|
15
|
+
import { linear } from "./linear.mjs";
|
|
16
|
+
import { linkedin } from "./linkedin.mjs";
|
|
17
|
+
import { microsoft } from "./microsoft-entra-id.mjs";
|
|
18
|
+
import { naver } from "./naver.mjs";
|
|
19
|
+
import { notion } from "./notion.mjs";
|
|
20
|
+
import { paybin } from "./paybin.mjs";
|
|
21
|
+
import { paypal } from "./paypal.mjs";
|
|
22
|
+
import { polar } from "./polar.mjs";
|
|
23
|
+
import { reddit } from "./reddit.mjs";
|
|
24
|
+
import { roblox } from "./roblox.mjs";
|
|
25
|
+
import { salesforce } from "./salesforce.mjs";
|
|
26
|
+
import { slack } from "./slack.mjs";
|
|
27
|
+
import { spotify } from "./spotify.mjs";
|
|
28
|
+
import { tiktok } from "./tiktok.mjs";
|
|
29
|
+
import { twitch } from "./twitch.mjs";
|
|
30
|
+
import { twitter } from "./twitter.mjs";
|
|
31
|
+
import { vercel } from "./vercel.mjs";
|
|
32
|
+
import { vk } from "./vk.mjs";
|
|
33
|
+
import { zoom } from "./zoom.mjs";
|
|
5
34
|
import * as z from "zod";
|
|
6
|
-
import { base64 } from "@better-auth/utils/base64";
|
|
7
|
-
import { betterFetch } from "@better-fetch/fetch";
|
|
8
|
-
import { createRemoteJWKSet, decodeJwt, decodeProtectedHeader, importJWK, jwtVerify } from "jose";
|
|
9
|
-
import { APIError } from "better-call";
|
|
10
35
|
|
|
11
|
-
//#region src/social-providers/apple.ts
|
|
12
|
-
const apple = (options) => {
|
|
13
|
-
const tokenEndpoint = "https://appleid.apple.com/auth/token";
|
|
14
|
-
return {
|
|
15
|
-
id: "apple",
|
|
16
|
-
name: "Apple",
|
|
17
|
-
async createAuthorizationURL({ state, scopes, redirectURI }) {
|
|
18
|
-
const _scope = options.disableDefaultScope ? [] : ["email", "name"];
|
|
19
|
-
if (options.scope) _scope.push(...options.scope);
|
|
20
|
-
if (scopes) _scope.push(...scopes);
|
|
21
|
-
return await createAuthorizationURL({
|
|
22
|
-
id: "apple",
|
|
23
|
-
options,
|
|
24
|
-
authorizationEndpoint: "https://appleid.apple.com/auth/authorize",
|
|
25
|
-
scopes: _scope,
|
|
26
|
-
state,
|
|
27
|
-
redirectURI,
|
|
28
|
-
responseMode: "form_post",
|
|
29
|
-
responseType: "code id_token"
|
|
30
|
-
});
|
|
31
|
-
},
|
|
32
|
-
validateAuthorizationCode: async ({ code, codeVerifier, redirectURI }) => {
|
|
33
|
-
return validateAuthorizationCode({
|
|
34
|
-
code,
|
|
35
|
-
codeVerifier,
|
|
36
|
-
redirectURI,
|
|
37
|
-
options,
|
|
38
|
-
tokenEndpoint
|
|
39
|
-
});
|
|
40
|
-
},
|
|
41
|
-
async verifyIdToken(token, nonce) {
|
|
42
|
-
if (options.disableIdTokenSignIn) return false;
|
|
43
|
-
if (options.verifyIdToken) return options.verifyIdToken(token, nonce);
|
|
44
|
-
const { kid, alg: jwtAlg } = decodeProtectedHeader(token);
|
|
45
|
-
if (!kid || !jwtAlg) return false;
|
|
46
|
-
const { payload: jwtClaims } = await jwtVerify(token, await getApplePublicKey(kid), {
|
|
47
|
-
algorithms: [jwtAlg],
|
|
48
|
-
issuer: "https://appleid.apple.com",
|
|
49
|
-
audience: options.audience && options.audience.length ? options.audience : options.appBundleIdentifier ? options.appBundleIdentifier : options.clientId,
|
|
50
|
-
maxTokenAge: "1h"
|
|
51
|
-
});
|
|
52
|
-
["email_verified", "is_private_email"].forEach((field) => {
|
|
53
|
-
if (jwtClaims[field] !== void 0) jwtClaims[field] = Boolean(jwtClaims[field]);
|
|
54
|
-
});
|
|
55
|
-
if (nonce && jwtClaims.nonce !== nonce) return false;
|
|
56
|
-
return !!jwtClaims;
|
|
57
|
-
},
|
|
58
|
-
refreshAccessToken: options.refreshAccessToken ? options.refreshAccessToken : async (refreshToken) => {
|
|
59
|
-
return refreshAccessToken({
|
|
60
|
-
refreshToken,
|
|
61
|
-
options: {
|
|
62
|
-
clientId: options.clientId,
|
|
63
|
-
clientKey: options.clientKey,
|
|
64
|
-
clientSecret: options.clientSecret
|
|
65
|
-
},
|
|
66
|
-
tokenEndpoint: "https://appleid.apple.com/auth/token"
|
|
67
|
-
});
|
|
68
|
-
},
|
|
69
|
-
async getUserInfo(token) {
|
|
70
|
-
if (options.getUserInfo) return options.getUserInfo(token);
|
|
71
|
-
if (!token.idToken) return null;
|
|
72
|
-
const profile = decodeJwt(token.idToken);
|
|
73
|
-
if (!profile) return null;
|
|
74
|
-
const name = token.user ? `${token.user.name?.firstName} ${token.user.name?.lastName}` : profile.name || profile.email;
|
|
75
|
-
const emailVerified = typeof profile.email_verified === "boolean" ? profile.email_verified : profile.email_verified === "true";
|
|
76
|
-
const enrichedProfile = {
|
|
77
|
-
...profile,
|
|
78
|
-
name
|
|
79
|
-
};
|
|
80
|
-
const userMap = await options.mapProfileToUser?.(enrichedProfile);
|
|
81
|
-
return {
|
|
82
|
-
user: {
|
|
83
|
-
id: profile.sub,
|
|
84
|
-
name: enrichedProfile.name,
|
|
85
|
-
emailVerified,
|
|
86
|
-
email: profile.email,
|
|
87
|
-
...userMap
|
|
88
|
-
},
|
|
89
|
-
data: enrichedProfile
|
|
90
|
-
};
|
|
91
|
-
},
|
|
92
|
-
options
|
|
93
|
-
};
|
|
94
|
-
};
|
|
95
|
-
const getApplePublicKey = async (kid) => {
|
|
96
|
-
const { data } = await betterFetch(`https://appleid.apple.com/auth/keys`);
|
|
97
|
-
if (!data?.keys) throw new APIError("BAD_REQUEST", { message: "Keys not found" });
|
|
98
|
-
const jwk = data.keys.find((key) => key.kid === kid);
|
|
99
|
-
if (!jwk) throw new Error(`JWK with kid ${kid} not found`);
|
|
100
|
-
return await importJWK(jwk, jwk.alg);
|
|
101
|
-
};
|
|
102
|
-
|
|
103
|
-
//#endregion
|
|
104
|
-
//#region src/social-providers/atlassian.ts
|
|
105
|
-
const atlassian = (options) => {
|
|
106
|
-
return {
|
|
107
|
-
id: "atlassian",
|
|
108
|
-
name: "Atlassian",
|
|
109
|
-
async createAuthorizationURL({ state, scopes, codeVerifier, redirectURI }) {
|
|
110
|
-
if (!options.clientId || !options.clientSecret) {
|
|
111
|
-
logger.error("Client Id and Secret are required for Atlassian");
|
|
112
|
-
throw new BetterAuthError("CLIENT_ID_AND_SECRET_REQUIRED");
|
|
113
|
-
}
|
|
114
|
-
if (!codeVerifier) throw new BetterAuthError("codeVerifier is required for Atlassian");
|
|
115
|
-
const _scopes = options.disableDefaultScope ? [] : ["read:jira-user", "offline_access"];
|
|
116
|
-
if (options.scope) _scopes.push(...options.scope);
|
|
117
|
-
if (scopes) _scopes.push(...scopes);
|
|
118
|
-
return createAuthorizationURL({
|
|
119
|
-
id: "atlassian",
|
|
120
|
-
options,
|
|
121
|
-
authorizationEndpoint: "https://auth.atlassian.com/authorize",
|
|
122
|
-
scopes: _scopes,
|
|
123
|
-
state,
|
|
124
|
-
codeVerifier,
|
|
125
|
-
redirectURI,
|
|
126
|
-
additionalParams: { audience: "api.atlassian.com" },
|
|
127
|
-
prompt: options.prompt
|
|
128
|
-
});
|
|
129
|
-
},
|
|
130
|
-
validateAuthorizationCode: async ({ code, codeVerifier, redirectURI }) => {
|
|
131
|
-
return validateAuthorizationCode({
|
|
132
|
-
code,
|
|
133
|
-
codeVerifier,
|
|
134
|
-
redirectURI,
|
|
135
|
-
options,
|
|
136
|
-
tokenEndpoint: "https://auth.atlassian.com/oauth/token"
|
|
137
|
-
});
|
|
138
|
-
},
|
|
139
|
-
refreshAccessToken: options.refreshAccessToken ? options.refreshAccessToken : async (refreshToken) => {
|
|
140
|
-
return refreshAccessToken({
|
|
141
|
-
refreshToken,
|
|
142
|
-
options: {
|
|
143
|
-
clientId: options.clientId,
|
|
144
|
-
clientSecret: options.clientSecret
|
|
145
|
-
},
|
|
146
|
-
tokenEndpoint: "https://auth.atlassian.com/oauth/token"
|
|
147
|
-
});
|
|
148
|
-
},
|
|
149
|
-
async getUserInfo(token) {
|
|
150
|
-
if (options.getUserInfo) return options.getUserInfo(token);
|
|
151
|
-
if (!token.accessToken) return null;
|
|
152
|
-
try {
|
|
153
|
-
const { data: profile } = await betterFetch("https://api.atlassian.com/me", { headers: { Authorization: `Bearer ${token.accessToken}` } });
|
|
154
|
-
if (!profile) return null;
|
|
155
|
-
const userMap = await options.mapProfileToUser?.(profile);
|
|
156
|
-
return {
|
|
157
|
-
user: {
|
|
158
|
-
id: profile.account_id,
|
|
159
|
-
name: profile.name,
|
|
160
|
-
email: profile.email,
|
|
161
|
-
image: profile.picture,
|
|
162
|
-
emailVerified: false,
|
|
163
|
-
...userMap
|
|
164
|
-
},
|
|
165
|
-
data: profile
|
|
166
|
-
};
|
|
167
|
-
} catch (error) {
|
|
168
|
-
logger.error("Failed to fetch user info from Figma:", error);
|
|
169
|
-
return null;
|
|
170
|
-
}
|
|
171
|
-
},
|
|
172
|
-
options
|
|
173
|
-
};
|
|
174
|
-
};
|
|
175
|
-
|
|
176
|
-
//#endregion
|
|
177
|
-
//#region src/social-providers/cognito.ts
|
|
178
|
-
const cognito = (options) => {
|
|
179
|
-
if (!options.domain || !options.region || !options.userPoolId) {
|
|
180
|
-
logger.error("Domain, region and userPoolId are required for Amazon Cognito. Make sure to provide them in the options.");
|
|
181
|
-
throw new BetterAuthError("DOMAIN_AND_REGION_REQUIRED");
|
|
182
|
-
}
|
|
183
|
-
const cleanDomain = options.domain.replace(/^https?:\/\//, "");
|
|
184
|
-
const authorizationEndpoint = `https://${cleanDomain}/oauth2/authorize`;
|
|
185
|
-
const tokenEndpoint = `https://${cleanDomain}/oauth2/token`;
|
|
186
|
-
const userInfoEndpoint = `https://${cleanDomain}/oauth2/userinfo`;
|
|
187
|
-
return {
|
|
188
|
-
id: "cognito",
|
|
189
|
-
name: "Cognito",
|
|
190
|
-
async createAuthorizationURL({ state, scopes, codeVerifier, redirectURI }) {
|
|
191
|
-
if (!options.clientId) {
|
|
192
|
-
logger.error("ClientId is required for Amazon Cognito. Make sure to provide them in the options.");
|
|
193
|
-
throw new BetterAuthError("CLIENT_ID_AND_SECRET_REQUIRED");
|
|
194
|
-
}
|
|
195
|
-
if (options.requireClientSecret && !options.clientSecret) {
|
|
196
|
-
logger.error("Client Secret is required when requireClientSecret is true. Make sure to provide it in the options.");
|
|
197
|
-
throw new BetterAuthError("CLIENT_SECRET_REQUIRED");
|
|
198
|
-
}
|
|
199
|
-
const _scopes = options.disableDefaultScope ? [] : [
|
|
200
|
-
"openid",
|
|
201
|
-
"profile",
|
|
202
|
-
"email"
|
|
203
|
-
];
|
|
204
|
-
if (options.scope) _scopes.push(...options.scope);
|
|
205
|
-
if (scopes) _scopes.push(...scopes);
|
|
206
|
-
const url = await createAuthorizationURL({
|
|
207
|
-
id: "cognito",
|
|
208
|
-
options: { ...options },
|
|
209
|
-
authorizationEndpoint,
|
|
210
|
-
scopes: _scopes,
|
|
211
|
-
state,
|
|
212
|
-
codeVerifier,
|
|
213
|
-
redirectURI,
|
|
214
|
-
prompt: options.prompt
|
|
215
|
-
});
|
|
216
|
-
const scopeValue = url.searchParams.get("scope");
|
|
217
|
-
if (scopeValue) {
|
|
218
|
-
url.searchParams.delete("scope");
|
|
219
|
-
const encodedScope = encodeURIComponent(scopeValue);
|
|
220
|
-
const urlString = url.toString();
|
|
221
|
-
const separator = urlString.includes("?") ? "&" : "?";
|
|
222
|
-
return new URL(`${urlString}${separator}scope=${encodedScope}`);
|
|
223
|
-
}
|
|
224
|
-
return url;
|
|
225
|
-
},
|
|
226
|
-
validateAuthorizationCode: async ({ code, codeVerifier, redirectURI }) => {
|
|
227
|
-
return validateAuthorizationCode({
|
|
228
|
-
code,
|
|
229
|
-
codeVerifier,
|
|
230
|
-
redirectURI,
|
|
231
|
-
options,
|
|
232
|
-
tokenEndpoint
|
|
233
|
-
});
|
|
234
|
-
},
|
|
235
|
-
refreshAccessToken: options.refreshAccessToken ? options.refreshAccessToken : async (refreshToken) => {
|
|
236
|
-
return refreshAccessToken({
|
|
237
|
-
refreshToken,
|
|
238
|
-
options: {
|
|
239
|
-
clientId: options.clientId,
|
|
240
|
-
clientKey: options.clientKey,
|
|
241
|
-
clientSecret: options.clientSecret
|
|
242
|
-
},
|
|
243
|
-
tokenEndpoint
|
|
244
|
-
});
|
|
245
|
-
},
|
|
246
|
-
async verifyIdToken(token, nonce) {
|
|
247
|
-
if (options.disableIdTokenSignIn) return false;
|
|
248
|
-
if (options.verifyIdToken) return options.verifyIdToken(token, nonce);
|
|
249
|
-
try {
|
|
250
|
-
const { kid, alg: jwtAlg } = decodeProtectedHeader(token);
|
|
251
|
-
if (!kid || !jwtAlg) return false;
|
|
252
|
-
const publicKey = await getCognitoPublicKey(kid, options.region, options.userPoolId);
|
|
253
|
-
const expectedIssuer = `https://cognito-idp.${options.region}.amazonaws.com/${options.userPoolId}`;
|
|
254
|
-
const { payload: jwtClaims } = await jwtVerify(token, publicKey, {
|
|
255
|
-
algorithms: [jwtAlg],
|
|
256
|
-
issuer: expectedIssuer,
|
|
257
|
-
audience: options.clientId,
|
|
258
|
-
maxTokenAge: "1h"
|
|
259
|
-
});
|
|
260
|
-
if (nonce && jwtClaims.nonce !== nonce) return false;
|
|
261
|
-
return true;
|
|
262
|
-
} catch (error) {
|
|
263
|
-
logger.error("Failed to verify ID token:", error);
|
|
264
|
-
return false;
|
|
265
|
-
}
|
|
266
|
-
},
|
|
267
|
-
async getUserInfo(token) {
|
|
268
|
-
if (options.getUserInfo) return options.getUserInfo(token);
|
|
269
|
-
if (token.idToken) try {
|
|
270
|
-
const profile = decodeJwt(token.idToken);
|
|
271
|
-
if (!profile) return null;
|
|
272
|
-
const name = profile.name || profile.given_name || profile.username || profile.email;
|
|
273
|
-
const enrichedProfile = {
|
|
274
|
-
...profile,
|
|
275
|
-
name
|
|
276
|
-
};
|
|
277
|
-
const userMap = await options.mapProfileToUser?.(enrichedProfile);
|
|
278
|
-
return {
|
|
279
|
-
user: {
|
|
280
|
-
id: profile.sub,
|
|
281
|
-
name: enrichedProfile.name,
|
|
282
|
-
email: profile.email,
|
|
283
|
-
image: profile.picture,
|
|
284
|
-
emailVerified: profile.email_verified,
|
|
285
|
-
...userMap
|
|
286
|
-
},
|
|
287
|
-
data: enrichedProfile
|
|
288
|
-
};
|
|
289
|
-
} catch (error) {
|
|
290
|
-
logger.error("Failed to decode ID token:", error);
|
|
291
|
-
}
|
|
292
|
-
if (token.accessToken) try {
|
|
293
|
-
const { data: userInfo } = await betterFetch(userInfoEndpoint, { headers: { Authorization: `Bearer ${token.accessToken}` } });
|
|
294
|
-
if (userInfo) {
|
|
295
|
-
const userMap = await options.mapProfileToUser?.(userInfo);
|
|
296
|
-
return {
|
|
297
|
-
user: {
|
|
298
|
-
id: userInfo.sub,
|
|
299
|
-
name: userInfo.name || userInfo.given_name || userInfo.username,
|
|
300
|
-
email: userInfo.email,
|
|
301
|
-
image: userInfo.picture,
|
|
302
|
-
emailVerified: userInfo.email_verified,
|
|
303
|
-
...userMap
|
|
304
|
-
},
|
|
305
|
-
data: userInfo
|
|
306
|
-
};
|
|
307
|
-
}
|
|
308
|
-
} catch (error) {
|
|
309
|
-
logger.error("Failed to fetch user info from Cognito:", error);
|
|
310
|
-
}
|
|
311
|
-
return null;
|
|
312
|
-
},
|
|
313
|
-
options
|
|
314
|
-
};
|
|
315
|
-
};
|
|
316
|
-
const getCognitoPublicKey = async (kid, region, userPoolId) => {
|
|
317
|
-
const COGNITO_JWKS_URI = `https://cognito-idp.${region}.amazonaws.com/${userPoolId}/.well-known/jwks.json`;
|
|
318
|
-
try {
|
|
319
|
-
const { data } = await betterFetch(COGNITO_JWKS_URI);
|
|
320
|
-
if (!data?.keys) throw new APIError("BAD_REQUEST", { message: "Keys not found" });
|
|
321
|
-
const jwk = data.keys.find((key) => key.kid === kid);
|
|
322
|
-
if (!jwk) throw new Error(`JWK with kid ${kid} not found`);
|
|
323
|
-
return await importJWK(jwk, jwk.alg);
|
|
324
|
-
} catch (error) {
|
|
325
|
-
logger.error("Failed to fetch Cognito public key:", error);
|
|
326
|
-
throw error;
|
|
327
|
-
}
|
|
328
|
-
};
|
|
329
|
-
|
|
330
|
-
//#endregion
|
|
331
|
-
//#region src/social-providers/discord.ts
|
|
332
|
-
const discord = (options) => {
|
|
333
|
-
return {
|
|
334
|
-
id: "discord",
|
|
335
|
-
name: "Discord",
|
|
336
|
-
createAuthorizationURL({ state, scopes, redirectURI }) {
|
|
337
|
-
const _scopes = options.disableDefaultScope ? [] : ["identify", "email"];
|
|
338
|
-
if (scopes) _scopes.push(...scopes);
|
|
339
|
-
if (options.scope) _scopes.push(...options.scope);
|
|
340
|
-
const permissionsParam = _scopes.includes("bot") && options.permissions !== void 0 ? `&permissions=${options.permissions}` : "";
|
|
341
|
-
return new URL(`https://discord.com/api/oauth2/authorize?scope=${_scopes.join("+")}&response_type=code&client_id=${options.clientId}&redirect_uri=${encodeURIComponent(options.redirectURI || redirectURI)}&state=${state}&prompt=${options.prompt || "none"}${permissionsParam}`);
|
|
342
|
-
},
|
|
343
|
-
validateAuthorizationCode: async ({ code, redirectURI }) => {
|
|
344
|
-
return validateAuthorizationCode({
|
|
345
|
-
code,
|
|
346
|
-
redirectURI,
|
|
347
|
-
options,
|
|
348
|
-
tokenEndpoint: "https://discord.com/api/oauth2/token"
|
|
349
|
-
});
|
|
350
|
-
},
|
|
351
|
-
refreshAccessToken: options.refreshAccessToken ? options.refreshAccessToken : async (refreshToken) => {
|
|
352
|
-
return refreshAccessToken({
|
|
353
|
-
refreshToken,
|
|
354
|
-
options: {
|
|
355
|
-
clientId: options.clientId,
|
|
356
|
-
clientKey: options.clientKey,
|
|
357
|
-
clientSecret: options.clientSecret
|
|
358
|
-
},
|
|
359
|
-
tokenEndpoint: "https://discord.com/api/oauth2/token"
|
|
360
|
-
});
|
|
361
|
-
},
|
|
362
|
-
async getUserInfo(token) {
|
|
363
|
-
if (options.getUserInfo) return options.getUserInfo(token);
|
|
364
|
-
const { data: profile, error } = await betterFetch("https://discord.com/api/users/@me", { headers: { authorization: `Bearer ${token.accessToken}` } });
|
|
365
|
-
if (error) return null;
|
|
366
|
-
if (profile.avatar === null) profile.image_url = `https://cdn.discordapp.com/embed/avatars/${profile.discriminator === "0" ? Number(BigInt(profile.id) >> BigInt(22)) % 6 : parseInt(profile.discriminator) % 5}.png`;
|
|
367
|
-
else {
|
|
368
|
-
const format = profile.avatar.startsWith("a_") ? "gif" : "png";
|
|
369
|
-
profile.image_url = `https://cdn.discordapp.com/avatars/${profile.id}/${profile.avatar}.${format}`;
|
|
370
|
-
}
|
|
371
|
-
const userMap = await options.mapProfileToUser?.(profile);
|
|
372
|
-
return {
|
|
373
|
-
user: {
|
|
374
|
-
id: profile.id,
|
|
375
|
-
name: profile.global_name || profile.username || "",
|
|
376
|
-
email: profile.email,
|
|
377
|
-
emailVerified: profile.verified,
|
|
378
|
-
image: profile.image_url,
|
|
379
|
-
...userMap
|
|
380
|
-
},
|
|
381
|
-
data: profile
|
|
382
|
-
};
|
|
383
|
-
},
|
|
384
|
-
options
|
|
385
|
-
};
|
|
386
|
-
};
|
|
387
|
-
|
|
388
|
-
//#endregion
|
|
389
|
-
//#region src/social-providers/dropbox.ts
|
|
390
|
-
const dropbox = (options) => {
|
|
391
|
-
const tokenEndpoint = "https://api.dropboxapi.com/oauth2/token";
|
|
392
|
-
return {
|
|
393
|
-
id: "dropbox",
|
|
394
|
-
name: "Dropbox",
|
|
395
|
-
createAuthorizationURL: async ({ state, scopes, codeVerifier, redirectURI }) => {
|
|
396
|
-
const _scopes = options.disableDefaultScope ? [] : ["account_info.read"];
|
|
397
|
-
if (options.scope) _scopes.push(...options.scope);
|
|
398
|
-
if (scopes) _scopes.push(...scopes);
|
|
399
|
-
const additionalParams = {};
|
|
400
|
-
if (options.accessType) additionalParams.token_access_type = options.accessType;
|
|
401
|
-
return await createAuthorizationURL({
|
|
402
|
-
id: "dropbox",
|
|
403
|
-
options,
|
|
404
|
-
authorizationEndpoint: "https://www.dropbox.com/oauth2/authorize",
|
|
405
|
-
scopes: _scopes,
|
|
406
|
-
state,
|
|
407
|
-
redirectURI,
|
|
408
|
-
codeVerifier,
|
|
409
|
-
additionalParams
|
|
410
|
-
});
|
|
411
|
-
},
|
|
412
|
-
validateAuthorizationCode: async ({ code, codeVerifier, redirectURI }) => {
|
|
413
|
-
return await validateAuthorizationCode({
|
|
414
|
-
code,
|
|
415
|
-
codeVerifier,
|
|
416
|
-
redirectURI,
|
|
417
|
-
options,
|
|
418
|
-
tokenEndpoint
|
|
419
|
-
});
|
|
420
|
-
},
|
|
421
|
-
refreshAccessToken: options.refreshAccessToken ? options.refreshAccessToken : async (refreshToken) => {
|
|
422
|
-
return refreshAccessToken({
|
|
423
|
-
refreshToken,
|
|
424
|
-
options: {
|
|
425
|
-
clientId: options.clientId,
|
|
426
|
-
clientKey: options.clientKey,
|
|
427
|
-
clientSecret: options.clientSecret
|
|
428
|
-
},
|
|
429
|
-
tokenEndpoint: "https://api.dropbox.com/oauth2/token"
|
|
430
|
-
});
|
|
431
|
-
},
|
|
432
|
-
async getUserInfo(token) {
|
|
433
|
-
if (options.getUserInfo) return options.getUserInfo(token);
|
|
434
|
-
const { data: profile, error } = await betterFetch("https://api.dropboxapi.com/2/users/get_current_account", {
|
|
435
|
-
method: "POST",
|
|
436
|
-
headers: { Authorization: `Bearer ${token.accessToken}` }
|
|
437
|
-
});
|
|
438
|
-
if (error) return null;
|
|
439
|
-
const userMap = await options.mapProfileToUser?.(profile);
|
|
440
|
-
return {
|
|
441
|
-
user: {
|
|
442
|
-
id: profile.account_id,
|
|
443
|
-
name: profile.name?.display_name,
|
|
444
|
-
email: profile.email,
|
|
445
|
-
emailVerified: profile.email_verified || false,
|
|
446
|
-
image: profile.profile_photo_url,
|
|
447
|
-
...userMap
|
|
448
|
-
},
|
|
449
|
-
data: profile
|
|
450
|
-
};
|
|
451
|
-
},
|
|
452
|
-
options
|
|
453
|
-
};
|
|
454
|
-
};
|
|
455
|
-
|
|
456
|
-
//#endregion
|
|
457
|
-
//#region src/social-providers/facebook.ts
|
|
458
|
-
const facebook = (options) => {
|
|
459
|
-
return {
|
|
460
|
-
id: "facebook",
|
|
461
|
-
name: "Facebook",
|
|
462
|
-
async createAuthorizationURL({ state, scopes, redirectURI, loginHint }) {
|
|
463
|
-
const _scopes = options.disableDefaultScope ? [] : ["email", "public_profile"];
|
|
464
|
-
if (options.scope) _scopes.push(...options.scope);
|
|
465
|
-
if (scopes) _scopes.push(...scopes);
|
|
466
|
-
return await createAuthorizationURL({
|
|
467
|
-
id: "facebook",
|
|
468
|
-
options,
|
|
469
|
-
authorizationEndpoint: "https://www.facebook.com/v21.0/dialog/oauth",
|
|
470
|
-
scopes: _scopes,
|
|
471
|
-
state,
|
|
472
|
-
redirectURI,
|
|
473
|
-
loginHint,
|
|
474
|
-
additionalParams: options.configId ? { config_id: options.configId } : {}
|
|
475
|
-
});
|
|
476
|
-
},
|
|
477
|
-
validateAuthorizationCode: async ({ code, redirectURI }) => {
|
|
478
|
-
return validateAuthorizationCode({
|
|
479
|
-
code,
|
|
480
|
-
redirectURI,
|
|
481
|
-
options,
|
|
482
|
-
tokenEndpoint: "https://graph.facebook.com/oauth/access_token"
|
|
483
|
-
});
|
|
484
|
-
},
|
|
485
|
-
async verifyIdToken(token, nonce) {
|
|
486
|
-
if (options.disableIdTokenSignIn) return false;
|
|
487
|
-
if (options.verifyIdToken) return options.verifyIdToken(token, nonce);
|
|
488
|
-
if (token.split(".").length === 3) try {
|
|
489
|
-
const { payload: jwtClaims } = await jwtVerify(token, createRemoteJWKSet(new URL("https://limited.facebook.com/.well-known/oauth/openid/jwks/")), {
|
|
490
|
-
algorithms: ["RS256"],
|
|
491
|
-
audience: options.clientId,
|
|
492
|
-
issuer: "https://www.facebook.com"
|
|
493
|
-
});
|
|
494
|
-
if (nonce && jwtClaims.nonce !== nonce) return false;
|
|
495
|
-
return !!jwtClaims;
|
|
496
|
-
} catch {
|
|
497
|
-
return false;
|
|
498
|
-
}
|
|
499
|
-
return true;
|
|
500
|
-
},
|
|
501
|
-
refreshAccessToken: options.refreshAccessToken ? options.refreshAccessToken : async (refreshToken) => {
|
|
502
|
-
return refreshAccessToken({
|
|
503
|
-
refreshToken,
|
|
504
|
-
options: {
|
|
505
|
-
clientId: options.clientId,
|
|
506
|
-
clientKey: options.clientKey,
|
|
507
|
-
clientSecret: options.clientSecret
|
|
508
|
-
},
|
|
509
|
-
tokenEndpoint: "https://graph.facebook.com/v18.0/oauth/access_token"
|
|
510
|
-
});
|
|
511
|
-
},
|
|
512
|
-
async getUserInfo(token) {
|
|
513
|
-
if (options.getUserInfo) return options.getUserInfo(token);
|
|
514
|
-
if (token.idToken && token.idToken.split(".").length === 3) {
|
|
515
|
-
const profile$1 = decodeJwt(token.idToken);
|
|
516
|
-
const user = {
|
|
517
|
-
id: profile$1.sub,
|
|
518
|
-
name: profile$1.name,
|
|
519
|
-
email: profile$1.email,
|
|
520
|
-
picture: { data: {
|
|
521
|
-
url: profile$1.picture,
|
|
522
|
-
height: 100,
|
|
523
|
-
width: 100,
|
|
524
|
-
is_silhouette: false
|
|
525
|
-
} }
|
|
526
|
-
};
|
|
527
|
-
const userMap$1 = await options.mapProfileToUser?.({
|
|
528
|
-
...user,
|
|
529
|
-
email_verified: false
|
|
530
|
-
});
|
|
531
|
-
return {
|
|
532
|
-
user: {
|
|
533
|
-
...user,
|
|
534
|
-
emailVerified: false,
|
|
535
|
-
...userMap$1
|
|
536
|
-
},
|
|
537
|
-
data: profile$1
|
|
538
|
-
};
|
|
539
|
-
}
|
|
540
|
-
const { data: profile, error } = await betterFetch("https://graph.facebook.com/me?fields=" + [
|
|
541
|
-
"id",
|
|
542
|
-
"name",
|
|
543
|
-
"email",
|
|
544
|
-
"picture",
|
|
545
|
-
...options?.fields || []
|
|
546
|
-
].join(","), { auth: {
|
|
547
|
-
type: "Bearer",
|
|
548
|
-
token: token.accessToken
|
|
549
|
-
} });
|
|
550
|
-
if (error) return null;
|
|
551
|
-
const userMap = await options.mapProfileToUser?.(profile);
|
|
552
|
-
return {
|
|
553
|
-
user: {
|
|
554
|
-
id: profile.id,
|
|
555
|
-
name: profile.name,
|
|
556
|
-
email: profile.email,
|
|
557
|
-
image: profile.picture.data.url,
|
|
558
|
-
emailVerified: profile.email_verified,
|
|
559
|
-
...userMap
|
|
560
|
-
},
|
|
561
|
-
data: profile
|
|
562
|
-
};
|
|
563
|
-
},
|
|
564
|
-
options
|
|
565
|
-
};
|
|
566
|
-
};
|
|
567
|
-
|
|
568
|
-
//#endregion
|
|
569
|
-
//#region src/social-providers/figma.ts
|
|
570
|
-
const figma = (options) => {
|
|
571
|
-
return {
|
|
572
|
-
id: "figma",
|
|
573
|
-
name: "Figma",
|
|
574
|
-
async createAuthorizationURL({ state, scopes, codeVerifier, redirectURI }) {
|
|
575
|
-
if (!options.clientId || !options.clientSecret) {
|
|
576
|
-
logger.error("Client Id and Client Secret are required for Figma. Make sure to provide them in the options.");
|
|
577
|
-
throw new BetterAuthError("CLIENT_ID_AND_SECRET_REQUIRED");
|
|
578
|
-
}
|
|
579
|
-
if (!codeVerifier) throw new BetterAuthError("codeVerifier is required for Figma");
|
|
580
|
-
const _scopes = options.disableDefaultScope ? [] : ["file_read"];
|
|
581
|
-
if (options.scope) _scopes.push(...options.scope);
|
|
582
|
-
if (scopes) _scopes.push(...scopes);
|
|
583
|
-
return await createAuthorizationURL({
|
|
584
|
-
id: "figma",
|
|
585
|
-
options,
|
|
586
|
-
authorizationEndpoint: "https://www.figma.com/oauth",
|
|
587
|
-
scopes: _scopes,
|
|
588
|
-
state,
|
|
589
|
-
codeVerifier,
|
|
590
|
-
redirectURI
|
|
591
|
-
});
|
|
592
|
-
},
|
|
593
|
-
validateAuthorizationCode: async ({ code, codeVerifier, redirectURI }) => {
|
|
594
|
-
return validateAuthorizationCode({
|
|
595
|
-
code,
|
|
596
|
-
codeVerifier,
|
|
597
|
-
redirectURI,
|
|
598
|
-
options,
|
|
599
|
-
tokenEndpoint: "https://www.figma.com/api/oauth/token"
|
|
600
|
-
});
|
|
601
|
-
},
|
|
602
|
-
refreshAccessToken: options.refreshAccessToken ? options.refreshAccessToken : async (refreshToken) => {
|
|
603
|
-
return refreshAccessToken({
|
|
604
|
-
refreshToken,
|
|
605
|
-
options: {
|
|
606
|
-
clientId: options.clientId,
|
|
607
|
-
clientKey: options.clientKey,
|
|
608
|
-
clientSecret: options.clientSecret
|
|
609
|
-
},
|
|
610
|
-
tokenEndpoint: "https://www.figma.com/api/oauth/token"
|
|
611
|
-
});
|
|
612
|
-
},
|
|
613
|
-
async getUserInfo(token) {
|
|
614
|
-
if (options.getUserInfo) return options.getUserInfo(token);
|
|
615
|
-
try {
|
|
616
|
-
const { data: profile } = await betterFetch("https://api.figma.com/v1/me", { headers: { Authorization: `Bearer ${token.accessToken}` } });
|
|
617
|
-
if (!profile) {
|
|
618
|
-
logger.error("Failed to fetch user from Figma");
|
|
619
|
-
return null;
|
|
620
|
-
}
|
|
621
|
-
const userMap = await options.mapProfileToUser?.(profile);
|
|
622
|
-
return {
|
|
623
|
-
user: {
|
|
624
|
-
id: profile.id,
|
|
625
|
-
name: profile.handle,
|
|
626
|
-
email: profile.email,
|
|
627
|
-
image: profile.img_url,
|
|
628
|
-
emailVerified: false,
|
|
629
|
-
...userMap
|
|
630
|
-
},
|
|
631
|
-
data: profile
|
|
632
|
-
};
|
|
633
|
-
} catch (error) {
|
|
634
|
-
logger.error("Failed to fetch user info from Figma:", error);
|
|
635
|
-
return null;
|
|
636
|
-
}
|
|
637
|
-
},
|
|
638
|
-
options
|
|
639
|
-
};
|
|
640
|
-
};
|
|
641
|
-
|
|
642
|
-
//#endregion
|
|
643
|
-
//#region src/social-providers/github.ts
|
|
644
|
-
const github = (options) => {
|
|
645
|
-
const tokenEndpoint = "https://github.com/login/oauth/access_token";
|
|
646
|
-
return {
|
|
647
|
-
id: "github",
|
|
648
|
-
name: "GitHub",
|
|
649
|
-
createAuthorizationURL({ state, scopes, loginHint, codeVerifier, redirectURI }) {
|
|
650
|
-
const _scopes = options.disableDefaultScope ? [] : ["read:user", "user:email"];
|
|
651
|
-
if (options.scope) _scopes.push(...options.scope);
|
|
652
|
-
if (scopes) _scopes.push(...scopes);
|
|
653
|
-
return createAuthorizationURL({
|
|
654
|
-
id: "github",
|
|
655
|
-
options,
|
|
656
|
-
authorizationEndpoint: "https://github.com/login/oauth/authorize",
|
|
657
|
-
scopes: _scopes,
|
|
658
|
-
state,
|
|
659
|
-
codeVerifier,
|
|
660
|
-
redirectURI,
|
|
661
|
-
loginHint,
|
|
662
|
-
prompt: options.prompt
|
|
663
|
-
});
|
|
664
|
-
},
|
|
665
|
-
validateAuthorizationCode: async ({ code, codeVerifier, redirectURI }) => {
|
|
666
|
-
return validateAuthorizationCode({
|
|
667
|
-
code,
|
|
668
|
-
codeVerifier,
|
|
669
|
-
redirectURI,
|
|
670
|
-
options,
|
|
671
|
-
tokenEndpoint
|
|
672
|
-
});
|
|
673
|
-
},
|
|
674
|
-
refreshAccessToken: options.refreshAccessToken ? options.refreshAccessToken : async (refreshToken) => {
|
|
675
|
-
return refreshAccessToken({
|
|
676
|
-
refreshToken,
|
|
677
|
-
options: {
|
|
678
|
-
clientId: options.clientId,
|
|
679
|
-
clientKey: options.clientKey,
|
|
680
|
-
clientSecret: options.clientSecret
|
|
681
|
-
},
|
|
682
|
-
tokenEndpoint: "https://github.com/login/oauth/access_token"
|
|
683
|
-
});
|
|
684
|
-
},
|
|
685
|
-
async getUserInfo(token) {
|
|
686
|
-
if (options.getUserInfo) return options.getUserInfo(token);
|
|
687
|
-
const { data: profile, error } = await betterFetch("https://api.github.com/user", { headers: {
|
|
688
|
-
"User-Agent": "better-auth",
|
|
689
|
-
authorization: `Bearer ${token.accessToken}`
|
|
690
|
-
} });
|
|
691
|
-
if (error) return null;
|
|
692
|
-
const { data: emails } = await betterFetch("https://api.github.com/user/emails", { headers: {
|
|
693
|
-
Authorization: `Bearer ${token.accessToken}`,
|
|
694
|
-
"User-Agent": "better-auth"
|
|
695
|
-
} });
|
|
696
|
-
if (!profile.email && emails) profile.email = (emails.find((e) => e.primary) ?? emails[0])?.email;
|
|
697
|
-
const emailVerified = emails?.find((e) => e.email === profile.email)?.verified ?? false;
|
|
698
|
-
const userMap = await options.mapProfileToUser?.(profile);
|
|
699
|
-
return {
|
|
700
|
-
user: {
|
|
701
|
-
id: profile.id,
|
|
702
|
-
name: profile.name || profile.login,
|
|
703
|
-
email: profile.email,
|
|
704
|
-
image: profile.avatar_url,
|
|
705
|
-
emailVerified,
|
|
706
|
-
...userMap
|
|
707
|
-
},
|
|
708
|
-
data: profile
|
|
709
|
-
};
|
|
710
|
-
},
|
|
711
|
-
options
|
|
712
|
-
};
|
|
713
|
-
};
|
|
714
|
-
|
|
715
|
-
//#endregion
|
|
716
|
-
//#region src/social-providers/gitlab.ts
|
|
717
|
-
const cleanDoubleSlashes = (input = "") => {
|
|
718
|
-
return input.split("://").map((str) => str.replace(/\/{2,}/g, "/")).join("://");
|
|
719
|
-
};
|
|
720
|
-
const issuerToEndpoints = (issuer) => {
|
|
721
|
-
let baseUrl = issuer || "https://gitlab.com";
|
|
722
|
-
return {
|
|
723
|
-
authorizationEndpoint: cleanDoubleSlashes(`${baseUrl}/oauth/authorize`),
|
|
724
|
-
tokenEndpoint: cleanDoubleSlashes(`${baseUrl}/oauth/token`),
|
|
725
|
-
userinfoEndpoint: cleanDoubleSlashes(`${baseUrl}/api/v4/user`)
|
|
726
|
-
};
|
|
727
|
-
};
|
|
728
|
-
const gitlab = (options) => {
|
|
729
|
-
const { authorizationEndpoint, tokenEndpoint, userinfoEndpoint } = issuerToEndpoints(options.issuer);
|
|
730
|
-
const issuerId = "gitlab";
|
|
731
|
-
return {
|
|
732
|
-
id: issuerId,
|
|
733
|
-
name: "Gitlab",
|
|
734
|
-
createAuthorizationURL: async ({ state, scopes, codeVerifier, loginHint, redirectURI }) => {
|
|
735
|
-
const _scopes = options.disableDefaultScope ? [] : ["read_user"];
|
|
736
|
-
if (options.scope) _scopes.push(...options.scope);
|
|
737
|
-
if (scopes) _scopes.push(...scopes);
|
|
738
|
-
return await createAuthorizationURL({
|
|
739
|
-
id: issuerId,
|
|
740
|
-
options,
|
|
741
|
-
authorizationEndpoint,
|
|
742
|
-
scopes: _scopes,
|
|
743
|
-
state,
|
|
744
|
-
redirectURI,
|
|
745
|
-
codeVerifier,
|
|
746
|
-
loginHint
|
|
747
|
-
});
|
|
748
|
-
},
|
|
749
|
-
validateAuthorizationCode: async ({ code, redirectURI, codeVerifier }) => {
|
|
750
|
-
return validateAuthorizationCode({
|
|
751
|
-
code,
|
|
752
|
-
redirectURI,
|
|
753
|
-
options,
|
|
754
|
-
codeVerifier,
|
|
755
|
-
tokenEndpoint
|
|
756
|
-
});
|
|
757
|
-
},
|
|
758
|
-
refreshAccessToken: options.refreshAccessToken ? options.refreshAccessToken : async (refreshToken) => {
|
|
759
|
-
return refreshAccessToken({
|
|
760
|
-
refreshToken,
|
|
761
|
-
options: {
|
|
762
|
-
clientId: options.clientId,
|
|
763
|
-
clientKey: options.clientKey,
|
|
764
|
-
clientSecret: options.clientSecret
|
|
765
|
-
},
|
|
766
|
-
tokenEndpoint
|
|
767
|
-
});
|
|
768
|
-
},
|
|
769
|
-
async getUserInfo(token) {
|
|
770
|
-
if (options.getUserInfo) return options.getUserInfo(token);
|
|
771
|
-
const { data: profile, error } = await betterFetch(userinfoEndpoint, { headers: { authorization: `Bearer ${token.accessToken}` } });
|
|
772
|
-
if (error || profile.state !== "active" || profile.locked) return null;
|
|
773
|
-
const userMap = await options.mapProfileToUser?.(profile);
|
|
774
|
-
return {
|
|
775
|
-
user: {
|
|
776
|
-
id: profile.id,
|
|
777
|
-
name: profile.name ?? profile.username,
|
|
778
|
-
email: profile.email,
|
|
779
|
-
image: profile.avatar_url,
|
|
780
|
-
emailVerified: profile.email_verified ?? false,
|
|
781
|
-
...userMap
|
|
782
|
-
},
|
|
783
|
-
data: profile
|
|
784
|
-
};
|
|
785
|
-
},
|
|
786
|
-
options
|
|
787
|
-
};
|
|
788
|
-
};
|
|
789
|
-
|
|
790
|
-
//#endregion
|
|
791
|
-
//#region src/social-providers/google.ts
|
|
792
|
-
const google = (options) => {
|
|
793
|
-
return {
|
|
794
|
-
id: "google",
|
|
795
|
-
name: "Google",
|
|
796
|
-
async createAuthorizationURL({ state, scopes, codeVerifier, redirectURI, loginHint, display }) {
|
|
797
|
-
if (!options.clientId || !options.clientSecret) {
|
|
798
|
-
logger.error("Client Id and Client Secret is required for Google. Make sure to provide them in the options.");
|
|
799
|
-
throw new BetterAuthError("CLIENT_ID_AND_SECRET_REQUIRED");
|
|
800
|
-
}
|
|
801
|
-
if (!codeVerifier) throw new BetterAuthError("codeVerifier is required for Google");
|
|
802
|
-
const _scopes = options.disableDefaultScope ? [] : [
|
|
803
|
-
"email",
|
|
804
|
-
"profile",
|
|
805
|
-
"openid"
|
|
806
|
-
];
|
|
807
|
-
if (options.scope) _scopes.push(...options.scope);
|
|
808
|
-
if (scopes) _scopes.push(...scopes);
|
|
809
|
-
return await createAuthorizationURL({
|
|
810
|
-
id: "google",
|
|
811
|
-
options,
|
|
812
|
-
authorizationEndpoint: "https://accounts.google.com/o/oauth2/auth",
|
|
813
|
-
scopes: _scopes,
|
|
814
|
-
state,
|
|
815
|
-
codeVerifier,
|
|
816
|
-
redirectURI,
|
|
817
|
-
prompt: options.prompt,
|
|
818
|
-
accessType: options.accessType,
|
|
819
|
-
display: display || options.display,
|
|
820
|
-
loginHint,
|
|
821
|
-
hd: options.hd,
|
|
822
|
-
additionalParams: { include_granted_scopes: "true" }
|
|
823
|
-
});
|
|
824
|
-
},
|
|
825
|
-
validateAuthorizationCode: async ({ code, codeVerifier, redirectURI }) => {
|
|
826
|
-
return validateAuthorizationCode({
|
|
827
|
-
code,
|
|
828
|
-
codeVerifier,
|
|
829
|
-
redirectURI,
|
|
830
|
-
options,
|
|
831
|
-
tokenEndpoint: "https://oauth2.googleapis.com/token"
|
|
832
|
-
});
|
|
833
|
-
},
|
|
834
|
-
refreshAccessToken: options.refreshAccessToken ? options.refreshAccessToken : async (refreshToken) => {
|
|
835
|
-
return refreshAccessToken({
|
|
836
|
-
refreshToken,
|
|
837
|
-
options: {
|
|
838
|
-
clientId: options.clientId,
|
|
839
|
-
clientKey: options.clientKey,
|
|
840
|
-
clientSecret: options.clientSecret
|
|
841
|
-
},
|
|
842
|
-
tokenEndpoint: "https://www.googleapis.com/oauth2/v4/token"
|
|
843
|
-
});
|
|
844
|
-
},
|
|
845
|
-
async verifyIdToken(token, nonce) {
|
|
846
|
-
if (options.disableIdTokenSignIn) return false;
|
|
847
|
-
if (options.verifyIdToken) return options.verifyIdToken(token, nonce);
|
|
848
|
-
const { kid, alg: jwtAlg } = decodeProtectedHeader(token);
|
|
849
|
-
if (!kid || !jwtAlg) return false;
|
|
850
|
-
const { payload: jwtClaims } = await jwtVerify(token, await getGooglePublicKey(kid), {
|
|
851
|
-
algorithms: [jwtAlg],
|
|
852
|
-
issuer: ["https://accounts.google.com", "accounts.google.com"],
|
|
853
|
-
audience: options.clientId,
|
|
854
|
-
maxTokenAge: "1h"
|
|
855
|
-
});
|
|
856
|
-
if (nonce && jwtClaims.nonce !== nonce) return false;
|
|
857
|
-
return true;
|
|
858
|
-
},
|
|
859
|
-
async getUserInfo(token) {
|
|
860
|
-
if (options.getUserInfo) return options.getUserInfo(token);
|
|
861
|
-
if (!token.idToken) return null;
|
|
862
|
-
const user = decodeJwt(token.idToken);
|
|
863
|
-
const userMap = await options.mapProfileToUser?.(user);
|
|
864
|
-
return {
|
|
865
|
-
user: {
|
|
866
|
-
id: user.sub,
|
|
867
|
-
name: user.name,
|
|
868
|
-
email: user.email,
|
|
869
|
-
image: user.picture,
|
|
870
|
-
emailVerified: user.email_verified,
|
|
871
|
-
...userMap
|
|
872
|
-
},
|
|
873
|
-
data: user
|
|
874
|
-
};
|
|
875
|
-
},
|
|
876
|
-
options
|
|
877
|
-
};
|
|
878
|
-
};
|
|
879
|
-
const getGooglePublicKey = async (kid) => {
|
|
880
|
-
const { data } = await betterFetch("https://www.googleapis.com/oauth2/v3/certs");
|
|
881
|
-
if (!data?.keys) throw new APIError("BAD_REQUEST", { message: "Keys not found" });
|
|
882
|
-
const jwk = data.keys.find((key) => key.kid === kid);
|
|
883
|
-
if (!jwk) throw new Error(`JWK with kid ${kid} not found`);
|
|
884
|
-
return await importJWK(jwk, jwk.alg);
|
|
885
|
-
};
|
|
886
|
-
|
|
887
|
-
//#endregion
|
|
888
|
-
//#region src/social-providers/huggingface.ts
|
|
889
|
-
const huggingface = (options) => {
|
|
890
|
-
return {
|
|
891
|
-
id: "huggingface",
|
|
892
|
-
name: "Hugging Face",
|
|
893
|
-
createAuthorizationURL({ state, scopes, codeVerifier, redirectURI }) {
|
|
894
|
-
const _scopes = options.disableDefaultScope ? [] : [
|
|
895
|
-
"openid",
|
|
896
|
-
"profile",
|
|
897
|
-
"email"
|
|
898
|
-
];
|
|
899
|
-
if (options.scope) _scopes.push(...options.scope);
|
|
900
|
-
if (scopes) _scopes.push(...scopes);
|
|
901
|
-
return createAuthorizationURL({
|
|
902
|
-
id: "huggingface",
|
|
903
|
-
options,
|
|
904
|
-
authorizationEndpoint: "https://huggingface.co/oauth/authorize",
|
|
905
|
-
scopes: _scopes,
|
|
906
|
-
state,
|
|
907
|
-
codeVerifier,
|
|
908
|
-
redirectURI
|
|
909
|
-
});
|
|
910
|
-
},
|
|
911
|
-
validateAuthorizationCode: async ({ code, codeVerifier, redirectURI }) => {
|
|
912
|
-
return validateAuthorizationCode({
|
|
913
|
-
code,
|
|
914
|
-
codeVerifier,
|
|
915
|
-
redirectURI,
|
|
916
|
-
options,
|
|
917
|
-
tokenEndpoint: "https://huggingface.co/oauth/token"
|
|
918
|
-
});
|
|
919
|
-
},
|
|
920
|
-
refreshAccessToken: options.refreshAccessToken ? options.refreshAccessToken : async (refreshToken) => {
|
|
921
|
-
return refreshAccessToken({
|
|
922
|
-
refreshToken,
|
|
923
|
-
options: {
|
|
924
|
-
clientId: options.clientId,
|
|
925
|
-
clientKey: options.clientKey,
|
|
926
|
-
clientSecret: options.clientSecret
|
|
927
|
-
},
|
|
928
|
-
tokenEndpoint: "https://huggingface.co/oauth/token"
|
|
929
|
-
});
|
|
930
|
-
},
|
|
931
|
-
async getUserInfo(token) {
|
|
932
|
-
if (options.getUserInfo) return options.getUserInfo(token);
|
|
933
|
-
const { data: profile, error } = await betterFetch("https://huggingface.co/oauth/userinfo", {
|
|
934
|
-
method: "GET",
|
|
935
|
-
headers: { Authorization: `Bearer ${token.accessToken}` }
|
|
936
|
-
});
|
|
937
|
-
if (error) return null;
|
|
938
|
-
const userMap = await options.mapProfileToUser?.(profile);
|
|
939
|
-
return {
|
|
940
|
-
user: {
|
|
941
|
-
id: profile.sub,
|
|
942
|
-
name: profile.name || profile.preferred_username,
|
|
943
|
-
email: profile.email,
|
|
944
|
-
image: profile.picture,
|
|
945
|
-
emailVerified: profile.email_verified ?? false,
|
|
946
|
-
...userMap
|
|
947
|
-
},
|
|
948
|
-
data: profile
|
|
949
|
-
};
|
|
950
|
-
},
|
|
951
|
-
options
|
|
952
|
-
};
|
|
953
|
-
};
|
|
954
|
-
|
|
955
|
-
//#endregion
|
|
956
|
-
//#region src/social-providers/kakao.ts
|
|
957
|
-
const kakao = (options) => {
|
|
958
|
-
return {
|
|
959
|
-
id: "kakao",
|
|
960
|
-
name: "Kakao",
|
|
961
|
-
createAuthorizationURL({ state, scopes, redirectURI }) {
|
|
962
|
-
const _scopes = options.disableDefaultScope ? [] : [
|
|
963
|
-
"account_email",
|
|
964
|
-
"profile_image",
|
|
965
|
-
"profile_nickname"
|
|
966
|
-
];
|
|
967
|
-
if (options.scope) _scopes.push(...options.scope);
|
|
968
|
-
if (scopes) _scopes.push(...scopes);
|
|
969
|
-
return createAuthorizationURL({
|
|
970
|
-
id: "kakao",
|
|
971
|
-
options,
|
|
972
|
-
authorizationEndpoint: "https://kauth.kakao.com/oauth/authorize",
|
|
973
|
-
scopes: _scopes,
|
|
974
|
-
state,
|
|
975
|
-
redirectURI
|
|
976
|
-
});
|
|
977
|
-
},
|
|
978
|
-
validateAuthorizationCode: async ({ code, redirectURI }) => {
|
|
979
|
-
return validateAuthorizationCode({
|
|
980
|
-
code,
|
|
981
|
-
redirectURI,
|
|
982
|
-
options,
|
|
983
|
-
tokenEndpoint: "https://kauth.kakao.com/oauth/token"
|
|
984
|
-
});
|
|
985
|
-
},
|
|
986
|
-
refreshAccessToken: options.refreshAccessToken ? options.refreshAccessToken : async (refreshToken) => {
|
|
987
|
-
return refreshAccessToken({
|
|
988
|
-
refreshToken,
|
|
989
|
-
options: {
|
|
990
|
-
clientId: options.clientId,
|
|
991
|
-
clientKey: options.clientKey,
|
|
992
|
-
clientSecret: options.clientSecret
|
|
993
|
-
},
|
|
994
|
-
tokenEndpoint: "https://kauth.kakao.com/oauth/token"
|
|
995
|
-
});
|
|
996
|
-
},
|
|
997
|
-
async getUserInfo(token) {
|
|
998
|
-
if (options.getUserInfo) return options.getUserInfo(token);
|
|
999
|
-
const { data: profile, error } = await betterFetch("https://kapi.kakao.com/v2/user/me", { headers: { Authorization: `Bearer ${token.accessToken}` } });
|
|
1000
|
-
if (error || !profile) return null;
|
|
1001
|
-
const userMap = await options.mapProfileToUser?.(profile);
|
|
1002
|
-
const account = profile.kakao_account || {};
|
|
1003
|
-
const kakaoProfile = account.profile || {};
|
|
1004
|
-
return {
|
|
1005
|
-
user: {
|
|
1006
|
-
id: String(profile.id),
|
|
1007
|
-
name: kakaoProfile.nickname || account.name || void 0,
|
|
1008
|
-
email: account.email,
|
|
1009
|
-
image: kakaoProfile.profile_image_url || kakaoProfile.thumbnail_image_url,
|
|
1010
|
-
emailVerified: !!account.is_email_valid && !!account.is_email_verified,
|
|
1011
|
-
...userMap
|
|
1012
|
-
},
|
|
1013
|
-
data: profile
|
|
1014
|
-
};
|
|
1015
|
-
},
|
|
1016
|
-
options
|
|
1017
|
-
};
|
|
1018
|
-
};
|
|
1019
|
-
|
|
1020
|
-
//#endregion
|
|
1021
|
-
//#region src/social-providers/kick.ts
|
|
1022
|
-
const kick = (options) => {
|
|
1023
|
-
return {
|
|
1024
|
-
id: "kick",
|
|
1025
|
-
name: "Kick",
|
|
1026
|
-
createAuthorizationURL({ state, scopes, redirectURI, codeVerifier }) {
|
|
1027
|
-
const _scopes = options.disableDefaultScope ? [] : ["user:read"];
|
|
1028
|
-
if (options.scope) _scopes.push(...options.scope);
|
|
1029
|
-
if (scopes) _scopes.push(...scopes);
|
|
1030
|
-
return createAuthorizationURL({
|
|
1031
|
-
id: "kick",
|
|
1032
|
-
redirectURI,
|
|
1033
|
-
options,
|
|
1034
|
-
authorizationEndpoint: "https://id.kick.com/oauth/authorize",
|
|
1035
|
-
scopes: _scopes,
|
|
1036
|
-
codeVerifier,
|
|
1037
|
-
state
|
|
1038
|
-
});
|
|
1039
|
-
},
|
|
1040
|
-
async validateAuthorizationCode({ code, redirectURI, codeVerifier }) {
|
|
1041
|
-
return validateAuthorizationCode({
|
|
1042
|
-
code,
|
|
1043
|
-
redirectURI,
|
|
1044
|
-
options,
|
|
1045
|
-
tokenEndpoint: "https://id.kick.com/oauth/token",
|
|
1046
|
-
codeVerifier
|
|
1047
|
-
});
|
|
1048
|
-
},
|
|
1049
|
-
refreshAccessToken: options.refreshAccessToken ? options.refreshAccessToken : async (refreshToken) => {
|
|
1050
|
-
return refreshAccessToken({
|
|
1051
|
-
refreshToken,
|
|
1052
|
-
options: {
|
|
1053
|
-
clientId: options.clientId,
|
|
1054
|
-
clientSecret: options.clientSecret
|
|
1055
|
-
},
|
|
1056
|
-
tokenEndpoint: "https://id.kick.com/oauth/token"
|
|
1057
|
-
});
|
|
1058
|
-
},
|
|
1059
|
-
async getUserInfo(token) {
|
|
1060
|
-
if (options.getUserInfo) return options.getUserInfo(token);
|
|
1061
|
-
const { data, error } = await betterFetch("https://api.kick.com/public/v1/users", {
|
|
1062
|
-
method: "GET",
|
|
1063
|
-
headers: { Authorization: `Bearer ${token.accessToken}` }
|
|
1064
|
-
});
|
|
1065
|
-
if (error) return null;
|
|
1066
|
-
const profile = data.data[0];
|
|
1067
|
-
const userMap = await options.mapProfileToUser?.(profile);
|
|
1068
|
-
return {
|
|
1069
|
-
user: {
|
|
1070
|
-
id: profile.user_id,
|
|
1071
|
-
name: profile.name,
|
|
1072
|
-
email: profile.email,
|
|
1073
|
-
image: profile.profile_picture,
|
|
1074
|
-
emailVerified: false,
|
|
1075
|
-
...userMap
|
|
1076
|
-
},
|
|
1077
|
-
data: profile
|
|
1078
|
-
};
|
|
1079
|
-
},
|
|
1080
|
-
options
|
|
1081
|
-
};
|
|
1082
|
-
};
|
|
1083
|
-
|
|
1084
|
-
//#endregion
|
|
1085
|
-
//#region src/social-providers/line.ts
|
|
1086
|
-
/**
|
|
1087
|
-
* LINE Login v2.1
|
|
1088
|
-
* - Authorization endpoint: https://access.line.me/oauth2/v2.1/authorize
|
|
1089
|
-
* - Token endpoint: https://api.line.me/oauth2/v2.1/token
|
|
1090
|
-
* - UserInfo endpoint: https://api.line.me/oauth2/v2.1/userinfo
|
|
1091
|
-
* - Verify ID token: https://api.line.me/oauth2/v2.1/verify
|
|
1092
|
-
*
|
|
1093
|
-
* Docs: https://developers.line.biz/en/reference/line-login/#issue-access-token
|
|
1094
|
-
*/
|
|
1095
|
-
const line = (options) => {
|
|
1096
|
-
const authorizationEndpoint = "https://access.line.me/oauth2/v2.1/authorize";
|
|
1097
|
-
const tokenEndpoint = "https://api.line.me/oauth2/v2.1/token";
|
|
1098
|
-
const userInfoEndpoint = "https://api.line.me/oauth2/v2.1/userinfo";
|
|
1099
|
-
const verifyIdTokenEndpoint = "https://api.line.me/oauth2/v2.1/verify";
|
|
1100
|
-
return {
|
|
1101
|
-
id: "line",
|
|
1102
|
-
name: "LINE",
|
|
1103
|
-
async createAuthorizationURL({ state, scopes, codeVerifier, redirectURI, loginHint }) {
|
|
1104
|
-
const _scopes = options.disableDefaultScope ? [] : [
|
|
1105
|
-
"openid",
|
|
1106
|
-
"profile",
|
|
1107
|
-
"email"
|
|
1108
|
-
];
|
|
1109
|
-
if (options.scope) _scopes.push(...options.scope);
|
|
1110
|
-
if (scopes) _scopes.push(...scopes);
|
|
1111
|
-
return await createAuthorizationURL({
|
|
1112
|
-
id: "line",
|
|
1113
|
-
options,
|
|
1114
|
-
authorizationEndpoint,
|
|
1115
|
-
scopes: _scopes,
|
|
1116
|
-
state,
|
|
1117
|
-
codeVerifier,
|
|
1118
|
-
redirectURI,
|
|
1119
|
-
loginHint
|
|
1120
|
-
});
|
|
1121
|
-
},
|
|
1122
|
-
validateAuthorizationCode: async ({ code, codeVerifier, redirectURI }) => {
|
|
1123
|
-
return validateAuthorizationCode({
|
|
1124
|
-
code,
|
|
1125
|
-
codeVerifier,
|
|
1126
|
-
redirectURI,
|
|
1127
|
-
options,
|
|
1128
|
-
tokenEndpoint
|
|
1129
|
-
});
|
|
1130
|
-
},
|
|
1131
|
-
refreshAccessToken: options.refreshAccessToken ? options.refreshAccessToken : async (refreshToken) => {
|
|
1132
|
-
return refreshAccessToken({
|
|
1133
|
-
refreshToken,
|
|
1134
|
-
options: {
|
|
1135
|
-
clientId: options.clientId,
|
|
1136
|
-
clientSecret: options.clientSecret
|
|
1137
|
-
},
|
|
1138
|
-
tokenEndpoint
|
|
1139
|
-
});
|
|
1140
|
-
},
|
|
1141
|
-
async verifyIdToken(token, nonce) {
|
|
1142
|
-
if (options.disableIdTokenSignIn) return false;
|
|
1143
|
-
if (options.verifyIdToken) return options.verifyIdToken(token, nonce);
|
|
1144
|
-
const body = new URLSearchParams();
|
|
1145
|
-
body.set("id_token", token);
|
|
1146
|
-
body.set("client_id", options.clientId);
|
|
1147
|
-
if (nonce) body.set("nonce", nonce);
|
|
1148
|
-
const { data, error } = await betterFetch(verifyIdTokenEndpoint, {
|
|
1149
|
-
method: "POST",
|
|
1150
|
-
headers: { "content-type": "application/x-www-form-urlencoded" },
|
|
1151
|
-
body
|
|
1152
|
-
});
|
|
1153
|
-
if (error || !data) return false;
|
|
1154
|
-
if (data.aud !== options.clientId) return false;
|
|
1155
|
-
if (data.nonce && data.nonce !== nonce) return false;
|
|
1156
|
-
return true;
|
|
1157
|
-
},
|
|
1158
|
-
async getUserInfo(token) {
|
|
1159
|
-
if (options.getUserInfo) return options.getUserInfo(token);
|
|
1160
|
-
let profile = null;
|
|
1161
|
-
if (token.idToken) try {
|
|
1162
|
-
profile = decodeJwt(token.idToken);
|
|
1163
|
-
} catch {}
|
|
1164
|
-
if (!profile) {
|
|
1165
|
-
const { data } = await betterFetch(userInfoEndpoint, { headers: { authorization: `Bearer ${token.accessToken}` } });
|
|
1166
|
-
profile = data || null;
|
|
1167
|
-
}
|
|
1168
|
-
if (!profile) return null;
|
|
1169
|
-
const userMap = await options.mapProfileToUser?.(profile);
|
|
1170
|
-
const id = profile.sub || profile.userId;
|
|
1171
|
-
const name = profile.name || profile.displayName;
|
|
1172
|
-
const image = profile.picture || profile.pictureUrl || void 0;
|
|
1173
|
-
return {
|
|
1174
|
-
user: {
|
|
1175
|
-
id,
|
|
1176
|
-
name,
|
|
1177
|
-
email: profile.email,
|
|
1178
|
-
image,
|
|
1179
|
-
emailVerified: false,
|
|
1180
|
-
...userMap
|
|
1181
|
-
},
|
|
1182
|
-
data: profile
|
|
1183
|
-
};
|
|
1184
|
-
},
|
|
1185
|
-
options
|
|
1186
|
-
};
|
|
1187
|
-
};
|
|
1188
|
-
|
|
1189
|
-
//#endregion
|
|
1190
|
-
//#region src/social-providers/linear.ts
|
|
1191
|
-
const linear = (options) => {
|
|
1192
|
-
const tokenEndpoint = "https://api.linear.app/oauth/token";
|
|
1193
|
-
return {
|
|
1194
|
-
id: "linear",
|
|
1195
|
-
name: "Linear",
|
|
1196
|
-
createAuthorizationURL({ state, scopes, loginHint, redirectURI }) {
|
|
1197
|
-
const _scopes = options.disableDefaultScope ? [] : ["read"];
|
|
1198
|
-
if (options.scope) _scopes.push(...options.scope);
|
|
1199
|
-
if (scopes) _scopes.push(...scopes);
|
|
1200
|
-
return createAuthorizationURL({
|
|
1201
|
-
id: "linear",
|
|
1202
|
-
options,
|
|
1203
|
-
authorizationEndpoint: "https://linear.app/oauth/authorize",
|
|
1204
|
-
scopes: _scopes,
|
|
1205
|
-
state,
|
|
1206
|
-
redirectURI,
|
|
1207
|
-
loginHint
|
|
1208
|
-
});
|
|
1209
|
-
},
|
|
1210
|
-
validateAuthorizationCode: async ({ code, redirectURI }) => {
|
|
1211
|
-
return validateAuthorizationCode({
|
|
1212
|
-
code,
|
|
1213
|
-
redirectURI,
|
|
1214
|
-
options,
|
|
1215
|
-
tokenEndpoint
|
|
1216
|
-
});
|
|
1217
|
-
},
|
|
1218
|
-
refreshAccessToken: options.refreshAccessToken ? options.refreshAccessToken : async (refreshToken) => {
|
|
1219
|
-
return refreshAccessToken({
|
|
1220
|
-
refreshToken,
|
|
1221
|
-
options: {
|
|
1222
|
-
clientId: options.clientId,
|
|
1223
|
-
clientKey: options.clientKey,
|
|
1224
|
-
clientSecret: options.clientSecret
|
|
1225
|
-
},
|
|
1226
|
-
tokenEndpoint
|
|
1227
|
-
});
|
|
1228
|
-
},
|
|
1229
|
-
async getUserInfo(token) {
|
|
1230
|
-
if (options.getUserInfo) return options.getUserInfo(token);
|
|
1231
|
-
const { data: profile, error } = await betterFetch("https://api.linear.app/graphql", {
|
|
1232
|
-
method: "POST",
|
|
1233
|
-
headers: {
|
|
1234
|
-
"Content-Type": "application/json",
|
|
1235
|
-
Authorization: `Bearer ${token.accessToken}`
|
|
1236
|
-
},
|
|
1237
|
-
body: JSON.stringify({ query: `
|
|
1238
|
-
query {
|
|
1239
|
-
viewer {
|
|
1240
|
-
id
|
|
1241
|
-
name
|
|
1242
|
-
email
|
|
1243
|
-
avatarUrl
|
|
1244
|
-
active
|
|
1245
|
-
createdAt
|
|
1246
|
-
updatedAt
|
|
1247
|
-
}
|
|
1248
|
-
}
|
|
1249
|
-
` })
|
|
1250
|
-
});
|
|
1251
|
-
if (error || !profile?.data?.viewer) return null;
|
|
1252
|
-
const userData = profile.data.viewer;
|
|
1253
|
-
const userMap = await options.mapProfileToUser?.(userData);
|
|
1254
|
-
return {
|
|
1255
|
-
user: {
|
|
1256
|
-
id: profile.data.viewer.id,
|
|
1257
|
-
name: profile.data.viewer.name,
|
|
1258
|
-
email: profile.data.viewer.email,
|
|
1259
|
-
image: profile.data.viewer.avatarUrl,
|
|
1260
|
-
emailVerified: false,
|
|
1261
|
-
...userMap
|
|
1262
|
-
},
|
|
1263
|
-
data: userData
|
|
1264
|
-
};
|
|
1265
|
-
},
|
|
1266
|
-
options
|
|
1267
|
-
};
|
|
1268
|
-
};
|
|
1269
|
-
|
|
1270
|
-
//#endregion
|
|
1271
|
-
//#region src/social-providers/linkedin.ts
|
|
1272
|
-
const linkedin = (options) => {
|
|
1273
|
-
const authorizationEndpoint = "https://www.linkedin.com/oauth/v2/authorization";
|
|
1274
|
-
const tokenEndpoint = "https://www.linkedin.com/oauth/v2/accessToken";
|
|
1275
|
-
return {
|
|
1276
|
-
id: "linkedin",
|
|
1277
|
-
name: "Linkedin",
|
|
1278
|
-
createAuthorizationURL: async ({ state, scopes, redirectURI, loginHint }) => {
|
|
1279
|
-
const _scopes = options.disableDefaultScope ? [] : [
|
|
1280
|
-
"profile",
|
|
1281
|
-
"email",
|
|
1282
|
-
"openid"
|
|
1283
|
-
];
|
|
1284
|
-
if (options.scope) _scopes.push(...options.scope);
|
|
1285
|
-
if (scopes) _scopes.push(...scopes);
|
|
1286
|
-
return await createAuthorizationURL({
|
|
1287
|
-
id: "linkedin",
|
|
1288
|
-
options,
|
|
1289
|
-
authorizationEndpoint,
|
|
1290
|
-
scopes: _scopes,
|
|
1291
|
-
state,
|
|
1292
|
-
loginHint,
|
|
1293
|
-
redirectURI
|
|
1294
|
-
});
|
|
1295
|
-
},
|
|
1296
|
-
validateAuthorizationCode: async ({ code, redirectURI }) => {
|
|
1297
|
-
return await validateAuthorizationCode({
|
|
1298
|
-
code,
|
|
1299
|
-
redirectURI,
|
|
1300
|
-
options,
|
|
1301
|
-
tokenEndpoint
|
|
1302
|
-
});
|
|
1303
|
-
},
|
|
1304
|
-
refreshAccessToken: options.refreshAccessToken ? options.refreshAccessToken : async (refreshToken) => {
|
|
1305
|
-
return refreshAccessToken({
|
|
1306
|
-
refreshToken,
|
|
1307
|
-
options: {
|
|
1308
|
-
clientId: options.clientId,
|
|
1309
|
-
clientKey: options.clientKey,
|
|
1310
|
-
clientSecret: options.clientSecret
|
|
1311
|
-
},
|
|
1312
|
-
tokenEndpoint
|
|
1313
|
-
});
|
|
1314
|
-
},
|
|
1315
|
-
async getUserInfo(token) {
|
|
1316
|
-
if (options.getUserInfo) return options.getUserInfo(token);
|
|
1317
|
-
const { data: profile, error } = await betterFetch("https://api.linkedin.com/v2/userinfo", {
|
|
1318
|
-
method: "GET",
|
|
1319
|
-
headers: { Authorization: `Bearer ${token.accessToken}` }
|
|
1320
|
-
});
|
|
1321
|
-
if (error) return null;
|
|
1322
|
-
const userMap = await options.mapProfileToUser?.(profile);
|
|
1323
|
-
return {
|
|
1324
|
-
user: {
|
|
1325
|
-
id: profile.sub,
|
|
1326
|
-
name: profile.name,
|
|
1327
|
-
email: profile.email,
|
|
1328
|
-
emailVerified: profile.email_verified || false,
|
|
1329
|
-
image: profile.picture,
|
|
1330
|
-
...userMap
|
|
1331
|
-
},
|
|
1332
|
-
data: profile
|
|
1333
|
-
};
|
|
1334
|
-
},
|
|
1335
|
-
options
|
|
1336
|
-
};
|
|
1337
|
-
};
|
|
1338
|
-
|
|
1339
|
-
//#endregion
|
|
1340
|
-
//#region src/social-providers/microsoft-entra-id.ts
|
|
1341
|
-
const microsoft = (options) => {
|
|
1342
|
-
const tenant = options.tenantId || "common";
|
|
1343
|
-
const authority = options.authority || "https://login.microsoftonline.com";
|
|
1344
|
-
const authorizationEndpoint = `${authority}/${tenant}/oauth2/v2.0/authorize`;
|
|
1345
|
-
const tokenEndpoint = `${authority}/${tenant}/oauth2/v2.0/token`;
|
|
1346
|
-
return {
|
|
1347
|
-
id: "microsoft",
|
|
1348
|
-
name: "Microsoft EntraID",
|
|
1349
|
-
createAuthorizationURL(data) {
|
|
1350
|
-
const scopes = options.disableDefaultScope ? [] : [
|
|
1351
|
-
"openid",
|
|
1352
|
-
"profile",
|
|
1353
|
-
"email",
|
|
1354
|
-
"User.Read",
|
|
1355
|
-
"offline_access"
|
|
1356
|
-
];
|
|
1357
|
-
if (options.scope) scopes.push(...options.scope);
|
|
1358
|
-
if (data.scopes) scopes.push(...data.scopes);
|
|
1359
|
-
return createAuthorizationURL({
|
|
1360
|
-
id: "microsoft",
|
|
1361
|
-
options,
|
|
1362
|
-
authorizationEndpoint,
|
|
1363
|
-
state: data.state,
|
|
1364
|
-
codeVerifier: data.codeVerifier,
|
|
1365
|
-
scopes,
|
|
1366
|
-
redirectURI: data.redirectURI,
|
|
1367
|
-
prompt: options.prompt,
|
|
1368
|
-
loginHint: data.loginHint
|
|
1369
|
-
});
|
|
1370
|
-
},
|
|
1371
|
-
validateAuthorizationCode({ code, codeVerifier, redirectURI }) {
|
|
1372
|
-
return validateAuthorizationCode({
|
|
1373
|
-
code,
|
|
1374
|
-
codeVerifier,
|
|
1375
|
-
redirectURI,
|
|
1376
|
-
options,
|
|
1377
|
-
tokenEndpoint
|
|
1378
|
-
});
|
|
1379
|
-
},
|
|
1380
|
-
async getUserInfo(token) {
|
|
1381
|
-
if (options.getUserInfo) return options.getUserInfo(token);
|
|
1382
|
-
if (!token.idToken) return null;
|
|
1383
|
-
const user = decodeJwt(token.idToken);
|
|
1384
|
-
const profilePhotoSize = options.profilePhotoSize || 48;
|
|
1385
|
-
await betterFetch(`https://graph.microsoft.com/v1.0/me/photos/${profilePhotoSize}x${profilePhotoSize}/$value`, {
|
|
1386
|
-
headers: { Authorization: `Bearer ${token.accessToken}` },
|
|
1387
|
-
async onResponse(context) {
|
|
1388
|
-
if (options.disableProfilePhoto || !context.response.ok) return;
|
|
1389
|
-
try {
|
|
1390
|
-
const pictureBuffer = await context.response.clone().arrayBuffer();
|
|
1391
|
-
user.picture = `data:image/jpeg;base64, ${base64.encode(pictureBuffer)}`;
|
|
1392
|
-
} catch (e) {
|
|
1393
|
-
logger.error(e && typeof e === "object" && "name" in e ? e.name : "", e);
|
|
1394
|
-
}
|
|
1395
|
-
}
|
|
1396
|
-
});
|
|
1397
|
-
const userMap = await options.mapProfileToUser?.(user);
|
|
1398
|
-
const emailVerified = user.email_verified !== void 0 ? user.email_verified : user.email && (user.verified_primary_email?.includes(user.email) || user.verified_secondary_email?.includes(user.email)) ? true : false;
|
|
1399
|
-
return {
|
|
1400
|
-
user: {
|
|
1401
|
-
id: user.sub,
|
|
1402
|
-
name: user.name,
|
|
1403
|
-
email: user.email,
|
|
1404
|
-
image: user.picture,
|
|
1405
|
-
emailVerified,
|
|
1406
|
-
...userMap
|
|
1407
|
-
},
|
|
1408
|
-
data: user
|
|
1409
|
-
};
|
|
1410
|
-
},
|
|
1411
|
-
refreshAccessToken: options.refreshAccessToken ? options.refreshAccessToken : async (refreshToken) => {
|
|
1412
|
-
const scopes = options.disableDefaultScope ? [] : [
|
|
1413
|
-
"openid",
|
|
1414
|
-
"profile",
|
|
1415
|
-
"email",
|
|
1416
|
-
"User.Read",
|
|
1417
|
-
"offline_access"
|
|
1418
|
-
];
|
|
1419
|
-
if (options.scope) scopes.push(...options.scope);
|
|
1420
|
-
return refreshAccessToken({
|
|
1421
|
-
refreshToken,
|
|
1422
|
-
options: {
|
|
1423
|
-
clientId: options.clientId,
|
|
1424
|
-
clientSecret: options.clientSecret
|
|
1425
|
-
},
|
|
1426
|
-
extraParams: { scope: scopes.join(" ") },
|
|
1427
|
-
tokenEndpoint
|
|
1428
|
-
});
|
|
1429
|
-
},
|
|
1430
|
-
options
|
|
1431
|
-
};
|
|
1432
|
-
};
|
|
1433
|
-
|
|
1434
|
-
//#endregion
|
|
1435
|
-
//#region src/social-providers/naver.ts
|
|
1436
|
-
const naver = (options) => {
|
|
1437
|
-
return {
|
|
1438
|
-
id: "naver",
|
|
1439
|
-
name: "Naver",
|
|
1440
|
-
createAuthorizationURL({ state, scopes, redirectURI }) {
|
|
1441
|
-
const _scopes = options.disableDefaultScope ? [] : ["profile", "email"];
|
|
1442
|
-
if (options.scope) _scopes.push(...options.scope);
|
|
1443
|
-
if (scopes) _scopes.push(...scopes);
|
|
1444
|
-
return createAuthorizationURL({
|
|
1445
|
-
id: "naver",
|
|
1446
|
-
options,
|
|
1447
|
-
authorizationEndpoint: "https://nid.naver.com/oauth2.0/authorize",
|
|
1448
|
-
scopes: _scopes,
|
|
1449
|
-
state,
|
|
1450
|
-
redirectURI
|
|
1451
|
-
});
|
|
1452
|
-
},
|
|
1453
|
-
validateAuthorizationCode: async ({ code, redirectURI }) => {
|
|
1454
|
-
return validateAuthorizationCode({
|
|
1455
|
-
code,
|
|
1456
|
-
redirectURI,
|
|
1457
|
-
options,
|
|
1458
|
-
tokenEndpoint: "https://nid.naver.com/oauth2.0/token"
|
|
1459
|
-
});
|
|
1460
|
-
},
|
|
1461
|
-
refreshAccessToken: options.refreshAccessToken ? options.refreshAccessToken : async (refreshToken) => {
|
|
1462
|
-
return refreshAccessToken({
|
|
1463
|
-
refreshToken,
|
|
1464
|
-
options: {
|
|
1465
|
-
clientId: options.clientId,
|
|
1466
|
-
clientKey: options.clientKey,
|
|
1467
|
-
clientSecret: options.clientSecret
|
|
1468
|
-
},
|
|
1469
|
-
tokenEndpoint: "https://nid.naver.com/oauth2.0/token"
|
|
1470
|
-
});
|
|
1471
|
-
},
|
|
1472
|
-
async getUserInfo(token) {
|
|
1473
|
-
if (options.getUserInfo) return options.getUserInfo(token);
|
|
1474
|
-
const { data: profile, error } = await betterFetch("https://openapi.naver.com/v1/nid/me", { headers: { Authorization: `Bearer ${token.accessToken}` } });
|
|
1475
|
-
if (error || !profile || profile.resultcode !== "00") return null;
|
|
1476
|
-
const userMap = await options.mapProfileToUser?.(profile);
|
|
1477
|
-
const res = profile.response || {};
|
|
1478
|
-
return {
|
|
1479
|
-
user: {
|
|
1480
|
-
id: res.id,
|
|
1481
|
-
name: res.name || res.nickname,
|
|
1482
|
-
email: res.email,
|
|
1483
|
-
image: res.profile_image,
|
|
1484
|
-
emailVerified: false,
|
|
1485
|
-
...userMap
|
|
1486
|
-
},
|
|
1487
|
-
data: profile
|
|
1488
|
-
};
|
|
1489
|
-
},
|
|
1490
|
-
options
|
|
1491
|
-
};
|
|
1492
|
-
};
|
|
1493
|
-
|
|
1494
|
-
//#endregion
|
|
1495
|
-
//#region src/social-providers/notion.ts
|
|
1496
|
-
const notion = (options) => {
|
|
1497
|
-
const tokenEndpoint = "https://api.notion.com/v1/oauth/token";
|
|
1498
|
-
return {
|
|
1499
|
-
id: "notion",
|
|
1500
|
-
name: "Notion",
|
|
1501
|
-
createAuthorizationURL({ state, scopes, loginHint, redirectURI }) {
|
|
1502
|
-
const _scopes = options.disableDefaultScope ? [] : [];
|
|
1503
|
-
if (options.scope) _scopes.push(...options.scope);
|
|
1504
|
-
if (scopes) _scopes.push(...scopes);
|
|
1505
|
-
return createAuthorizationURL({
|
|
1506
|
-
id: "notion",
|
|
1507
|
-
options,
|
|
1508
|
-
authorizationEndpoint: "https://api.notion.com/v1/oauth/authorize",
|
|
1509
|
-
scopes: _scopes,
|
|
1510
|
-
state,
|
|
1511
|
-
redirectURI,
|
|
1512
|
-
loginHint,
|
|
1513
|
-
additionalParams: { owner: "user" }
|
|
1514
|
-
});
|
|
1515
|
-
},
|
|
1516
|
-
validateAuthorizationCode: async ({ code, redirectURI }) => {
|
|
1517
|
-
return validateAuthorizationCode({
|
|
1518
|
-
code,
|
|
1519
|
-
redirectURI,
|
|
1520
|
-
options,
|
|
1521
|
-
tokenEndpoint,
|
|
1522
|
-
authentication: "basic"
|
|
1523
|
-
});
|
|
1524
|
-
},
|
|
1525
|
-
refreshAccessToken: options.refreshAccessToken ? options.refreshAccessToken : async (refreshToken) => {
|
|
1526
|
-
return refreshAccessToken({
|
|
1527
|
-
refreshToken,
|
|
1528
|
-
options: {
|
|
1529
|
-
clientId: options.clientId,
|
|
1530
|
-
clientKey: options.clientKey,
|
|
1531
|
-
clientSecret: options.clientSecret
|
|
1532
|
-
},
|
|
1533
|
-
tokenEndpoint
|
|
1534
|
-
});
|
|
1535
|
-
},
|
|
1536
|
-
async getUserInfo(token) {
|
|
1537
|
-
if (options.getUserInfo) return options.getUserInfo(token);
|
|
1538
|
-
const { data: profile, error } = await betterFetch("https://api.notion.com/v1/users/me", { headers: {
|
|
1539
|
-
Authorization: `Bearer ${token.accessToken}`,
|
|
1540
|
-
"Notion-Version": "2022-06-28"
|
|
1541
|
-
} });
|
|
1542
|
-
if (error || !profile) return null;
|
|
1543
|
-
const userProfile = profile.bot?.owner?.user;
|
|
1544
|
-
if (!userProfile) return null;
|
|
1545
|
-
const userMap = await options.mapProfileToUser?.(userProfile);
|
|
1546
|
-
return {
|
|
1547
|
-
user: {
|
|
1548
|
-
id: userProfile.id,
|
|
1549
|
-
name: userProfile.name || "Notion User",
|
|
1550
|
-
email: userProfile.person?.email || null,
|
|
1551
|
-
image: userProfile.avatar_url,
|
|
1552
|
-
emailVerified: false,
|
|
1553
|
-
...userMap
|
|
1554
|
-
},
|
|
1555
|
-
data: userProfile
|
|
1556
|
-
};
|
|
1557
|
-
},
|
|
1558
|
-
options
|
|
1559
|
-
};
|
|
1560
|
-
};
|
|
1561
|
-
|
|
1562
|
-
//#endregion
|
|
1563
|
-
//#region src/social-providers/paybin.ts
|
|
1564
|
-
const paybin = (options) => {
|
|
1565
|
-
const issuer = options.issuer || "https://idp.paybin.io";
|
|
1566
|
-
const authorizationEndpoint = `${issuer}/oauth2/authorize`;
|
|
1567
|
-
const tokenEndpoint = `${issuer}/oauth2/token`;
|
|
1568
|
-
return {
|
|
1569
|
-
id: "paybin",
|
|
1570
|
-
name: "Paybin",
|
|
1571
|
-
async createAuthorizationURL({ state, scopes, codeVerifier, redirectURI, loginHint }) {
|
|
1572
|
-
if (!options.clientId || !options.clientSecret) {
|
|
1573
|
-
logger.error("Client Id and Client Secret is required for Paybin. Make sure to provide them in the options.");
|
|
1574
|
-
throw new BetterAuthError("CLIENT_ID_AND_SECRET_REQUIRED");
|
|
1575
|
-
}
|
|
1576
|
-
if (!codeVerifier) throw new BetterAuthError("codeVerifier is required for Paybin");
|
|
1577
|
-
const _scopes = options.disableDefaultScope ? [] : [
|
|
1578
|
-
"openid",
|
|
1579
|
-
"email",
|
|
1580
|
-
"profile"
|
|
1581
|
-
];
|
|
1582
|
-
if (options.scope) _scopes.push(...options.scope);
|
|
1583
|
-
if (scopes) _scopes.push(...scopes);
|
|
1584
|
-
return await createAuthorizationURL({
|
|
1585
|
-
id: "paybin",
|
|
1586
|
-
options,
|
|
1587
|
-
authorizationEndpoint,
|
|
1588
|
-
scopes: _scopes,
|
|
1589
|
-
state,
|
|
1590
|
-
codeVerifier,
|
|
1591
|
-
redirectURI,
|
|
1592
|
-
prompt: options.prompt,
|
|
1593
|
-
loginHint
|
|
1594
|
-
});
|
|
1595
|
-
},
|
|
1596
|
-
validateAuthorizationCode: async ({ code, codeVerifier, redirectURI }) => {
|
|
1597
|
-
return validateAuthorizationCode({
|
|
1598
|
-
code,
|
|
1599
|
-
codeVerifier,
|
|
1600
|
-
redirectURI,
|
|
1601
|
-
options,
|
|
1602
|
-
tokenEndpoint
|
|
1603
|
-
});
|
|
1604
|
-
},
|
|
1605
|
-
refreshAccessToken: options.refreshAccessToken ? options.refreshAccessToken : async (refreshToken) => {
|
|
1606
|
-
return refreshAccessToken({
|
|
1607
|
-
refreshToken,
|
|
1608
|
-
options: {
|
|
1609
|
-
clientId: options.clientId,
|
|
1610
|
-
clientKey: options.clientKey,
|
|
1611
|
-
clientSecret: options.clientSecret
|
|
1612
|
-
},
|
|
1613
|
-
tokenEndpoint
|
|
1614
|
-
});
|
|
1615
|
-
},
|
|
1616
|
-
async getUserInfo(token) {
|
|
1617
|
-
if (options.getUserInfo) return options.getUserInfo(token);
|
|
1618
|
-
if (!token.idToken) return null;
|
|
1619
|
-
const user = decodeJwt(token.idToken);
|
|
1620
|
-
const userMap = await options.mapProfileToUser?.(user);
|
|
1621
|
-
return {
|
|
1622
|
-
user: {
|
|
1623
|
-
id: user.sub,
|
|
1624
|
-
name: user.name || user.preferred_username || (user.email ? user.email.split("@")[0] : "User") || "User",
|
|
1625
|
-
email: user.email,
|
|
1626
|
-
image: user.picture,
|
|
1627
|
-
emailVerified: user.email_verified || false,
|
|
1628
|
-
...userMap
|
|
1629
|
-
},
|
|
1630
|
-
data: user
|
|
1631
|
-
};
|
|
1632
|
-
},
|
|
1633
|
-
options
|
|
1634
|
-
};
|
|
1635
|
-
};
|
|
1636
|
-
|
|
1637
|
-
//#endregion
|
|
1638
|
-
//#region src/social-providers/paypal.ts
|
|
1639
|
-
const paypal = (options) => {
|
|
1640
|
-
const isSandbox = (options.environment || "sandbox") === "sandbox";
|
|
1641
|
-
const authorizationEndpoint = isSandbox ? "https://www.sandbox.paypal.com/signin/authorize" : "https://www.paypal.com/signin/authorize";
|
|
1642
|
-
const tokenEndpoint = isSandbox ? "https://api-m.sandbox.paypal.com/v1/oauth2/token" : "https://api-m.paypal.com/v1/oauth2/token";
|
|
1643
|
-
const userInfoEndpoint = isSandbox ? "https://api-m.sandbox.paypal.com/v1/identity/oauth2/userinfo" : "https://api-m.paypal.com/v1/identity/oauth2/userinfo";
|
|
1644
|
-
return {
|
|
1645
|
-
id: "paypal",
|
|
1646
|
-
name: "PayPal",
|
|
1647
|
-
async createAuthorizationURL({ state, codeVerifier, redirectURI }) {
|
|
1648
|
-
if (!options.clientId || !options.clientSecret) {
|
|
1649
|
-
logger.error("Client Id and Client Secret is required for PayPal. Make sure to provide them in the options.");
|
|
1650
|
-
throw new BetterAuthError("CLIENT_ID_AND_SECRET_REQUIRED");
|
|
1651
|
-
}
|
|
1652
|
-
return await createAuthorizationURL({
|
|
1653
|
-
id: "paypal",
|
|
1654
|
-
options,
|
|
1655
|
-
authorizationEndpoint,
|
|
1656
|
-
scopes: [],
|
|
1657
|
-
state,
|
|
1658
|
-
codeVerifier,
|
|
1659
|
-
redirectURI,
|
|
1660
|
-
prompt: options.prompt
|
|
1661
|
-
});
|
|
1662
|
-
},
|
|
1663
|
-
validateAuthorizationCode: async ({ code, redirectURI }) => {
|
|
1664
|
-
/**
|
|
1665
|
-
* PayPal requires Basic Auth for token exchange
|
|
1666
|
-
**/
|
|
1667
|
-
const credentials = base64.encode(`${options.clientId}:${options.clientSecret}`);
|
|
1668
|
-
try {
|
|
1669
|
-
const response = await betterFetch(tokenEndpoint, {
|
|
1670
|
-
method: "POST",
|
|
1671
|
-
headers: {
|
|
1672
|
-
Authorization: `Basic ${credentials}`,
|
|
1673
|
-
Accept: "application/json",
|
|
1674
|
-
"Accept-Language": "en_US",
|
|
1675
|
-
"Content-Type": "application/x-www-form-urlencoded"
|
|
1676
|
-
},
|
|
1677
|
-
body: new URLSearchParams({
|
|
1678
|
-
grant_type: "authorization_code",
|
|
1679
|
-
code,
|
|
1680
|
-
redirect_uri: redirectURI
|
|
1681
|
-
}).toString()
|
|
1682
|
-
});
|
|
1683
|
-
if (!response.data) throw new BetterAuthError("FAILED_TO_GET_ACCESS_TOKEN");
|
|
1684
|
-
const data = response.data;
|
|
1685
|
-
return {
|
|
1686
|
-
accessToken: data.access_token,
|
|
1687
|
-
refreshToken: data.refresh_token,
|
|
1688
|
-
accessTokenExpiresAt: data.expires_in ? new Date(Date.now() + data.expires_in * 1e3) : void 0,
|
|
1689
|
-
idToken: data.id_token
|
|
1690
|
-
};
|
|
1691
|
-
} catch (error) {
|
|
1692
|
-
logger.error("PayPal token exchange failed:", error);
|
|
1693
|
-
throw new BetterAuthError("FAILED_TO_GET_ACCESS_TOKEN");
|
|
1694
|
-
}
|
|
1695
|
-
},
|
|
1696
|
-
refreshAccessToken: options.refreshAccessToken ? options.refreshAccessToken : async (refreshToken) => {
|
|
1697
|
-
const credentials = base64.encode(`${options.clientId}:${options.clientSecret}`);
|
|
1698
|
-
try {
|
|
1699
|
-
const response = await betterFetch(tokenEndpoint, {
|
|
1700
|
-
method: "POST",
|
|
1701
|
-
headers: {
|
|
1702
|
-
Authorization: `Basic ${credentials}`,
|
|
1703
|
-
Accept: "application/json",
|
|
1704
|
-
"Accept-Language": "en_US",
|
|
1705
|
-
"Content-Type": "application/x-www-form-urlencoded"
|
|
1706
|
-
},
|
|
1707
|
-
body: new URLSearchParams({
|
|
1708
|
-
grant_type: "refresh_token",
|
|
1709
|
-
refresh_token: refreshToken
|
|
1710
|
-
}).toString()
|
|
1711
|
-
});
|
|
1712
|
-
if (!response.data) throw new BetterAuthError("FAILED_TO_REFRESH_ACCESS_TOKEN");
|
|
1713
|
-
const data = response.data;
|
|
1714
|
-
return {
|
|
1715
|
-
accessToken: data.access_token,
|
|
1716
|
-
refreshToken: data.refresh_token,
|
|
1717
|
-
accessTokenExpiresAt: data.expires_in ? new Date(Date.now() + data.expires_in * 1e3) : void 0
|
|
1718
|
-
};
|
|
1719
|
-
} catch (error) {
|
|
1720
|
-
logger.error("PayPal token refresh failed:", error);
|
|
1721
|
-
throw new BetterAuthError("FAILED_TO_REFRESH_ACCESS_TOKEN");
|
|
1722
|
-
}
|
|
1723
|
-
},
|
|
1724
|
-
async verifyIdToken(token, nonce) {
|
|
1725
|
-
if (options.disableIdTokenSignIn) return false;
|
|
1726
|
-
if (options.verifyIdToken) return options.verifyIdToken(token, nonce);
|
|
1727
|
-
try {
|
|
1728
|
-
return !!decodeJwt(token).sub;
|
|
1729
|
-
} catch (error) {
|
|
1730
|
-
logger.error("Failed to verify PayPal ID token:", error);
|
|
1731
|
-
return false;
|
|
1732
|
-
}
|
|
1733
|
-
},
|
|
1734
|
-
async getUserInfo(token) {
|
|
1735
|
-
if (options.getUserInfo) return options.getUserInfo(token);
|
|
1736
|
-
if (!token.accessToken) {
|
|
1737
|
-
logger.error("Access token is required to fetch PayPal user info");
|
|
1738
|
-
return null;
|
|
1739
|
-
}
|
|
1740
|
-
try {
|
|
1741
|
-
const response = await betterFetch(`${userInfoEndpoint}?schema=paypalv1.1`, { headers: {
|
|
1742
|
-
Authorization: `Bearer ${token.accessToken}`,
|
|
1743
|
-
Accept: "application/json"
|
|
1744
|
-
} });
|
|
1745
|
-
if (!response.data) {
|
|
1746
|
-
logger.error("Failed to fetch user info from PayPal");
|
|
1747
|
-
return null;
|
|
1748
|
-
}
|
|
1749
|
-
const userInfo = response.data;
|
|
1750
|
-
const userMap = await options.mapProfileToUser?.(userInfo);
|
|
1751
|
-
return {
|
|
1752
|
-
user: {
|
|
1753
|
-
id: userInfo.user_id,
|
|
1754
|
-
name: userInfo.name,
|
|
1755
|
-
email: userInfo.email,
|
|
1756
|
-
image: userInfo.picture,
|
|
1757
|
-
emailVerified: userInfo.email_verified,
|
|
1758
|
-
...userMap
|
|
1759
|
-
},
|
|
1760
|
-
data: userInfo
|
|
1761
|
-
};
|
|
1762
|
-
} catch (error) {
|
|
1763
|
-
logger.error("Failed to fetch user info from PayPal:", error);
|
|
1764
|
-
return null;
|
|
1765
|
-
}
|
|
1766
|
-
},
|
|
1767
|
-
options
|
|
1768
|
-
};
|
|
1769
|
-
};
|
|
1770
|
-
|
|
1771
|
-
//#endregion
|
|
1772
|
-
//#region src/social-providers/polar.ts
|
|
1773
|
-
const polar = (options) => {
|
|
1774
|
-
return {
|
|
1775
|
-
id: "polar",
|
|
1776
|
-
name: "Polar",
|
|
1777
|
-
createAuthorizationURL({ state, scopes, codeVerifier, redirectURI }) {
|
|
1778
|
-
const _scopes = options.disableDefaultScope ? [] : [
|
|
1779
|
-
"openid",
|
|
1780
|
-
"profile",
|
|
1781
|
-
"email"
|
|
1782
|
-
];
|
|
1783
|
-
if (options.scope) _scopes.push(...options.scope);
|
|
1784
|
-
if (scopes) _scopes.push(...scopes);
|
|
1785
|
-
return createAuthorizationURL({
|
|
1786
|
-
id: "polar",
|
|
1787
|
-
options,
|
|
1788
|
-
authorizationEndpoint: "https://polar.sh/oauth2/authorize",
|
|
1789
|
-
scopes: _scopes,
|
|
1790
|
-
state,
|
|
1791
|
-
codeVerifier,
|
|
1792
|
-
redirectURI,
|
|
1793
|
-
prompt: options.prompt
|
|
1794
|
-
});
|
|
1795
|
-
},
|
|
1796
|
-
validateAuthorizationCode: async ({ code, codeVerifier, redirectURI }) => {
|
|
1797
|
-
return validateAuthorizationCode({
|
|
1798
|
-
code,
|
|
1799
|
-
codeVerifier,
|
|
1800
|
-
redirectURI,
|
|
1801
|
-
options,
|
|
1802
|
-
tokenEndpoint: "https://api.polar.sh/v1/oauth2/token"
|
|
1803
|
-
});
|
|
1804
|
-
},
|
|
1805
|
-
refreshAccessToken: options.refreshAccessToken ? options.refreshAccessToken : async (refreshToken) => {
|
|
1806
|
-
return refreshAccessToken({
|
|
1807
|
-
refreshToken,
|
|
1808
|
-
options: {
|
|
1809
|
-
clientId: options.clientId,
|
|
1810
|
-
clientKey: options.clientKey,
|
|
1811
|
-
clientSecret: options.clientSecret
|
|
1812
|
-
},
|
|
1813
|
-
tokenEndpoint: "https://api.polar.sh/v1/oauth2/token"
|
|
1814
|
-
});
|
|
1815
|
-
},
|
|
1816
|
-
async getUserInfo(token) {
|
|
1817
|
-
if (options.getUserInfo) return options.getUserInfo(token);
|
|
1818
|
-
const { data: profile, error } = await betterFetch("https://api.polar.sh/v1/oauth2/userinfo", { headers: { Authorization: `Bearer ${token.accessToken}` } });
|
|
1819
|
-
if (error) return null;
|
|
1820
|
-
const userMap = await options.mapProfileToUser?.(profile);
|
|
1821
|
-
return {
|
|
1822
|
-
user: {
|
|
1823
|
-
id: profile.id,
|
|
1824
|
-
name: profile.public_name || profile.username,
|
|
1825
|
-
email: profile.email,
|
|
1826
|
-
image: profile.avatar_url,
|
|
1827
|
-
emailVerified: profile.email_verified ?? false,
|
|
1828
|
-
...userMap
|
|
1829
|
-
},
|
|
1830
|
-
data: profile
|
|
1831
|
-
};
|
|
1832
|
-
},
|
|
1833
|
-
options
|
|
1834
|
-
};
|
|
1835
|
-
};
|
|
1836
|
-
|
|
1837
|
-
//#endregion
|
|
1838
|
-
//#region src/social-providers/reddit.ts
|
|
1839
|
-
const reddit = (options) => {
|
|
1840
|
-
return {
|
|
1841
|
-
id: "reddit",
|
|
1842
|
-
name: "Reddit",
|
|
1843
|
-
createAuthorizationURL({ state, scopes, redirectURI }) {
|
|
1844
|
-
const _scopes = options.disableDefaultScope ? [] : ["identity"];
|
|
1845
|
-
if (options.scope) _scopes.push(...options.scope);
|
|
1846
|
-
if (scopes) _scopes.push(...scopes);
|
|
1847
|
-
return createAuthorizationURL({
|
|
1848
|
-
id: "reddit",
|
|
1849
|
-
options,
|
|
1850
|
-
authorizationEndpoint: "https://www.reddit.com/api/v1/authorize",
|
|
1851
|
-
scopes: _scopes,
|
|
1852
|
-
state,
|
|
1853
|
-
redirectURI,
|
|
1854
|
-
duration: options.duration
|
|
1855
|
-
});
|
|
1856
|
-
},
|
|
1857
|
-
validateAuthorizationCode: async ({ code, redirectURI }) => {
|
|
1858
|
-
const body = new URLSearchParams({
|
|
1859
|
-
grant_type: "authorization_code",
|
|
1860
|
-
code,
|
|
1861
|
-
redirect_uri: options.redirectURI || redirectURI
|
|
1862
|
-
});
|
|
1863
|
-
const { data, error } = await betterFetch("https://www.reddit.com/api/v1/access_token", {
|
|
1864
|
-
method: "POST",
|
|
1865
|
-
headers: {
|
|
1866
|
-
"content-type": "application/x-www-form-urlencoded",
|
|
1867
|
-
accept: "text/plain",
|
|
1868
|
-
"user-agent": "better-auth",
|
|
1869
|
-
Authorization: `Basic ${base64.encode(`${options.clientId}:${options.clientSecret}`)}`
|
|
1870
|
-
},
|
|
1871
|
-
body: body.toString()
|
|
1872
|
-
});
|
|
1873
|
-
if (error) throw error;
|
|
1874
|
-
return getOAuth2Tokens(data);
|
|
1875
|
-
},
|
|
1876
|
-
refreshAccessToken: options.refreshAccessToken ? options.refreshAccessToken : async (refreshToken) => {
|
|
1877
|
-
return refreshAccessToken({
|
|
1878
|
-
refreshToken,
|
|
1879
|
-
options: {
|
|
1880
|
-
clientId: options.clientId,
|
|
1881
|
-
clientKey: options.clientKey,
|
|
1882
|
-
clientSecret: options.clientSecret
|
|
1883
|
-
},
|
|
1884
|
-
authentication: "basic",
|
|
1885
|
-
tokenEndpoint: "https://www.reddit.com/api/v1/access_token"
|
|
1886
|
-
});
|
|
1887
|
-
},
|
|
1888
|
-
async getUserInfo(token) {
|
|
1889
|
-
if (options.getUserInfo) return options.getUserInfo(token);
|
|
1890
|
-
const { data: profile, error } = await betterFetch("https://oauth.reddit.com/api/v1/me", { headers: {
|
|
1891
|
-
Authorization: `Bearer ${token.accessToken}`,
|
|
1892
|
-
"User-Agent": "better-auth"
|
|
1893
|
-
} });
|
|
1894
|
-
if (error) return null;
|
|
1895
|
-
const userMap = await options.mapProfileToUser?.(profile);
|
|
1896
|
-
return {
|
|
1897
|
-
user: {
|
|
1898
|
-
id: profile.id,
|
|
1899
|
-
name: profile.name,
|
|
1900
|
-
email: profile.oauth_client_id,
|
|
1901
|
-
emailVerified: profile.has_verified_email,
|
|
1902
|
-
image: profile.icon_img?.split("?")[0],
|
|
1903
|
-
...userMap
|
|
1904
|
-
},
|
|
1905
|
-
data: profile
|
|
1906
|
-
};
|
|
1907
|
-
},
|
|
1908
|
-
options
|
|
1909
|
-
};
|
|
1910
|
-
};
|
|
1911
|
-
|
|
1912
|
-
//#endregion
|
|
1913
|
-
//#region src/social-providers/roblox.ts
|
|
1914
|
-
const roblox = (options) => {
|
|
1915
|
-
return {
|
|
1916
|
-
id: "roblox",
|
|
1917
|
-
name: "Roblox",
|
|
1918
|
-
createAuthorizationURL({ state, scopes, redirectURI }) {
|
|
1919
|
-
const _scopes = options.disableDefaultScope ? [] : ["openid", "profile"];
|
|
1920
|
-
if (options.scope) _scopes.push(...options.scope);
|
|
1921
|
-
if (scopes) _scopes.push(...scopes);
|
|
1922
|
-
return new URL(`https://apis.roblox.com/oauth/v1/authorize?scope=${_scopes.join("+")}&response_type=code&client_id=${options.clientId}&redirect_uri=${encodeURIComponent(options.redirectURI || redirectURI)}&state=${state}&prompt=${options.prompt || "select_account consent"}`);
|
|
1923
|
-
},
|
|
1924
|
-
validateAuthorizationCode: async ({ code, redirectURI }) => {
|
|
1925
|
-
return validateAuthorizationCode({
|
|
1926
|
-
code,
|
|
1927
|
-
redirectURI: options.redirectURI || redirectURI,
|
|
1928
|
-
options,
|
|
1929
|
-
tokenEndpoint: "https://apis.roblox.com/oauth/v1/token",
|
|
1930
|
-
authentication: "post"
|
|
1931
|
-
});
|
|
1932
|
-
},
|
|
1933
|
-
refreshAccessToken: options.refreshAccessToken ? options.refreshAccessToken : async (refreshToken) => {
|
|
1934
|
-
return refreshAccessToken({
|
|
1935
|
-
refreshToken,
|
|
1936
|
-
options: {
|
|
1937
|
-
clientId: options.clientId,
|
|
1938
|
-
clientKey: options.clientKey,
|
|
1939
|
-
clientSecret: options.clientSecret
|
|
1940
|
-
},
|
|
1941
|
-
tokenEndpoint: "https://apis.roblox.com/oauth/v1/token"
|
|
1942
|
-
});
|
|
1943
|
-
},
|
|
1944
|
-
async getUserInfo(token) {
|
|
1945
|
-
if (options.getUserInfo) return options.getUserInfo(token);
|
|
1946
|
-
const { data: profile, error } = await betterFetch("https://apis.roblox.com/oauth/v1/userinfo", { headers: { authorization: `Bearer ${token.accessToken}` } });
|
|
1947
|
-
if (error) return null;
|
|
1948
|
-
const userMap = await options.mapProfileToUser?.(profile);
|
|
1949
|
-
return {
|
|
1950
|
-
user: {
|
|
1951
|
-
id: profile.sub,
|
|
1952
|
-
name: profile.nickname || profile.preferred_username || "",
|
|
1953
|
-
image: profile.picture,
|
|
1954
|
-
email: profile.preferred_username || null,
|
|
1955
|
-
emailVerified: false,
|
|
1956
|
-
...userMap
|
|
1957
|
-
},
|
|
1958
|
-
data: { ...profile }
|
|
1959
|
-
};
|
|
1960
|
-
},
|
|
1961
|
-
options
|
|
1962
|
-
};
|
|
1963
|
-
};
|
|
1964
|
-
|
|
1965
|
-
//#endregion
|
|
1966
|
-
//#region src/social-providers/salesforce.ts
|
|
1967
|
-
const salesforce = (options) => {
|
|
1968
|
-
const isSandbox = (options.environment ?? "production") === "sandbox";
|
|
1969
|
-
const authorizationEndpoint = options.loginUrl ? `https://${options.loginUrl}/services/oauth2/authorize` : isSandbox ? "https://test.salesforce.com/services/oauth2/authorize" : "https://login.salesforce.com/services/oauth2/authorize";
|
|
1970
|
-
const tokenEndpoint = options.loginUrl ? `https://${options.loginUrl}/services/oauth2/token` : isSandbox ? "https://test.salesforce.com/services/oauth2/token" : "https://login.salesforce.com/services/oauth2/token";
|
|
1971
|
-
const userInfoEndpoint = options.loginUrl ? `https://${options.loginUrl}/services/oauth2/userinfo` : isSandbox ? "https://test.salesforce.com/services/oauth2/userinfo" : "https://login.salesforce.com/services/oauth2/userinfo";
|
|
1972
|
-
return {
|
|
1973
|
-
id: "salesforce",
|
|
1974
|
-
name: "Salesforce",
|
|
1975
|
-
async createAuthorizationURL({ state, scopes, codeVerifier, redirectURI }) {
|
|
1976
|
-
if (!options.clientId || !options.clientSecret) {
|
|
1977
|
-
logger.error("Client Id and Client Secret are required for Salesforce. Make sure to provide them in the options.");
|
|
1978
|
-
throw new BetterAuthError("CLIENT_ID_AND_SECRET_REQUIRED");
|
|
1979
|
-
}
|
|
1980
|
-
if (!codeVerifier) throw new BetterAuthError("codeVerifier is required for Salesforce");
|
|
1981
|
-
const _scopes = options.disableDefaultScope ? [] : [
|
|
1982
|
-
"openid",
|
|
1983
|
-
"email",
|
|
1984
|
-
"profile"
|
|
1985
|
-
];
|
|
1986
|
-
if (options.scope) _scopes.push(...options.scope);
|
|
1987
|
-
if (scopes) _scopes.push(...scopes);
|
|
1988
|
-
return createAuthorizationURL({
|
|
1989
|
-
id: "salesforce",
|
|
1990
|
-
options,
|
|
1991
|
-
authorizationEndpoint,
|
|
1992
|
-
scopes: _scopes,
|
|
1993
|
-
state,
|
|
1994
|
-
codeVerifier,
|
|
1995
|
-
redirectURI: options.redirectURI || redirectURI
|
|
1996
|
-
});
|
|
1997
|
-
},
|
|
1998
|
-
validateAuthorizationCode: async ({ code, codeVerifier, redirectURI }) => {
|
|
1999
|
-
return validateAuthorizationCode({
|
|
2000
|
-
code,
|
|
2001
|
-
codeVerifier,
|
|
2002
|
-
redirectURI: options.redirectURI || redirectURI,
|
|
2003
|
-
options,
|
|
2004
|
-
tokenEndpoint
|
|
2005
|
-
});
|
|
2006
|
-
},
|
|
2007
|
-
refreshAccessToken: options.refreshAccessToken ? options.refreshAccessToken : async (refreshToken) => {
|
|
2008
|
-
return refreshAccessToken({
|
|
2009
|
-
refreshToken,
|
|
2010
|
-
options: {
|
|
2011
|
-
clientId: options.clientId,
|
|
2012
|
-
clientSecret: options.clientSecret
|
|
2013
|
-
},
|
|
2014
|
-
tokenEndpoint
|
|
2015
|
-
});
|
|
2016
|
-
},
|
|
2017
|
-
async getUserInfo(token) {
|
|
2018
|
-
if (options.getUserInfo) return options.getUserInfo(token);
|
|
2019
|
-
try {
|
|
2020
|
-
const { data: user } = await betterFetch(userInfoEndpoint, { headers: { Authorization: `Bearer ${token.accessToken}` } });
|
|
2021
|
-
if (!user) {
|
|
2022
|
-
logger.error("Failed to fetch user info from Salesforce");
|
|
2023
|
-
return null;
|
|
2024
|
-
}
|
|
2025
|
-
const userMap = await options.mapProfileToUser?.(user);
|
|
2026
|
-
return {
|
|
2027
|
-
user: {
|
|
2028
|
-
id: user.user_id,
|
|
2029
|
-
name: user.name,
|
|
2030
|
-
email: user.email,
|
|
2031
|
-
image: user.photos?.picture || user.photos?.thumbnail,
|
|
2032
|
-
emailVerified: user.email_verified ?? false,
|
|
2033
|
-
...userMap
|
|
2034
|
-
},
|
|
2035
|
-
data: user
|
|
2036
|
-
};
|
|
2037
|
-
} catch (error) {
|
|
2038
|
-
logger.error("Failed to fetch user info from Salesforce:", error);
|
|
2039
|
-
return null;
|
|
2040
|
-
}
|
|
2041
|
-
},
|
|
2042
|
-
options
|
|
2043
|
-
};
|
|
2044
|
-
};
|
|
2045
|
-
|
|
2046
|
-
//#endregion
|
|
2047
|
-
//#region src/social-providers/slack.ts
|
|
2048
|
-
const slack = (options) => {
|
|
2049
|
-
return {
|
|
2050
|
-
id: "slack",
|
|
2051
|
-
name: "Slack",
|
|
2052
|
-
createAuthorizationURL({ state, scopes, redirectURI }) {
|
|
2053
|
-
const _scopes = options.disableDefaultScope ? [] : [
|
|
2054
|
-
"openid",
|
|
2055
|
-
"profile",
|
|
2056
|
-
"email"
|
|
2057
|
-
];
|
|
2058
|
-
if (scopes) _scopes.push(...scopes);
|
|
2059
|
-
if (options.scope) _scopes.push(...options.scope);
|
|
2060
|
-
const url = new URL("https://slack.com/openid/connect/authorize");
|
|
2061
|
-
url.searchParams.set("scope", _scopes.join(" "));
|
|
2062
|
-
url.searchParams.set("response_type", "code");
|
|
2063
|
-
url.searchParams.set("client_id", options.clientId);
|
|
2064
|
-
url.searchParams.set("redirect_uri", options.redirectURI || redirectURI);
|
|
2065
|
-
url.searchParams.set("state", state);
|
|
2066
|
-
return url;
|
|
2067
|
-
},
|
|
2068
|
-
validateAuthorizationCode: async ({ code, redirectURI }) => {
|
|
2069
|
-
return validateAuthorizationCode({
|
|
2070
|
-
code,
|
|
2071
|
-
redirectURI,
|
|
2072
|
-
options,
|
|
2073
|
-
tokenEndpoint: "https://slack.com/api/openid.connect.token"
|
|
2074
|
-
});
|
|
2075
|
-
},
|
|
2076
|
-
refreshAccessToken: options.refreshAccessToken ? options.refreshAccessToken : async (refreshToken) => {
|
|
2077
|
-
return refreshAccessToken({
|
|
2078
|
-
refreshToken,
|
|
2079
|
-
options: {
|
|
2080
|
-
clientId: options.clientId,
|
|
2081
|
-
clientKey: options.clientKey,
|
|
2082
|
-
clientSecret: options.clientSecret
|
|
2083
|
-
},
|
|
2084
|
-
tokenEndpoint: "https://slack.com/api/openid.connect.token"
|
|
2085
|
-
});
|
|
2086
|
-
},
|
|
2087
|
-
async getUserInfo(token) {
|
|
2088
|
-
if (options.getUserInfo) return options.getUserInfo(token);
|
|
2089
|
-
const { data: profile, error } = await betterFetch("https://slack.com/api/openid.connect.userInfo", { headers: { authorization: `Bearer ${token.accessToken}` } });
|
|
2090
|
-
if (error) return null;
|
|
2091
|
-
const userMap = await options.mapProfileToUser?.(profile);
|
|
2092
|
-
return {
|
|
2093
|
-
user: {
|
|
2094
|
-
id: profile["https://slack.com/user_id"],
|
|
2095
|
-
name: profile.name || "",
|
|
2096
|
-
email: profile.email,
|
|
2097
|
-
emailVerified: profile.email_verified,
|
|
2098
|
-
image: profile.picture || profile["https://slack.com/user_image_512"],
|
|
2099
|
-
...userMap
|
|
2100
|
-
},
|
|
2101
|
-
data: profile
|
|
2102
|
-
};
|
|
2103
|
-
},
|
|
2104
|
-
options
|
|
2105
|
-
};
|
|
2106
|
-
};
|
|
2107
|
-
|
|
2108
|
-
//#endregion
|
|
2109
|
-
//#region src/social-providers/spotify.ts
|
|
2110
|
-
const spotify = (options) => {
|
|
2111
|
-
return {
|
|
2112
|
-
id: "spotify",
|
|
2113
|
-
name: "Spotify",
|
|
2114
|
-
createAuthorizationURL({ state, scopes, codeVerifier, redirectURI }) {
|
|
2115
|
-
const _scopes = options.disableDefaultScope ? [] : ["user-read-email"];
|
|
2116
|
-
if (options.scope) _scopes.push(...options.scope);
|
|
2117
|
-
if (scopes) _scopes.push(...scopes);
|
|
2118
|
-
return createAuthorizationURL({
|
|
2119
|
-
id: "spotify",
|
|
2120
|
-
options,
|
|
2121
|
-
authorizationEndpoint: "https://accounts.spotify.com/authorize",
|
|
2122
|
-
scopes: _scopes,
|
|
2123
|
-
state,
|
|
2124
|
-
codeVerifier,
|
|
2125
|
-
redirectURI
|
|
2126
|
-
});
|
|
2127
|
-
},
|
|
2128
|
-
validateAuthorizationCode: async ({ code, codeVerifier, redirectURI }) => {
|
|
2129
|
-
return validateAuthorizationCode({
|
|
2130
|
-
code,
|
|
2131
|
-
codeVerifier,
|
|
2132
|
-
redirectURI,
|
|
2133
|
-
options,
|
|
2134
|
-
tokenEndpoint: "https://accounts.spotify.com/api/token"
|
|
2135
|
-
});
|
|
2136
|
-
},
|
|
2137
|
-
refreshAccessToken: options.refreshAccessToken ? options.refreshAccessToken : async (refreshToken) => {
|
|
2138
|
-
return refreshAccessToken({
|
|
2139
|
-
refreshToken,
|
|
2140
|
-
options: {
|
|
2141
|
-
clientId: options.clientId,
|
|
2142
|
-
clientKey: options.clientKey,
|
|
2143
|
-
clientSecret: options.clientSecret
|
|
2144
|
-
},
|
|
2145
|
-
tokenEndpoint: "https://accounts.spotify.com/api/token"
|
|
2146
|
-
});
|
|
2147
|
-
},
|
|
2148
|
-
async getUserInfo(token) {
|
|
2149
|
-
if (options.getUserInfo) return options.getUserInfo(token);
|
|
2150
|
-
const { data: profile, error } = await betterFetch("https://api.spotify.com/v1/me", {
|
|
2151
|
-
method: "GET",
|
|
2152
|
-
headers: { Authorization: `Bearer ${token.accessToken}` }
|
|
2153
|
-
});
|
|
2154
|
-
if (error) return null;
|
|
2155
|
-
const userMap = await options.mapProfileToUser?.(profile);
|
|
2156
|
-
return {
|
|
2157
|
-
user: {
|
|
2158
|
-
id: profile.id,
|
|
2159
|
-
name: profile.display_name,
|
|
2160
|
-
email: profile.email,
|
|
2161
|
-
image: profile.images[0]?.url,
|
|
2162
|
-
emailVerified: false,
|
|
2163
|
-
...userMap
|
|
2164
|
-
},
|
|
2165
|
-
data: profile
|
|
2166
|
-
};
|
|
2167
|
-
},
|
|
2168
|
-
options
|
|
2169
|
-
};
|
|
2170
|
-
};
|
|
2171
|
-
|
|
2172
|
-
//#endregion
|
|
2173
|
-
//#region src/social-providers/tiktok.ts
|
|
2174
|
-
const tiktok = (options) => {
|
|
2175
|
-
return {
|
|
2176
|
-
id: "tiktok",
|
|
2177
|
-
name: "TikTok",
|
|
2178
|
-
createAuthorizationURL({ state, scopes, redirectURI }) {
|
|
2179
|
-
const _scopes = options.disableDefaultScope ? [] : ["user.info.profile"];
|
|
2180
|
-
if (options.scope) _scopes.push(...options.scope);
|
|
2181
|
-
if (scopes) _scopes.push(...scopes);
|
|
2182
|
-
return new URL(`https://www.tiktok.com/v2/auth/authorize?scope=${_scopes.join(",")}&response_type=code&client_key=${options.clientKey}&redirect_uri=${encodeURIComponent(options.redirectURI || redirectURI)}&state=${state}`);
|
|
2183
|
-
},
|
|
2184
|
-
validateAuthorizationCode: async ({ code, redirectURI }) => {
|
|
2185
|
-
return validateAuthorizationCode({
|
|
2186
|
-
code,
|
|
2187
|
-
redirectURI: options.redirectURI || redirectURI,
|
|
2188
|
-
options: {
|
|
2189
|
-
clientKey: options.clientKey,
|
|
2190
|
-
clientSecret: options.clientSecret
|
|
2191
|
-
},
|
|
2192
|
-
tokenEndpoint: "https://open.tiktokapis.com/v2/oauth/token/"
|
|
2193
|
-
});
|
|
2194
|
-
},
|
|
2195
|
-
refreshAccessToken: options.refreshAccessToken ? options.refreshAccessToken : async (refreshToken) => {
|
|
2196
|
-
return refreshAccessToken({
|
|
2197
|
-
refreshToken,
|
|
2198
|
-
options: { clientSecret: options.clientSecret },
|
|
2199
|
-
tokenEndpoint: "https://open.tiktokapis.com/v2/oauth/token/",
|
|
2200
|
-
authentication: "post",
|
|
2201
|
-
extraParams: { client_key: options.clientKey }
|
|
2202
|
-
});
|
|
2203
|
-
},
|
|
2204
|
-
async getUserInfo(token) {
|
|
2205
|
-
if (options.getUserInfo) return options.getUserInfo(token);
|
|
2206
|
-
const { data: profile, error } = await betterFetch(`https://open.tiktokapis.com/v2/user/info/?fields=${[
|
|
2207
|
-
"open_id",
|
|
2208
|
-
"avatar_large_url",
|
|
2209
|
-
"display_name",
|
|
2210
|
-
"username"
|
|
2211
|
-
].join(",")}`, { headers: { authorization: `Bearer ${token.accessToken}` } });
|
|
2212
|
-
if (error) return null;
|
|
2213
|
-
return {
|
|
2214
|
-
user: {
|
|
2215
|
-
email: profile.data.user.email || profile.data.user.username,
|
|
2216
|
-
id: profile.data.user.open_id,
|
|
2217
|
-
name: profile.data.user.display_name || profile.data.user.username,
|
|
2218
|
-
image: profile.data.user.avatar_large_url,
|
|
2219
|
-
emailVerified: false
|
|
2220
|
-
},
|
|
2221
|
-
data: profile
|
|
2222
|
-
};
|
|
2223
|
-
},
|
|
2224
|
-
options
|
|
2225
|
-
};
|
|
2226
|
-
};
|
|
2227
|
-
|
|
2228
|
-
//#endregion
|
|
2229
|
-
//#region src/social-providers/twitch.ts
|
|
2230
|
-
const twitch = (options) => {
|
|
2231
|
-
return {
|
|
2232
|
-
id: "twitch",
|
|
2233
|
-
name: "Twitch",
|
|
2234
|
-
createAuthorizationURL({ state, scopes, redirectURI }) {
|
|
2235
|
-
const _scopes = options.disableDefaultScope ? [] : ["user:read:email", "openid"];
|
|
2236
|
-
if (options.scope) _scopes.push(...options.scope);
|
|
2237
|
-
if (scopes) _scopes.push(...scopes);
|
|
2238
|
-
return createAuthorizationURL({
|
|
2239
|
-
id: "twitch",
|
|
2240
|
-
redirectURI,
|
|
2241
|
-
options,
|
|
2242
|
-
authorizationEndpoint: "https://id.twitch.tv/oauth2/authorize",
|
|
2243
|
-
scopes: _scopes,
|
|
2244
|
-
state,
|
|
2245
|
-
claims: options.claims || [
|
|
2246
|
-
"email",
|
|
2247
|
-
"email_verified",
|
|
2248
|
-
"preferred_username",
|
|
2249
|
-
"picture"
|
|
2250
|
-
]
|
|
2251
|
-
});
|
|
2252
|
-
},
|
|
2253
|
-
validateAuthorizationCode: async ({ code, redirectURI }) => {
|
|
2254
|
-
return validateAuthorizationCode({
|
|
2255
|
-
code,
|
|
2256
|
-
redirectURI,
|
|
2257
|
-
options,
|
|
2258
|
-
tokenEndpoint: "https://id.twitch.tv/oauth2/token"
|
|
2259
|
-
});
|
|
2260
|
-
},
|
|
2261
|
-
refreshAccessToken: options.refreshAccessToken ? options.refreshAccessToken : async (refreshToken) => {
|
|
2262
|
-
return refreshAccessToken({
|
|
2263
|
-
refreshToken,
|
|
2264
|
-
options: {
|
|
2265
|
-
clientId: options.clientId,
|
|
2266
|
-
clientKey: options.clientKey,
|
|
2267
|
-
clientSecret: options.clientSecret
|
|
2268
|
-
},
|
|
2269
|
-
tokenEndpoint: "https://id.twitch.tv/oauth2/token"
|
|
2270
|
-
});
|
|
2271
|
-
},
|
|
2272
|
-
async getUserInfo(token) {
|
|
2273
|
-
if (options.getUserInfo) return options.getUserInfo(token);
|
|
2274
|
-
const idToken = token.idToken;
|
|
2275
|
-
if (!idToken) {
|
|
2276
|
-
logger.error("No idToken found in token");
|
|
2277
|
-
return null;
|
|
2278
|
-
}
|
|
2279
|
-
const profile = decodeJwt(idToken);
|
|
2280
|
-
const userMap = await options.mapProfileToUser?.(profile);
|
|
2281
|
-
return {
|
|
2282
|
-
user: {
|
|
2283
|
-
id: profile.sub,
|
|
2284
|
-
name: profile.preferred_username,
|
|
2285
|
-
email: profile.email,
|
|
2286
|
-
image: profile.picture,
|
|
2287
|
-
emailVerified: profile.email_verified,
|
|
2288
|
-
...userMap
|
|
2289
|
-
},
|
|
2290
|
-
data: profile
|
|
2291
|
-
};
|
|
2292
|
-
},
|
|
2293
|
-
options
|
|
2294
|
-
};
|
|
2295
|
-
};
|
|
2296
|
-
|
|
2297
|
-
//#endregion
|
|
2298
|
-
//#region src/social-providers/twitter.ts
|
|
2299
|
-
const twitter = (options) => {
|
|
2300
|
-
return {
|
|
2301
|
-
id: "twitter",
|
|
2302
|
-
name: "Twitter",
|
|
2303
|
-
createAuthorizationURL(data) {
|
|
2304
|
-
const _scopes = options.disableDefaultScope ? [] : [
|
|
2305
|
-
"users.read",
|
|
2306
|
-
"tweet.read",
|
|
2307
|
-
"offline.access",
|
|
2308
|
-
"users.email"
|
|
2309
|
-
];
|
|
2310
|
-
if (options.scope) _scopes.push(...options.scope);
|
|
2311
|
-
if (data.scopes) _scopes.push(...data.scopes);
|
|
2312
|
-
return createAuthorizationURL({
|
|
2313
|
-
id: "twitter",
|
|
2314
|
-
options,
|
|
2315
|
-
authorizationEndpoint: "https://x.com/i/oauth2/authorize",
|
|
2316
|
-
scopes: _scopes,
|
|
2317
|
-
state: data.state,
|
|
2318
|
-
codeVerifier: data.codeVerifier,
|
|
2319
|
-
redirectURI: data.redirectURI
|
|
2320
|
-
});
|
|
2321
|
-
},
|
|
2322
|
-
validateAuthorizationCode: async ({ code, codeVerifier, redirectURI }) => {
|
|
2323
|
-
return validateAuthorizationCode({
|
|
2324
|
-
code,
|
|
2325
|
-
codeVerifier,
|
|
2326
|
-
authentication: "basic",
|
|
2327
|
-
redirectURI,
|
|
2328
|
-
options,
|
|
2329
|
-
tokenEndpoint: "https://api.x.com/2/oauth2/token"
|
|
2330
|
-
});
|
|
2331
|
-
},
|
|
2332
|
-
refreshAccessToken: options.refreshAccessToken ? options.refreshAccessToken : async (refreshToken) => {
|
|
2333
|
-
return refreshAccessToken({
|
|
2334
|
-
refreshToken,
|
|
2335
|
-
options: {
|
|
2336
|
-
clientId: options.clientId,
|
|
2337
|
-
clientKey: options.clientKey,
|
|
2338
|
-
clientSecret: options.clientSecret
|
|
2339
|
-
},
|
|
2340
|
-
authentication: "basic",
|
|
2341
|
-
tokenEndpoint: "https://api.x.com/2/oauth2/token"
|
|
2342
|
-
});
|
|
2343
|
-
},
|
|
2344
|
-
async getUserInfo(token) {
|
|
2345
|
-
if (options.getUserInfo) return options.getUserInfo(token);
|
|
2346
|
-
const { data: profile, error: profileError } = await betterFetch("https://api.x.com/2/users/me?user.fields=profile_image_url", {
|
|
2347
|
-
method: "GET",
|
|
2348
|
-
headers: { Authorization: `Bearer ${token.accessToken}` }
|
|
2349
|
-
});
|
|
2350
|
-
if (profileError) return null;
|
|
2351
|
-
const { data: emailData, error: emailError } = await betterFetch("https://api.x.com/2/users/me?user.fields=confirmed_email", {
|
|
2352
|
-
method: "GET",
|
|
2353
|
-
headers: { Authorization: `Bearer ${token.accessToken}` }
|
|
2354
|
-
});
|
|
2355
|
-
let emailVerified = false;
|
|
2356
|
-
if (!emailError && emailData?.data?.confirmed_email) {
|
|
2357
|
-
profile.data.email = emailData.data.confirmed_email;
|
|
2358
|
-
emailVerified = true;
|
|
2359
|
-
}
|
|
2360
|
-
const userMap = await options.mapProfileToUser?.(profile);
|
|
2361
|
-
return {
|
|
2362
|
-
user: {
|
|
2363
|
-
id: profile.data.id,
|
|
2364
|
-
name: profile.data.name,
|
|
2365
|
-
email: profile.data.email || profile.data.username || null,
|
|
2366
|
-
image: profile.data.profile_image_url,
|
|
2367
|
-
emailVerified,
|
|
2368
|
-
...userMap
|
|
2369
|
-
},
|
|
2370
|
-
data: profile
|
|
2371
|
-
};
|
|
2372
|
-
},
|
|
2373
|
-
options
|
|
2374
|
-
};
|
|
2375
|
-
};
|
|
2376
|
-
|
|
2377
|
-
//#endregion
|
|
2378
|
-
//#region src/social-providers/vercel.ts
|
|
2379
|
-
const vercel = (options) => {
|
|
2380
|
-
return {
|
|
2381
|
-
id: "vercel",
|
|
2382
|
-
name: "Vercel",
|
|
2383
|
-
createAuthorizationURL({ state, scopes, codeVerifier, redirectURI }) {
|
|
2384
|
-
if (!codeVerifier) throw new BetterAuthError("codeVerifier is required for Vercel");
|
|
2385
|
-
let _scopes = void 0;
|
|
2386
|
-
if (options.scope !== void 0 || scopes !== void 0) {
|
|
2387
|
-
_scopes = [];
|
|
2388
|
-
if (options.scope) _scopes.push(...options.scope);
|
|
2389
|
-
if (scopes) _scopes.push(...scopes);
|
|
2390
|
-
}
|
|
2391
|
-
return createAuthorizationURL({
|
|
2392
|
-
id: "vercel",
|
|
2393
|
-
options,
|
|
2394
|
-
authorizationEndpoint: "https://vercel.com/oauth/authorize",
|
|
2395
|
-
scopes: _scopes,
|
|
2396
|
-
state,
|
|
2397
|
-
codeVerifier,
|
|
2398
|
-
redirectURI
|
|
2399
|
-
});
|
|
2400
|
-
},
|
|
2401
|
-
validateAuthorizationCode: async ({ code, codeVerifier, redirectURI }) => {
|
|
2402
|
-
return validateAuthorizationCode({
|
|
2403
|
-
code,
|
|
2404
|
-
codeVerifier,
|
|
2405
|
-
redirectURI,
|
|
2406
|
-
options,
|
|
2407
|
-
tokenEndpoint: "https://api.vercel.com/login/oauth/token"
|
|
2408
|
-
});
|
|
2409
|
-
},
|
|
2410
|
-
async getUserInfo(token) {
|
|
2411
|
-
if (options.getUserInfo) return options.getUserInfo(token);
|
|
2412
|
-
const { data: profile, error } = await betterFetch("https://api.vercel.com/login/oauth/userinfo", { headers: { Authorization: `Bearer ${token.accessToken}` } });
|
|
2413
|
-
if (error || !profile) return null;
|
|
2414
|
-
const userMap = await options.mapProfileToUser?.(profile);
|
|
2415
|
-
return {
|
|
2416
|
-
user: {
|
|
2417
|
-
id: profile.sub,
|
|
2418
|
-
name: profile.name ?? profile.preferred_username,
|
|
2419
|
-
email: profile.email,
|
|
2420
|
-
image: profile.picture,
|
|
2421
|
-
emailVerified: profile.email_verified ?? false,
|
|
2422
|
-
...userMap
|
|
2423
|
-
},
|
|
2424
|
-
data: profile
|
|
2425
|
-
};
|
|
2426
|
-
},
|
|
2427
|
-
options
|
|
2428
|
-
};
|
|
2429
|
-
};
|
|
2430
|
-
|
|
2431
|
-
//#endregion
|
|
2432
|
-
//#region src/social-providers/vk.ts
|
|
2433
|
-
const vk = (options) => {
|
|
2434
|
-
return {
|
|
2435
|
-
id: "vk",
|
|
2436
|
-
name: "VK",
|
|
2437
|
-
async createAuthorizationURL({ state, scopes, codeVerifier, redirectURI }) {
|
|
2438
|
-
const _scopes = options.disableDefaultScope ? [] : ["email", "phone"];
|
|
2439
|
-
if (options.scope) _scopes.push(...options.scope);
|
|
2440
|
-
if (scopes) _scopes.push(...scopes);
|
|
2441
|
-
return createAuthorizationURL({
|
|
2442
|
-
id: "vk",
|
|
2443
|
-
options,
|
|
2444
|
-
authorizationEndpoint: "https://id.vk.com/authorize",
|
|
2445
|
-
scopes: _scopes,
|
|
2446
|
-
state,
|
|
2447
|
-
redirectURI,
|
|
2448
|
-
codeVerifier
|
|
2449
|
-
});
|
|
2450
|
-
},
|
|
2451
|
-
validateAuthorizationCode: async ({ code, codeVerifier, redirectURI, deviceId }) => {
|
|
2452
|
-
return validateAuthorizationCode({
|
|
2453
|
-
code,
|
|
2454
|
-
codeVerifier,
|
|
2455
|
-
redirectURI: options.redirectURI || redirectURI,
|
|
2456
|
-
options,
|
|
2457
|
-
deviceId,
|
|
2458
|
-
tokenEndpoint: "https://id.vk.com/oauth2/auth"
|
|
2459
|
-
});
|
|
2460
|
-
},
|
|
2461
|
-
refreshAccessToken: options.refreshAccessToken ? options.refreshAccessToken : async (refreshToken) => {
|
|
2462
|
-
return refreshAccessToken({
|
|
2463
|
-
refreshToken,
|
|
2464
|
-
options: {
|
|
2465
|
-
clientId: options.clientId,
|
|
2466
|
-
clientKey: options.clientKey,
|
|
2467
|
-
clientSecret: options.clientSecret
|
|
2468
|
-
},
|
|
2469
|
-
tokenEndpoint: "https://id.vk.com/oauth2/auth"
|
|
2470
|
-
});
|
|
2471
|
-
},
|
|
2472
|
-
async getUserInfo(data) {
|
|
2473
|
-
if (options.getUserInfo) return options.getUserInfo(data);
|
|
2474
|
-
if (!data.accessToken) return null;
|
|
2475
|
-
const formBody = new URLSearchParams({
|
|
2476
|
-
access_token: data.accessToken,
|
|
2477
|
-
client_id: options.clientId
|
|
2478
|
-
}).toString();
|
|
2479
|
-
const { data: profile, error } = await betterFetch("https://id.vk.com/oauth2/user_info", {
|
|
2480
|
-
method: "POST",
|
|
2481
|
-
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
2482
|
-
body: formBody
|
|
2483
|
-
});
|
|
2484
|
-
if (error) return null;
|
|
2485
|
-
const userMap = await options.mapProfileToUser?.(profile);
|
|
2486
|
-
if (!profile.user.email && !userMap?.email) return null;
|
|
2487
|
-
return {
|
|
2488
|
-
user: {
|
|
2489
|
-
id: profile.user.user_id,
|
|
2490
|
-
first_name: profile.user.first_name,
|
|
2491
|
-
last_name: profile.user.last_name,
|
|
2492
|
-
email: profile.user.email,
|
|
2493
|
-
image: profile.user.avatar,
|
|
2494
|
-
emailVerified: false,
|
|
2495
|
-
birthday: profile.user.birthday,
|
|
2496
|
-
sex: profile.user.sex,
|
|
2497
|
-
name: `${profile.user.first_name} ${profile.user.last_name}`,
|
|
2498
|
-
...userMap
|
|
2499
|
-
},
|
|
2500
|
-
data: profile
|
|
2501
|
-
};
|
|
2502
|
-
},
|
|
2503
|
-
options
|
|
2504
|
-
};
|
|
2505
|
-
};
|
|
2506
|
-
|
|
2507
|
-
//#endregion
|
|
2508
|
-
//#region src/social-providers/zoom.ts
|
|
2509
|
-
const zoom = (userOptions) => {
|
|
2510
|
-
const options = {
|
|
2511
|
-
pkce: true,
|
|
2512
|
-
...userOptions
|
|
2513
|
-
};
|
|
2514
|
-
return {
|
|
2515
|
-
id: "zoom",
|
|
2516
|
-
name: "Zoom",
|
|
2517
|
-
createAuthorizationURL: async ({ state, redirectURI, codeVerifier }) => {
|
|
2518
|
-
const params = new URLSearchParams({
|
|
2519
|
-
response_type: "code",
|
|
2520
|
-
redirect_uri: options.redirectURI ? options.redirectURI : redirectURI,
|
|
2521
|
-
client_id: options.clientId,
|
|
2522
|
-
state
|
|
2523
|
-
});
|
|
2524
|
-
if (options.pkce) {
|
|
2525
|
-
const codeChallenge = await generateCodeChallenge(codeVerifier);
|
|
2526
|
-
params.set("code_challenge_method", "S256");
|
|
2527
|
-
params.set("code_challenge", codeChallenge);
|
|
2528
|
-
}
|
|
2529
|
-
const url = new URL("https://zoom.us/oauth/authorize");
|
|
2530
|
-
url.search = params.toString();
|
|
2531
|
-
return url;
|
|
2532
|
-
},
|
|
2533
|
-
validateAuthorizationCode: async ({ code, redirectURI, codeVerifier }) => {
|
|
2534
|
-
return validateAuthorizationCode({
|
|
2535
|
-
code,
|
|
2536
|
-
redirectURI: options.redirectURI || redirectURI,
|
|
2537
|
-
codeVerifier,
|
|
2538
|
-
options,
|
|
2539
|
-
tokenEndpoint: "https://zoom.us/oauth/token",
|
|
2540
|
-
authentication: "post"
|
|
2541
|
-
});
|
|
2542
|
-
},
|
|
2543
|
-
refreshAccessToken: options.refreshAccessToken ? options.refreshAccessToken : async (refreshToken) => refreshAccessToken({
|
|
2544
|
-
refreshToken,
|
|
2545
|
-
options: {
|
|
2546
|
-
clientId: options.clientId,
|
|
2547
|
-
clientKey: options.clientKey,
|
|
2548
|
-
clientSecret: options.clientSecret
|
|
2549
|
-
},
|
|
2550
|
-
tokenEndpoint: "https://zoom.us/oauth/token"
|
|
2551
|
-
}),
|
|
2552
|
-
async getUserInfo(token) {
|
|
2553
|
-
if (options.getUserInfo) return options.getUserInfo(token);
|
|
2554
|
-
const { data: profile, error } = await betterFetch("https://api.zoom.us/v2/users/me", { headers: { authorization: `Bearer ${token.accessToken}` } });
|
|
2555
|
-
if (error) return null;
|
|
2556
|
-
const userMap = await options.mapProfileToUser?.(profile);
|
|
2557
|
-
return {
|
|
2558
|
-
user: {
|
|
2559
|
-
id: profile.id,
|
|
2560
|
-
name: profile.display_name,
|
|
2561
|
-
image: profile.pic_url,
|
|
2562
|
-
email: profile.email,
|
|
2563
|
-
emailVerified: Boolean(profile.verified),
|
|
2564
|
-
...userMap
|
|
2565
|
-
},
|
|
2566
|
-
data: { ...profile }
|
|
2567
|
-
};
|
|
2568
|
-
}
|
|
2569
|
-
};
|
|
2570
|
-
};
|
|
2571
|
-
|
|
2572
|
-
//#endregion
|
|
2573
36
|
//#region src/social-providers/index.ts
|
|
2574
37
|
const socialProviders = {
|
|
2575
38
|
apple,
|