@better-auth/core 1.5.0-beta.3 → 1.5.0-beta.5

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