@xylex-group/athena 2.12.0 → 2.13.0

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 (96) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +1190 -1184
  3. package/bin/athena-js.js +104 -76
  4. package/dist/admin.cjs +45 -0
  5. package/dist/admin.cjs.map +1 -0
  6. package/dist/admin.d.cts +64 -0
  7. package/dist/admin.d.ts +64 -0
  8. package/dist/admin.js +41 -0
  9. package/dist/admin.js.map +1 -0
  10. package/dist/athena-auth-url-oLux_57a.d.cts +377 -0
  11. package/dist/athena-auth-url-oLux_57a.d.ts +377 -0
  12. package/dist/browser.cjs +33 -46
  13. package/dist/browser.cjs.map +1 -1
  14. package/dist/browser.d.cts +12 -10
  15. package/dist/browser.d.ts +12 -10
  16. package/dist/browser.js +33 -46
  17. package/dist/browser.js.map +1 -1
  18. package/dist/cli/index.cjs +150 -48
  19. package/dist/cli/index.cjs.map +1 -1
  20. package/dist/cli/index.d.cts +14 -5
  21. package/dist/cli/index.d.ts +14 -5
  22. package/dist/cli/index.js +150 -49
  23. package/dist/cli/index.js.map +1 -1
  24. package/dist/{client-QUbAs7E8.d.ts → client-BJjUwDSg.d.cts} +109 -1448
  25. package/dist/{client-C3x75Zgn.d.cts → client-DVOKSYDI.d.ts} +109 -1448
  26. package/dist/cookies.cjs +18 -0
  27. package/dist/cookies.cjs.map +1 -1
  28. package/dist/cookies.d.cts +2 -1
  29. package/dist/cookies.d.ts +2 -1
  30. package/dist/cookies.js +17 -1
  31. package/dist/cookies.js.map +1 -1
  32. package/dist/{index-CVcQCGyG.d.ts → index-CFL9xbFR.d.cts} +2 -0
  33. package/dist/{index-CVcQCGyG.d.cts → index-UKJpunc6.d.ts} +2 -0
  34. package/dist/index.cjs +101 -46
  35. package/dist/index.cjs.map +1 -1
  36. package/dist/index.d.cts +13 -10
  37. package/dist/index.d.ts +13 -10
  38. package/dist/index.js +101 -47
  39. package/dist/index.js.map +1 -1
  40. package/dist/{model-form-CD1uhWSh.d.cts → model-form-Dw4zEL5a.d.cts} +2 -2
  41. package/dist/{model-form-Dm69I-oO.d.ts → model-form-yRg71q3H.d.ts} +2 -2
  42. package/dist/{module-CW3tWJ10.d.ts → module-DBteUIob.d.ts} +12 -9
  43. package/dist/{module-Cxcurfes.d.cts → module-yoX49uQC.d.cts} +12 -9
  44. package/dist/next/client.cjs +452 -46
  45. package/dist/next/client.cjs.map +1 -1
  46. package/dist/next/client.d.cts +7 -4
  47. package/dist/next/client.d.ts +7 -4
  48. package/dist/next/client.js +430 -47
  49. package/dist/next/client.js.map +1 -1
  50. package/dist/next/server.cjs +292 -46
  51. package/dist/next/server.cjs.map +1 -1
  52. package/dist/next/server.d.cts +81 -5
  53. package/dist/next/server.d.ts +81 -5
  54. package/dist/next/server.js +280 -47
  55. package/dist/next/server.js.map +1 -1
  56. package/dist/organization.cjs +72 -0
  57. package/dist/organization.cjs.map +1 -0
  58. package/dist/organization.d.cts +43 -0
  59. package/dist/organization.d.ts +43 -0
  60. package/dist/organization.js +70 -0
  61. package/dist/organization.js.map +1 -0
  62. package/dist/payload-BzVbUgY_.d.cts +219 -0
  63. package/dist/payload-DEOWN_oo.d.ts +219 -0
  64. package/dist/{pipeline-BAwb6Vzm.d.ts → pipeline-Bj6RWt1A.d.ts} +1 -1
  65. package/dist/{pipeline-B14jVK7J.d.cts → pipeline-Dwck-wZO.d.cts} +1 -1
  66. package/dist/react.cjs +2 -10
  67. package/dist/react.cjs.map +1 -1
  68. package/dist/react.d.cts +26 -7
  69. package/dist/react.d.ts +26 -7
  70. package/dist/react.js +2 -10
  71. package/dist/react.js.map +1 -1
  72. package/dist/session-cookie-detection-BqOp9mO6.d.cts +46 -0
  73. package/dist/session-cookie-detection-BqOp9mO6.d.ts +46 -0
  74. package/dist/social-providers.cjs +4189 -0
  75. package/dist/social-providers.cjs.map +1 -0
  76. package/dist/social-providers.d.cts +4948 -0
  77. package/dist/social-providers.d.ts +4948 -0
  78. package/dist/social-providers.js +4117 -0
  79. package/dist/social-providers.js.map +1 -0
  80. package/dist/{types-Cq4-NoB4.d.ts → types-BRP7sfSF.d.ts} +50 -2
  81. package/dist/{types-Dr849HD6.d.cts → types-BU8uJH_b.d.cts} +50 -2
  82. package/dist/{types-CwJCPpLD.d.ts → types-CH78O90i.d.cts} +2 -2
  83. package/dist/{types-CwJCPpLD.d.cts → types-CH78O90i.d.ts} +2 -2
  84. package/dist/{types-DMOoYnPS.d.cts → types-CjC9_5ZQ.d.cts} +3 -3
  85. package/dist/{types-BcVmPBP-.d.ts → types-DPgejxYC.d.ts} +3 -3
  86. package/dist/types-eAKAh71s.d.cts +1476 -0
  87. package/dist/types-eAKAh71s.d.ts +1476 -0
  88. package/dist/utils.cjs +438 -12
  89. package/dist/utils.cjs.map +1 -1
  90. package/dist/utils.d.cts +76 -11
  91. package/dist/utils.d.ts +76 -11
  92. package/dist/utils.js +390 -13
  93. package/dist/utils.js.map +1 -1
  94. package/package.json +49 -22
  95. package/dist/shared-0Kdc74lu.d.ts +0 -35
  96. package/dist/shared-C0wVICRv.d.cts +0 -35
@@ -0,0 +1,4117 @@
1
+ import * as z from 'zod';
2
+ import { decodeJwt, decodeProtectedHeader, jwtVerify, createRemoteJWKSet, importJWK } from 'jose';
3
+
4
+ // src/auth/social-providers/registry.ts
5
+
6
+ // src/auth/env/logger.ts
7
+ var levels = ["debug", "info", "success", "warn", "error"];
8
+ function shouldPublishLog(current, level) {
9
+ return levels.indexOf(level) >= levels.indexOf(current);
10
+ }
11
+ function resolveLogLevel() {
12
+ try {
13
+ const env = typeof process !== "undefined" && process.env ? process.env.ATHENA_AUTH_LOG_LEVEL : void 0;
14
+ if (env === "debug" || env === "info" || env === "warn" || env === "error" || env === "success") {
15
+ return env;
16
+ }
17
+ } catch {
18
+ }
19
+ return "warn";
20
+ }
21
+ function log(level, message, ...args) {
22
+ const current = resolveLogLevel();
23
+ if (!shouldPublishLog(current, level === "info" ? "info" : level)) return;
24
+ const fn = level === "error" ? console.error : level === "warn" ? console.warn : console.log;
25
+ fn(`[athena-auth] ${message}`, ...args);
26
+ }
27
+ var logger = {
28
+ debug: (message, ...args) => log("debug", message, ...args),
29
+ info: (message, ...args) => log("info", message, ...args),
30
+ success: (message, ...args) => log("info", message, ...args),
31
+ warn: (message, ...args) => log("warn", message, ...args),
32
+ error: (message, ...args) => log("error", message, ...args)
33
+ };
34
+
35
+ // src/auth/error/athena-auth-error.ts
36
+ var AthenaAuthError = class extends Error {
37
+ constructor(message, options) {
38
+ super(message, options);
39
+ this.name = "AthenaAuthError";
40
+ this.message = message;
41
+ this.stack = "";
42
+ }
43
+ };
44
+
45
+ // src/auth/error/api-error.ts
46
+ var APIError = class _APIError extends Error {
47
+ status;
48
+ statusCode;
49
+ body;
50
+ headers;
51
+ constructor(status, body) {
52
+ const message = typeof body === "string" ? body : body?.message ?? (typeof status === "string" ? status : `HTTP ${status}`);
53
+ super(message);
54
+ this.name = "APIError";
55
+ this.status = status;
56
+ this.statusCode = typeof status === "number" ? status : 500;
57
+ this.body = typeof body === "string" ? { message: body } : body;
58
+ this.headers = void 0;
59
+ }
60
+ static fromStatus(status, body) {
61
+ return new _APIError(status, body);
62
+ }
63
+ static from(status, error) {
64
+ return new _APIError(status, {
65
+ message: error.message,
66
+ code: error.code
67
+ });
68
+ }
69
+ };
70
+
71
+ // src/auth/utils/base64.ts
72
+ function toUint8Array(input) {
73
+ if (typeof input === "string") {
74
+ return new TextEncoder().encode(input);
75
+ }
76
+ if (input instanceof Uint8Array) {
77
+ return input;
78
+ }
79
+ return new Uint8Array(input);
80
+ }
81
+ function bytesToBase64(bytes, urlSafe, padding) {
82
+ let binary = "";
83
+ for (let i = 0; i < bytes.length; i++) {
84
+ binary += String.fromCharCode(bytes[i]);
85
+ }
86
+ let encoded = btoa(binary);
87
+ if (urlSafe) {
88
+ encoded = encoded.replace(/\+/g, "-").replace(/\//g, "_");
89
+ }
90
+ if (!padding) {
91
+ encoded = encoded.replace(/=+$/g, "");
92
+ }
93
+ return encoded;
94
+ }
95
+ var base64 = {
96
+ encode(input) {
97
+ return bytesToBase64(toUint8Array(input), false, true);
98
+ }
99
+ };
100
+ var base64Url = {
101
+ encode(input, options) {
102
+ return bytesToBase64(toUint8Array(input), true, options?.padding !== false);
103
+ }
104
+ };
105
+
106
+ // src/auth/fetch/body.ts
107
+ function normalizeFetchBody(body) {
108
+ if (body == null) return body;
109
+ if (typeof body === "string") return body;
110
+ if (body instanceof URLSearchParams) return body;
111
+ if (body instanceof FormData) return body;
112
+ if (body instanceof Blob) return body;
113
+ if (typeof body === "object") {
114
+ return JSON.stringify(body);
115
+ }
116
+ return String(body);
117
+ }
118
+ function applyFetchQuery(url, query) {
119
+ if (!query) return url;
120
+ const u = new URL(url);
121
+ for (const [key, value] of Object.entries(query)) {
122
+ if (value === void 0 || value === null) continue;
123
+ u.searchParams.set(key, String(value));
124
+ }
125
+ return u.toString();
126
+ }
127
+
128
+ // src/auth/fetch/athena-fetch.ts
129
+ async function athenaFetch(url, options = {}) {
130
+ const headers = new Headers(options.headers);
131
+ if (options.auth?.token) {
132
+ const type = options.auth.type ?? "Bearer";
133
+ headers.set("authorization", `${type} ${options.auth.token}`);
134
+ }
135
+ const target = applyFetchQuery(url, options.query);
136
+ const body = normalizeFetchBody(options.body);
137
+ if (body != null && typeof body === "string" && !headers.has("content-type") && body.startsWith("{")) {
138
+ headers.set("content-type", "application/json");
139
+ }
140
+ try {
141
+ const response = await fetch(target, {
142
+ method: options.method ?? "GET",
143
+ headers,
144
+ body: body ?? void 0,
145
+ redirect: options.redirect ?? "follow",
146
+ signal: options.signal
147
+ });
148
+ options.onResponse?.({ response });
149
+ if (!response.ok) {
150
+ let message = response.statusText || `HTTP ${response.status}`;
151
+ try {
152
+ const errBody = await response.clone().json();
153
+ message = errBody.message || errBody.error_description || errBody.error || message;
154
+ } catch {
155
+ try {
156
+ message = await response.clone().text() || message;
157
+ } catch {
158
+ }
159
+ }
160
+ const error = {
161
+ message,
162
+ status: response.status,
163
+ statusText: response.statusText
164
+ };
165
+ options.onError?.({ response, error });
166
+ return { data: null, error };
167
+ }
168
+ const contentType = response.headers.get("content-type") ?? "";
169
+ if (contentType.includes("application/json") || contentType.includes("+json")) {
170
+ const data = await response.json();
171
+ return { data, error: null };
172
+ }
173
+ const text = await response.text();
174
+ if (!text) {
175
+ return { data: {}, error: null };
176
+ }
177
+ try {
178
+ return { data: JSON.parse(text), error: null };
179
+ } catch {
180
+ return { data: text, error: null };
181
+ }
182
+ } catch (cause) {
183
+ const error = {
184
+ message: cause instanceof Error ? cause.message : String(cause),
185
+ status: 0,
186
+ statusText: "NetworkError"
187
+ };
188
+ options.onError?.({
189
+ response: new Response(null, { status: 502, statusText: "Network Error" }),
190
+ error: cause
191
+ });
192
+ return { data: null, error };
193
+ }
194
+ }
195
+
196
+ // src/auth/oauth2/reject-redirects.ts
197
+ var HTTP_REDIRECT_STATUSES = /* @__PURE__ */ new Set([301, 302, 303, 307, 308]);
198
+ function isRedirectResponse(response) {
199
+ return response.type === "opaqueredirect" || HTTP_REDIRECT_STATUSES.has(response.status);
200
+ }
201
+ function redirectRefused(endpoint) {
202
+ return new AthenaAuthError(
203
+ `The OAuth endpoint "${endpoint}" returned an HTTP redirect. Server-side OAuth fetches refuse redirects to prevent SSRF; configure the final endpoint URL.`
204
+ );
205
+ }
206
+ var NO_FOLLOW_REDIRECT = { redirect: "manual" };
207
+ async function fetchRefusingRedirects(url, options) {
208
+ let redirected = false;
209
+ const result = await athenaFetch(url, {
210
+ ...options,
211
+ ...NO_FOLLOW_REDIRECT,
212
+ onError(context) {
213
+ if (isRedirectResponse(context.response)) redirected = true;
214
+ }
215
+ });
216
+ if (redirected) throw redirectRefused(url);
217
+ return result;
218
+ }
219
+
220
+ // src/auth/oauth2/token-utils.ts
221
+ function getOAuth2Tokens(data) {
222
+ const getDate = (seconds) => {
223
+ const now = /* @__PURE__ */ new Date();
224
+ return new Date(now.getTime() + seconds * 1e3);
225
+ };
226
+ return {
227
+ tokenType: data.token_type,
228
+ accessToken: data.access_token,
229
+ refreshToken: data.refresh_token,
230
+ accessTokenExpiresAt: data.expires_in ? getDate(data.expires_in) : void 0,
231
+ refreshTokenExpiresAt: data.refresh_token_expires_in ? getDate(data.refresh_token_expires_in) : void 0,
232
+ scopes: data?.scope ? typeof data.scope === "string" ? data.scope.split(" ") : data.scope : [],
233
+ idToken: data.id_token,
234
+ raw: data
235
+ };
236
+ }
237
+
238
+ // src/auth/oauth2/client-id.ts
239
+ function getPrimaryClientId(clientId) {
240
+ const value = Array.isArray(clientId) ? clientId[0] : clientId;
241
+ return typeof value === "string" && value.length > 0 ? value : void 0;
242
+ }
243
+
244
+ // src/auth/oauth2/pkce.ts
245
+ async function generateCodeChallenge(codeVerifier) {
246
+ const encoder = new TextEncoder();
247
+ const data = encoder.encode(codeVerifier);
248
+ const hash = await crypto.subtle.digest("SHA-256", data);
249
+ return base64Url.encode(new Uint8Array(hash), {
250
+ padding: false
251
+ });
252
+ }
253
+
254
+ // src/auth/oauth2/create-authorization-url.ts
255
+ async function createAuthorizationURL({
256
+ id: _id,
257
+ options,
258
+ authorizationEndpoint: authorizationEndpoint2,
259
+ state,
260
+ codeVerifier,
261
+ scopes,
262
+ claims,
263
+ redirectURI,
264
+ duration,
265
+ prompt,
266
+ accessType,
267
+ responseType,
268
+ display,
269
+ loginHint,
270
+ hd,
271
+ responseMode,
272
+ additionalParams,
273
+ scopeJoiner
274
+ }) {
275
+ options = typeof options === "function" ? await options() : options;
276
+ const url = new URL(options.authorizationEndpoint || authorizationEndpoint2);
277
+ url.searchParams.set("response_type", responseType || "code");
278
+ const primaryClientId = Array.isArray(options.clientId) ? options.clientId[0] : options.clientId;
279
+ url.searchParams.set("client_id", primaryClientId);
280
+ url.searchParams.set("state", state);
281
+ if (scopes) {
282
+ url.searchParams.set("scope", scopes.join(scopeJoiner || " "));
283
+ }
284
+ url.searchParams.set("redirect_uri", options.redirectURI || redirectURI);
285
+ duration && url.searchParams.set("duration", duration);
286
+ display && url.searchParams.set("display", display);
287
+ loginHint && url.searchParams.set("login_hint", loginHint);
288
+ prompt && url.searchParams.set("prompt", prompt);
289
+ hd && url.searchParams.set("hd", hd);
290
+ accessType && url.searchParams.set("access_type", accessType);
291
+ responseMode && url.searchParams.set("response_mode", responseMode);
292
+ if (codeVerifier) {
293
+ const codeChallenge = await generateCodeChallenge(codeVerifier);
294
+ url.searchParams.set("code_challenge_method", "S256");
295
+ url.searchParams.set("code_challenge", codeChallenge);
296
+ }
297
+ if (claims) {
298
+ const claimsObj = claims.reduce(
299
+ (acc, claim) => {
300
+ acc[claim] = null;
301
+ return acc;
302
+ },
303
+ {}
304
+ );
305
+ url.searchParams.set(
306
+ "claims",
307
+ JSON.stringify({
308
+ id_token: { email: null, email_verified: null, ...claimsObj }
309
+ })
310
+ );
311
+ }
312
+ if (additionalParams) {
313
+ Object.entries(additionalParams).forEach(([key, value]) => {
314
+ url.searchParams.set(key, value);
315
+ });
316
+ }
317
+ return url;
318
+ }
319
+
320
+ // src/auth/oauth2/refresh-access-token.ts
321
+ function createRefreshAccessTokenRequest({
322
+ refreshToken,
323
+ options,
324
+ authentication,
325
+ extraParams,
326
+ resource
327
+ }) {
328
+ const body = new URLSearchParams();
329
+ const headers = {
330
+ "content-type": "application/x-www-form-urlencoded",
331
+ accept: "application/json"
332
+ };
333
+ body.set("grant_type", "refresh_token");
334
+ body.set("refresh_token", refreshToken);
335
+ if (authentication === "basic") {
336
+ const primaryClientId = Array.isArray(options.clientId) ? options.clientId[0] : options.clientId;
337
+ if (primaryClientId) {
338
+ headers["authorization"] = "Basic " + base64.encode(`${primaryClientId}:${options.clientSecret ?? ""}`);
339
+ } else {
340
+ headers["authorization"] = "Basic " + base64.encode(`:${options.clientSecret ?? ""}`);
341
+ }
342
+ } else {
343
+ const primaryClientId = Array.isArray(options.clientId) ? options.clientId[0] : options.clientId;
344
+ body.set("client_id", primaryClientId);
345
+ if (options.clientSecret) {
346
+ body.set("client_secret", options.clientSecret);
347
+ }
348
+ }
349
+ if (resource) {
350
+ if (typeof resource === "string") {
351
+ body.append("resource", resource);
352
+ } else {
353
+ for (const _resource of resource) {
354
+ body.append("resource", _resource);
355
+ }
356
+ }
357
+ }
358
+ if (extraParams) {
359
+ for (const [key, value] of Object.entries(extraParams)) {
360
+ body.set(key, value);
361
+ }
362
+ }
363
+ return {
364
+ body,
365
+ headers
366
+ };
367
+ }
368
+ async function refreshAccessToken({
369
+ refreshToken,
370
+ options,
371
+ tokenEndpoint: tokenEndpoint2,
372
+ authentication,
373
+ extraParams
374
+ }) {
375
+ const { body, headers } = await createRefreshAccessTokenRequest({
376
+ refreshToken,
377
+ options,
378
+ authentication,
379
+ extraParams
380
+ });
381
+ const { data, error } = await fetchRefusingRedirects(tokenEndpoint2, {
382
+ method: "POST",
383
+ body,
384
+ headers
385
+ });
386
+ if (error) {
387
+ throw error;
388
+ }
389
+ const tokens = {
390
+ accessToken: data.access_token,
391
+ refreshToken: data.refresh_token,
392
+ tokenType: data.token_type,
393
+ scopes: data.scope?.split(" "),
394
+ idToken: data.id_token
395
+ };
396
+ if (data.expires_in) {
397
+ const now = /* @__PURE__ */ new Date();
398
+ tokens.accessTokenExpiresAt = new Date(
399
+ now.getTime() + data.expires_in * 1e3
400
+ );
401
+ }
402
+ if (data.refresh_token_expires_in) {
403
+ const now = /* @__PURE__ */ new Date();
404
+ tokens.refreshTokenExpiresAt = new Date(
405
+ now.getTime() + data.refresh_token_expires_in * 1e3
406
+ );
407
+ }
408
+ return tokens;
409
+ }
410
+ async function authorizationCodeRequest({
411
+ code,
412
+ codeVerifier,
413
+ redirectURI,
414
+ options,
415
+ authentication,
416
+ deviceId,
417
+ headers,
418
+ additionalParams = {},
419
+ resource
420
+ }) {
421
+ options = typeof options === "function" ? await options() : options;
422
+ return createAuthorizationCodeRequest({
423
+ code,
424
+ codeVerifier,
425
+ redirectURI,
426
+ options,
427
+ authentication,
428
+ deviceId,
429
+ headers,
430
+ additionalParams,
431
+ resource
432
+ });
433
+ }
434
+ function createAuthorizationCodeRequest({
435
+ code,
436
+ codeVerifier,
437
+ redirectURI,
438
+ options,
439
+ authentication,
440
+ deviceId,
441
+ headers,
442
+ additionalParams = {},
443
+ resource
444
+ }) {
445
+ const body = new URLSearchParams();
446
+ const requestHeaders = {
447
+ "content-type": "application/x-www-form-urlencoded",
448
+ accept: "application/json",
449
+ ...headers
450
+ };
451
+ body.set("grant_type", "authorization_code");
452
+ body.set("code", code);
453
+ codeVerifier && body.set("code_verifier", codeVerifier);
454
+ options.clientKey && body.set("client_key", options.clientKey);
455
+ deviceId && body.set("device_id", deviceId);
456
+ body.set("redirect_uri", options.redirectURI || redirectURI);
457
+ if (resource) {
458
+ if (typeof resource === "string") {
459
+ body.append("resource", resource);
460
+ } else {
461
+ for (const _resource of resource) {
462
+ body.append("resource", _resource);
463
+ }
464
+ }
465
+ }
466
+ if (authentication === "basic") {
467
+ const primaryClientId = Array.isArray(options.clientId) ? options.clientId[0] : options.clientId;
468
+ const encodedCredentials = base64.encode(
469
+ `${primaryClientId}:${options.clientSecret ?? ""}`
470
+ );
471
+ requestHeaders["authorization"] = `Basic ${encodedCredentials}`;
472
+ } else {
473
+ const primaryClientId = Array.isArray(options.clientId) ? options.clientId[0] : options.clientId;
474
+ body.set("client_id", primaryClientId);
475
+ if (options.clientSecret) {
476
+ body.set("client_secret", options.clientSecret);
477
+ }
478
+ }
479
+ for (const [key, value] of Object.entries(additionalParams)) {
480
+ if (!body.has(key)) body.append(key, value);
481
+ }
482
+ return {
483
+ body,
484
+ headers: requestHeaders
485
+ };
486
+ }
487
+ async function validateAuthorizationCode({
488
+ code,
489
+ codeVerifier,
490
+ redirectURI,
491
+ options,
492
+ tokenEndpoint: tokenEndpoint2,
493
+ authentication,
494
+ deviceId,
495
+ headers,
496
+ additionalParams = {},
497
+ resource
498
+ }) {
499
+ const { body, headers: requestHeaders } = await authorizationCodeRequest({
500
+ code,
501
+ codeVerifier,
502
+ redirectURI,
503
+ options,
504
+ authentication,
505
+ deviceId,
506
+ headers,
507
+ additionalParams,
508
+ resource
509
+ });
510
+ const { data, error } = await fetchRefusingRedirects(tokenEndpoint2, {
511
+ method: "POST",
512
+ body,
513
+ headers: requestHeaders
514
+ });
515
+ if (error) {
516
+ throw error;
517
+ }
518
+ const tokens = getOAuth2Tokens(data);
519
+ return tokens;
520
+ }
521
+
522
+ // src/auth/social-providers/apple-crypto.ts
523
+ async function sha256Hex(value) {
524
+ const data = new TextEncoder().encode(value);
525
+ const digest = await crypto.subtle.digest("SHA-256", data);
526
+ return Array.from(new Uint8Array(digest)).map((byte) => byte.toString(16).padStart(2, "0")).join("");
527
+ }
528
+ async function nonceMatches(jwtNonce, nonce) {
529
+ if (typeof jwtNonce !== "string") {
530
+ return false;
531
+ }
532
+ if (jwtNonce === nonce) {
533
+ return true;
534
+ }
535
+ return jwtNonce === await sha256Hex(nonce);
536
+ }
537
+ async function getJwksPublicKey(jwksUrl, kid) {
538
+ const { data } = await athenaFetch(jwksUrl);
539
+ if (!data?.keys) {
540
+ throw new APIError("BAD_REQUEST", {
541
+ message: "Keys not found"
542
+ });
543
+ }
544
+ const jwk = data.keys.find((key) => key.kid === kid);
545
+ if (!jwk) {
546
+ throw new Error(`JWK with kid ${kid} not found`);
547
+ }
548
+ return await importJWK(jwk, jwk.alg);
549
+ }
550
+
551
+ // src/auth/social-providers/apple-keys.ts
552
+ var getApplePublicKey = async (kid) => {
553
+ return getJwksPublicKey("https://appleid.apple.com/auth/keys", kid);
554
+ };
555
+
556
+ // src/auth/social-providers/apple.ts
557
+ var apple = (options) => {
558
+ const tokenEndpoint2 = "https://appleid.apple.com/auth/token";
559
+ return {
560
+ id: "apple",
561
+ name: "Apple",
562
+ async createAuthorizationURL({ state, scopes, redirectURI }) {
563
+ if (!getPrimaryClientId(options.clientId) || !options.clientSecret) {
564
+ logger.error(
565
+ "Client ID and client secret are required for Apple. Make sure to provide them in the options."
566
+ );
567
+ throw new AthenaAuthError("CLIENT_ID_AND_SECRET_REQUIRED");
568
+ }
569
+ const _scope = options.disableDefaultScope ? [] : ["email", "name"];
570
+ if (options.scope) _scope.push(...options.scope);
571
+ if (scopes) _scope.push(...scopes);
572
+ const url = await createAuthorizationURL({
573
+ id: "apple",
574
+ options,
575
+ authorizationEndpoint: "https://appleid.apple.com/auth/authorize",
576
+ scopes: _scope,
577
+ state,
578
+ redirectURI,
579
+ responseMode: "form_post",
580
+ responseType: "code id_token"
581
+ });
582
+ return url;
583
+ },
584
+ validateAuthorizationCode: async ({ code, codeVerifier, redirectURI }) => {
585
+ return validateAuthorizationCode({
586
+ code,
587
+ codeVerifier,
588
+ redirectURI,
589
+ options,
590
+ tokenEndpoint: tokenEndpoint2
591
+ });
592
+ },
593
+ async verifyIdToken(token, nonce) {
594
+ if (options.disableIdTokenSignIn) {
595
+ return false;
596
+ }
597
+ if (options.verifyIdToken) {
598
+ return options.verifyIdToken(token, nonce);
599
+ }
600
+ try {
601
+ const decodedHeader = decodeProtectedHeader(token);
602
+ const { kid, alg: jwtAlg } = decodedHeader;
603
+ if (!kid || !jwtAlg) return false;
604
+ const publicKey = await getApplePublicKey(kid);
605
+ const { payload: jwtClaims } = await jwtVerify(token, publicKey, {
606
+ algorithms: [jwtAlg],
607
+ issuer: "https://appleid.apple.com",
608
+ audience: options.audience && options.audience.length ? options.audience : options.appBundleIdentifier ? options.appBundleIdentifier : options.clientId,
609
+ maxTokenAge: "1h"
610
+ });
611
+ ["email_verified", "is_private_email"].forEach((field) => {
612
+ if (jwtClaims[field] !== void 0) {
613
+ jwtClaims[field] = Boolean(jwtClaims[field]);
614
+ }
615
+ });
616
+ if (nonce && !await nonceMatches(jwtClaims.nonce, nonce)) {
617
+ return false;
618
+ }
619
+ return !!jwtClaims;
620
+ } catch {
621
+ return false;
622
+ }
623
+ },
624
+ refreshAccessToken: options.refreshAccessToken ? options.refreshAccessToken : async (refreshToken) => {
625
+ return refreshAccessToken({
626
+ refreshToken,
627
+ options,
628
+ tokenEndpoint: tokenEndpoint2
629
+ });
630
+ },
631
+ async getUserInfo(token) {
632
+ if (options.getUserInfo) {
633
+ return options.getUserInfo(token);
634
+ }
635
+ if (!token.idToken) {
636
+ return null;
637
+ }
638
+ const profile = decodeJwt(token.idToken);
639
+ if (!profile) {
640
+ return null;
641
+ }
642
+ let name;
643
+ if (token.user?.name) {
644
+ const firstName = token.user.name.firstName || "";
645
+ const lastName = token.user.name.lastName || "";
646
+ const fullName = `${firstName} ${lastName}`.trim();
647
+ name = fullName;
648
+ } else {
649
+ name = profile.name || "";
650
+ }
651
+ const emailVerified = typeof profile.email_verified === "boolean" ? profile.email_verified : profile.email_verified === "true";
652
+ const enrichedProfile = {
653
+ ...profile,
654
+ name
655
+ };
656
+ const userMap = await options.mapProfileToUser?.(enrichedProfile);
657
+ return {
658
+ user: {
659
+ id: profile.sub,
660
+ name: enrichedProfile.name,
661
+ emailVerified,
662
+ email: profile.email,
663
+ ...userMap
664
+ },
665
+ data: enrichedProfile
666
+ };
667
+ },
668
+ options
669
+ };
670
+ };
671
+
672
+ // src/auth/social-providers/atlassian.ts
673
+ var atlassian = (options) => {
674
+ const tokenEndpoint2 = "https://auth.atlassian.com/oauth/token";
675
+ return {
676
+ id: "atlassian",
677
+ name: "Atlassian",
678
+ async createAuthorizationURL({ state, scopes, codeVerifier, redirectURI }) {
679
+ if (!options.clientId || !options.clientSecret) {
680
+ logger.error("Client Id and Secret are required for Atlassian");
681
+ throw new AthenaAuthError("CLIENT_ID_AND_SECRET_REQUIRED");
682
+ }
683
+ if (!codeVerifier) {
684
+ throw new AthenaAuthError("codeVerifier is required for Atlassian");
685
+ }
686
+ const _scopes = options.disableDefaultScope ? [] : ["read:jira-user", "offline_access"];
687
+ if (options.scope) _scopes.push(...options.scope);
688
+ if (scopes) _scopes.push(...scopes);
689
+ return createAuthorizationURL({
690
+ id: "atlassian",
691
+ options,
692
+ authorizationEndpoint: "https://auth.atlassian.com/authorize",
693
+ scopes: _scopes,
694
+ state,
695
+ codeVerifier,
696
+ redirectURI,
697
+ additionalParams: {
698
+ audience: "api.atlassian.com"
699
+ },
700
+ prompt: options.prompt
701
+ });
702
+ },
703
+ validateAuthorizationCode: async ({ code, codeVerifier, redirectURI }) => {
704
+ return validateAuthorizationCode({
705
+ code,
706
+ codeVerifier,
707
+ redirectURI,
708
+ options,
709
+ tokenEndpoint: tokenEndpoint2
710
+ });
711
+ },
712
+ refreshAccessToken: options.refreshAccessToken ? options.refreshAccessToken : async (refreshToken) => {
713
+ return refreshAccessToken({
714
+ refreshToken,
715
+ options: {
716
+ clientId: options.clientId,
717
+ clientSecret: options.clientSecret
718
+ },
719
+ tokenEndpoint: tokenEndpoint2
720
+ });
721
+ },
722
+ async getUserInfo(token) {
723
+ if (options.getUserInfo) {
724
+ return options.getUserInfo(token);
725
+ }
726
+ if (!token.accessToken) {
727
+ return null;
728
+ }
729
+ try {
730
+ const { data: profile } = await athenaFetch("https://api.atlassian.com/me", {
731
+ headers: { Authorization: `Bearer ${token.accessToken}` }
732
+ });
733
+ if (!profile) return null;
734
+ const userMap = await options.mapProfileToUser?.(profile);
735
+ return {
736
+ user: {
737
+ id: profile.account_id,
738
+ name: profile.name,
739
+ email: profile.email,
740
+ image: profile.picture,
741
+ emailVerified: false,
742
+ ...userMap
743
+ },
744
+ data: profile
745
+ };
746
+ } catch (error) {
747
+ logger.error("Failed to fetch user info from Figma:", error);
748
+ return null;
749
+ }
750
+ },
751
+ options
752
+ };
753
+ };
754
+
755
+ // src/auth/social-providers/helpers/trim-trailing-slash.ts
756
+ function trimTrailingSlash(value) {
757
+ let result = value;
758
+ while (result.endsWith("/")) {
759
+ result = result.slice(0, -1);
760
+ }
761
+ return result;
762
+ }
763
+
764
+ // src/auth/social-providers/athena.ts
765
+ function resolveIssuer(options) {
766
+ if (options.issuer) return trimTrailingSlash(options.issuer);
767
+ if (options.authorizationEndpoint) {
768
+ try {
769
+ const url = new URL(options.authorizationEndpoint);
770
+ return `${url.protocol}//${url.host}`;
771
+ } catch {
772
+ }
773
+ }
774
+ throw new Error(
775
+ "Athena social provider requires `issuer` (or a full `authorizationEndpoint`) in options."
776
+ );
777
+ }
778
+ var athena = (options) => {
779
+ const issuer = () => resolveIssuer(options);
780
+ const authorizationEndpoint2 = () => options.authorizationEndpoint || `${issuer()}/oauth2/authorize`;
781
+ const tokenEndpoint2 = () => options.tokenEndpoint || `${issuer()}/oauth2/token`;
782
+ const userInfoEndpoint = () => options.userInfoEndpoint || `${issuer()}/oauth2/userinfo`;
783
+ return {
784
+ id: "athena",
785
+ name: "Athena",
786
+ createAuthorizationURL({
787
+ state,
788
+ scopes,
789
+ codeVerifier,
790
+ redirectURI,
791
+ loginHint,
792
+ display
793
+ }) {
794
+ const _scopes = options.disableDefaultScope ? [] : ["openid", "profile", "email"];
795
+ if (options.scope) _scopes.push(...options.scope);
796
+ if (scopes) _scopes.push(...scopes);
797
+ return createAuthorizationURL({
798
+ id: "athena",
799
+ options,
800
+ authorizationEndpoint: authorizationEndpoint2(),
801
+ scopes: _scopes,
802
+ state,
803
+ codeVerifier,
804
+ redirectURI,
805
+ loginHint,
806
+ display,
807
+ prompt: options.prompt
808
+ });
809
+ },
810
+ validateAuthorizationCode: async ({
811
+ code,
812
+ codeVerifier,
813
+ redirectURI
814
+ }) => {
815
+ return validateAuthorizationCode({
816
+ code,
817
+ codeVerifier,
818
+ redirectURI,
819
+ options,
820
+ tokenEndpoint: tokenEndpoint2()
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: tokenEndpoint2()
832
+ });
833
+ },
834
+ async getUserInfo(token) {
835
+ if (options.getUserInfo) {
836
+ return options.getUserInfo(token);
837
+ }
838
+ if (!token.accessToken) {
839
+ return null;
840
+ }
841
+ const { data: profile, error } = await athenaFetch(
842
+ userInfoEndpoint(),
843
+ {
844
+ headers: {
845
+ Authorization: `Bearer ${token.accessToken}`,
846
+ "User-Agent": "athena-auth",
847
+ Accept: "application/json"
848
+ }
849
+ }
850
+ );
851
+ if (error || !profile) {
852
+ logger.error("Athena OAuth userinfo failed:", error);
853
+ return null;
854
+ }
855
+ const userMap = await options.mapProfileToUser?.(profile);
856
+ return {
857
+ user: {
858
+ id: profile.sub,
859
+ name: profile.name,
860
+ email: profile.email,
861
+ image: profile.picture,
862
+ emailVerified: profile.email_verified ?? false,
863
+ ...userMap
864
+ },
865
+ data: profile
866
+ };
867
+ },
868
+ options
869
+ };
870
+ };
871
+
872
+ // src/auth/social-providers/cognito-keys.ts
873
+ var getCognitoPublicKey = async (kid, region, userPoolId) => {
874
+ const jwksUri = `https://cognito-idp.${region}.amazonaws.com/${userPoolId}/.well-known/jwks.json`;
875
+ try {
876
+ return await getJwksPublicKey(jwksUri, kid);
877
+ } catch (error) {
878
+ logger.error("Failed to fetch Cognito public key:", error);
879
+ throw error;
880
+ }
881
+ };
882
+
883
+ // src/auth/social-providers/cognito.ts
884
+ var cognito = (options) => {
885
+ if (!options.domain || !options.region || !options.userPoolId) {
886
+ logger.error(
887
+ "Domain, region and userPoolId are required for Amazon Cognito. Make sure to provide them in the options."
888
+ );
889
+ throw new AthenaAuthError("DOMAIN_AND_REGION_REQUIRED");
890
+ }
891
+ const cleanDomain = options.domain.replace(/^https?:\/\//, "");
892
+ const authorizationEndpoint2 = `https://${cleanDomain}/oauth2/authorize`;
893
+ const tokenEndpoint2 = `https://${cleanDomain}/oauth2/token`;
894
+ const userInfoEndpoint = `https://${cleanDomain}/oauth2/userinfo`;
895
+ return {
896
+ id: "cognito",
897
+ name: "Cognito",
898
+ async createAuthorizationURL({ state, scopes, codeVerifier, redirectURI }) {
899
+ if (!getPrimaryClientId(options.clientId)) {
900
+ logger.error(
901
+ "ClientId is required for Amazon Cognito. Make sure to provide them in the options."
902
+ );
903
+ throw new AthenaAuthError("CLIENT_ID_AND_SECRET_REQUIRED");
904
+ }
905
+ if (options.requireClientSecret && !options.clientSecret) {
906
+ logger.error(
907
+ "Client Secret is required when requireClientSecret is true. Make sure to provide it in the options."
908
+ );
909
+ throw new AthenaAuthError("CLIENT_SECRET_REQUIRED");
910
+ }
911
+ const _scopes = options.disableDefaultScope ? [] : ["openid", "profile", "email"];
912
+ if (options.scope) _scopes.push(...options.scope);
913
+ if (scopes) _scopes.push(...scopes);
914
+ const url = await createAuthorizationURL({
915
+ id: "cognito",
916
+ options: {
917
+ ...options
918
+ },
919
+ authorizationEndpoint: authorizationEndpoint2,
920
+ scopes: _scopes,
921
+ state,
922
+ codeVerifier,
923
+ redirectURI,
924
+ prompt: options.prompt
925
+ });
926
+ const scopeValue = url.searchParams.get("scope");
927
+ if (scopeValue) {
928
+ url.searchParams.delete("scope");
929
+ const encodedScope = encodeURIComponent(scopeValue);
930
+ const urlString = url.toString();
931
+ const separator = urlString.includes("?") ? "&" : "?";
932
+ return new URL(`${urlString}${separator}scope=${encodedScope}`);
933
+ }
934
+ return url;
935
+ },
936
+ validateAuthorizationCode: async ({ code, codeVerifier, redirectURI }) => {
937
+ return validateAuthorizationCode({
938
+ code,
939
+ codeVerifier,
940
+ redirectURI,
941
+ options,
942
+ tokenEndpoint: tokenEndpoint2
943
+ });
944
+ },
945
+ refreshAccessToken: options.refreshAccessToken ? options.refreshAccessToken : async (refreshToken) => {
946
+ return refreshAccessToken({
947
+ refreshToken,
948
+ options: {
949
+ clientId: options.clientId,
950
+ clientKey: options.clientKey,
951
+ clientSecret: options.clientSecret
952
+ },
953
+ tokenEndpoint: tokenEndpoint2
954
+ });
955
+ },
956
+ async verifyIdToken(token, nonce) {
957
+ if (options.disableIdTokenSignIn) {
958
+ return false;
959
+ }
960
+ if (options.verifyIdToken) {
961
+ return options.verifyIdToken(token, nonce);
962
+ }
963
+ try {
964
+ const decodedHeader = decodeProtectedHeader(token);
965
+ const { kid, alg: jwtAlg } = decodedHeader;
966
+ if (!kid || !jwtAlg) return false;
967
+ const publicKey = await getCognitoPublicKey(
968
+ kid,
969
+ options.region,
970
+ options.userPoolId
971
+ );
972
+ const expectedIssuer = `https://cognito-idp.${options.region}.amazonaws.com/${options.userPoolId}`;
973
+ const { payload: jwtClaims } = await jwtVerify(token, publicKey, {
974
+ algorithms: [jwtAlg],
975
+ issuer: expectedIssuer,
976
+ audience: options.clientId,
977
+ maxTokenAge: "1h"
978
+ });
979
+ if (nonce && jwtClaims.nonce !== nonce) {
980
+ return false;
981
+ }
982
+ return true;
983
+ } catch (error) {
984
+ logger.error("Failed to verify ID token:", error);
985
+ return false;
986
+ }
987
+ },
988
+ async getUserInfo(token) {
989
+ if (options.getUserInfo) {
990
+ return options.getUserInfo(token);
991
+ }
992
+ if (token.idToken) {
993
+ try {
994
+ const profile = decodeJwt(token.idToken);
995
+ if (!profile) {
996
+ return null;
997
+ }
998
+ const name = profile.name || profile.given_name || profile.username || "";
999
+ const enrichedProfile = {
1000
+ ...profile,
1001
+ name
1002
+ };
1003
+ const userMap = await options.mapProfileToUser?.(enrichedProfile);
1004
+ return {
1005
+ user: {
1006
+ id: profile.sub,
1007
+ name: enrichedProfile.name,
1008
+ email: profile.email,
1009
+ image: profile.picture,
1010
+ emailVerified: profile.email_verified,
1011
+ ...userMap
1012
+ },
1013
+ data: enrichedProfile
1014
+ };
1015
+ } catch (error) {
1016
+ logger.error("Failed to decode ID token:", error);
1017
+ }
1018
+ }
1019
+ if (token.accessToken) {
1020
+ try {
1021
+ const { data: userInfo } = await athenaFetch(
1022
+ userInfoEndpoint,
1023
+ {
1024
+ headers: {
1025
+ Authorization: `Bearer ${token.accessToken}`
1026
+ }
1027
+ }
1028
+ );
1029
+ if (userInfo) {
1030
+ const userMap = await options.mapProfileToUser?.(userInfo);
1031
+ return {
1032
+ user: {
1033
+ id: userInfo.sub,
1034
+ name: userInfo.name || userInfo.given_name || userInfo.username || "",
1035
+ email: userInfo.email,
1036
+ image: userInfo.picture,
1037
+ emailVerified: userInfo.email_verified,
1038
+ ...userMap
1039
+ },
1040
+ data: userInfo
1041
+ };
1042
+ }
1043
+ } catch (error) {
1044
+ logger.error("Failed to fetch user info from Cognito:", error);
1045
+ }
1046
+ }
1047
+ return null;
1048
+ },
1049
+ options
1050
+ };
1051
+ };
1052
+
1053
+ // src/auth/social-providers/discord.ts
1054
+ var discord = (options) => {
1055
+ const tokenEndpoint2 = "https://discord.com/api/oauth2/token";
1056
+ return {
1057
+ id: "discord",
1058
+ name: "Discord",
1059
+ createAuthorizationURL({ state, scopes, redirectURI }) {
1060
+ const _scopes = options.disableDefaultScope ? [] : ["identify", "email"];
1061
+ if (scopes) _scopes.push(...scopes);
1062
+ if (options.scope) _scopes.push(...options.scope);
1063
+ const hasBotScope = _scopes.includes("bot");
1064
+ const permissionsParam = hasBotScope && options.permissions !== void 0 ? `&permissions=${options.permissions}` : "";
1065
+ return new URL(
1066
+ `https://discord.com/api/oauth2/authorize?scope=${_scopes.join(
1067
+ "+"
1068
+ )}&response_type=code&client_id=${options.clientId}&redirect_uri=${encodeURIComponent(
1069
+ options.redirectURI || redirectURI
1070
+ )}&state=${state}&prompt=${options.prompt || "none"}${permissionsParam}`
1071
+ );
1072
+ },
1073
+ validateAuthorizationCode: async ({ code, redirectURI }) => {
1074
+ return validateAuthorizationCode({
1075
+ code,
1076
+ redirectURI,
1077
+ options,
1078
+ tokenEndpoint: tokenEndpoint2
1079
+ });
1080
+ },
1081
+ refreshAccessToken: options.refreshAccessToken ? options.refreshAccessToken : async (refreshToken) => {
1082
+ return refreshAccessToken({
1083
+ refreshToken,
1084
+ options: {
1085
+ clientId: options.clientId,
1086
+ clientKey: options.clientKey,
1087
+ clientSecret: options.clientSecret
1088
+ },
1089
+ tokenEndpoint: tokenEndpoint2
1090
+ });
1091
+ },
1092
+ async getUserInfo(token) {
1093
+ if (options.getUserInfo) {
1094
+ return options.getUserInfo(token);
1095
+ }
1096
+ const { data: profile, error } = await athenaFetch(
1097
+ "https://discord.com/api/users/@me",
1098
+ {
1099
+ headers: {
1100
+ authorization: `Bearer ${token.accessToken}`
1101
+ }
1102
+ }
1103
+ );
1104
+ if (error) {
1105
+ return null;
1106
+ }
1107
+ if (profile.avatar === null) {
1108
+ const defaultAvatarNumber = profile.discriminator === "0" ? Number(BigInt(profile.id) >> BigInt(22)) % 6 : parseInt(profile.discriminator) % 5;
1109
+ profile.image_url = `https://cdn.discordapp.com/embed/avatars/${defaultAvatarNumber}.png`;
1110
+ } else {
1111
+ const format = profile.avatar.startsWith("a_") ? "gif" : "png";
1112
+ profile.image_url = `https://cdn.discordapp.com/avatars/${profile.id}/${profile.avatar}.${format}`;
1113
+ }
1114
+ const userMap = await options.mapProfileToUser?.(profile);
1115
+ return {
1116
+ user: {
1117
+ id: profile.id,
1118
+ name: profile.global_name || profile.username || "",
1119
+ email: profile.email,
1120
+ emailVerified: profile.verified,
1121
+ image: profile.image_url,
1122
+ ...userMap
1123
+ },
1124
+ data: profile
1125
+ };
1126
+ },
1127
+ options
1128
+ };
1129
+ };
1130
+
1131
+ // src/auth/social-providers/dropbox.ts
1132
+ var dropbox = (options) => {
1133
+ const tokenEndpoint2 = "https://api.dropboxapi.com/oauth2/token";
1134
+ return {
1135
+ id: "dropbox",
1136
+ name: "Dropbox",
1137
+ createAuthorizationURL: async ({
1138
+ state,
1139
+ scopes,
1140
+ codeVerifier,
1141
+ redirectURI
1142
+ }) => {
1143
+ const _scopes = options.disableDefaultScope ? [] : ["account_info.read"];
1144
+ if (options.scope) _scopes.push(...options.scope);
1145
+ if (scopes) _scopes.push(...scopes);
1146
+ const additionalParams = {};
1147
+ if (options.accessType) {
1148
+ additionalParams.token_access_type = options.accessType;
1149
+ }
1150
+ return await createAuthorizationURL({
1151
+ id: "dropbox",
1152
+ options,
1153
+ authorizationEndpoint: "https://www.dropbox.com/oauth2/authorize",
1154
+ scopes: _scopes,
1155
+ state,
1156
+ redirectURI,
1157
+ codeVerifier,
1158
+ additionalParams
1159
+ });
1160
+ },
1161
+ validateAuthorizationCode: async ({ code, codeVerifier, redirectURI }) => {
1162
+ return await validateAuthorizationCode({
1163
+ code,
1164
+ codeVerifier,
1165
+ redirectURI,
1166
+ options,
1167
+ tokenEndpoint: tokenEndpoint2
1168
+ });
1169
+ },
1170
+ refreshAccessToken: options.refreshAccessToken ? options.refreshAccessToken : async (refreshToken) => {
1171
+ return refreshAccessToken({
1172
+ refreshToken,
1173
+ options: {
1174
+ clientId: options.clientId,
1175
+ clientKey: options.clientKey,
1176
+ clientSecret: options.clientSecret
1177
+ },
1178
+ tokenEndpoint: tokenEndpoint2
1179
+ });
1180
+ },
1181
+ async getUserInfo(token) {
1182
+ if (options.getUserInfo) {
1183
+ return options.getUserInfo(token);
1184
+ }
1185
+ const { data: profile, error } = await athenaFetch(
1186
+ "https://api.dropboxapi.com/2/users/get_current_account",
1187
+ {
1188
+ method: "POST",
1189
+ headers: {
1190
+ Authorization: `Bearer ${token.accessToken}`
1191
+ }
1192
+ }
1193
+ );
1194
+ if (error) {
1195
+ return null;
1196
+ }
1197
+ const userMap = await options.mapProfileToUser?.(profile);
1198
+ return {
1199
+ user: {
1200
+ id: profile.account_id,
1201
+ name: profile.name?.display_name,
1202
+ email: profile.email,
1203
+ emailVerified: profile.email_verified || false,
1204
+ image: profile.profile_photo_url,
1205
+ ...userMap
1206
+ },
1207
+ data: profile
1208
+ };
1209
+ },
1210
+ options
1211
+ };
1212
+ };
1213
+
1214
+ // src/auth/social-providers/facebook-verify.ts
1215
+ async function verifyFacebookAccessToken(accessToken, options) {
1216
+ const primaryClientId = getPrimaryClientId(options.clientId);
1217
+ if (!primaryClientId || !options.clientSecret) {
1218
+ return null;
1219
+ }
1220
+ const clientIds = Array.isArray(options.clientId) ? options.clientId : [options.clientId];
1221
+ const appAccessToken = `${primaryClientId}|${options.clientSecret}`;
1222
+ const { data, error } = await athenaFetch(
1223
+ "https://graph.facebook.com/debug_token",
1224
+ {
1225
+ query: {
1226
+ input_token: accessToken,
1227
+ access_token: appAccessToken
1228
+ }
1229
+ }
1230
+ );
1231
+ if (error || !data?.data) {
1232
+ return null;
1233
+ }
1234
+ const { is_valid, app_id, user_id } = data.data;
1235
+ if (is_valid !== true || !app_id || !clientIds.includes(app_id) || !user_id) {
1236
+ return null;
1237
+ }
1238
+ return user_id;
1239
+ }
1240
+
1241
+ // src/auth/social-providers/facebook.ts
1242
+ var facebook = (options) => {
1243
+ return {
1244
+ id: "facebook",
1245
+ name: "Facebook",
1246
+ async createAuthorizationURL({ state, scopes, redirectURI, loginHint }) {
1247
+ if (!getPrimaryClientId(options.clientId) || !options.clientSecret) {
1248
+ logger.error(
1249
+ "Client ID and client secret are required for Facebook. Make sure to provide them in the options."
1250
+ );
1251
+ throw new AthenaAuthError("CLIENT_ID_AND_SECRET_REQUIRED");
1252
+ }
1253
+ const _scopes = options.disableDefaultScope ? [] : ["email", "public_profile"];
1254
+ if (options.scope) _scopes.push(...options.scope);
1255
+ if (scopes) _scopes.push(...scopes);
1256
+ return await createAuthorizationURL({
1257
+ id: "facebook",
1258
+ options,
1259
+ authorizationEndpoint: "https://www.facebook.com/v24.0/dialog/oauth",
1260
+ scopes: _scopes,
1261
+ state,
1262
+ redirectURI,
1263
+ loginHint,
1264
+ additionalParams: options.configId ? {
1265
+ config_id: options.configId
1266
+ } : {}
1267
+ });
1268
+ },
1269
+ validateAuthorizationCode: async ({ code, redirectURI }) => {
1270
+ return validateAuthorizationCode({
1271
+ code,
1272
+ redirectURI,
1273
+ options,
1274
+ tokenEndpoint: "https://graph.facebook.com/v24.0/oauth/access_token"
1275
+ });
1276
+ },
1277
+ async verifyIdToken(token, nonce) {
1278
+ if (options.disableIdTokenSignIn) {
1279
+ return false;
1280
+ }
1281
+ if (options.verifyIdToken) {
1282
+ return options.verifyIdToken(token, nonce);
1283
+ }
1284
+ if (token.split(".").length === 3) {
1285
+ try {
1286
+ const { payload: jwtClaims } = await jwtVerify(
1287
+ token,
1288
+ createRemoteJWKSet(
1289
+ // https://developers.facebook.com/docs/facebook-login/limited-login/token/#jwks
1290
+ new URL(
1291
+ "https://limited.facebook.com/.well-known/oauth/openid/jwks/"
1292
+ )
1293
+ ),
1294
+ {
1295
+ algorithms: ["RS256"],
1296
+ audience: options.clientId,
1297
+ issuer: "https://www.facebook.com"
1298
+ }
1299
+ );
1300
+ if (nonce && jwtClaims.nonce !== nonce) {
1301
+ return false;
1302
+ }
1303
+ return !!jwtClaims;
1304
+ } catch {
1305
+ return false;
1306
+ }
1307
+ }
1308
+ return await verifyFacebookAccessToken(token, options) !== null;
1309
+ },
1310
+ refreshAccessToken: options.refreshAccessToken ? options.refreshAccessToken : async (refreshToken) => {
1311
+ return refreshAccessToken({
1312
+ refreshToken,
1313
+ options: {
1314
+ clientId: options.clientId,
1315
+ clientKey: options.clientKey,
1316
+ clientSecret: options.clientSecret
1317
+ },
1318
+ tokenEndpoint: "https://graph.facebook.com/v24.0/oauth/access_token"
1319
+ });
1320
+ },
1321
+ async getUserInfo(token) {
1322
+ if (options.getUserInfo) {
1323
+ return options.getUserInfo(token);
1324
+ }
1325
+ if (token.idToken && token.idToken.split(".").length === 3) {
1326
+ const profile2 = decodeJwt(token.idToken);
1327
+ const user = {
1328
+ id: profile2.sub,
1329
+ name: profile2.name,
1330
+ email: profile2.email,
1331
+ picture: {
1332
+ data: {
1333
+ url: profile2.picture,
1334
+ height: 100,
1335
+ width: 100,
1336
+ is_silhouette: false
1337
+ }
1338
+ }
1339
+ };
1340
+ const userMap2 = await options.mapProfileToUser?.({
1341
+ ...user,
1342
+ email_verified: false
1343
+ });
1344
+ return {
1345
+ user: {
1346
+ ...user,
1347
+ emailVerified: false,
1348
+ ...userMap2
1349
+ },
1350
+ data: profile2
1351
+ };
1352
+ }
1353
+ const accessToken = token.accessToken;
1354
+ if (!accessToken) {
1355
+ return null;
1356
+ }
1357
+ const tokenUserId = await verifyFacebookAccessToken(accessToken, options);
1358
+ if (!tokenUserId) {
1359
+ return null;
1360
+ }
1361
+ const fields = [
1362
+ "id",
1363
+ "name",
1364
+ "email",
1365
+ "picture",
1366
+ ...options?.fields || []
1367
+ ];
1368
+ const { data: profile, error } = await athenaFetch(
1369
+ "https://graph.facebook.com/me?fields=" + fields.join(","),
1370
+ {
1371
+ auth: {
1372
+ type: "Bearer",
1373
+ token: accessToken
1374
+ }
1375
+ }
1376
+ );
1377
+ if (error) {
1378
+ return null;
1379
+ }
1380
+ if (profile.id !== tokenUserId) {
1381
+ return null;
1382
+ }
1383
+ const userMap = await options.mapProfileToUser?.(profile);
1384
+ return {
1385
+ user: {
1386
+ id: profile.id,
1387
+ name: profile.name,
1388
+ email: profile.email,
1389
+ image: profile.picture.data.url,
1390
+ emailVerified: profile.email_verified ?? false,
1391
+ ...userMap
1392
+ },
1393
+ data: profile
1394
+ };
1395
+ },
1396
+ options
1397
+ };
1398
+ };
1399
+
1400
+ // src/auth/social-providers/figma.ts
1401
+ var figma = (options) => {
1402
+ const tokenEndpoint2 = "https://api.figma.com/v1/oauth/token";
1403
+ return {
1404
+ id: "figma",
1405
+ name: "Figma",
1406
+ async createAuthorizationURL({ state, scopes, codeVerifier, redirectURI }) {
1407
+ if (!options.clientId || !options.clientSecret) {
1408
+ logger.error(
1409
+ "Client Id and Client Secret are required for Figma. Make sure to provide them in the options."
1410
+ );
1411
+ throw new AthenaAuthError("CLIENT_ID_AND_SECRET_REQUIRED");
1412
+ }
1413
+ if (!codeVerifier) {
1414
+ throw new AthenaAuthError("codeVerifier is required for Figma");
1415
+ }
1416
+ const _scopes = options.disableDefaultScope ? [] : ["current_user:read"];
1417
+ if (options.scope) _scopes.push(...options.scope);
1418
+ if (scopes) _scopes.push(...scopes);
1419
+ const url = await createAuthorizationURL({
1420
+ id: "figma",
1421
+ options,
1422
+ authorizationEndpoint: "https://www.figma.com/oauth",
1423
+ scopes: _scopes,
1424
+ state,
1425
+ codeVerifier,
1426
+ redirectURI
1427
+ });
1428
+ return url;
1429
+ },
1430
+ validateAuthorizationCode: async ({ code, codeVerifier, redirectURI }) => {
1431
+ return validateAuthorizationCode({
1432
+ code,
1433
+ codeVerifier,
1434
+ redirectURI,
1435
+ options,
1436
+ tokenEndpoint: tokenEndpoint2,
1437
+ authentication: "basic"
1438
+ });
1439
+ },
1440
+ refreshAccessToken: options.refreshAccessToken ? options.refreshAccessToken : async (refreshToken) => {
1441
+ return refreshAccessToken({
1442
+ refreshToken,
1443
+ options: {
1444
+ clientId: options.clientId,
1445
+ clientKey: options.clientKey,
1446
+ clientSecret: options.clientSecret
1447
+ },
1448
+ tokenEndpoint: tokenEndpoint2,
1449
+ authentication: "basic"
1450
+ });
1451
+ },
1452
+ async getUserInfo(token) {
1453
+ if (options.getUserInfo) {
1454
+ return options.getUserInfo(token);
1455
+ }
1456
+ try {
1457
+ const { data: profile } = await athenaFetch(
1458
+ "https://api.figma.com/v1/me",
1459
+ {
1460
+ headers: {
1461
+ Authorization: `Bearer ${token.accessToken}`
1462
+ }
1463
+ }
1464
+ );
1465
+ if (!profile) {
1466
+ logger.error("Failed to fetch user from Figma");
1467
+ return null;
1468
+ }
1469
+ const userMap = await options.mapProfileToUser?.(profile);
1470
+ return {
1471
+ user: {
1472
+ id: profile.id,
1473
+ name: profile.handle,
1474
+ email: profile.email,
1475
+ image: profile.img_url,
1476
+ emailVerified: false,
1477
+ ...userMap
1478
+ },
1479
+ data: profile
1480
+ };
1481
+ } catch (error) {
1482
+ logger.error("Failed to fetch user info from Figma:", error);
1483
+ return null;
1484
+ }
1485
+ },
1486
+ options
1487
+ };
1488
+ };
1489
+
1490
+ // src/auth/social-providers/github.ts
1491
+ var github = (options) => {
1492
+ const tokenEndpoint2 = "https://github.com/login/oauth/access_token";
1493
+ return {
1494
+ id: "github",
1495
+ name: "GitHub",
1496
+ createAuthorizationURL({
1497
+ state,
1498
+ scopes,
1499
+ loginHint,
1500
+ codeVerifier,
1501
+ redirectURI
1502
+ }) {
1503
+ const _scopes = options.disableDefaultScope ? [] : ["read:user", "user:email"];
1504
+ if (options.scope) _scopes.push(...options.scope);
1505
+ if (scopes) _scopes.push(...scopes);
1506
+ return createAuthorizationURL({
1507
+ id: "github",
1508
+ options,
1509
+ authorizationEndpoint: "https://github.com/login/oauth/authorize",
1510
+ scopes: _scopes,
1511
+ state,
1512
+ codeVerifier,
1513
+ redirectURI,
1514
+ loginHint,
1515
+ prompt: options.prompt
1516
+ });
1517
+ },
1518
+ validateAuthorizationCode: async ({ code, codeVerifier, redirectURI }) => {
1519
+ const { body, headers: requestHeaders } = createAuthorizationCodeRequest({
1520
+ code,
1521
+ codeVerifier,
1522
+ redirectURI,
1523
+ options
1524
+ });
1525
+ const { data, error } = await athenaFetch(tokenEndpoint2, {
1526
+ method: "POST",
1527
+ body,
1528
+ headers: requestHeaders
1529
+ });
1530
+ if (error) {
1531
+ logger.error("GitHub OAuth token exchange failed:", error);
1532
+ return null;
1533
+ }
1534
+ if ("error" in data) {
1535
+ logger.error("GitHub OAuth token exchange failed:", data);
1536
+ return null;
1537
+ }
1538
+ return getOAuth2Tokens(data);
1539
+ },
1540
+ refreshAccessToken: options.refreshAccessToken ? options.refreshAccessToken : async (refreshToken) => {
1541
+ return refreshAccessToken({
1542
+ refreshToken,
1543
+ options: {
1544
+ clientId: options.clientId,
1545
+ clientKey: options.clientKey,
1546
+ clientSecret: options.clientSecret
1547
+ },
1548
+ tokenEndpoint: tokenEndpoint2
1549
+ });
1550
+ },
1551
+ async getUserInfo(token) {
1552
+ if (options.getUserInfo) {
1553
+ return options.getUserInfo(token);
1554
+ }
1555
+ const { data: profile, error } = await athenaFetch(
1556
+ "https://api.github.com/user",
1557
+ {
1558
+ headers: {
1559
+ "User-Agent": "athena-auth",
1560
+ authorization: `Bearer ${token.accessToken}`
1561
+ }
1562
+ }
1563
+ );
1564
+ if (error) {
1565
+ return null;
1566
+ }
1567
+ const { data: emails } = await athenaFetch("https://api.github.com/user/emails", {
1568
+ headers: {
1569
+ Authorization: `Bearer ${token.accessToken}`,
1570
+ "User-Agent": "athena-auth"
1571
+ }
1572
+ });
1573
+ if (!profile.email && emails) {
1574
+ profile.email = (emails.find((e) => e.primary) ?? emails[0])?.email;
1575
+ }
1576
+ const emailVerified = emails?.find((e) => e.email === profile.email)?.verified ?? false;
1577
+ const userMap = await options.mapProfileToUser?.(profile);
1578
+ return {
1579
+ user: {
1580
+ id: profile.id,
1581
+ name: profile.name || profile.login || "",
1582
+ email: profile.email,
1583
+ image: profile.avatar_url,
1584
+ emailVerified,
1585
+ ...userMap
1586
+ },
1587
+ data: profile
1588
+ };
1589
+ },
1590
+ options
1591
+ };
1592
+ };
1593
+
1594
+ // src/auth/social-providers/gitlab.ts
1595
+ var cleanDoubleSlashes = (input = "") => {
1596
+ return input.split("://").map((str) => str.replace(/\/{2,}/g, "/")).join("://");
1597
+ };
1598
+ var issuerToEndpoints = (issuer) => {
1599
+ const baseUrl = issuer || "https://gitlab.com";
1600
+ return {
1601
+ authorizationEndpoint: cleanDoubleSlashes(`${baseUrl}/oauth/authorize`),
1602
+ tokenEndpoint: cleanDoubleSlashes(`${baseUrl}/oauth/token`),
1603
+ userinfoEndpoint: cleanDoubleSlashes(`${baseUrl}/api/v4/user`)
1604
+ };
1605
+ };
1606
+ var gitlab = (options) => {
1607
+ const { authorizationEndpoint: authorizationEndpoint2, tokenEndpoint: tokenEndpoint2, userinfoEndpoint: userinfoEndpoint2 } = issuerToEndpoints(options.issuer);
1608
+ const issuerId = "gitlab";
1609
+ const issuerName = "Gitlab";
1610
+ return {
1611
+ id: issuerId,
1612
+ name: issuerName,
1613
+ createAuthorizationURL: async ({
1614
+ state,
1615
+ scopes,
1616
+ codeVerifier,
1617
+ loginHint,
1618
+ redirectURI
1619
+ }) => {
1620
+ const _scopes = options.disableDefaultScope ? [] : ["read_user"];
1621
+ if (options.scope) _scopes.push(...options.scope);
1622
+ if (scopes) _scopes.push(...scopes);
1623
+ return await createAuthorizationURL({
1624
+ id: issuerId,
1625
+ options,
1626
+ authorizationEndpoint: authorizationEndpoint2,
1627
+ scopes: _scopes,
1628
+ state,
1629
+ redirectURI,
1630
+ codeVerifier,
1631
+ loginHint
1632
+ });
1633
+ },
1634
+ validateAuthorizationCode: async ({ code, redirectURI, codeVerifier }) => {
1635
+ return validateAuthorizationCode({
1636
+ code,
1637
+ redirectURI,
1638
+ options,
1639
+ codeVerifier,
1640
+ tokenEndpoint: tokenEndpoint2
1641
+ });
1642
+ },
1643
+ refreshAccessToken: options.refreshAccessToken ? options.refreshAccessToken : async (refreshToken) => {
1644
+ return refreshAccessToken({
1645
+ refreshToken,
1646
+ options: {
1647
+ clientId: options.clientId,
1648
+ clientKey: options.clientKey,
1649
+ clientSecret: options.clientSecret
1650
+ },
1651
+ tokenEndpoint: tokenEndpoint2
1652
+ });
1653
+ },
1654
+ async getUserInfo(token) {
1655
+ if (options.getUserInfo) {
1656
+ return options.getUserInfo(token);
1657
+ }
1658
+ const { data: profile, error } = await athenaFetch(
1659
+ userinfoEndpoint2,
1660
+ { headers: { authorization: `Bearer ${token.accessToken}` } }
1661
+ );
1662
+ if (error || profile.state !== "active" || profile.locked) {
1663
+ return null;
1664
+ }
1665
+ const userMap = await options.mapProfileToUser?.(profile);
1666
+ return {
1667
+ user: {
1668
+ id: profile.id,
1669
+ name: profile.name ?? profile.username ?? "",
1670
+ email: profile.email,
1671
+ image: profile.avatar_url,
1672
+ emailVerified: profile.email_verified ?? false,
1673
+ ...userMap
1674
+ },
1675
+ data: profile
1676
+ };
1677
+ },
1678
+ options
1679
+ };
1680
+ };
1681
+
1682
+ // src/auth/social-providers/google-types.ts
1683
+ var GOOGLE_ID_TOKEN_MAX_AGE = "1h";
1684
+
1685
+ // src/auth/social-providers/google-keys.ts
1686
+ var getGooglePublicKey = async (kid) => {
1687
+ return getJwksPublicKey("https://www.googleapis.com/oauth2/v3/certs", kid);
1688
+ };
1689
+
1690
+ // src/auth/social-providers/google-verify.ts
1691
+ var verifyGoogleIdToken = async ({
1692
+ token,
1693
+ audience,
1694
+ nonce
1695
+ }) => {
1696
+ try {
1697
+ const { kid, alg: jwtAlg } = decodeProtectedHeader(token);
1698
+ if (!kid || !jwtAlg) return null;
1699
+ const publicKey = await getGooglePublicKey(kid);
1700
+ const { payload: jwtClaims } = await jwtVerify(token, publicKey, {
1701
+ algorithms: [jwtAlg],
1702
+ issuer: ["https://accounts.google.com", "accounts.google.com"],
1703
+ audience,
1704
+ maxTokenAge: GOOGLE_ID_TOKEN_MAX_AGE
1705
+ });
1706
+ if (nonce && jwtClaims.nonce !== nonce) {
1707
+ return null;
1708
+ }
1709
+ return jwtClaims;
1710
+ } catch {
1711
+ return null;
1712
+ }
1713
+ };
1714
+ var isGoogleHostedDomainAllowed = (configuredHostedDomain, tokenHostedDomain) => {
1715
+ if (!configuredHostedDomain) return true;
1716
+ if (typeof tokenHostedDomain !== "string" || !tokenHostedDomain) {
1717
+ return false;
1718
+ }
1719
+ if (configuredHostedDomain === "*") return true;
1720
+ return tokenHostedDomain === configuredHostedDomain;
1721
+ };
1722
+
1723
+ // src/auth/social-providers/helpers/merge-scopes.ts
1724
+ function mergeScopes(defaults, optionScopes, requestScopes, disableDefaultScope) {
1725
+ const scopes = disableDefaultScope ? [] : [...defaults];
1726
+ if (optionScopes) scopes.push(...optionScopes);
1727
+ if (requestScopes) scopes.push(...requestScopes);
1728
+ return [...new Set(scopes)];
1729
+ }
1730
+
1731
+ // src/auth/social-providers/google.ts
1732
+ var google = (options) => {
1733
+ return {
1734
+ id: "google",
1735
+ name: "Google",
1736
+ async createAuthorizationURL({
1737
+ state,
1738
+ scopes,
1739
+ codeVerifier,
1740
+ redirectURI,
1741
+ loginHint,
1742
+ display
1743
+ }) {
1744
+ if (!getPrimaryClientId(options.clientId) || !options.clientSecret) {
1745
+ logger.error(
1746
+ "Client Id and Client Secret is required for Google. Make sure to provide them in the options."
1747
+ );
1748
+ throw new AthenaAuthError("CLIENT_ID_AND_SECRET_REQUIRED");
1749
+ }
1750
+ if (!codeVerifier) {
1751
+ throw new AthenaAuthError("codeVerifier is required for Google");
1752
+ }
1753
+ const _scopes = mergeScopes(
1754
+ ["email", "profile", "openid"],
1755
+ options.scope,
1756
+ scopes,
1757
+ options.disableDefaultScope
1758
+ );
1759
+ const url = await createAuthorizationURL({
1760
+ id: "google",
1761
+ options,
1762
+ authorizationEndpoint: "https://accounts.google.com/o/oauth2/v2/auth",
1763
+ scopes: _scopes,
1764
+ state,
1765
+ codeVerifier,
1766
+ redirectURI,
1767
+ prompt: options.prompt,
1768
+ accessType: options.accessType,
1769
+ display: display || options.display,
1770
+ loginHint,
1771
+ hd: options.hd,
1772
+ additionalParams: {
1773
+ include_granted_scopes: "true"
1774
+ }
1775
+ });
1776
+ return url;
1777
+ },
1778
+ validateAuthorizationCode: async ({ code, codeVerifier, redirectURI }) => {
1779
+ return validateAuthorizationCode({
1780
+ code,
1781
+ codeVerifier,
1782
+ redirectURI,
1783
+ options,
1784
+ tokenEndpoint: "https://oauth2.googleapis.com/token"
1785
+ });
1786
+ },
1787
+ refreshAccessToken: options.refreshAccessToken ? options.refreshAccessToken : async (refreshToken) => {
1788
+ return refreshAccessToken({
1789
+ refreshToken,
1790
+ options: {
1791
+ clientId: options.clientId,
1792
+ clientKey: options.clientKey,
1793
+ clientSecret: options.clientSecret
1794
+ },
1795
+ tokenEndpoint: "https://oauth2.googleapis.com/token"
1796
+ });
1797
+ },
1798
+ async verifyIdToken(token, nonce) {
1799
+ if (options.disableIdTokenSignIn) {
1800
+ return false;
1801
+ }
1802
+ if (options.verifyIdToken) {
1803
+ return options.verifyIdToken(token, nonce);
1804
+ }
1805
+ const jwtClaims = await verifyGoogleIdToken({
1806
+ token,
1807
+ audience: options.clientId,
1808
+ nonce
1809
+ });
1810
+ if (!jwtClaims) {
1811
+ return false;
1812
+ }
1813
+ return isGoogleHostedDomainAllowed(options.hd, jwtClaims.hd);
1814
+ },
1815
+ async getUserInfo(token) {
1816
+ if (options.getUserInfo) {
1817
+ return options.getUserInfo(token);
1818
+ }
1819
+ if (!token.idToken) {
1820
+ return null;
1821
+ }
1822
+ const user = decodeJwt(token.idToken);
1823
+ if (!isGoogleHostedDomainAllowed(options.hd, user.hd)) {
1824
+ logger.error(
1825
+ `Google sign-in rejected: id token hosted domain (hd) "${user.hd ?? "<missing>"}" does not satisfy the configured "hd" option "${options.hd}".`
1826
+ );
1827
+ return null;
1828
+ }
1829
+ const userMap = await options.mapProfileToUser?.(user);
1830
+ return {
1831
+ user: {
1832
+ id: user.sub,
1833
+ name: user.name,
1834
+ email: user.email,
1835
+ image: user.picture,
1836
+ emailVerified: user.email_verified,
1837
+ ...userMap
1838
+ },
1839
+ data: user
1840
+ };
1841
+ },
1842
+ options
1843
+ };
1844
+ };
1845
+
1846
+ // src/auth/social-providers/huggingface.ts
1847
+ var huggingface = (options) => {
1848
+ const tokenEndpoint2 = "https://huggingface.co/oauth/token";
1849
+ return {
1850
+ id: "huggingface",
1851
+ name: "Hugging Face",
1852
+ createAuthorizationURL({ state, scopes, codeVerifier, redirectURI }) {
1853
+ const _scopes = options.disableDefaultScope ? [] : ["openid", "profile", "email"];
1854
+ if (options.scope) _scopes.push(...options.scope);
1855
+ if (scopes) _scopes.push(...scopes);
1856
+ return createAuthorizationURL({
1857
+ id: "huggingface",
1858
+ options,
1859
+ authorizationEndpoint: "https://huggingface.co/oauth/authorize",
1860
+ scopes: _scopes,
1861
+ state,
1862
+ codeVerifier,
1863
+ redirectURI
1864
+ });
1865
+ },
1866
+ validateAuthorizationCode: async ({ code, codeVerifier, redirectURI }) => {
1867
+ return validateAuthorizationCode({
1868
+ code,
1869
+ codeVerifier,
1870
+ redirectURI,
1871
+ options,
1872
+ tokenEndpoint: tokenEndpoint2
1873
+ });
1874
+ },
1875
+ refreshAccessToken: options.refreshAccessToken ? options.refreshAccessToken : async (refreshToken) => {
1876
+ return refreshAccessToken({
1877
+ refreshToken,
1878
+ options: {
1879
+ clientId: options.clientId,
1880
+ clientKey: options.clientKey,
1881
+ clientSecret: options.clientSecret
1882
+ },
1883
+ tokenEndpoint: tokenEndpoint2
1884
+ });
1885
+ },
1886
+ async getUserInfo(token) {
1887
+ if (options.getUserInfo) {
1888
+ return options.getUserInfo(token);
1889
+ }
1890
+ const { data: profile, error } = await athenaFetch(
1891
+ "https://huggingface.co/oauth/userinfo",
1892
+ {
1893
+ method: "GET",
1894
+ headers: {
1895
+ Authorization: `Bearer ${token.accessToken}`
1896
+ }
1897
+ }
1898
+ );
1899
+ if (error) {
1900
+ return null;
1901
+ }
1902
+ const userMap = await options.mapProfileToUser?.(profile);
1903
+ return {
1904
+ user: {
1905
+ id: profile.sub,
1906
+ name: profile.name || profile.preferred_username || "",
1907
+ email: profile.email,
1908
+ image: profile.picture,
1909
+ emailVerified: profile.email_verified ?? false,
1910
+ ...userMap
1911
+ },
1912
+ data: profile
1913
+ };
1914
+ },
1915
+ options
1916
+ };
1917
+ };
1918
+
1919
+ // src/auth/social-providers/kakao.ts
1920
+ var kakao = (options) => {
1921
+ const tokenEndpoint2 = "https://kauth.kakao.com/oauth/token";
1922
+ return {
1923
+ id: "kakao",
1924
+ name: "Kakao",
1925
+ createAuthorizationURL({ state, scopes, redirectURI }) {
1926
+ const _scopes = options.disableDefaultScope ? [] : ["account_email", "profile_image", "profile_nickname"];
1927
+ if (options.scope) _scopes.push(...options.scope);
1928
+ if (scopes) _scopes.push(...scopes);
1929
+ return createAuthorizationURL({
1930
+ id: "kakao",
1931
+ options,
1932
+ authorizationEndpoint: "https://kauth.kakao.com/oauth/authorize",
1933
+ scopes: _scopes,
1934
+ state,
1935
+ redirectURI
1936
+ });
1937
+ },
1938
+ validateAuthorizationCode: async ({ code, redirectURI }) => {
1939
+ return validateAuthorizationCode({
1940
+ code,
1941
+ redirectURI,
1942
+ options,
1943
+ tokenEndpoint: tokenEndpoint2
1944
+ });
1945
+ },
1946
+ refreshAccessToken: options.refreshAccessToken ? options.refreshAccessToken : async (refreshToken) => {
1947
+ return refreshAccessToken({
1948
+ refreshToken,
1949
+ options: {
1950
+ clientId: options.clientId,
1951
+ clientKey: options.clientKey,
1952
+ clientSecret: options.clientSecret
1953
+ },
1954
+ tokenEndpoint: tokenEndpoint2
1955
+ });
1956
+ },
1957
+ async getUserInfo(token) {
1958
+ if (options.getUserInfo) {
1959
+ return options.getUserInfo(token);
1960
+ }
1961
+ const { data: profile, error } = await athenaFetch(
1962
+ "https://kapi.kakao.com/v2/user/me",
1963
+ {
1964
+ headers: {
1965
+ Authorization: `Bearer ${token.accessToken}`
1966
+ }
1967
+ }
1968
+ );
1969
+ if (error || !profile) {
1970
+ return null;
1971
+ }
1972
+ const userMap = await options.mapProfileToUser?.(profile);
1973
+ const account = profile.kakao_account || {};
1974
+ const kakaoProfile = account.profile || {};
1975
+ const user = {
1976
+ id: String(profile.id),
1977
+ name: kakaoProfile.nickname || account.name || "",
1978
+ email: account.email,
1979
+ image: kakaoProfile.profile_image_url || kakaoProfile.thumbnail_image_url,
1980
+ emailVerified: !!account.is_email_valid && !!account.is_email_verified,
1981
+ ...userMap
1982
+ };
1983
+ return {
1984
+ user,
1985
+ data: profile
1986
+ };
1987
+ },
1988
+ options
1989
+ };
1990
+ };
1991
+
1992
+ // src/auth/social-providers/kick.ts
1993
+ var kick = (options) => {
1994
+ return {
1995
+ id: "kick",
1996
+ name: "Kick",
1997
+ createAuthorizationURL({ state, scopes, redirectURI, codeVerifier }) {
1998
+ const _scopes = options.disableDefaultScope ? [] : ["user:read"];
1999
+ if (options.scope) _scopes.push(...options.scope);
2000
+ if (scopes) _scopes.push(...scopes);
2001
+ return createAuthorizationURL({
2002
+ id: "kick",
2003
+ redirectURI,
2004
+ options,
2005
+ authorizationEndpoint: "https://id.kick.com/oauth/authorize",
2006
+ scopes: _scopes,
2007
+ codeVerifier,
2008
+ state
2009
+ });
2010
+ },
2011
+ async validateAuthorizationCode({ code, redirectURI, codeVerifier }) {
2012
+ return validateAuthorizationCode({
2013
+ code,
2014
+ redirectURI,
2015
+ options,
2016
+ tokenEndpoint: "https://id.kick.com/oauth/token",
2017
+ codeVerifier
2018
+ });
2019
+ },
2020
+ refreshAccessToken: options.refreshAccessToken ? options.refreshAccessToken : async (refreshToken) => {
2021
+ return refreshAccessToken({
2022
+ refreshToken,
2023
+ options: {
2024
+ clientId: options.clientId,
2025
+ clientSecret: options.clientSecret
2026
+ },
2027
+ tokenEndpoint: "https://id.kick.com/oauth/token"
2028
+ });
2029
+ },
2030
+ async getUserInfo(token) {
2031
+ if (options.getUserInfo) {
2032
+ return options.getUserInfo(token);
2033
+ }
2034
+ const { data, error } = await athenaFetch("https://api.kick.com/public/v1/users", {
2035
+ method: "GET",
2036
+ headers: {
2037
+ Authorization: `Bearer ${token.accessToken}`
2038
+ }
2039
+ });
2040
+ if (error) {
2041
+ return null;
2042
+ }
2043
+ const profile = data.data[0];
2044
+ const userMap = await options.mapProfileToUser?.(profile);
2045
+ return {
2046
+ user: {
2047
+ id: profile.user_id,
2048
+ name: profile.name,
2049
+ email: profile.email,
2050
+ image: profile.profile_picture,
2051
+ emailVerified: false,
2052
+ ...userMap
2053
+ },
2054
+ data: profile
2055
+ };
2056
+ },
2057
+ options
2058
+ };
2059
+ };
2060
+ var line = (options) => {
2061
+ const authorizationEndpoint2 = "https://access.line.me/oauth2/v2.1/authorize";
2062
+ const tokenEndpoint2 = "https://api.line.me/oauth2/v2.1/token";
2063
+ const userInfoEndpoint = "https://api.line.me/oauth2/v2.1/userinfo";
2064
+ const verifyIdTokenEndpoint = "https://api.line.me/oauth2/v2.1/verify";
2065
+ return {
2066
+ id: "line",
2067
+ name: "LINE",
2068
+ async createAuthorizationURL({
2069
+ state,
2070
+ scopes,
2071
+ codeVerifier,
2072
+ redirectURI,
2073
+ loginHint
2074
+ }) {
2075
+ const _scopes = options.disableDefaultScope ? [] : ["openid", "profile", "email"];
2076
+ if (options.scope) _scopes.push(...options.scope);
2077
+ if (scopes) _scopes.push(...scopes);
2078
+ return await createAuthorizationURL({
2079
+ id: "line",
2080
+ options,
2081
+ authorizationEndpoint: authorizationEndpoint2,
2082
+ scopes: _scopes,
2083
+ state,
2084
+ codeVerifier,
2085
+ redirectURI,
2086
+ loginHint
2087
+ });
2088
+ },
2089
+ validateAuthorizationCode: async ({ code, codeVerifier, redirectURI }) => {
2090
+ return validateAuthorizationCode({
2091
+ code,
2092
+ codeVerifier,
2093
+ redirectURI,
2094
+ options,
2095
+ tokenEndpoint: tokenEndpoint2
2096
+ });
2097
+ },
2098
+ refreshAccessToken: options.refreshAccessToken ? options.refreshAccessToken : async (refreshToken) => {
2099
+ return refreshAccessToken({
2100
+ refreshToken,
2101
+ options: {
2102
+ clientId: options.clientId,
2103
+ clientSecret: options.clientSecret
2104
+ },
2105
+ tokenEndpoint: tokenEndpoint2
2106
+ });
2107
+ },
2108
+ async verifyIdToken(token, nonce) {
2109
+ if (options.disableIdTokenSignIn) {
2110
+ return false;
2111
+ }
2112
+ if (options.verifyIdToken) {
2113
+ return options.verifyIdToken(token, nonce);
2114
+ }
2115
+ const body = new URLSearchParams();
2116
+ body.set("id_token", token);
2117
+ body.set("client_id", options.clientId);
2118
+ if (nonce) body.set("nonce", nonce);
2119
+ const { data, error } = await athenaFetch(
2120
+ verifyIdTokenEndpoint,
2121
+ {
2122
+ method: "POST",
2123
+ headers: {
2124
+ "content-type": "application/x-www-form-urlencoded"
2125
+ },
2126
+ body
2127
+ }
2128
+ );
2129
+ if (error || !data) {
2130
+ return false;
2131
+ }
2132
+ if (data.aud !== options.clientId) return false;
2133
+ if (data.nonce && data.nonce !== nonce) return false;
2134
+ return true;
2135
+ },
2136
+ async getUserInfo(token) {
2137
+ if (options.getUserInfo) {
2138
+ return options.getUserInfo(token);
2139
+ }
2140
+ let profile = null;
2141
+ if (token.idToken) {
2142
+ try {
2143
+ profile = decodeJwt(token.idToken);
2144
+ } catch {
2145
+ }
2146
+ }
2147
+ if (!profile) {
2148
+ const { data } = await athenaFetch(userInfoEndpoint, {
2149
+ headers: {
2150
+ authorization: `Bearer ${token.accessToken}`
2151
+ }
2152
+ });
2153
+ profile = data || null;
2154
+ }
2155
+ if (!profile) return null;
2156
+ const userMap = await options.mapProfileToUser?.(profile);
2157
+ const id = profile.sub || profile.userId;
2158
+ const name = profile.name || profile.displayName || "";
2159
+ const image = profile.picture || profile.pictureUrl || void 0;
2160
+ const email = profile.email;
2161
+ return {
2162
+ user: {
2163
+ id,
2164
+ name,
2165
+ email,
2166
+ image,
2167
+ // LINE does not expose email verification status in ID token/userinfo
2168
+ emailVerified: false,
2169
+ ...userMap
2170
+ },
2171
+ data: profile
2172
+ };
2173
+ },
2174
+ options
2175
+ };
2176
+ };
2177
+
2178
+ // src/auth/social-providers/linear.ts
2179
+ var linear = (options) => {
2180
+ const tokenEndpoint2 = "https://api.linear.app/oauth/token";
2181
+ return {
2182
+ id: "linear",
2183
+ name: "Linear",
2184
+ createAuthorizationURL({ state, scopes, loginHint, redirectURI }) {
2185
+ const _scopes = options.disableDefaultScope ? [] : ["read"];
2186
+ if (options.scope) _scopes.push(...options.scope);
2187
+ if (scopes) _scopes.push(...scopes);
2188
+ return createAuthorizationURL({
2189
+ id: "linear",
2190
+ options,
2191
+ authorizationEndpoint: "https://linear.app/oauth/authorize",
2192
+ scopes: _scopes,
2193
+ state,
2194
+ redirectURI,
2195
+ loginHint
2196
+ });
2197
+ },
2198
+ validateAuthorizationCode: async ({ code, redirectURI }) => {
2199
+ return validateAuthorizationCode({
2200
+ code,
2201
+ redirectURI,
2202
+ options,
2203
+ tokenEndpoint: tokenEndpoint2
2204
+ });
2205
+ },
2206
+ refreshAccessToken: options.refreshAccessToken ? options.refreshAccessToken : async (refreshToken) => {
2207
+ return refreshAccessToken({
2208
+ refreshToken,
2209
+ options: {
2210
+ clientId: options.clientId,
2211
+ clientKey: options.clientKey,
2212
+ clientSecret: options.clientSecret
2213
+ },
2214
+ tokenEndpoint: tokenEndpoint2
2215
+ });
2216
+ },
2217
+ async getUserInfo(token) {
2218
+ if (options.getUserInfo) {
2219
+ return options.getUserInfo(token);
2220
+ }
2221
+ const { data: profile, error } = await athenaFetch(
2222
+ "https://api.linear.app/graphql",
2223
+ {
2224
+ method: "POST",
2225
+ headers: {
2226
+ "Content-Type": "application/json",
2227
+ Authorization: `Bearer ${token.accessToken}`
2228
+ },
2229
+ body: JSON.stringify({
2230
+ query: `
2231
+ query {
2232
+ viewer {
2233
+ id
2234
+ name
2235
+ email
2236
+ avatarUrl
2237
+ active
2238
+ createdAt
2239
+ updatedAt
2240
+ }
2241
+ }
2242
+ `
2243
+ })
2244
+ }
2245
+ );
2246
+ if (error || !profile?.data?.viewer) {
2247
+ return null;
2248
+ }
2249
+ const userData = profile.data.viewer;
2250
+ const userMap = await options.mapProfileToUser?.(userData);
2251
+ return {
2252
+ user: {
2253
+ id: profile.data.viewer.id,
2254
+ name: profile.data.viewer.name,
2255
+ email: profile.data.viewer.email,
2256
+ image: profile.data.viewer.avatarUrl,
2257
+ emailVerified: false,
2258
+ ...userMap
2259
+ },
2260
+ data: userData
2261
+ };
2262
+ },
2263
+ options
2264
+ };
2265
+ };
2266
+
2267
+ // src/auth/social-providers/linkedin.ts
2268
+ var linkedin = (options) => {
2269
+ const authorizationEndpoint2 = "https://www.linkedin.com/oauth/v2/authorization";
2270
+ const tokenEndpoint2 = "https://www.linkedin.com/oauth/v2/accessToken";
2271
+ return {
2272
+ id: "linkedin",
2273
+ name: "Linkedin",
2274
+ createAuthorizationURL: async ({
2275
+ state,
2276
+ scopes,
2277
+ redirectURI,
2278
+ loginHint
2279
+ }) => {
2280
+ const _scopes = options.disableDefaultScope ? [] : ["profile", "email", "openid"];
2281
+ if (options.scope) _scopes.push(...options.scope);
2282
+ if (scopes) _scopes.push(...scopes);
2283
+ return await createAuthorizationURL({
2284
+ id: "linkedin",
2285
+ options,
2286
+ authorizationEndpoint: authorizationEndpoint2,
2287
+ scopes: _scopes,
2288
+ state,
2289
+ loginHint,
2290
+ redirectURI
2291
+ });
2292
+ },
2293
+ validateAuthorizationCode: async ({ code, redirectURI }) => {
2294
+ return await validateAuthorizationCode({
2295
+ code,
2296
+ redirectURI,
2297
+ options,
2298
+ tokenEndpoint: tokenEndpoint2
2299
+ });
2300
+ },
2301
+ refreshAccessToken: options.refreshAccessToken ? options.refreshAccessToken : async (refreshToken) => {
2302
+ return refreshAccessToken({
2303
+ refreshToken,
2304
+ options: {
2305
+ clientId: options.clientId,
2306
+ clientKey: options.clientKey,
2307
+ clientSecret: options.clientSecret
2308
+ },
2309
+ tokenEndpoint: tokenEndpoint2
2310
+ });
2311
+ },
2312
+ async getUserInfo(token) {
2313
+ if (options.getUserInfo) {
2314
+ return options.getUserInfo(token);
2315
+ }
2316
+ const { data: profile, error } = await athenaFetch(
2317
+ "https://api.linkedin.com/v2/userinfo",
2318
+ {
2319
+ method: "GET",
2320
+ headers: {
2321
+ Authorization: `Bearer ${token.accessToken}`
2322
+ }
2323
+ }
2324
+ );
2325
+ if (error) {
2326
+ return null;
2327
+ }
2328
+ const userMap = await options.mapProfileToUser?.(profile);
2329
+ return {
2330
+ user: {
2331
+ id: profile.sub,
2332
+ name: profile.name,
2333
+ email: profile.email,
2334
+ emailVerified: profile.email_verified ?? false,
2335
+ image: profile.picture,
2336
+ ...userMap
2337
+ },
2338
+ data: profile
2339
+ };
2340
+ },
2341
+ options
2342
+ };
2343
+ };
2344
+
2345
+ // src/auth/social-providers/microsoft-keys.ts
2346
+ var getMicrosoftPublicKey = async (kid, tenant, authority) => {
2347
+ return getJwksPublicKey(
2348
+ `${authority}/${tenant}/discovery/v2.0/keys`,
2349
+ kid
2350
+ );
2351
+ };
2352
+
2353
+ // src/auth/social-providers/microsoft-types.ts
2354
+ var MICROSOFT_CONSUMER_TENANT_ID = "9188040d-6c67-4c5b-b112-36a304b66dad";
2355
+
2356
+ // src/auth/social-providers/microsoft-entra-id.ts
2357
+ var microsoft = (options) => {
2358
+ const tenant = options.tenantId || "common";
2359
+ const authority = trimTrailingSlash(
2360
+ options.authority || "https://login.microsoftonline.com"
2361
+ );
2362
+ const authorizationEndpoint2 = `${authority}/${tenant}/oauth2/v2.0/authorize`;
2363
+ const tokenEndpoint2 = `${authority}/${tenant}/oauth2/v2.0/token`;
2364
+ return {
2365
+ id: "microsoft",
2366
+ name: "Microsoft EntraID",
2367
+ createAuthorizationURL(data) {
2368
+ if (!getPrimaryClientId(options.clientId)) {
2369
+ logger.error(
2370
+ "Client Id is required for Microsoft Entra ID. Make sure to provide it in the options."
2371
+ );
2372
+ throw new AthenaAuthError("CLIENT_ID_AND_SECRET_REQUIRED");
2373
+ }
2374
+ const scopes = options.disableDefaultScope ? [] : ["openid", "profile", "email", "User.Read", "offline_access"];
2375
+ if (options.scope) scopes.push(...options.scope);
2376
+ if (data.scopes) scopes.push(...data.scopes);
2377
+ return createAuthorizationURL({
2378
+ id: "microsoft",
2379
+ options,
2380
+ authorizationEndpoint: authorizationEndpoint2,
2381
+ state: data.state,
2382
+ codeVerifier: data.codeVerifier,
2383
+ scopes,
2384
+ redirectURI: data.redirectURI,
2385
+ prompt: options.prompt,
2386
+ loginHint: data.loginHint
2387
+ });
2388
+ },
2389
+ validateAuthorizationCode({ code, codeVerifier, redirectURI }) {
2390
+ return validateAuthorizationCode({
2391
+ code,
2392
+ codeVerifier,
2393
+ redirectURI,
2394
+ options,
2395
+ tokenEndpoint: tokenEndpoint2
2396
+ });
2397
+ },
2398
+ async verifyIdToken(token, nonce) {
2399
+ if (options.disableIdTokenSignIn) {
2400
+ return false;
2401
+ }
2402
+ if (options.verifyIdToken) {
2403
+ return options.verifyIdToken(token, nonce);
2404
+ }
2405
+ try {
2406
+ const { kid, alg: jwtAlg } = decodeProtectedHeader(token);
2407
+ if (!kid || !jwtAlg) return false;
2408
+ const publicKey = await getMicrosoftPublicKey(kid, tenant, authority);
2409
+ const verifyOptions = {
2410
+ algorithms: [jwtAlg],
2411
+ audience: options.clientId,
2412
+ maxTokenAge: "1h"
2413
+ };
2414
+ if (tenant !== "common" && tenant !== "organizations" && tenant !== "consumers") {
2415
+ verifyOptions.issuer = `${authority}/${tenant}/v2.0`;
2416
+ }
2417
+ const { payload: jwtClaims } = await jwtVerify(
2418
+ token,
2419
+ publicKey,
2420
+ verifyOptions
2421
+ );
2422
+ if (nonce && jwtClaims.nonce !== nonce) {
2423
+ return false;
2424
+ }
2425
+ const tid = jwtClaims.tid;
2426
+ if (typeof tid !== "string" || jwtClaims.iss !== `${authority}/${tid}/v2.0`) {
2427
+ return false;
2428
+ }
2429
+ if (tenant === "organizations" && tid === MICROSOFT_CONSUMER_TENANT_ID) {
2430
+ return false;
2431
+ }
2432
+ if (tenant === "consumers" && tid !== MICROSOFT_CONSUMER_TENANT_ID) {
2433
+ return false;
2434
+ }
2435
+ return true;
2436
+ } catch (error) {
2437
+ logger.error("Failed to verify ID token:", error);
2438
+ return false;
2439
+ }
2440
+ },
2441
+ async getUserInfo(token) {
2442
+ if (options.getUserInfo) {
2443
+ return options.getUserInfo(token);
2444
+ }
2445
+ if (!token.idToken) {
2446
+ return null;
2447
+ }
2448
+ const user = decodeJwt(token.idToken);
2449
+ const profilePhotoSize = options.profilePhotoSize || 48;
2450
+ await athenaFetch(
2451
+ `https://graph.microsoft.com/v1.0/me/photos/${profilePhotoSize}x${profilePhotoSize}/$value`,
2452
+ {
2453
+ headers: {
2454
+ Authorization: `Bearer ${token.accessToken}`
2455
+ },
2456
+ async onResponse(context) {
2457
+ if (options.disableProfilePhoto || !context.response.ok) {
2458
+ return;
2459
+ }
2460
+ try {
2461
+ const response = context.response.clone();
2462
+ const pictureBuffer = await response.arrayBuffer();
2463
+ const pictureBase64 = base64.encode(pictureBuffer);
2464
+ user.picture = `data:image/jpeg;base64, ${pictureBase64}`;
2465
+ } catch (e) {
2466
+ logger.error(
2467
+ e && typeof e === "object" && "name" in e ? e.name : "",
2468
+ e
2469
+ );
2470
+ }
2471
+ }
2472
+ }
2473
+ );
2474
+ const userMap = await options.mapProfileToUser?.(user);
2475
+ const emailVerified = user.email_verified !== void 0 ? user.email_verified : user.email && (user.verified_primary_email?.includes(user.email) || user.verified_secondary_email?.includes(user.email)) ? true : false;
2476
+ return {
2477
+ user: {
2478
+ id: user.sub,
2479
+ name: user.name,
2480
+ email: user.email,
2481
+ image: user.picture,
2482
+ emailVerified,
2483
+ ...userMap
2484
+ },
2485
+ data: user
2486
+ };
2487
+ },
2488
+ refreshAccessToken: options.refreshAccessToken ? options.refreshAccessToken : async (refreshToken) => {
2489
+ const scopes = options.disableDefaultScope ? [] : ["openid", "profile", "email", "User.Read", "offline_access"];
2490
+ if (options.scope) scopes.push(...options.scope);
2491
+ return refreshAccessToken({
2492
+ refreshToken,
2493
+ options: {
2494
+ clientId: options.clientId,
2495
+ clientSecret: options.clientSecret
2496
+ },
2497
+ extraParams: {
2498
+ scope: scopes.join(" ")
2499
+ // Include the scopes in request to microsoft
2500
+ },
2501
+ tokenEndpoint: tokenEndpoint2
2502
+ });
2503
+ },
2504
+ options
2505
+ };
2506
+ };
2507
+
2508
+ // src/auth/social-providers/naver.ts
2509
+ var naver = (options) => {
2510
+ const tokenEndpoint2 = "https://nid.naver.com/oauth2.0/token";
2511
+ return {
2512
+ id: "naver",
2513
+ name: "Naver",
2514
+ createAuthorizationURL({ state, scopes, redirectURI }) {
2515
+ const _scopes = options.disableDefaultScope ? [] : ["profile", "email"];
2516
+ if (options.scope) _scopes.push(...options.scope);
2517
+ if (scopes) _scopes.push(...scopes);
2518
+ return createAuthorizationURL({
2519
+ id: "naver",
2520
+ options,
2521
+ authorizationEndpoint: "https://nid.naver.com/oauth2.0/authorize",
2522
+ scopes: _scopes,
2523
+ state,
2524
+ redirectURI
2525
+ });
2526
+ },
2527
+ validateAuthorizationCode: async ({ code, redirectURI }) => {
2528
+ return validateAuthorizationCode({
2529
+ code,
2530
+ redirectURI,
2531
+ options,
2532
+ tokenEndpoint: tokenEndpoint2
2533
+ });
2534
+ },
2535
+ refreshAccessToken: options.refreshAccessToken ? options.refreshAccessToken : async (refreshToken) => {
2536
+ return refreshAccessToken({
2537
+ refreshToken,
2538
+ options: {
2539
+ clientId: options.clientId,
2540
+ clientKey: options.clientKey,
2541
+ clientSecret: options.clientSecret
2542
+ },
2543
+ tokenEndpoint: tokenEndpoint2
2544
+ });
2545
+ },
2546
+ async getUserInfo(token) {
2547
+ if (options.getUserInfo) {
2548
+ return options.getUserInfo(token);
2549
+ }
2550
+ const { data: profile, error } = await athenaFetch(
2551
+ "https://openapi.naver.com/v1/nid/me",
2552
+ {
2553
+ headers: {
2554
+ Authorization: `Bearer ${token.accessToken}`
2555
+ }
2556
+ }
2557
+ );
2558
+ if (error || !profile || profile.resultcode !== "00") {
2559
+ return null;
2560
+ }
2561
+ const userMap = await options.mapProfileToUser?.(profile);
2562
+ const res = profile.response || {};
2563
+ const user = {
2564
+ id: res.id,
2565
+ name: res.name || res.nickname || "",
2566
+ email: res.email,
2567
+ image: res.profile_image,
2568
+ emailVerified: false,
2569
+ ...userMap
2570
+ };
2571
+ return {
2572
+ user,
2573
+ data: profile
2574
+ };
2575
+ },
2576
+ options
2577
+ };
2578
+ };
2579
+
2580
+ // src/auth/social-providers/notion.ts
2581
+ var notion = (options) => {
2582
+ const tokenEndpoint2 = "https://api.notion.com/v1/oauth/token";
2583
+ return {
2584
+ id: "notion",
2585
+ name: "Notion",
2586
+ createAuthorizationURL({ state, scopes, loginHint, redirectURI }) {
2587
+ const _scopes = options.disableDefaultScope ? [] : [];
2588
+ if (options.scope) _scopes.push(...options.scope);
2589
+ if (scopes) _scopes.push(...scopes);
2590
+ return createAuthorizationURL({
2591
+ id: "notion",
2592
+ options,
2593
+ authorizationEndpoint: "https://api.notion.com/v1/oauth/authorize",
2594
+ scopes: _scopes,
2595
+ state,
2596
+ redirectURI,
2597
+ loginHint,
2598
+ additionalParams: {
2599
+ owner: "user"
2600
+ }
2601
+ });
2602
+ },
2603
+ validateAuthorizationCode: async ({ code, redirectURI }) => {
2604
+ return validateAuthorizationCode({
2605
+ code,
2606
+ redirectURI,
2607
+ options,
2608
+ tokenEndpoint: tokenEndpoint2,
2609
+ authentication: "basic"
2610
+ });
2611
+ },
2612
+ refreshAccessToken: options.refreshAccessToken ? options.refreshAccessToken : async (refreshToken) => {
2613
+ return refreshAccessToken({
2614
+ refreshToken,
2615
+ options: {
2616
+ clientId: options.clientId,
2617
+ clientKey: options.clientKey,
2618
+ clientSecret: options.clientSecret
2619
+ },
2620
+ tokenEndpoint: tokenEndpoint2
2621
+ });
2622
+ },
2623
+ async getUserInfo(token) {
2624
+ if (options.getUserInfo) {
2625
+ return options.getUserInfo(token);
2626
+ }
2627
+ const { data: profile, error } = await athenaFetch("https://api.notion.com/v1/users/me", {
2628
+ headers: {
2629
+ Authorization: `Bearer ${token.accessToken}`,
2630
+ "Notion-Version": "2022-06-28"
2631
+ }
2632
+ });
2633
+ if (error || !profile) {
2634
+ return null;
2635
+ }
2636
+ const userProfile = profile.bot?.owner?.user;
2637
+ if (!userProfile) {
2638
+ return null;
2639
+ }
2640
+ const userMap = await options.mapProfileToUser?.(userProfile);
2641
+ return {
2642
+ user: {
2643
+ id: userProfile.id,
2644
+ name: userProfile.name || "",
2645
+ email: userProfile.person?.email || null,
2646
+ image: userProfile.avatar_url,
2647
+ emailVerified: false,
2648
+ ...userMap
2649
+ },
2650
+ data: userProfile
2651
+ };
2652
+ },
2653
+ options
2654
+ };
2655
+ };
2656
+ var paybin = (options) => {
2657
+ const issuer = options.issuer || "https://idp.paybin.io";
2658
+ const authorizationEndpoint2 = `${issuer}/oauth2/authorize`;
2659
+ const tokenEndpoint2 = `${issuer}/oauth2/token`;
2660
+ return {
2661
+ id: "paybin",
2662
+ name: "Paybin",
2663
+ async createAuthorizationURL({
2664
+ state,
2665
+ scopes,
2666
+ codeVerifier,
2667
+ redirectURI,
2668
+ loginHint
2669
+ }) {
2670
+ if (!options.clientId || !options.clientSecret) {
2671
+ logger.error(
2672
+ "Client Id and Client Secret is required for Paybin. Make sure to provide them in the options."
2673
+ );
2674
+ throw new AthenaAuthError("CLIENT_ID_AND_SECRET_REQUIRED");
2675
+ }
2676
+ if (!codeVerifier) {
2677
+ throw new AthenaAuthError("codeVerifier is required for Paybin");
2678
+ }
2679
+ const _scopes = options.disableDefaultScope ? [] : ["openid", "email", "profile"];
2680
+ if (options.scope) _scopes.push(...options.scope);
2681
+ if (scopes) _scopes.push(...scopes);
2682
+ const url = await createAuthorizationURL({
2683
+ id: "paybin",
2684
+ options,
2685
+ authorizationEndpoint: authorizationEndpoint2,
2686
+ scopes: _scopes,
2687
+ state,
2688
+ codeVerifier,
2689
+ redirectURI,
2690
+ prompt: options.prompt,
2691
+ loginHint
2692
+ });
2693
+ return url;
2694
+ },
2695
+ validateAuthorizationCode: async ({ code, codeVerifier, redirectURI }) => {
2696
+ return validateAuthorizationCode({
2697
+ code,
2698
+ codeVerifier,
2699
+ redirectURI,
2700
+ options,
2701
+ tokenEndpoint: tokenEndpoint2
2702
+ });
2703
+ },
2704
+ refreshAccessToken: options.refreshAccessToken ? options.refreshAccessToken : async (refreshToken) => {
2705
+ return refreshAccessToken({
2706
+ refreshToken,
2707
+ options: {
2708
+ clientId: options.clientId,
2709
+ clientKey: options.clientKey,
2710
+ clientSecret: options.clientSecret
2711
+ },
2712
+ tokenEndpoint: tokenEndpoint2
2713
+ });
2714
+ },
2715
+ async getUserInfo(token) {
2716
+ if (options.getUserInfo) {
2717
+ return options.getUserInfo(token);
2718
+ }
2719
+ if (!token.idToken) {
2720
+ return null;
2721
+ }
2722
+ const user = decodeJwt(token.idToken);
2723
+ const userMap = await options.mapProfileToUser?.(user);
2724
+ return {
2725
+ user: {
2726
+ id: user.sub,
2727
+ name: user.name || user.preferred_username || "",
2728
+ email: user.email,
2729
+ image: user.picture,
2730
+ emailVerified: user.email_verified || false,
2731
+ ...userMap
2732
+ },
2733
+ data: user
2734
+ };
2735
+ },
2736
+ options
2737
+ };
2738
+ };
2739
+
2740
+ // src/auth/social-providers/paypal-keys.ts
2741
+ var getPayPalPublicKey = async (kid, jwksUri) => {
2742
+ return getJwksPublicKey(jwksUri, kid);
2743
+ };
2744
+
2745
+ // src/auth/social-providers/paypal.ts
2746
+ var PAYPAL_ID_TOKEN_ALGORITHMS = ["RS256", "HS256"];
2747
+ var paypal = (options) => {
2748
+ const environment = options.environment || "sandbox";
2749
+ const isSandbox = environment === "sandbox";
2750
+ const authorizationEndpoint2 = isSandbox ? "https://www.sandbox.paypal.com/signin/authorize" : "https://www.paypal.com/signin/authorize";
2751
+ const tokenEndpoint2 = isSandbox ? "https://api-m.sandbox.paypal.com/v1/oauth2/token" : "https://api-m.paypal.com/v1/oauth2/token";
2752
+ const userInfoEndpoint = isSandbox ? "https://api-m.sandbox.paypal.com/v1/identity/oauth2/userinfo" : "https://api-m.paypal.com/v1/identity/oauth2/userinfo";
2753
+ const issuer = isSandbox ? "https://www.sandbox.paypal.com" : "https://www.paypal.com";
2754
+ const jwksEndpoint = isSandbox ? "https://api.sandbox.paypal.com/v1/oauth2/certs" : "https://api.paypal.com/v1/oauth2/certs";
2755
+ return {
2756
+ id: "paypal",
2757
+ name: "PayPal",
2758
+ async createAuthorizationURL({ state, codeVerifier, redirectURI }) {
2759
+ if (!options.clientId || !options.clientSecret) {
2760
+ logger.error(
2761
+ "Client Id and Client Secret is required for PayPal. Make sure to provide them in the options."
2762
+ );
2763
+ throw new AthenaAuthError("CLIENT_ID_AND_SECRET_REQUIRED");
2764
+ }
2765
+ const _scopes = [];
2766
+ const url = await createAuthorizationURL({
2767
+ id: "paypal",
2768
+ options,
2769
+ authorizationEndpoint: authorizationEndpoint2,
2770
+ scopes: _scopes,
2771
+ state,
2772
+ codeVerifier,
2773
+ redirectURI,
2774
+ prompt: options.prompt
2775
+ });
2776
+ return url;
2777
+ },
2778
+ validateAuthorizationCode: async ({ code, redirectURI }) => {
2779
+ const credentials = base64.encode(
2780
+ `${options.clientId}:${options.clientSecret}`
2781
+ );
2782
+ try {
2783
+ const response = await athenaFetch(tokenEndpoint2, {
2784
+ method: "POST",
2785
+ headers: {
2786
+ Authorization: `Basic ${credentials}`,
2787
+ Accept: "application/json",
2788
+ "Accept-Language": "en_US",
2789
+ "Content-Type": "application/x-www-form-urlencoded"
2790
+ },
2791
+ body: new URLSearchParams({
2792
+ grant_type: "authorization_code",
2793
+ code,
2794
+ redirect_uri: redirectURI
2795
+ }).toString()
2796
+ });
2797
+ if (!response.data) {
2798
+ throw new AthenaAuthError("FAILED_TO_GET_ACCESS_TOKEN");
2799
+ }
2800
+ const data = response.data;
2801
+ const result = {
2802
+ accessToken: data.access_token,
2803
+ refreshToken: data.refresh_token,
2804
+ accessTokenExpiresAt: data.expires_in ? new Date(Date.now() + data.expires_in * 1e3) : void 0,
2805
+ idToken: data.id_token
2806
+ };
2807
+ return result;
2808
+ } catch (error) {
2809
+ logger.error("PayPal token exchange failed:", error);
2810
+ throw new AthenaAuthError("FAILED_TO_GET_ACCESS_TOKEN");
2811
+ }
2812
+ },
2813
+ refreshAccessToken: options.refreshAccessToken ? options.refreshAccessToken : async (refreshToken) => {
2814
+ const credentials = base64.encode(
2815
+ `${options.clientId}:${options.clientSecret}`
2816
+ );
2817
+ try {
2818
+ const response = await athenaFetch(tokenEndpoint2, {
2819
+ method: "POST",
2820
+ headers: {
2821
+ Authorization: `Basic ${credentials}`,
2822
+ Accept: "application/json",
2823
+ "Accept-Language": "en_US",
2824
+ "Content-Type": "application/x-www-form-urlencoded"
2825
+ },
2826
+ body: new URLSearchParams({
2827
+ grant_type: "refresh_token",
2828
+ refresh_token: refreshToken
2829
+ }).toString()
2830
+ });
2831
+ if (!response.data) {
2832
+ throw new AthenaAuthError("FAILED_TO_REFRESH_ACCESS_TOKEN");
2833
+ }
2834
+ const data = response.data;
2835
+ return {
2836
+ accessToken: data.access_token,
2837
+ refreshToken: data.refresh_token,
2838
+ accessTokenExpiresAt: data.expires_in ? new Date(Date.now() + data.expires_in * 1e3) : void 0
2839
+ };
2840
+ } catch (error) {
2841
+ logger.error("PayPal token refresh failed:", error);
2842
+ throw new AthenaAuthError("FAILED_TO_REFRESH_ACCESS_TOKEN");
2843
+ }
2844
+ },
2845
+ async verifyIdToken(token, nonce) {
2846
+ if (options.disableIdTokenSignIn) {
2847
+ return false;
2848
+ }
2849
+ if (options.verifyIdToken) {
2850
+ return options.verifyIdToken(token, nonce);
2851
+ }
2852
+ try {
2853
+ const { kid, alg: jwtAlg } = decodeProtectedHeader(token);
2854
+ if (!jwtAlg) return false;
2855
+ if (!PAYPAL_ID_TOKEN_ALGORITHMS.includes(
2856
+ jwtAlg
2857
+ )) {
2858
+ return false;
2859
+ }
2860
+ const key = jwtAlg === "HS256" ? new TextEncoder().encode(options.clientSecret) : kid ? await getPayPalPublicKey(kid, jwksEndpoint) : void 0;
2861
+ if (!key) return false;
2862
+ const { payload: jwtClaims } = await jwtVerify(token, key, {
2863
+ algorithms: [jwtAlg],
2864
+ issuer,
2865
+ audience: options.clientId,
2866
+ maxTokenAge: "1h"
2867
+ });
2868
+ if (nonce && jwtClaims.nonce !== nonce) {
2869
+ return false;
2870
+ }
2871
+ return true;
2872
+ } catch (error) {
2873
+ logger.error("Failed to verify PayPal ID token:", error);
2874
+ return false;
2875
+ }
2876
+ },
2877
+ async getUserInfo(token) {
2878
+ if (options.getUserInfo) {
2879
+ return options.getUserInfo(token);
2880
+ }
2881
+ if (!token.accessToken) {
2882
+ logger.error("Access token is required to fetch PayPal user info");
2883
+ return null;
2884
+ }
2885
+ try {
2886
+ const response = await athenaFetch(
2887
+ `${userInfoEndpoint}?schema=paypalv1.1`,
2888
+ {
2889
+ headers: {
2890
+ Authorization: `Bearer ${token.accessToken}`,
2891
+ Accept: "application/json"
2892
+ }
2893
+ }
2894
+ );
2895
+ if (!response.data) {
2896
+ logger.error("Failed to fetch user info from PayPal");
2897
+ return null;
2898
+ }
2899
+ const userInfo = response.data;
2900
+ if (token.idToken) {
2901
+ let idTokenSubject;
2902
+ try {
2903
+ idTokenSubject = decodeJwt(token.idToken).sub;
2904
+ } catch (error) {
2905
+ logger.error("Failed to decode PayPal ID token:", error);
2906
+ return null;
2907
+ }
2908
+ const userInfoSubject = userInfo.sub ?? userInfo.user_id;
2909
+ if (!idTokenSubject || userInfoSubject !== idTokenSubject) {
2910
+ logger.error(
2911
+ "PayPal user info subject does not match ID token subject"
2912
+ );
2913
+ return null;
2914
+ }
2915
+ }
2916
+ const userMap = await options.mapProfileToUser?.(userInfo);
2917
+ const result = {
2918
+ user: {
2919
+ id: userInfo.user_id,
2920
+ name: userInfo.name,
2921
+ email: userInfo.email,
2922
+ image: userInfo.picture,
2923
+ emailVerified: userInfo.email_verified,
2924
+ ...userMap
2925
+ },
2926
+ data: userInfo
2927
+ };
2928
+ return result;
2929
+ } catch (error) {
2930
+ logger.error("Failed to fetch user info from PayPal:", error);
2931
+ return null;
2932
+ }
2933
+ },
2934
+ options
2935
+ };
2936
+ };
2937
+
2938
+ // src/auth/social-providers/polar.ts
2939
+ var polar = (options) => {
2940
+ const tokenEndpoint2 = "https://api.polar.sh/v1/oauth2/token";
2941
+ return {
2942
+ id: "polar",
2943
+ name: "Polar",
2944
+ createAuthorizationURL({ state, scopes, codeVerifier, redirectURI }) {
2945
+ const _scopes = options.disableDefaultScope ? [] : ["openid", "profile", "email"];
2946
+ if (options.scope) _scopes.push(...options.scope);
2947
+ if (scopes) _scopes.push(...scopes);
2948
+ return createAuthorizationURL({
2949
+ id: "polar",
2950
+ options,
2951
+ authorizationEndpoint: "https://polar.sh/oauth2/authorize",
2952
+ scopes: _scopes,
2953
+ state,
2954
+ codeVerifier,
2955
+ redirectURI,
2956
+ prompt: options.prompt
2957
+ });
2958
+ },
2959
+ validateAuthorizationCode: async ({ code, codeVerifier, redirectURI }) => {
2960
+ return validateAuthorizationCode({
2961
+ code,
2962
+ codeVerifier,
2963
+ redirectURI,
2964
+ options,
2965
+ tokenEndpoint: tokenEndpoint2
2966
+ });
2967
+ },
2968
+ refreshAccessToken: options.refreshAccessToken ? options.refreshAccessToken : async (refreshToken) => {
2969
+ return refreshAccessToken({
2970
+ refreshToken,
2971
+ options: {
2972
+ clientId: options.clientId,
2973
+ clientKey: options.clientKey,
2974
+ clientSecret: options.clientSecret
2975
+ },
2976
+ tokenEndpoint: tokenEndpoint2
2977
+ });
2978
+ },
2979
+ async getUserInfo(token) {
2980
+ if (options.getUserInfo) {
2981
+ return options.getUserInfo(token);
2982
+ }
2983
+ const { data: profile, error } = await athenaFetch(
2984
+ "https://api.polar.sh/v1/oauth2/userinfo",
2985
+ {
2986
+ headers: {
2987
+ Authorization: `Bearer ${token.accessToken}`
2988
+ }
2989
+ }
2990
+ );
2991
+ if (error) {
2992
+ return null;
2993
+ }
2994
+ const userMap = await options.mapProfileToUser?.(profile);
2995
+ return {
2996
+ user: {
2997
+ id: profile.id,
2998
+ name: profile.public_name || profile.username || "",
2999
+ email: profile.email,
3000
+ image: profile.avatar_url,
3001
+ emailVerified: profile.email_verified ?? false,
3002
+ ...userMap
3003
+ },
3004
+ data: profile
3005
+ };
3006
+ },
3007
+ options
3008
+ };
3009
+ };
3010
+
3011
+ // src/auth/social-providers/railway.ts
3012
+ var authorizationEndpoint = "https://backboard.railway.com/oauth/auth";
3013
+ var tokenEndpoint = "https://backboard.railway.com/oauth/token";
3014
+ var userinfoEndpoint = "https://backboard.railway.com/oauth/me";
3015
+ var railway = (options) => {
3016
+ return {
3017
+ id: "railway",
3018
+ name: "Railway",
3019
+ createAuthorizationURL({ state, scopes, codeVerifier, redirectURI }) {
3020
+ const _scopes = options.disableDefaultScope ? [] : ["openid", "email", "profile"];
3021
+ if (options.scope) _scopes.push(...options.scope);
3022
+ if (scopes) _scopes.push(...scopes);
3023
+ return createAuthorizationURL({
3024
+ id: "railway",
3025
+ options,
3026
+ authorizationEndpoint,
3027
+ scopes: _scopes,
3028
+ state,
3029
+ codeVerifier,
3030
+ redirectURI
3031
+ });
3032
+ },
3033
+ validateAuthorizationCode: async ({ code, codeVerifier, redirectURI }) => {
3034
+ return validateAuthorizationCode({
3035
+ code,
3036
+ codeVerifier,
3037
+ redirectURI,
3038
+ options,
3039
+ tokenEndpoint,
3040
+ authentication: "basic"
3041
+ });
3042
+ },
3043
+ refreshAccessToken: options.refreshAccessToken ? options.refreshAccessToken : async (refreshToken) => {
3044
+ return refreshAccessToken({
3045
+ refreshToken,
3046
+ options: {
3047
+ clientId: options.clientId,
3048
+ clientKey: options.clientKey,
3049
+ clientSecret: options.clientSecret
3050
+ },
3051
+ tokenEndpoint,
3052
+ authentication: "basic"
3053
+ });
3054
+ },
3055
+ async getUserInfo(token) {
3056
+ if (options.getUserInfo) {
3057
+ return options.getUserInfo(token);
3058
+ }
3059
+ const { data: profile, error } = await athenaFetch(
3060
+ userinfoEndpoint,
3061
+ { headers: { authorization: `Bearer ${token.accessToken}` } }
3062
+ );
3063
+ if (error || !profile) {
3064
+ return null;
3065
+ }
3066
+ const userMap = await options.mapProfileToUser?.(profile);
3067
+ return {
3068
+ user: {
3069
+ id: profile.sub,
3070
+ name: profile.name,
3071
+ email: profile.email,
3072
+ image: profile.picture,
3073
+ emailVerified: false,
3074
+ ...userMap
3075
+ },
3076
+ data: profile
3077
+ };
3078
+ },
3079
+ options
3080
+ };
3081
+ };
3082
+
3083
+ // src/auth/social-providers/reddit.ts
3084
+ var reddit = (options) => {
3085
+ return {
3086
+ id: "reddit",
3087
+ name: "Reddit",
3088
+ createAuthorizationURL({ state, scopes, redirectURI }) {
3089
+ const _scopes = options.disableDefaultScope ? [] : ["identity"];
3090
+ if (options.scope) _scopes.push(...options.scope);
3091
+ if (scopes) _scopes.push(...scopes);
3092
+ return createAuthorizationURL({
3093
+ id: "reddit",
3094
+ options,
3095
+ authorizationEndpoint: "https://www.reddit.com/api/v1/authorize",
3096
+ scopes: _scopes,
3097
+ state,
3098
+ redirectURI,
3099
+ duration: options.duration
3100
+ });
3101
+ },
3102
+ validateAuthorizationCode: async ({ code, redirectURI }) => {
3103
+ const body = new URLSearchParams({
3104
+ grant_type: "authorization_code",
3105
+ code,
3106
+ redirect_uri: options.redirectURI || redirectURI
3107
+ });
3108
+ const headers = {
3109
+ "content-type": "application/x-www-form-urlencoded",
3110
+ accept: "text/plain",
3111
+ "User-Agent": "athena-auth",
3112
+ Authorization: `Basic ${base64.encode(
3113
+ `${options.clientId}:${options.clientSecret}`
3114
+ )}`
3115
+ };
3116
+ const { data, error } = await athenaFetch(
3117
+ "https://www.reddit.com/api/v1/access_token",
3118
+ {
3119
+ method: "POST",
3120
+ headers,
3121
+ body: body.toString()
3122
+ }
3123
+ );
3124
+ if (error) {
3125
+ throw error;
3126
+ }
3127
+ return getOAuth2Tokens(data);
3128
+ },
3129
+ refreshAccessToken: options.refreshAccessToken ? options.refreshAccessToken : async (refreshToken) => {
3130
+ return refreshAccessToken({
3131
+ refreshToken,
3132
+ options: {
3133
+ clientId: options.clientId,
3134
+ clientKey: options.clientKey,
3135
+ clientSecret: options.clientSecret
3136
+ },
3137
+ authentication: "basic",
3138
+ tokenEndpoint: "https://www.reddit.com/api/v1/access_token"
3139
+ });
3140
+ },
3141
+ async getUserInfo(token) {
3142
+ if (options.getUserInfo) {
3143
+ return options.getUserInfo(token);
3144
+ }
3145
+ const { data: profile, error } = await athenaFetch(
3146
+ "https://oauth.reddit.com/api/v1/me",
3147
+ {
3148
+ headers: {
3149
+ Authorization: `Bearer ${token.accessToken}`,
3150
+ "User-Agent": "athena-auth"
3151
+ }
3152
+ }
3153
+ );
3154
+ if (error) {
3155
+ return null;
3156
+ }
3157
+ const userMap = await options.mapProfileToUser?.(profile);
3158
+ const email = userMap?.email || `${profile.id}@reddit.invalid`;
3159
+ return {
3160
+ user: {
3161
+ id: profile.id,
3162
+ name: profile.name,
3163
+ image: profile.icon_img?.split("?")[0],
3164
+ ...userMap,
3165
+ email,
3166
+ emailVerified: userMap?.emailVerified ?? false
3167
+ },
3168
+ data: profile
3169
+ };
3170
+ },
3171
+ options
3172
+ };
3173
+ };
3174
+
3175
+ // src/auth/social-providers/roblox.ts
3176
+ var roblox = (options) => {
3177
+ const tokenEndpoint2 = "https://apis.roblox.com/oauth/v1/token";
3178
+ return {
3179
+ id: "roblox",
3180
+ name: "Roblox",
3181
+ createAuthorizationURL({ state, scopes, redirectURI }) {
3182
+ const _scopes = options.disableDefaultScope ? [] : ["openid", "profile"];
3183
+ if (options.scope) _scopes.push(...options.scope);
3184
+ if (scopes) _scopes.push(...scopes);
3185
+ return new URL(
3186
+ `https://apis.roblox.com/oauth/v1/authorize?scope=${_scopes.join(
3187
+ "+"
3188
+ )}&response_type=code&client_id=${options.clientId}&redirect_uri=${encodeURIComponent(
3189
+ options.redirectURI || redirectURI
3190
+ )}&state=${state}&prompt=${options.prompt || "select_account consent"}`
3191
+ );
3192
+ },
3193
+ validateAuthorizationCode: async ({ code, redirectURI }) => {
3194
+ return validateAuthorizationCode({
3195
+ code,
3196
+ redirectURI: options.redirectURI || redirectURI,
3197
+ options,
3198
+ tokenEndpoint: tokenEndpoint2,
3199
+ authentication: "post"
3200
+ });
3201
+ },
3202
+ refreshAccessToken: options.refreshAccessToken ? options.refreshAccessToken : async (refreshToken) => {
3203
+ return refreshAccessToken({
3204
+ refreshToken,
3205
+ options: {
3206
+ clientId: options.clientId,
3207
+ clientKey: options.clientKey,
3208
+ clientSecret: options.clientSecret
3209
+ },
3210
+ tokenEndpoint: tokenEndpoint2
3211
+ });
3212
+ },
3213
+ async getUserInfo(token) {
3214
+ if (options.getUserInfo) {
3215
+ return options.getUserInfo(token);
3216
+ }
3217
+ const { data: profile, error } = await athenaFetch(
3218
+ "https://apis.roblox.com/oauth/v1/userinfo",
3219
+ {
3220
+ headers: {
3221
+ authorization: `Bearer ${token.accessToken}`
3222
+ }
3223
+ }
3224
+ );
3225
+ if (error) {
3226
+ return null;
3227
+ }
3228
+ const userMap = await options.mapProfileToUser?.(profile);
3229
+ return {
3230
+ user: {
3231
+ id: profile.sub,
3232
+ name: profile.nickname || profile.preferred_username || "",
3233
+ image: profile.picture,
3234
+ email: profile.preferred_username || null,
3235
+ // Roblox does not provide email
3236
+ emailVerified: false,
3237
+ ...userMap
3238
+ },
3239
+ data: {
3240
+ ...profile
3241
+ }
3242
+ };
3243
+ },
3244
+ options
3245
+ };
3246
+ };
3247
+
3248
+ // src/auth/social-providers/salesforce.ts
3249
+ var salesforce = (options) => {
3250
+ const environment = options.environment ?? "production";
3251
+ const isSandbox = environment === "sandbox";
3252
+ const authorizationEndpoint2 = options.loginUrl ? `https://${options.loginUrl}/services/oauth2/authorize` : isSandbox ? "https://test.salesforce.com/services/oauth2/authorize" : "https://login.salesforce.com/services/oauth2/authorize";
3253
+ const tokenEndpoint2 = options.loginUrl ? `https://${options.loginUrl}/services/oauth2/token` : isSandbox ? "https://test.salesforce.com/services/oauth2/token" : "https://login.salesforce.com/services/oauth2/token";
3254
+ 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";
3255
+ return {
3256
+ id: "salesforce",
3257
+ name: "Salesforce",
3258
+ async createAuthorizationURL({ state, scopes, codeVerifier, redirectURI }) {
3259
+ if (!options.clientId || !options.clientSecret) {
3260
+ logger.error(
3261
+ "Client Id and Client Secret are required for Salesforce. Make sure to provide them in the options."
3262
+ );
3263
+ throw new AthenaAuthError("CLIENT_ID_AND_SECRET_REQUIRED");
3264
+ }
3265
+ if (!codeVerifier) {
3266
+ throw new AthenaAuthError("codeVerifier is required for Salesforce");
3267
+ }
3268
+ const _scopes = options.disableDefaultScope ? [] : ["openid", "email", "profile"];
3269
+ if (options.scope) _scopes.push(...options.scope);
3270
+ if (scopes) _scopes.push(...scopes);
3271
+ return createAuthorizationURL({
3272
+ id: "salesforce",
3273
+ options,
3274
+ authorizationEndpoint: authorizationEndpoint2,
3275
+ scopes: _scopes,
3276
+ state,
3277
+ codeVerifier,
3278
+ redirectURI: options.redirectURI || redirectURI
3279
+ });
3280
+ },
3281
+ validateAuthorizationCode: async ({ code, codeVerifier, redirectURI }) => {
3282
+ return validateAuthorizationCode({
3283
+ code,
3284
+ codeVerifier,
3285
+ redirectURI: options.redirectURI || redirectURI,
3286
+ options,
3287
+ tokenEndpoint: tokenEndpoint2
3288
+ });
3289
+ },
3290
+ refreshAccessToken: options.refreshAccessToken ? options.refreshAccessToken : async (refreshToken) => {
3291
+ return refreshAccessToken({
3292
+ refreshToken,
3293
+ options: {
3294
+ clientId: options.clientId,
3295
+ clientSecret: options.clientSecret
3296
+ },
3297
+ tokenEndpoint: tokenEndpoint2
3298
+ });
3299
+ },
3300
+ async getUserInfo(token) {
3301
+ if (options.getUserInfo) {
3302
+ return options.getUserInfo(token);
3303
+ }
3304
+ try {
3305
+ const { data: user } = await athenaFetch(
3306
+ userInfoEndpoint,
3307
+ {
3308
+ headers: {
3309
+ Authorization: `Bearer ${token.accessToken}`
3310
+ }
3311
+ }
3312
+ );
3313
+ if (!user) {
3314
+ logger.error("Failed to fetch user info from Salesforce");
3315
+ return null;
3316
+ }
3317
+ const userMap = await options.mapProfileToUser?.(user);
3318
+ return {
3319
+ user: {
3320
+ id: user.user_id,
3321
+ name: user.name,
3322
+ email: user.email,
3323
+ image: user.photos?.picture || user.photos?.thumbnail,
3324
+ emailVerified: user.email_verified ?? false,
3325
+ ...userMap
3326
+ },
3327
+ data: user
3328
+ };
3329
+ } catch (error) {
3330
+ logger.error("Failed to fetch user info from Salesforce:", error);
3331
+ return null;
3332
+ }
3333
+ },
3334
+ options
3335
+ };
3336
+ };
3337
+
3338
+ // src/auth/social-providers/slack.ts
3339
+ var slack = (options) => {
3340
+ const tokenEndpoint2 = "https://slack.com/api/openid.connect.token";
3341
+ return {
3342
+ id: "slack",
3343
+ name: "Slack",
3344
+ createAuthorizationURL({ state, scopes, redirectURI }) {
3345
+ const _scopes = options.disableDefaultScope ? [] : ["openid", "profile", "email"];
3346
+ if (scopes) _scopes.push(...scopes);
3347
+ if (options.scope) _scopes.push(...options.scope);
3348
+ const url = new URL("https://slack.com/openid/connect/authorize");
3349
+ url.searchParams.set("scope", _scopes.join(" "));
3350
+ url.searchParams.set("response_type", "code");
3351
+ url.searchParams.set("client_id", options.clientId);
3352
+ url.searchParams.set("redirect_uri", options.redirectURI || redirectURI);
3353
+ url.searchParams.set("state", state);
3354
+ return url;
3355
+ },
3356
+ validateAuthorizationCode: async ({ code, redirectURI }) => {
3357
+ return validateAuthorizationCode({
3358
+ code,
3359
+ redirectURI,
3360
+ options,
3361
+ tokenEndpoint: tokenEndpoint2
3362
+ });
3363
+ },
3364
+ refreshAccessToken: options.refreshAccessToken ? options.refreshAccessToken : async (refreshToken) => {
3365
+ return refreshAccessToken({
3366
+ refreshToken,
3367
+ options: {
3368
+ clientId: options.clientId,
3369
+ clientKey: options.clientKey,
3370
+ clientSecret: options.clientSecret
3371
+ },
3372
+ tokenEndpoint: tokenEndpoint2
3373
+ });
3374
+ },
3375
+ async getUserInfo(token) {
3376
+ if (options.getUserInfo) {
3377
+ return options.getUserInfo(token);
3378
+ }
3379
+ const { data: profile, error } = await athenaFetch(
3380
+ "https://slack.com/api/openid.connect.userInfo",
3381
+ {
3382
+ headers: {
3383
+ authorization: `Bearer ${token.accessToken}`
3384
+ }
3385
+ }
3386
+ );
3387
+ if (error) {
3388
+ return null;
3389
+ }
3390
+ const userMap = await options.mapProfileToUser?.(profile);
3391
+ return {
3392
+ user: {
3393
+ id: profile["https://slack.com/user_id"],
3394
+ name: profile.name || "",
3395
+ email: profile.email,
3396
+ emailVerified: profile.email_verified,
3397
+ image: profile.picture || profile["https://slack.com/user_image_512"],
3398
+ ...userMap
3399
+ },
3400
+ data: profile
3401
+ };
3402
+ },
3403
+ options
3404
+ };
3405
+ };
3406
+
3407
+ // src/auth/social-providers/spotify.ts
3408
+ var spotify = (options) => {
3409
+ const tokenEndpoint2 = "https://accounts.spotify.com/api/token";
3410
+ return {
3411
+ id: "spotify",
3412
+ name: "Spotify",
3413
+ createAuthorizationURL({ state, scopes, codeVerifier, redirectURI }) {
3414
+ const _scopes = options.disableDefaultScope ? [] : ["user-read-email"];
3415
+ if (options.scope) _scopes.push(...options.scope);
3416
+ if (scopes) _scopes.push(...scopes);
3417
+ return createAuthorizationURL({
3418
+ id: "spotify",
3419
+ options,
3420
+ authorizationEndpoint: "https://accounts.spotify.com/authorize",
3421
+ scopes: _scopes,
3422
+ state,
3423
+ codeVerifier,
3424
+ redirectURI
3425
+ });
3426
+ },
3427
+ validateAuthorizationCode: async ({ code, codeVerifier, redirectURI }) => {
3428
+ return validateAuthorizationCode({
3429
+ code,
3430
+ codeVerifier,
3431
+ redirectURI,
3432
+ options,
3433
+ tokenEndpoint: tokenEndpoint2
3434
+ });
3435
+ },
3436
+ refreshAccessToken: options.refreshAccessToken ? options.refreshAccessToken : async (refreshToken) => {
3437
+ return refreshAccessToken({
3438
+ refreshToken,
3439
+ options: {
3440
+ clientId: options.clientId,
3441
+ clientKey: options.clientKey,
3442
+ clientSecret: options.clientSecret
3443
+ },
3444
+ tokenEndpoint: tokenEndpoint2
3445
+ });
3446
+ },
3447
+ async getUserInfo(token) {
3448
+ if (options.getUserInfo) {
3449
+ return options.getUserInfo(token);
3450
+ }
3451
+ const { data: profile, error } = await athenaFetch(
3452
+ "https://api.spotify.com/v1/me",
3453
+ {
3454
+ method: "GET",
3455
+ headers: {
3456
+ Authorization: `Bearer ${token.accessToken}`
3457
+ }
3458
+ }
3459
+ );
3460
+ if (error) {
3461
+ return null;
3462
+ }
3463
+ const userMap = await options.mapProfileToUser?.(profile);
3464
+ return {
3465
+ user: {
3466
+ id: profile.id,
3467
+ name: profile.display_name,
3468
+ email: profile.email,
3469
+ image: profile.images[0]?.url,
3470
+ emailVerified: false,
3471
+ ...userMap
3472
+ },
3473
+ data: profile
3474
+ };
3475
+ },
3476
+ options
3477
+ };
3478
+ };
3479
+
3480
+ // src/auth/social-providers/tiktok.ts
3481
+ var tiktok = (options) => {
3482
+ const tokenEndpoint2 = "https://open.tiktokapis.com/v2/oauth/token/";
3483
+ return {
3484
+ id: "tiktok",
3485
+ name: "TikTok",
3486
+ createAuthorizationURL({ state, scopes, redirectURI }) {
3487
+ const _scopes = options.disableDefaultScope ? [] : ["user.info.profile"];
3488
+ if (options.scope) _scopes.push(...options.scope);
3489
+ if (scopes) _scopes.push(...scopes);
3490
+ return new URL(
3491
+ `https://www.tiktok.com/v2/auth/authorize?scope=${_scopes.join(
3492
+ ","
3493
+ )}&response_type=code&client_key=${options.clientKey}&redirect_uri=${encodeURIComponent(
3494
+ options.redirectURI || redirectURI
3495
+ )}&state=${state}`
3496
+ );
3497
+ },
3498
+ validateAuthorizationCode: async ({ code, redirectURI }) => {
3499
+ return validateAuthorizationCode({
3500
+ code,
3501
+ redirectURI: options.redirectURI || redirectURI,
3502
+ options: {
3503
+ clientKey: options.clientKey,
3504
+ clientSecret: options.clientSecret
3505
+ },
3506
+ tokenEndpoint: tokenEndpoint2
3507
+ });
3508
+ },
3509
+ refreshAccessToken: options.refreshAccessToken ? options.refreshAccessToken : async (refreshToken) => {
3510
+ return refreshAccessToken({
3511
+ refreshToken,
3512
+ options: {
3513
+ clientSecret: options.clientSecret
3514
+ },
3515
+ tokenEndpoint: tokenEndpoint2,
3516
+ authentication: "post",
3517
+ extraParams: {
3518
+ client_key: options.clientKey
3519
+ }
3520
+ });
3521
+ },
3522
+ async getUserInfo(token) {
3523
+ if (options.getUserInfo) {
3524
+ return options.getUserInfo(token);
3525
+ }
3526
+ const fields = [
3527
+ "open_id",
3528
+ "avatar_large_url",
3529
+ "display_name",
3530
+ "username"
3531
+ ];
3532
+ const { data: profile, error } = await athenaFetch(
3533
+ `https://open.tiktokapis.com/v2/user/info/?fields=${fields.join(",")}`,
3534
+ {
3535
+ headers: {
3536
+ authorization: `Bearer ${token.accessToken}`
3537
+ }
3538
+ }
3539
+ );
3540
+ if (error) {
3541
+ return null;
3542
+ }
3543
+ return {
3544
+ user: {
3545
+ email: profile.data.user.email || profile.data.user.username,
3546
+ id: profile.data.user.open_id,
3547
+ name: profile.data.user.display_name || profile.data.user.username || "",
3548
+ image: profile.data.user.avatar_large_url,
3549
+ emailVerified: false
3550
+ },
3551
+ data: profile
3552
+ };
3553
+ },
3554
+ options
3555
+ };
3556
+ };
3557
+ var twitch = (options) => {
3558
+ const tokenEndpoint2 = "https://id.twitch.tv/oauth2/token";
3559
+ return {
3560
+ id: "twitch",
3561
+ name: "Twitch",
3562
+ createAuthorizationURL({ state, scopes, redirectURI }) {
3563
+ const _scopes = options.disableDefaultScope ? [] : ["user:read:email", "openid"];
3564
+ if (options.scope) _scopes.push(...options.scope);
3565
+ if (scopes) _scopes.push(...scopes);
3566
+ return createAuthorizationURL({
3567
+ id: "twitch",
3568
+ redirectURI,
3569
+ options,
3570
+ authorizationEndpoint: "https://id.twitch.tv/oauth2/authorize",
3571
+ scopes: _scopes,
3572
+ state,
3573
+ claims: options.claims || [
3574
+ "email",
3575
+ "email_verified",
3576
+ "preferred_username",
3577
+ "picture"
3578
+ ]
3579
+ });
3580
+ },
3581
+ validateAuthorizationCode: async ({ code, redirectURI }) => {
3582
+ return validateAuthorizationCode({
3583
+ code,
3584
+ redirectURI,
3585
+ options,
3586
+ tokenEndpoint: tokenEndpoint2
3587
+ });
3588
+ },
3589
+ refreshAccessToken: options.refreshAccessToken ? options.refreshAccessToken : async (refreshToken) => {
3590
+ return refreshAccessToken({
3591
+ refreshToken,
3592
+ options: {
3593
+ clientId: options.clientId,
3594
+ clientKey: options.clientKey,
3595
+ clientSecret: options.clientSecret
3596
+ },
3597
+ tokenEndpoint: tokenEndpoint2
3598
+ });
3599
+ },
3600
+ async getUserInfo(token) {
3601
+ if (options.getUserInfo) {
3602
+ return options.getUserInfo(token);
3603
+ }
3604
+ const idToken = token.idToken;
3605
+ if (!idToken) {
3606
+ logger.error("No idToken found in token");
3607
+ return null;
3608
+ }
3609
+ const profile = decodeJwt(idToken);
3610
+ const userMap = await options.mapProfileToUser?.(profile);
3611
+ return {
3612
+ user: {
3613
+ id: profile.sub,
3614
+ name: profile.preferred_username,
3615
+ email: profile.email,
3616
+ image: profile.picture,
3617
+ emailVerified: profile.email_verified,
3618
+ ...userMap
3619
+ },
3620
+ data: profile
3621
+ };
3622
+ },
3623
+ options
3624
+ };
3625
+ };
3626
+
3627
+ // src/auth/social-providers/twitter.ts
3628
+ var twitter = (options) => {
3629
+ const tokenEndpoint2 = "https://api.x.com/2/oauth2/token";
3630
+ return {
3631
+ id: "twitter",
3632
+ name: "Twitter",
3633
+ createAuthorizationURL(data) {
3634
+ const _scopes = options.disableDefaultScope ? [] : ["users.read", "tweet.read", "offline.access", "users.email"];
3635
+ if (options.scope) _scopes.push(...options.scope);
3636
+ if (data.scopes) _scopes.push(...data.scopes);
3637
+ return createAuthorizationURL({
3638
+ id: "twitter",
3639
+ options,
3640
+ authorizationEndpoint: "https://x.com/i/oauth2/authorize",
3641
+ scopes: _scopes,
3642
+ state: data.state,
3643
+ codeVerifier: data.codeVerifier,
3644
+ redirectURI: data.redirectURI
3645
+ });
3646
+ },
3647
+ validateAuthorizationCode: async ({ code, codeVerifier, redirectURI }) => {
3648
+ return validateAuthorizationCode({
3649
+ code,
3650
+ codeVerifier,
3651
+ authentication: "basic",
3652
+ redirectURI,
3653
+ options,
3654
+ tokenEndpoint: tokenEndpoint2
3655
+ });
3656
+ },
3657
+ refreshAccessToken: options.refreshAccessToken ? options.refreshAccessToken : async (refreshToken) => {
3658
+ return refreshAccessToken({
3659
+ refreshToken,
3660
+ options: {
3661
+ clientId: options.clientId,
3662
+ clientKey: options.clientKey,
3663
+ clientSecret: options.clientSecret
3664
+ },
3665
+ authentication: "basic",
3666
+ tokenEndpoint: tokenEndpoint2
3667
+ });
3668
+ },
3669
+ async getUserInfo(token) {
3670
+ if (options.getUserInfo) {
3671
+ return options.getUserInfo(token);
3672
+ }
3673
+ const { data: profile, error: profileError } = await athenaFetch(
3674
+ "https://api.x.com/2/users/me?user.fields=profile_image_url",
3675
+ {
3676
+ method: "GET",
3677
+ headers: {
3678
+ Authorization: `Bearer ${token.accessToken}`
3679
+ }
3680
+ }
3681
+ );
3682
+ if (profileError) {
3683
+ return null;
3684
+ }
3685
+ const { data: emailData, error: emailError } = await athenaFetch("https://api.x.com/2/users/me?user.fields=confirmed_email", {
3686
+ method: "GET",
3687
+ headers: {
3688
+ Authorization: `Bearer ${token.accessToken}`
3689
+ }
3690
+ });
3691
+ let emailVerified = false;
3692
+ if (!emailError && emailData?.data?.confirmed_email) {
3693
+ profile.data.email = emailData.data.confirmed_email;
3694
+ emailVerified = true;
3695
+ }
3696
+ const userMap = await options.mapProfileToUser?.(profile);
3697
+ return {
3698
+ user: {
3699
+ id: profile.data.id,
3700
+ name: profile.data.name,
3701
+ email: profile.data.email || profile.data.username || null,
3702
+ image: profile.data.profile_image_url,
3703
+ emailVerified,
3704
+ ...userMap
3705
+ },
3706
+ data: profile
3707
+ };
3708
+ },
3709
+ options
3710
+ };
3711
+ };
3712
+
3713
+ // src/auth/social-providers/vercel.ts
3714
+ var vercel = (options) => {
3715
+ return {
3716
+ id: "vercel",
3717
+ name: "Vercel",
3718
+ createAuthorizationURL({ state, scopes, codeVerifier, redirectURI }) {
3719
+ if (!codeVerifier) {
3720
+ throw new AthenaAuthError("codeVerifier is required for Vercel");
3721
+ }
3722
+ let _scopes = void 0;
3723
+ if (options.scope !== void 0 || scopes !== void 0) {
3724
+ _scopes = [];
3725
+ if (options.scope) _scopes.push(...options.scope);
3726
+ if (scopes) _scopes.push(...scopes);
3727
+ }
3728
+ return createAuthorizationURL({
3729
+ id: "vercel",
3730
+ options,
3731
+ authorizationEndpoint: "https://vercel.com/oauth/authorize",
3732
+ scopes: _scopes,
3733
+ state,
3734
+ codeVerifier,
3735
+ redirectURI
3736
+ });
3737
+ },
3738
+ validateAuthorizationCode: async ({ code, codeVerifier, redirectURI }) => {
3739
+ return validateAuthorizationCode({
3740
+ code,
3741
+ codeVerifier,
3742
+ redirectURI,
3743
+ options,
3744
+ tokenEndpoint: "https://api.vercel.com/login/oauth/token"
3745
+ });
3746
+ },
3747
+ async getUserInfo(token) {
3748
+ if (options.getUserInfo) {
3749
+ return options.getUserInfo(token);
3750
+ }
3751
+ const { data: profile, error } = await athenaFetch(
3752
+ "https://api.vercel.com/login/oauth/userinfo",
3753
+ {
3754
+ headers: {
3755
+ Authorization: `Bearer ${token.accessToken}`
3756
+ }
3757
+ }
3758
+ );
3759
+ if (error || !profile) {
3760
+ return null;
3761
+ }
3762
+ const userMap = await options.mapProfileToUser?.(profile);
3763
+ return {
3764
+ user: {
3765
+ id: profile.sub,
3766
+ name: profile.name ?? profile.preferred_username ?? "",
3767
+ email: profile.email,
3768
+ image: profile.picture,
3769
+ emailVerified: profile.email_verified ?? false,
3770
+ ...userMap
3771
+ },
3772
+ data: profile
3773
+ };
3774
+ },
3775
+ options
3776
+ };
3777
+ };
3778
+
3779
+ // src/auth/social-providers/vk.ts
3780
+ var vk = (options) => {
3781
+ const tokenEndpoint2 = "https://id.vk.com/oauth2/auth";
3782
+ return {
3783
+ id: "vk",
3784
+ name: "VK",
3785
+ async createAuthorizationURL({ state, scopes, codeVerifier, redirectURI }) {
3786
+ const _scopes = options.disableDefaultScope ? [] : ["email", "phone"];
3787
+ if (options.scope) _scopes.push(...options.scope);
3788
+ if (scopes) _scopes.push(...scopes);
3789
+ const authorizationEndpoint2 = "https://id.vk.com/authorize";
3790
+ return createAuthorizationURL({
3791
+ id: "vk",
3792
+ options,
3793
+ authorizationEndpoint: authorizationEndpoint2,
3794
+ scopes: _scopes,
3795
+ state,
3796
+ redirectURI,
3797
+ codeVerifier
3798
+ });
3799
+ },
3800
+ validateAuthorizationCode: async ({
3801
+ code,
3802
+ codeVerifier,
3803
+ redirectURI,
3804
+ deviceId
3805
+ }) => {
3806
+ return validateAuthorizationCode({
3807
+ code,
3808
+ codeVerifier,
3809
+ redirectURI: options.redirectURI || redirectURI,
3810
+ options,
3811
+ deviceId,
3812
+ tokenEndpoint: tokenEndpoint2
3813
+ });
3814
+ },
3815
+ refreshAccessToken: options.refreshAccessToken ? options.refreshAccessToken : async (refreshToken) => {
3816
+ return refreshAccessToken({
3817
+ refreshToken,
3818
+ options: {
3819
+ clientId: options.clientId,
3820
+ clientKey: options.clientKey,
3821
+ clientSecret: options.clientSecret
3822
+ },
3823
+ tokenEndpoint: tokenEndpoint2
3824
+ });
3825
+ },
3826
+ async getUserInfo(data) {
3827
+ if (options.getUserInfo) {
3828
+ return options.getUserInfo(data);
3829
+ }
3830
+ if (!data.accessToken) {
3831
+ return null;
3832
+ }
3833
+ const formBody = new URLSearchParams({
3834
+ access_token: data.accessToken,
3835
+ client_id: options.clientId
3836
+ }).toString();
3837
+ const { data: profile, error } = await athenaFetch(
3838
+ "https://id.vk.com/oauth2/user_info",
3839
+ {
3840
+ method: "POST",
3841
+ headers: {
3842
+ "Content-Type": "application/x-www-form-urlencoded"
3843
+ },
3844
+ body: formBody
3845
+ }
3846
+ );
3847
+ if (error) {
3848
+ return null;
3849
+ }
3850
+ const userMap = await options.mapProfileToUser?.(profile);
3851
+ if (!profile.user.email && !userMap?.email) {
3852
+ return null;
3853
+ }
3854
+ return {
3855
+ user: {
3856
+ id: profile.user.user_id,
3857
+ first_name: profile.user.first_name,
3858
+ last_name: profile.user.last_name,
3859
+ email: profile.user.email,
3860
+ image: profile.user.avatar,
3861
+ emailVerified: false,
3862
+ birthday: profile.user.birthday,
3863
+ sex: profile.user.sex,
3864
+ name: `${profile.user.first_name} ${profile.user.last_name}`,
3865
+ ...userMap
3866
+ },
3867
+ data: profile
3868
+ };
3869
+ },
3870
+ options
3871
+ };
3872
+ };
3873
+
3874
+ // src/auth/social-providers/wechat.ts
3875
+ var wechat = (options) => {
3876
+ return {
3877
+ id: "wechat",
3878
+ name: "WeChat",
3879
+ createAuthorizationURL({ state, scopes, redirectURI }) {
3880
+ const _scopes = options.disableDefaultScope ? [] : ["snsapi_login"];
3881
+ options.scope && _scopes.push(...options.scope);
3882
+ scopes && _scopes.push(...scopes);
3883
+ const url = new URL("https://open.weixin.qq.com/connect/qrconnect");
3884
+ url.searchParams.set("scope", _scopes.join(","));
3885
+ url.searchParams.set("response_type", "code");
3886
+ url.searchParams.set("appid", options.clientId);
3887
+ url.searchParams.set("redirect_uri", options.redirectURI || redirectURI);
3888
+ url.searchParams.set("state", state);
3889
+ url.searchParams.set("lang", options.lang || "cn");
3890
+ url.hash = "wechat_redirect";
3891
+ return url;
3892
+ },
3893
+ // WeChat uses non-standard token exchange (appid/secret instead of
3894
+ // client_id/client_secret, GET instead of POST), so shared helpers
3895
+ // like validateAuthorizationCode/getOAuth2Tokens cannot be used directly.
3896
+ validateAuthorizationCode: async ({ code }) => {
3897
+ const params = new URLSearchParams({
3898
+ appid: options.clientId,
3899
+ secret: options.clientSecret,
3900
+ code,
3901
+ grant_type: "authorization_code"
3902
+ });
3903
+ const { data: tokenData, error } = await athenaFetch(
3904
+ "https://api.weixin.qq.com/sns/oauth2/access_token?" + params.toString(),
3905
+ {
3906
+ method: "GET"
3907
+ }
3908
+ );
3909
+ if (error || !tokenData || tokenData.errcode) {
3910
+ throw new Error(
3911
+ `Failed to validate authorization code: ${tokenData?.errmsg || error?.message || "Unknown error"}`
3912
+ );
3913
+ }
3914
+ return {
3915
+ tokenType: "Bearer",
3916
+ accessToken: tokenData.access_token,
3917
+ refreshToken: tokenData.refresh_token,
3918
+ accessTokenExpiresAt: new Date(
3919
+ Date.now() + tokenData.expires_in * 1e3
3920
+ ),
3921
+ scopes: tokenData.scope.split(","),
3922
+ // WeChat requires openid for the userinfo endpoint, which is
3923
+ // returned alongside the access token.
3924
+ openid: tokenData.openid,
3925
+ unionid: tokenData.unionid
3926
+ };
3927
+ },
3928
+ refreshAccessToken: options.refreshAccessToken ? options.refreshAccessToken : async (refreshToken) => {
3929
+ const params = new URLSearchParams({
3930
+ appid: options.clientId,
3931
+ grant_type: "refresh_token",
3932
+ refresh_token: refreshToken
3933
+ });
3934
+ const { data: tokenData, error } = await athenaFetch(
3935
+ "https://api.weixin.qq.com/sns/oauth2/refresh_token?" + params.toString(),
3936
+ {
3937
+ method: "GET"
3938
+ }
3939
+ );
3940
+ if (error || !tokenData || tokenData.errcode) {
3941
+ throw new Error(
3942
+ `Failed to refresh access token: ${tokenData?.errmsg || error?.message || "Unknown error"}`
3943
+ );
3944
+ }
3945
+ return {
3946
+ tokenType: "Bearer",
3947
+ accessToken: tokenData.access_token,
3948
+ refreshToken: tokenData.refresh_token,
3949
+ accessTokenExpiresAt: new Date(
3950
+ Date.now() + tokenData.expires_in * 1e3
3951
+ ),
3952
+ scopes: tokenData.scope.split(",")
3953
+ };
3954
+ },
3955
+ async getUserInfo(token) {
3956
+ if (options.getUserInfo) {
3957
+ return options.getUserInfo(token);
3958
+ }
3959
+ const openid = token.openid;
3960
+ if (!openid) {
3961
+ return null;
3962
+ }
3963
+ const params = new URLSearchParams({
3964
+ access_token: token.accessToken || "",
3965
+ openid,
3966
+ lang: "zh_CN"
3967
+ });
3968
+ const { data: profile, error } = await athenaFetch("https://api.weixin.qq.com/sns/userinfo?" + params.toString(), {
3969
+ method: "GET"
3970
+ });
3971
+ if (error || !profile || profile.errcode) {
3972
+ return null;
3973
+ }
3974
+ const userMap = await options.mapProfileToUser?.(profile);
3975
+ return {
3976
+ user: {
3977
+ id: profile.unionid || profile.openid || openid,
3978
+ name: profile.nickname,
3979
+ // WeChat does not return an email, and the OAuth callback rejects a
3980
+ // missing one, so the default sign-in would always fail. Synthesize a
3981
+ // stable, non-routable placeholder (RFC 2606 `.invalid`) keyed to the
3982
+ // user's WeChat id, left unverified. Applications that collect a real
3983
+ // email override it via `mapProfileToUser`.
3984
+ email: profile.email || `${profile.unionid || profile.openid || openid}@wechat.invalid`,
3985
+ image: profile.headimgurl,
3986
+ emailVerified: false,
3987
+ ...userMap
3988
+ },
3989
+ data: profile
3990
+ };
3991
+ },
3992
+ options
3993
+ };
3994
+ };
3995
+
3996
+ // src/auth/social-providers/zoom.ts
3997
+ var zoom = (userOptions) => {
3998
+ const options = {
3999
+ pkce: true,
4000
+ ...userOptions
4001
+ };
4002
+ return {
4003
+ id: "zoom",
4004
+ name: "Zoom",
4005
+ createAuthorizationURL: async ({ state, redirectURI, codeVerifier }) => {
4006
+ const params = new URLSearchParams({
4007
+ response_type: "code",
4008
+ redirect_uri: options.redirectURI ? options.redirectURI : redirectURI,
4009
+ client_id: options.clientId,
4010
+ state
4011
+ });
4012
+ if (options.pkce) {
4013
+ const codeChallenge = await generateCodeChallenge(codeVerifier);
4014
+ params.set("code_challenge_method", "S256");
4015
+ params.set("code_challenge", codeChallenge);
4016
+ }
4017
+ const url = new URL("https://zoom.us/oauth/authorize");
4018
+ url.search = params.toString();
4019
+ return url;
4020
+ },
4021
+ validateAuthorizationCode: async ({ code, redirectURI, codeVerifier }) => {
4022
+ return validateAuthorizationCode({
4023
+ code,
4024
+ redirectURI: options.redirectURI || redirectURI,
4025
+ codeVerifier,
4026
+ options,
4027
+ tokenEndpoint: "https://zoom.us/oauth/token",
4028
+ authentication: "post"
4029
+ });
4030
+ },
4031
+ refreshAccessToken: options.refreshAccessToken ? options.refreshAccessToken : async (refreshToken) => refreshAccessToken({
4032
+ refreshToken,
4033
+ options: {
4034
+ clientId: options.clientId,
4035
+ clientKey: options.clientKey,
4036
+ clientSecret: options.clientSecret
4037
+ },
4038
+ tokenEndpoint: "https://zoom.us/oauth/token"
4039
+ }),
4040
+ async getUserInfo(token) {
4041
+ if (options.getUserInfo) {
4042
+ return options.getUserInfo(token);
4043
+ }
4044
+ const { data: profile, error } = await athenaFetch(
4045
+ "https://api.zoom.us/v2/users/me",
4046
+ {
4047
+ headers: {
4048
+ authorization: `Bearer ${token.accessToken}`
4049
+ }
4050
+ }
4051
+ );
4052
+ if (error) {
4053
+ return null;
4054
+ }
4055
+ const userMap = await options.mapProfileToUser?.(profile);
4056
+ return {
4057
+ user: {
4058
+ id: profile.id,
4059
+ name: profile.display_name,
4060
+ image: profile.pic_url,
4061
+ email: profile.email,
4062
+ emailVerified: Boolean(profile.verified),
4063
+ ...userMap
4064
+ },
4065
+ data: {
4066
+ ...profile
4067
+ }
4068
+ };
4069
+ }
4070
+ };
4071
+ };
4072
+
4073
+ // src/auth/social-providers/registry.ts
4074
+ var socialProviders = {
4075
+ apple,
4076
+ atlassian,
4077
+ athena,
4078
+ cognito,
4079
+ discord,
4080
+ facebook,
4081
+ figma,
4082
+ github,
4083
+ microsoft,
4084
+ google,
4085
+ huggingface,
4086
+ slack,
4087
+ spotify,
4088
+ twitch,
4089
+ twitter,
4090
+ dropbox,
4091
+ kick,
4092
+ linear,
4093
+ linkedin,
4094
+ gitlab,
4095
+ tiktok,
4096
+ reddit,
4097
+ roblox,
4098
+ salesforce,
4099
+ vk,
4100
+ zoom,
4101
+ notion,
4102
+ kakao,
4103
+ naver,
4104
+ line,
4105
+ paybin,
4106
+ paypal,
4107
+ polar,
4108
+ railway,
4109
+ vercel,
4110
+ wechat
4111
+ };
4112
+ var socialProviderList = Object.keys(socialProviders);
4113
+ var SocialProviderListEnum = z.enum(socialProviderList).or(z.string());
4114
+
4115
+ export { MICROSOFT_CONSUMER_TENANT_ID, SocialProviderListEnum, apple, athena, atlassian, cognito, discord, dropbox, facebook, figma, getApplePublicKey, getCognitoPublicKey, getGooglePublicKey, getJwksPublicKey, getMicrosoftPublicKey, getPayPalPublicKey, github, gitlab, google, huggingface, isGoogleHostedDomainAllowed, kakao, kick, line, linear, linkedin, mergeScopes, microsoft, naver, notion, paybin, paypal, polar, railway, reddit, roblox, salesforce, slack, socialProviderList, socialProviders, spotify, tiktok, trimTrailingSlash, twitch, twitter, vercel, verifyFacebookAccessToken, verifyGoogleIdToken, vk, wechat, zoom };
4116
+ //# sourceMappingURL=social-providers.js.map
4117
+ //# sourceMappingURL=social-providers.js.map