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