@gatwech/core 0.0.1-beta.1

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