@better-auth/core 1.3.26 → 1.3.28

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