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