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