@better-auth/core 1.4.12-beta.2 → 1.4.13

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