better-auth 1.6.11 → 1.6.13
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/dist/api/index.d.mts +12 -48
- package/dist/api/routes/account.d.mts +2 -23
- package/dist/api/routes/account.mjs +94 -73
- package/dist/api/routes/callback.d.mts +1 -1
- package/dist/api/routes/callback.mjs +39 -42
- package/dist/api/routes/email-verification.d.mts +1 -0
- package/dist/api/routes/email-verification.mjs +4 -3
- package/dist/api/routes/password.mjs +1 -1
- package/dist/api/routes/session.mjs +15 -10
- package/dist/api/routes/sign-in.d.mts +1 -0
- package/dist/api/routes/sign-in.mjs +3 -2
- package/dist/api/routes/sign-up.d.mts +1 -0
- package/dist/api/routes/sign-up.mjs +9 -7
- package/dist/api/routes/update-user.mjs +7 -7
- package/dist/client/fetch-plugins.mjs +2 -1
- package/dist/client/parser.mjs +0 -1
- package/dist/client/plugins/index.d.mts +3 -3
- package/dist/client/proxy.mjs +2 -1
- package/dist/context/create-context.mjs +10 -14
- package/dist/context/helpers.mjs +3 -2
- package/dist/cookies/cookie-utils.d.mts +24 -1
- package/dist/cookies/cookie-utils.mjs +85 -22
- package/dist/cookies/index.d.mts +2 -3
- package/dist/cookies/index.mjs +39 -11
- package/dist/cookies/session-store.mjs +4 -23
- package/dist/db/get-migration.mjs +4 -4
- package/dist/db/index.d.mts +2 -2
- package/dist/db/index.mjs +3 -2
- package/dist/db/internal-adapter.mjs +56 -50
- package/dist/db/schema.d.mts +15 -2
- package/dist/db/schema.mjs +26 -1
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +2 -2
- package/dist/oauth2/errors.mjs +16 -1
- package/dist/oauth2/index.d.mts +2 -2
- package/dist/oauth2/index.mjs +3 -3
- package/dist/oauth2/link-account.d.mts +27 -1
- package/dist/oauth2/link-account.mjs +27 -4
- package/dist/oauth2/state.mjs +8 -2
- package/dist/package.mjs +1 -1
- package/dist/plugins/access/access.mjs +11 -6
- package/dist/plugins/admin/admin.mjs +0 -4
- package/dist/plugins/admin/client.d.mts +1 -1
- package/dist/plugins/admin/routes.mjs +3 -3
- package/dist/plugins/anonymous/index.mjs +2 -2
- package/dist/plugins/bearer/index.mjs +4 -9
- package/dist/plugins/captcha/index.mjs +2 -2
- package/dist/plugins/email-otp/routes.mjs +1 -1
- package/dist/plugins/generic-oauth/index.d.mts +1 -1
- package/dist/plugins/generic-oauth/index.mjs +6 -6
- package/dist/plugins/generic-oauth/routes.mjs +37 -34
- package/dist/plugins/generic-oauth/types.d.mts +7 -0
- package/dist/plugins/last-login-method/client.mjs +2 -2
- package/dist/plugins/magic-link/index.mjs +0 -1
- package/dist/plugins/mcp/index.mjs +2 -5
- package/dist/plugins/multi-session/index.mjs +2 -2
- package/dist/plugins/oauth-proxy/index.mjs +45 -32
- package/dist/plugins/oauth-proxy/utils.mjs +3 -10
- package/dist/plugins/oidc-provider/index.mjs +2 -5
- package/dist/plugins/one-tap/client.mjs +9 -2
- package/dist/plugins/one-tap/index.mjs +16 -39
- package/dist/plugins/open-api/generator.mjs +16 -5
- package/dist/plugins/organization/adapter.mjs +61 -56
- package/dist/plugins/organization/client.d.mts +2 -1
- package/dist/plugins/organization/error-codes.d.mts +1 -0
- package/dist/plugins/organization/error-codes.mjs +2 -1
- package/dist/plugins/organization/routes/crud-invites.mjs +3 -0
- package/dist/plugins/organization/routes/crud-org.d.mts +4 -4
- package/dist/plugins/organization/routes/crud-org.mjs +2 -2
- package/dist/plugins/organization/types.d.mts +3 -3
- package/dist/plugins/phone-number/routes.mjs +1 -1
- package/dist/plugins/two-factor/backup-codes/index.d.mts +4 -3
- package/dist/plugins/two-factor/client.mjs +2 -1
- package/dist/plugins/two-factor/index.mjs +3 -2
- package/dist/plugins/username/index.d.mts +24 -2
- package/dist/plugins/username/index.mjs +49 -3
- package/dist/state.d.mts +2 -2
- package/dist/state.mjs +18 -4
- package/dist/test-utils/headers.mjs +2 -7
- package/dist/test-utils/test-instance.d.mts +36 -144
- package/dist/utils/index.d.mts +1 -1
- package/dist/utils/url.d.mts +2 -1
- package/dist/utils/url.mjs +9 -3
- package/package.json +15 -14
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { symmetricDecodeJWT, symmetricEncodeJWT } from "../crypto/jwt.mjs";
|
|
2
|
+
import { parseCookies } from "./cookie-utils.mjs";
|
|
2
3
|
import { safeJSONParse } from "@better-auth/core/utils/json";
|
|
3
4
|
import * as z from "zod";
|
|
4
5
|
//#region src/cookies/session-store.ts
|
|
@@ -6,20 +7,6 @@ const ALLOWED_COOKIE_SIZE = 4096;
|
|
|
6
7
|
const ESTIMATED_EMPTY_COOKIE_SIZE = 200;
|
|
7
8
|
const CHUNK_SIZE = ALLOWED_COOKIE_SIZE - ESTIMATED_EMPTY_COOKIE_SIZE;
|
|
8
9
|
/**
|
|
9
|
-
* Parse cookies from the request headers
|
|
10
|
-
*/
|
|
11
|
-
function parseCookiesFromContext(ctx) {
|
|
12
|
-
const cookieHeader = ctx.headers?.get("cookie");
|
|
13
|
-
if (!cookieHeader) return {};
|
|
14
|
-
const cookies = {};
|
|
15
|
-
const pairs = cookieHeader.split("; ");
|
|
16
|
-
for (const pair of pairs) {
|
|
17
|
-
const [name, ...valueParts] = pair.split("=");
|
|
18
|
-
if (name && valueParts.length > 0) cookies[name] = valueParts.join("=");
|
|
19
|
-
}
|
|
20
|
-
return cookies;
|
|
21
|
-
}
|
|
22
|
-
/**
|
|
23
10
|
* Extract the chunk index from a cookie name
|
|
24
11
|
*/
|
|
25
12
|
function getChunkIndex(cookieName) {
|
|
@@ -33,8 +20,8 @@ function getChunkIndex(cookieName) {
|
|
|
33
20
|
*/
|
|
34
21
|
function readExistingChunks(cookieName, ctx) {
|
|
35
22
|
const chunks = {};
|
|
36
|
-
const cookies =
|
|
37
|
-
for (const [name, value] of
|
|
23
|
+
const cookies = parseCookies(ctx.headers?.get("cookie") || "");
|
|
24
|
+
for (const [name, value] of cookies) if (name.startsWith(cookieName)) chunks[name] = value;
|
|
38
25
|
return chunks;
|
|
39
26
|
}
|
|
40
27
|
/**
|
|
@@ -140,13 +127,7 @@ function getChunkedCookie(ctx, cookieName) {
|
|
|
140
127
|
const chunks = [];
|
|
141
128
|
const cookieHeader = ctx.headers?.get("cookie");
|
|
142
129
|
if (!cookieHeader) return null;
|
|
143
|
-
const
|
|
144
|
-
const pairs = cookieHeader.split("; ");
|
|
145
|
-
for (const pair of pairs) {
|
|
146
|
-
const [name, ...valueParts] = pair.split("=");
|
|
147
|
-
if (name && valueParts.length > 0) cookies[name] = valueParts.join("=");
|
|
148
|
-
}
|
|
149
|
-
for (const [name, val] of Object.entries(cookies)) if (name.startsWith(cookieName + ".")) {
|
|
130
|
+
for (const [name, val] of parseCookies(cookieHeader)) if (name.startsWith(cookieName + ".")) {
|
|
150
131
|
const indexStr = name.split(".").at(-1);
|
|
151
132
|
const index = parseInt(indexStr || "0", 10);
|
|
152
133
|
if (!isNaN(index)) chunks.push({
|
|
@@ -269,13 +269,14 @@ async function getMigrations(config) {
|
|
|
269
269
|
return `${model}.${field}`;
|
|
270
270
|
}
|
|
271
271
|
}
|
|
272
|
+
const deferredIndexes = [];
|
|
272
273
|
if (toBeAdded.length) for (const table of toBeAdded) for (const [fieldName, field] of Object.entries(table.fields)) {
|
|
273
274
|
const type = getType(field, fieldName);
|
|
274
275
|
const builder = db.schema.alterTable(table.table);
|
|
275
276
|
if (field.index) {
|
|
276
277
|
const indexName = `${table.table}_${fieldName}_${field.unique ? "uidx" : "idx"}`;
|
|
277
278
|
const indexBuilder = db.schema.createIndex(indexName).on(table.table).columns([fieldName]);
|
|
278
|
-
|
|
279
|
+
deferredIndexes.push(field.unique ? indexBuilder.unique() : indexBuilder);
|
|
279
280
|
}
|
|
280
281
|
const built = builder.addColumn(fieldName, type, (col) => {
|
|
281
282
|
col = field.required !== false ? col.notNull() : col;
|
|
@@ -287,7 +288,6 @@ async function getMigrations(config) {
|
|
|
287
288
|
});
|
|
288
289
|
migrations.push(built);
|
|
289
290
|
}
|
|
290
|
-
const toBeIndexed = [];
|
|
291
291
|
if (toBeCreated.length) for (const table of toBeCreated) {
|
|
292
292
|
const idType = getType({ type: useNumberId ? "number" : "string" }, "id");
|
|
293
293
|
let dbT = db.schema.createTable(table.table).addColumn("id", idType, (col) => {
|
|
@@ -315,12 +315,12 @@ async function getMigrations(config) {
|
|
|
315
315
|
});
|
|
316
316
|
if (field.index) {
|
|
317
317
|
const builder = db.schema.createIndex(`${table.table}_${fieldName}_${field.unique ? "uidx" : "idx"}`).on(table.table).columns([fieldName]);
|
|
318
|
-
|
|
318
|
+
deferredIndexes.push(field.unique ? builder.unique() : builder);
|
|
319
319
|
}
|
|
320
320
|
}
|
|
321
321
|
migrations.push(dbT);
|
|
322
322
|
}
|
|
323
|
-
|
|
323
|
+
for (const index of deferredIndexes) migrations.push(index);
|
|
324
324
|
async function runMigrations() {
|
|
325
325
|
for (const migration of migrations) await migration.execute();
|
|
326
326
|
}
|
package/dist/db/index.d.mts
CHANGED
|
@@ -3,7 +3,7 @@ import { convertFromDB, convertToDB } from "./field-converter.mjs";
|
|
|
3
3
|
import { getSchema } from "./get-schema.mjs";
|
|
4
4
|
import { DatabaseHooksEntry, getWithHooks } from "./with-hooks.mjs";
|
|
5
5
|
import { createInternalAdapter } from "./internal-adapter.mjs";
|
|
6
|
-
import { getSessionDefaultFields, mergeSchema, parseAccountInput, parseAccountOutput, parseAdditionalUserInput, parseInputData, parseSessionInput, parseSessionOutput, parseUserInput, parseUserOutput } from "./schema.mjs";
|
|
6
|
+
import { buildSyntheticUserOutput, getSessionDefaultFields, mergeSchema, parseAccountInput, parseAccountOutput, parseAdditionalUserInput, parseInputData, parseSessionInput, parseSessionOutput, parseUserInput, parseUserOutput } from "./schema.mjs";
|
|
7
7
|
import { FieldAttributeToSchema, toZodSchema } from "./to-zod.mjs";
|
|
8
8
|
export * from "@better-auth/core/db";
|
|
9
|
-
export { DatabaseHooksEntry, FieldAttributeToObject, FieldAttributeToSchema, InferAdditionalFieldsFromPluginOptions, InferFieldsInputClient, InferFieldsOutput, RemoveFieldsWithReturnedFalse, convertFromDB, convertToDB, createInternalAdapter, getSchema, getSessionDefaultFields, getWithHooks, mergeSchema, parseAccountInput, parseAccountOutput, parseAdditionalUserInput, parseInputData, parseSessionInput, parseSessionOutput, parseUserInput, parseUserOutput, toZodSchema };
|
|
9
|
+
export { DatabaseHooksEntry, FieldAttributeToObject, FieldAttributeToSchema, InferAdditionalFieldsFromPluginOptions, InferFieldsInputClient, InferFieldsOutput, RemoveFieldsWithReturnedFalse, buildSyntheticUserOutput, convertFromDB, convertToDB, createInternalAdapter, getSchema, getSessionDefaultFields, getWithHooks, mergeSchema, parseAccountInput, parseAccountOutput, parseAdditionalUserInput, parseInputData, parseSessionInput, parseSessionOutput, parseUserInput, parseUserOutput, toZodSchema };
|
package/dist/db/index.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { __exportAll, __reExport } from "../_virtual/_rolldown/runtime.mjs";
|
|
2
2
|
import { getSchema } from "./get-schema.mjs";
|
|
3
|
-
import { getSessionDefaultFields, mergeSchema, parseAccountInput, parseAccountOutput, parseAdditionalUserInput, parseInputData, parseSessionInput, parseSessionOutput, parseUserInput, parseUserOutput } from "./schema.mjs";
|
|
3
|
+
import { buildSyntheticUserOutput, getSessionDefaultFields, mergeSchema, parseAccountInput, parseAccountOutput, parseAdditionalUserInput, parseInputData, parseSessionInput, parseSessionOutput, parseUserInput, parseUserOutput } from "./schema.mjs";
|
|
4
4
|
import { convertFromDB, convertToDB } from "./field-converter.mjs";
|
|
5
5
|
import { getWithHooks } from "./with-hooks.mjs";
|
|
6
6
|
import { createInternalAdapter } from "./internal-adapter.mjs";
|
|
@@ -8,6 +8,7 @@ import { toZodSchema } from "./to-zod.mjs";
|
|
|
8
8
|
export * from "@better-auth/core/db";
|
|
9
9
|
//#region src/db/index.ts
|
|
10
10
|
var db_exports = /* @__PURE__ */ __exportAll({
|
|
11
|
+
buildSyntheticUserOutput: () => buildSyntheticUserOutput,
|
|
11
12
|
convertFromDB: () => convertFromDB,
|
|
12
13
|
convertToDB: () => convertToDB,
|
|
13
14
|
createInternalAdapter: () => createInternalAdapter,
|
|
@@ -28,4 +29,4 @@ var db_exports = /* @__PURE__ */ __exportAll({
|
|
|
28
29
|
import * as import__better_auth_core_db from "@better-auth/core/db";
|
|
29
30
|
__reExport(db_exports, import__better_auth_core_db);
|
|
30
31
|
//#endregion
|
|
31
|
-
export { convertFromDB, convertToDB, createInternalAdapter, db_exports, getSchema, getSessionDefaultFields, getWithHooks, mergeSchema, parseAccountInput, parseAccountOutput, parseAdditionalUserInput, parseInputData, parseSessionInput, parseSessionOutput, parseUserInput, parseUserOutput, toZodSchema };
|
|
32
|
+
export { buildSyntheticUserOutput, convertFromDB, convertToDB, createInternalAdapter, db_exports, getSchema, getSessionDefaultFields, getWithHooks, mergeSchema, parseAccountInput, parseAccountOutput, parseAdditionalUserInput, parseInputData, parseSessionInput, parseSessionOutput, parseUserInput, parseUserOutput, toZodSchema };
|
|
@@ -388,21 +388,29 @@ const createInternalAdapter = (adapter, ctx) => {
|
|
|
388
388
|
value: id
|
|
389
389
|
}], "account", void 0);
|
|
390
390
|
},
|
|
391
|
-
|
|
391
|
+
deleteUserSessions: async (userId) => {
|
|
392
392
|
if (secondaryStorage) {
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
await secondaryStorage.delete(`active-sessions-${userIdOrSessionTokens}`);
|
|
399
|
-
} else for (const sessionToken of userIdOrSessionTokens) if (await secondaryStorage.get(sessionToken)) await secondaryStorage.delete(sessionToken);
|
|
393
|
+
const activeSession = await secondaryStorage.get(`active-sessions-${userId}`);
|
|
394
|
+
const sessions = activeSession ? safeJSONParse(activeSession) : [];
|
|
395
|
+
if (!sessions) return;
|
|
396
|
+
for (const session of sessions) await secondaryStorage.delete(session.token);
|
|
397
|
+
await secondaryStorage.delete(`active-sessions-${userId}`);
|
|
400
398
|
if (!options.session?.storeSessionInDatabase || ctx.options.session?.preserveSessionInDatabase) return;
|
|
401
399
|
}
|
|
402
400
|
await deleteManyWithHooks([{
|
|
403
|
-
field:
|
|
404
|
-
value:
|
|
405
|
-
|
|
401
|
+
field: "userId",
|
|
402
|
+
value: userId
|
|
403
|
+
}], "session", void 0);
|
|
404
|
+
},
|
|
405
|
+
deleteSessions: async (sessionTokens) => {
|
|
406
|
+
if (secondaryStorage) {
|
|
407
|
+
for (const sessionToken of sessionTokens) if (await secondaryStorage.get(sessionToken)) await secondaryStorage.delete(sessionToken);
|
|
408
|
+
if (!options.session?.storeSessionInDatabase || ctx.options.session?.preserveSessionInDatabase) return;
|
|
409
|
+
}
|
|
410
|
+
await deleteManyWithHooks([{
|
|
411
|
+
field: "token",
|
|
412
|
+
value: sessionTokens,
|
|
413
|
+
operator: "in"
|
|
406
414
|
}], "session", void 0);
|
|
407
415
|
},
|
|
408
416
|
findOAuthUser: async (email, accountId, providerId) => {
|
|
@@ -532,15 +540,6 @@ const createInternalAdapter = (adapter, ctx) => {
|
|
|
532
540
|
}]
|
|
533
541
|
});
|
|
534
542
|
},
|
|
535
|
-
findAccount: async (accountId) => {
|
|
536
|
-
return await (await getCurrentAdapter(adapter)).findOne({
|
|
537
|
-
model: "account",
|
|
538
|
-
where: [{
|
|
539
|
-
field: "accountId",
|
|
540
|
-
value: accountId
|
|
541
|
-
}]
|
|
542
|
-
});
|
|
543
|
-
},
|
|
544
543
|
findAccountByProviderId: async (accountId, providerId) => {
|
|
545
544
|
return await (await getCurrentAdapter(adapter)).findOne({
|
|
546
545
|
model: "account",
|
|
@@ -639,17 +638,23 @@ const createInternalAdapter = (adapter, ctx) => {
|
|
|
639
638
|
const storageOption = getStorageOption(identifier, options.verification?.storeIdentifier);
|
|
640
639
|
const storedIdentifier = await processIdentifier(identifier, storageOption);
|
|
641
640
|
const identifiersToTry = storageOption && storageOption !== "plain" ? [storedIdentifier, identifier] : [storedIdentifier];
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
641
|
+
const hydrateCachedVerification = (raw) => {
|
|
642
|
+
if (!raw) return null;
|
|
643
|
+
const candidate = typeof raw === "string" ? safeJSONParse(raw) : typeof raw === "object" ? raw : null;
|
|
644
|
+
if (!candidate) return null;
|
|
645
|
+
const expiresAt = new Date(candidate.expiresAt);
|
|
646
|
+
if (!Number.isFinite(expiresAt.getTime())) return null;
|
|
647
|
+
return {
|
|
648
|
+
...candidate,
|
|
649
|
+
expiresAt
|
|
648
650
|
};
|
|
651
|
+
};
|
|
652
|
+
let consumed = null;
|
|
653
|
+
if (secondaryStorage && !options.verification?.storeInDatabase) {
|
|
649
654
|
const consumeCacheKey = async (key) => {
|
|
650
|
-
if (secondaryStorage.getAndDelete) return
|
|
655
|
+
if (secondaryStorage.getAndDelete) return hydrateCachedVerification(await secondaryStorage.getAndDelete(key));
|
|
651
656
|
return withVerificationConsumeLock(key, async () => {
|
|
652
|
-
const parsed =
|
|
657
|
+
const parsed = hydrateCachedVerification(await secondaryStorage.get(key));
|
|
653
658
|
if (!parsed) return null;
|
|
654
659
|
await secondaryStorage.delete(key);
|
|
655
660
|
return parsed;
|
|
@@ -659,17 +664,16 @@ const createInternalAdapter = (adapter, ctx) => {
|
|
|
659
664
|
const cached = await consumeCacheKey(`verification:${stored}`);
|
|
660
665
|
if (!cached) continue;
|
|
661
666
|
await Promise.all(identifiersToTry.filter((candidate) => candidate !== stored).map((candidate) => secondaryStorage.delete(`verification:${candidate}`)));
|
|
662
|
-
|
|
667
|
+
consumed = cached;
|
|
668
|
+
break;
|
|
663
669
|
}
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
async function consumeByIdentifier(id) {
|
|
667
|
-
const where = [{
|
|
668
|
-
field: "identifier",
|
|
669
|
-
value: id
|
|
670
|
-
}];
|
|
671
|
-
return withVerificationConsumeLock(`verification:${id}`, () => runWithTransaction(adapter, async () => {
|
|
670
|
+
} else {
|
|
671
|
+
const consumeByIdentifier = async (id) => withVerificationConsumeLock(`verification:${id}`, () => runWithTransaction(adapter, async () => {
|
|
672
672
|
const txAdapter = await getCurrentAdapter(adapter);
|
|
673
|
+
const where = [{
|
|
674
|
+
field: "identifier",
|
|
675
|
+
value: id
|
|
676
|
+
}];
|
|
673
677
|
const latest = (await txAdapter.findMany({
|
|
674
678
|
model: "verification",
|
|
675
679
|
where,
|
|
@@ -680,30 +684,32 @@ const createInternalAdapter = (adapter, ctx) => {
|
|
|
680
684
|
limit: 1
|
|
681
685
|
}))[0] ?? null;
|
|
682
686
|
if (!latest) return null;
|
|
683
|
-
|
|
687
|
+
return consumeOneWithHooks("verification", [{
|
|
684
688
|
field: "id",
|
|
685
689
|
value: latest.id
|
|
686
|
-
}]
|
|
687
|
-
|
|
688
|
-
const consumed = await txAdapter.consumeOne({
|
|
690
|
+
}], async () => {
|
|
691
|
+
const row = await txAdapter.consumeOne({
|
|
689
692
|
model: "verification",
|
|
690
|
-
where:
|
|
693
|
+
where: [{
|
|
694
|
+
field: "id",
|
|
695
|
+
value: latest.id
|
|
696
|
+
}]
|
|
691
697
|
});
|
|
692
|
-
if (!
|
|
698
|
+
if (!row) return null;
|
|
693
699
|
await txAdapter.deleteMany({
|
|
694
700
|
model: "verification",
|
|
695
701
|
where
|
|
696
702
|
});
|
|
697
|
-
return
|
|
703
|
+
return row;
|
|
698
704
|
}, latest);
|
|
699
705
|
}));
|
|
706
|
+
for (const stored of identifiersToTry) {
|
|
707
|
+
consumed = await consumeByIdentifier(stored);
|
|
708
|
+
if (consumed) break;
|
|
709
|
+
}
|
|
710
|
+
if (consumed && secondaryStorage) await Promise.all(identifiersToTry.map((stored) => secondaryStorage.delete(`verification:${stored}`)));
|
|
700
711
|
}
|
|
701
|
-
|
|
702
|
-
for (const stored of identifiersToTry) {
|
|
703
|
-
consumed = await consumeByIdentifier(stored);
|
|
704
|
-
if (consumed) break;
|
|
705
|
-
}
|
|
706
|
-
if (consumed && secondaryStorage) await Promise.all(identifiersToTry.map((stored) => secondaryStorage.delete(`verification:${stored}`)));
|
|
712
|
+
if (!consumed || consumed.expiresAt < /* @__PURE__ */ new Date()) return null;
|
|
707
713
|
return consumed;
|
|
708
714
|
},
|
|
709
715
|
updateVerificationByIdentifier: async (identifier, data) => {
|
package/dist/db/schema.d.mts
CHANGED
|
@@ -4,8 +4,21 @@ import { BetterAuthPluginDBSchema, DBFieldAttribute } from "@better-auth/core/db
|
|
|
4
4
|
|
|
5
5
|
//#region src/db/schema.d.ts
|
|
6
6
|
declare function parseUserOutput<T extends User$1>(options: BetterAuthOptions, user: T): T;
|
|
7
|
+
/**
|
|
8
|
+
* Builds a synthetic user object that matches the shape of a real user
|
|
9
|
+
* returned from the database. This ensures enumeration protection works
|
|
10
|
+
* correctly by making synthetic and real user responses indistinguishable.
|
|
11
|
+
*
|
|
12
|
+
* The function iterates over the user output schema and:
|
|
13
|
+
* - Includes all fields that should be returned (returned !== false)
|
|
14
|
+
* - Uses provided values when available
|
|
15
|
+
* - Sets optional fields to null when no value is provided
|
|
16
|
+
* - Applies default values where defined
|
|
17
|
+
* - Always includes the 'id' field (not part of schema but always present)
|
|
18
|
+
*/
|
|
19
|
+
declare function buildSyntheticUserOutput(options: BetterAuthOptions, data: Record<string, unknown>): Record<string, unknown>;
|
|
7
20
|
declare function parseSessionOutput<T extends Session$1>(options: BetterAuthOptions, session: T): T;
|
|
8
|
-
declare function parseAccountOutput<T extends Account>(options: BetterAuthOptions, account: T): Omit<T, "idToken" | "accessToken" | "refreshToken" | "
|
|
21
|
+
declare function parseAccountOutput<T extends Account>(options: BetterAuthOptions, account: T): Omit<T, "idToken" | "accessToken" | "refreshToken" | "password" | "accessTokenExpiresAt" | "refreshTokenExpiresAt">;
|
|
9
22
|
declare function parseInputData<T extends Record<string, any>>(data: T, schema: {
|
|
10
23
|
fields: Record<string, DBFieldAttribute>;
|
|
11
24
|
action?: ("create" | "update") | undefined;
|
|
@@ -45,4 +58,4 @@ declare function mergeSchema<S extends BetterAuthPluginDBSchema>(schema: S, newS
|
|
|
45
58
|
} | undefined;
|
|
46
59
|
} | undefined } | undefined): S;
|
|
47
60
|
//#endregion
|
|
48
|
-
export { getSessionDefaultFields, mergeSchema, parseAccountInput, parseAccountOutput, parseAdditionalUserInput, parseInputData, parseSessionInput, parseSessionOutput, parseUserInput, parseUserOutput };
|
|
61
|
+
export { buildSyntheticUserOutput, getSessionDefaultFields, mergeSchema, parseAccountInput, parseAccountOutput, parseAdditionalUserInput, parseInputData, parseSessionInput, parseSessionOutput, parseUserInput, parseUserOutput };
|
package/dist/db/schema.mjs
CHANGED
|
@@ -24,6 +24,31 @@ function getFields(options, modelName, mode) {
|
|
|
24
24
|
function parseUserOutput(options, user) {
|
|
25
25
|
return filterOutputFields(user, getFields(options, "user", "output"));
|
|
26
26
|
}
|
|
27
|
+
/**
|
|
28
|
+
* Builds a synthetic user object that matches the shape of a real user
|
|
29
|
+
* returned from the database. This ensures enumeration protection works
|
|
30
|
+
* correctly by making synthetic and real user responses indistinguishable.
|
|
31
|
+
*
|
|
32
|
+
* The function iterates over the user output schema and:
|
|
33
|
+
* - Includes all fields that should be returned (returned !== false)
|
|
34
|
+
* - Uses provided values when available
|
|
35
|
+
* - Sets optional fields to null when no value is provided
|
|
36
|
+
* - Applies default values where defined
|
|
37
|
+
* - Always includes the 'id' field (not part of schema but always present)
|
|
38
|
+
*/
|
|
39
|
+
function buildSyntheticUserOutput(options, data) {
|
|
40
|
+
const schema = getFields(options, "user", "output");
|
|
41
|
+
const result = {};
|
|
42
|
+
for (const key in schema) {
|
|
43
|
+
const fieldAttr = schema[key];
|
|
44
|
+
if (fieldAttr.returned === false) continue;
|
|
45
|
+
if (key in data && data[key] !== void 0) result[key] = data[key];
|
|
46
|
+
else if (fieldAttr.defaultValue !== void 0) result[key] = typeof fieldAttr.defaultValue === "function" ? fieldAttr.defaultValue() : fieldAttr.defaultValue;
|
|
47
|
+
else if (!fieldAttr.required) result[key] = null;
|
|
48
|
+
}
|
|
49
|
+
if ("id" in data) result.id = data.id;
|
|
50
|
+
return result;
|
|
51
|
+
}
|
|
27
52
|
function parseSessionOutput(options, session) {
|
|
28
53
|
return filterOutputFields(session, getFields(options, "session", "output"));
|
|
29
54
|
}
|
|
@@ -121,4 +146,4 @@ function mergeSchema(schema, newSchema) {
|
|
|
121
146
|
return schema;
|
|
122
147
|
}
|
|
123
148
|
//#endregion
|
|
124
|
-
export { getSessionDefaultFields, mergeSchema, parseAccountInput, parseAccountOutput, parseAdditionalUserInput, parseInputData, parseSessionInput, parseSessionOutput, parseUserInput, parseUserOutput };
|
|
149
|
+
export { buildSyntheticUserOutput, getSessionDefaultFields, mergeSchema, parseAccountInput, parseAccountOutput, parseAdditionalUserInput, parseInputData, parseSessionInput, parseSessionOutput, parseUserInput, parseUserOutput };
|
package/dist/index.d.mts
CHANGED
|
@@ -10,7 +10,7 @@ import { betterAuth } from "./auth/full.mjs";
|
|
|
10
10
|
import { generateState, parseState } from "./oauth2/state.mjs";
|
|
11
11
|
import { StateData, generateGenericState, parseGenericState } from "./state.mjs";
|
|
12
12
|
import { HIDE_METADATA } from "./utils/hide-metadata.mjs";
|
|
13
|
-
import { getBaseURL, getHost, getHostFromSource, getOrigin, getProtocol, getProtocolFromSource, isDynamicBaseURLConfig, isRequestLike, matchesHostPattern, resolveBaseURL, resolveDynamicBaseURL } from "./utils/url.mjs";
|
|
13
|
+
import { getBaseURL, getHost, getHostFromSource, getOrigin, getProtocol, getProtocolFromSource, isDynamicBaseURLConfig, isRequestLike, matchesHostPattern, resolveBaseURL, resolveDynamicBaseURL, trimTrailingSlashes } from "./utils/url.mjs";
|
|
14
14
|
import { APIError } from "./api/index.mjs";
|
|
15
15
|
import { StandardSchemaV1 } from "@better-auth/core";
|
|
16
16
|
import { getCurrentAdapter } from "@better-auth/core/context";
|
|
@@ -27,4 +27,4 @@ export * from "@better-auth/core/utils/json";
|
|
|
27
27
|
export * from "@better-auth/core/social-providers";
|
|
28
28
|
export * from "better-call";
|
|
29
29
|
export * from "zod";
|
|
30
|
-
export { APIError, Account, AdditionalSessionFieldsInput, AdditionalUserFieldsInput, Auth, BetterAuthAdvancedOptions, BetterAuthClientOptions, BetterAuthClientPlugin, BetterAuthCookies, BetterAuthOptions, BetterAuthPlugin, BetterAuthRateLimitOptions, ClientAtomListener, ClientStore, DBAdapter, DBAdapterInstance, DBAdapterSchemaCreation, DBTransactionAdapter, ExtractPluginField, FilteredAPI, HIDE_METADATA, HasRequiredKeys, InferAPI, InferActions, InferAdditionalFromClient, InferClientAPI, InferErrorCodes, InferOptionSchema, InferPluginContext, InferPluginErrorCodes, InferPluginFieldFromTuple, InferPluginIDs, InferPluginTypes, InferSessionAPI, InferSessionFromClient, InferUserFromClient, IsAny, IsSignal, type JSONWebKeySet, type JWTPayload, JoinConfig, JoinOption, OverrideMerge, Prettify, PrettifyDeep, RateLimit, RequiredKeysOf, Session, SessionQueryParams, type StandardSchemaV1, StateData, StoreIdentifierOption, StripEmptyObjects, type TelemetryEvent, UnionToIntersection, User, Verification, Where, betterAuth, createTelemetry, generateGenericState, generateState, getBaseURL, getCurrentAdapter, getHost, getHostFromSource, getOrigin, getProtocol, getProtocolFromSource, getTelemetryAuthConfig, isDynamicBaseURLConfig, isRequestLike, matchesHostPattern, parseGenericState, parseState, resolveBaseURL, resolveDynamicBaseURL };
|
|
30
|
+
export { APIError, Account, AdditionalSessionFieldsInput, AdditionalUserFieldsInput, Auth, BetterAuthAdvancedOptions, BetterAuthClientOptions, BetterAuthClientPlugin, BetterAuthCookies, BetterAuthOptions, BetterAuthPlugin, BetterAuthRateLimitOptions, ClientAtomListener, ClientStore, DBAdapter, DBAdapterInstance, DBAdapterSchemaCreation, DBTransactionAdapter, ExtractPluginField, FilteredAPI, HIDE_METADATA, HasRequiredKeys, InferAPI, InferActions, InferAdditionalFromClient, InferClientAPI, InferErrorCodes, InferOptionSchema, InferPluginContext, InferPluginErrorCodes, InferPluginFieldFromTuple, InferPluginIDs, InferPluginTypes, InferSessionAPI, InferSessionFromClient, InferUserFromClient, IsAny, IsSignal, type JSONWebKeySet, type JWTPayload, JoinConfig, JoinOption, OverrideMerge, Prettify, PrettifyDeep, RateLimit, RequiredKeysOf, Session, SessionQueryParams, type StandardSchemaV1, StateData, StoreIdentifierOption, StripEmptyObjects, type TelemetryEvent, UnionToIntersection, User, Verification, Where, betterAuth, createTelemetry, generateGenericState, generateState, getBaseURL, getCurrentAdapter, getHost, getHostFromSource, getOrigin, getProtocol, getProtocolFromSource, getTelemetryAuthConfig, isDynamicBaseURLConfig, isRequestLike, matchesHostPattern, parseGenericState, parseState, resolveBaseURL, resolveDynamicBaseURL, trimTrailingSlashes };
|
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { getBaseURL, getHost, getHostFromSource, getOrigin, getProtocol, getProtocolFromSource, isDynamicBaseURLConfig, isRequestLike, matchesHostPattern, resolveBaseURL, resolveDynamicBaseURL } from "./utils/url.mjs";
|
|
1
|
+
import { getBaseURL, getHost, getHostFromSource, getOrigin, getProtocol, getProtocolFromSource, isDynamicBaseURLConfig, isRequestLike, matchesHostPattern, resolveBaseURL, resolveDynamicBaseURL, trimTrailingSlashes } from "./utils/url.mjs";
|
|
2
2
|
import { generateGenericState, parseGenericState } from "./state.mjs";
|
|
3
3
|
import { generateState, parseState } from "./oauth2/state.mjs";
|
|
4
4
|
import { HIDE_METADATA } from "./utils/hide-metadata.mjs";
|
|
@@ -14,4 +14,4 @@ export * from "@better-auth/core/oauth2";
|
|
|
14
14
|
export * from "@better-auth/core/utils/error-codes";
|
|
15
15
|
export * from "@better-auth/core/utils/id";
|
|
16
16
|
export * from "@better-auth/core/utils/json";
|
|
17
|
-
export { APIError, HIDE_METADATA, betterAuth, createTelemetry, generateGenericState, generateState, getBaseURL, getCurrentAdapter, getHost, getHostFromSource, getOrigin, getProtocol, getProtocolFromSource, getTelemetryAuthConfig, isDynamicBaseURLConfig, isRequestLike, matchesHostPattern, parseGenericState, parseState, resolveBaseURL, resolveDynamicBaseURL };
|
|
17
|
+
export { APIError, HIDE_METADATA, betterAuth, createTelemetry, generateGenericState, generateState, getBaseURL, getCurrentAdapter, getHost, getHostFromSource, getOrigin, getProtocol, getProtocolFromSource, getTelemetryAuthConfig, isDynamicBaseURLConfig, isRequestLike, matchesHostPattern, parseGenericState, parseState, resolveBaseURL, resolveDynamicBaseURL, trimTrailingSlashes };
|
package/dist/oauth2/errors.mjs
CHANGED
|
@@ -1,6 +1,21 @@
|
|
|
1
1
|
//#region src/oauth2/errors.ts
|
|
2
2
|
const HANDLING_DOCS_URL = "https://www.better-auth.com/docs/concepts/oauth#handling-providers-without-email";
|
|
3
3
|
/**
|
|
4
|
+
* Redirect the user to the OAuth error page with a machine-readable `error`
|
|
5
|
+
* code (and optional `error_description`).
|
|
6
|
+
*
|
|
7
|
+
* Every OAuth callback path routes its failures through this helper so the
|
|
8
|
+
* query parameter name, the `?`/`&` separator, and URL encoding are decided in
|
|
9
|
+
* one place. The error page reads the `error` query parameter, so callers must
|
|
10
|
+
* never hand-build the redirect with a different parameter name.
|
|
11
|
+
*/
|
|
12
|
+
function redirectOnError(ctx, errorURL, error, description) {
|
|
13
|
+
const params = new URLSearchParams({ error });
|
|
14
|
+
if (description) params.set("error_description", description);
|
|
15
|
+
const sep = errorURL.includes("?") ? "&" : "?";
|
|
16
|
+
throw ctx.redirect(`${errorURL}${sep}${params.toString()}`);
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
4
19
|
* Build the logger message shown when an OAuth provider does not return an
|
|
5
20
|
* email address. Kept in one place so every rejection site points users at
|
|
6
21
|
* the same workaround docs.
|
|
@@ -9,4 +24,4 @@ function missingEmailLogMessage(providerId, options) {
|
|
|
9
24
|
return `${options?.source === "generic" ? `Generic OAuth provider "${providerId}"` : `Provider "${providerId}"`} did not return an email${options?.source === "id_token" ? " in the id token" : ""}. Either request the provider's email scope, or synthesize one via \`mapProfileToUser\`. See ${HANDLING_DOCS_URL}`;
|
|
10
25
|
}
|
|
11
26
|
//#endregion
|
|
12
|
-
export { missingEmailLogMessage };
|
|
27
|
+
export { missingEmailLogMessage, redirectOnError };
|
package/dist/oauth2/index.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { generateState, parseState } from "./state.mjs";
|
|
2
|
-
import { handleOAuthUserInfo } from "./link-account.mjs";
|
|
2
|
+
import { applyUpdateUserInfoOnLink, handleOAuthUserInfo } from "./link-account.mjs";
|
|
3
3
|
import { decryptOAuthToken, setTokenUtil } from "./utils.mjs";
|
|
4
4
|
export * from "@better-auth/core/oauth2";
|
|
5
|
-
export { decryptOAuthToken, generateState, handleOAuthUserInfo, parseState, setTokenUtil };
|
|
5
|
+
export { applyUpdateUserInfoOnLink, decryptOAuthToken, generateState, handleOAuthUserInfo, parseState, setTokenUtil };
|
package/dist/oauth2/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { generateState, parseState } from "./state.mjs";
|
|
2
1
|
import { decryptOAuthToken, setTokenUtil } from "./utils.mjs";
|
|
3
|
-
import { handleOAuthUserInfo } from "./link-account.mjs";
|
|
2
|
+
import { applyUpdateUserInfoOnLink, handleOAuthUserInfo } from "./link-account.mjs";
|
|
3
|
+
import { generateState, parseState } from "./state.mjs";
|
|
4
4
|
export * from "@better-auth/core/oauth2";
|
|
5
|
-
export { decryptOAuthToken, generateState, handleOAuthUserInfo, parseState, setTokenUtil };
|
|
5
|
+
export { applyUpdateUserInfoOnLink, decryptOAuthToken, generateState, handleOAuthUserInfo, parseState, setTokenUtil };
|
|
@@ -42,5 +42,31 @@ declare function handleOAuthUserInfo(c: GenericEndpointContext, opts: {
|
|
|
42
42
|
error: null;
|
|
43
43
|
isRegister: boolean;
|
|
44
44
|
}>;
|
|
45
|
+
/**
|
|
46
|
+
* Provider profile a freshly linked account may copy onto the local user.
|
|
47
|
+
* `id` is the provider's account id (never the local user id), and `email`/
|
|
48
|
+
* `emailVerified` are identity anchors; all three are stripped before the
|
|
49
|
+
* remaining fields are written.
|
|
50
|
+
*/
|
|
51
|
+
type LinkedProviderProfile = {
|
|
52
|
+
id: string | number;
|
|
53
|
+
name?: string | undefined;
|
|
54
|
+
email?: string | null | undefined;
|
|
55
|
+
emailVerified?: boolean | undefined;
|
|
56
|
+
image?: string | null | undefined;
|
|
57
|
+
};
|
|
58
|
+
/**
|
|
59
|
+
* Apply the `account.accountLinking.updateUserInfoOnLink` policy: when enabled,
|
|
60
|
+
* copy the freshly linked provider's profile onto the local user, matching the
|
|
61
|
+
* field set persisted on sign-up. The local `email` and `emailVerified` are
|
|
62
|
+
* never changed, so a link can't rebind the account's identity, and
|
|
63
|
+
* `updateUser` drops `undefined` fields, so a provider that omits one leaves
|
|
64
|
+
* the existing column intact.
|
|
65
|
+
*
|
|
66
|
+
* Returns the updated user so a caller that issues a session can seed the
|
|
67
|
+
* cookie cache with the fresh row. Returns `undefined` when the policy is
|
|
68
|
+
* disabled or the update fails: a failed profile sync must not abort the link.
|
|
69
|
+
*/
|
|
70
|
+
declare function applyUpdateUserInfoOnLink(c: GenericEndpointContext, userId: string, userInfo: LinkedProviderProfile): Promise<User | undefined>;
|
|
45
71
|
//#endregion
|
|
46
|
-
export { handleOAuthUserInfo };
|
|
72
|
+
export { applyUpdateUserInfoOnLink, handleOAuthUserInfo };
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { isAPIError } from "../utils/is-api-error.mjs";
|
|
2
2
|
import { setAccountCookie } from "../cookies/session-store.mjs";
|
|
3
|
+
import { redirectOnError } from "./errors.mjs";
|
|
3
4
|
import { setTokenUtil } from "./utils.mjs";
|
|
4
5
|
import { createEmailVerificationToken } from "../api/routes/email-verification.mjs";
|
|
5
6
|
import { isDevelopment, logger } from "@better-auth/core/env";
|
|
@@ -8,8 +9,7 @@ async function handleOAuthUserInfo(c, opts) {
|
|
|
8
9
|
const { userInfo, account, callbackURL, disableSignUp, overrideUserInfo } = opts;
|
|
9
10
|
const dbUser = await c.context.internalAdapter.findOAuthUser(userInfo.email.toLowerCase(), account.accountId, account.providerId).catch((e) => {
|
|
10
11
|
logger.error("Better auth was unable to query your database.\nError: ", e);
|
|
11
|
-
|
|
12
|
-
throw c.redirect(`${errorURL}?error=internal_server_error`);
|
|
12
|
+
redirectOnError(c, c.context.options.onAPIError?.errorURL || `${c.context.baseURL}/error`, "internal_server_error");
|
|
13
13
|
});
|
|
14
14
|
let user = dbUser?.user;
|
|
15
15
|
const isRegister = !user;
|
|
@@ -46,6 +46,7 @@ async function handleOAuthUserInfo(c, opts) {
|
|
|
46
46
|
};
|
|
47
47
|
}
|
|
48
48
|
if (userInfo.emailVerified && !dbUser.user.emailVerified && userInfo.email.toLowerCase() === dbUser.user.email) await c.context.internalAdapter.updateUser(dbUser.user.id, { emailVerified: true });
|
|
49
|
+
user = await applyUpdateUserInfoOnLink(c, dbUser.user.id, userInfo) ?? user;
|
|
49
50
|
} else {
|
|
50
51
|
const freshTokens = c.context.options.account?.updateAccountOnSignIn !== false ? Object.fromEntries(Object.entries({
|
|
51
52
|
idToken: account.idToken,
|
|
@@ -96,7 +97,7 @@ async function handleOAuthUserInfo(c, opts) {
|
|
|
96
97
|
if (c.context.options.account?.storeAccountCookie) await setAccountCookie(c, createdAccount);
|
|
97
98
|
if (!userInfo.emailVerified && user && c.context.options.emailVerification?.sendOnSignUp && c.context.options.emailVerification?.sendVerificationEmail) {
|
|
98
99
|
const token = await createEmailVerificationToken(c.context.secret, user.email, void 0, c.context.options.emailVerification?.expiresIn);
|
|
99
|
-
const url = `${c.context.baseURL}/verify-email?token=${token}&callbackURL=${callbackURL}`;
|
|
100
|
+
const url = `${c.context.baseURL}/verify-email?token=${token}&callbackURL=${encodeURIComponent(callbackURL || "/")}`;
|
|
100
101
|
await c.context.runInBackgroundOrAwait(c.context.options.emailVerification.sendVerificationEmail({
|
|
101
102
|
user,
|
|
102
103
|
url,
|
|
@@ -137,5 +138,27 @@ async function handleOAuthUserInfo(c, opts) {
|
|
|
137
138
|
isRegister
|
|
138
139
|
};
|
|
139
140
|
}
|
|
141
|
+
/**
|
|
142
|
+
* Apply the `account.accountLinking.updateUserInfoOnLink` policy: when enabled,
|
|
143
|
+
* copy the freshly linked provider's profile onto the local user, matching the
|
|
144
|
+
* field set persisted on sign-up. The local `email` and `emailVerified` are
|
|
145
|
+
* never changed, so a link can't rebind the account's identity, and
|
|
146
|
+
* `updateUser` drops `undefined` fields, so a provider that omits one leaves
|
|
147
|
+
* the existing column intact.
|
|
148
|
+
*
|
|
149
|
+
* Returns the updated user so a caller that issues a session can seed the
|
|
150
|
+
* cookie cache with the fresh row. Returns `undefined` when the policy is
|
|
151
|
+
* disabled or the update fails: a failed profile sync must not abort the link.
|
|
152
|
+
*/
|
|
153
|
+
async function applyUpdateUserInfoOnLink(c, userId, userInfo) {
|
|
154
|
+
if (c.context.options.account?.accountLinking?.updateUserInfoOnLink !== true) return;
|
|
155
|
+
const { id: _id, email: _email, emailVerified: _emailVerified, ...profile } = userInfo;
|
|
156
|
+
try {
|
|
157
|
+
return await c.context.internalAdapter.updateUser(userId, profile);
|
|
158
|
+
} catch (e) {
|
|
159
|
+
c.context.logger.warn("Could not update user info on account link", e);
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
140
163
|
//#endregion
|
|
141
|
-
export { handleOAuthUserInfo };
|
|
164
|
+
export { applyUpdateUserInfoOnLink, handleOAuthUserInfo };
|
package/dist/oauth2/state.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { generateRandomString } from "../crypto/random.mjs";
|
|
2
|
+
import { redirectOnError } from "./errors.mjs";
|
|
2
3
|
import { setOAuthState } from "../api/state/oauth.mjs";
|
|
3
4
|
import { StateError, generateGenericState, parseGenericState } from "../state.mjs";
|
|
4
5
|
import { APIError, BASE_ERROR_CODES } from "@better-auth/core/error";
|
|
@@ -36,8 +37,13 @@ async function parseState(c) {
|
|
|
36
37
|
parsedData = await parseGenericState(c, state);
|
|
37
38
|
} catch (error) {
|
|
38
39
|
c.context.logger.error("Failed to parse state", error);
|
|
39
|
-
|
|
40
|
-
|
|
40
|
+
let code = "internal_server_error";
|
|
41
|
+
let redirectErrorURL = errorURL;
|
|
42
|
+
if (error instanceof StateError) {
|
|
43
|
+
code = error.code === "state_security_mismatch" ? "state_mismatch" : error.code;
|
|
44
|
+
redirectErrorURL = error.errorURL ?? errorURL;
|
|
45
|
+
}
|
|
46
|
+
redirectOnError(c, redirectErrorURL, code);
|
|
41
47
|
}
|
|
42
48
|
if (!parsedData.errorURL) parsedData.errorURL = errorURL;
|
|
43
49
|
if (parsedData) await setOAuthState(parsedData);
|
package/dist/package.mjs
CHANGED
|
@@ -6,14 +6,19 @@ function role(statements) {
|
|
|
6
6
|
let success = false;
|
|
7
7
|
for (const [requestedResource, requestedActions] of Object.entries(request)) {
|
|
8
8
|
const allowedActions = statements[requestedResource];
|
|
9
|
-
if (!allowedActions)
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
9
|
+
if (!allowedActions) {
|
|
10
|
+
if (connector === "AND") return {
|
|
11
|
+
success: false,
|
|
12
|
+
error: `You are not allowed to access resource: ${requestedResource}`
|
|
13
|
+
};
|
|
14
|
+
success = false;
|
|
15
|
+
continue;
|
|
16
|
+
}
|
|
17
|
+
if (Array.isArray(requestedActions)) success = requestedActions.length > 0 && requestedActions.every((requestedAction) => allowedActions.includes(requestedAction));
|
|
14
18
|
else if (typeof requestedActions === "object") {
|
|
15
19
|
const actions = requestedActions;
|
|
16
|
-
if (actions.
|
|
20
|
+
if (!Array.isArray(actions.actions) || actions.actions.length === 0) success = false;
|
|
21
|
+
else if (actions.connector === "OR") success = actions.actions.some((requestedAction) => allowedActions.includes(requestedAction));
|
|
17
22
|
else success = actions.actions.every((requestedAction) => allowedActions.includes(requestedAction));
|
|
18
23
|
} else throw new BetterAuthError("Invalid access control request");
|
|
19
24
|
if (success && connector === "OR") return { success };
|
|
@@ -42,10 +42,6 @@ const admin = (options) => {
|
|
|
42
42
|
});
|
|
43
43
|
return;
|
|
44
44
|
}
|
|
45
|
-
if (ctx && (ctx.path.startsWith("/callback") || ctx.path.startsWith("/oauth2/callback"))) {
|
|
46
|
-
const redirectURI = ctx.context.options.onAPIError?.errorURL || `${ctx.context.baseURL}/error`;
|
|
47
|
-
throw ctx.redirect(`${redirectURI}?error=banned&error_description=${opts.bannedUserMessage}`);
|
|
48
|
-
}
|
|
49
45
|
throw APIError.from("FORBIDDEN", {
|
|
50
46
|
message: opts.bannedUserMessage,
|
|
51
47
|
code: "BANNED_USER"
|
|
@@ -462,7 +462,7 @@ const banUser = (opts) => createAuthEndpoint("/admin/ban-user", {
|
|
|
462
462
|
banExpires: ctx.body.banExpiresIn ? getDate(ctx.body.banExpiresIn, "sec") : opts?.defaultBanExpiresIn ? getDate(opts.defaultBanExpiresIn, "sec") : void 0,
|
|
463
463
|
updatedAt: /* @__PURE__ */ new Date()
|
|
464
464
|
});
|
|
465
|
-
await ctx.context.internalAdapter.
|
|
465
|
+
await ctx.context.internalAdapter.deleteUserSessions(ctx.body.userId);
|
|
466
466
|
return ctx.json({ user: parseUserOutput(ctx.context.options, user) });
|
|
467
467
|
});
|
|
468
468
|
const impersonateUserBodySchema = z.object({ userId: z.coerce.string().meta({ description: "The user id" }) });
|
|
@@ -658,7 +658,7 @@ const revokeUserSessions = (opts) => createAuthEndpoint("/admin/revoke-user-sess
|
|
|
658
658
|
options: opts,
|
|
659
659
|
permissions: { session: ["revoke"] }
|
|
660
660
|
})) throw APIError.from("FORBIDDEN", ADMIN_ERROR_CODES.YOU_ARE_NOT_ALLOWED_TO_REVOKE_USERS_SESSIONS);
|
|
661
|
-
await ctx.context.internalAdapter.
|
|
661
|
+
await ctx.context.internalAdapter.deleteUserSessions(ctx.body.userId);
|
|
662
662
|
return ctx.json({ success: true });
|
|
663
663
|
});
|
|
664
664
|
const removeUserBodySchema = z.object({ userId: z.coerce.string().meta({ description: "The user id" }) });
|
|
@@ -703,7 +703,7 @@ const removeUser = (opts) => createAuthEndpoint("/admin/remove-user", {
|
|
|
703
703
|
})) throw APIError.from("FORBIDDEN", ADMIN_ERROR_CODES.YOU_ARE_NOT_ALLOWED_TO_DELETE_USERS);
|
|
704
704
|
if (ctx.body.userId === ctx.context.session.user.id) throw APIError.from("BAD_REQUEST", ADMIN_ERROR_CODES.YOU_CANNOT_REMOVE_YOURSELF);
|
|
705
705
|
if (!await ctx.context.internalAdapter.findUserById(ctx.body.userId)) throw APIError.from("NOT_FOUND", BASE_ERROR_CODES.USER_NOT_FOUND);
|
|
706
|
-
await ctx.context.internalAdapter.
|
|
706
|
+
await ctx.context.internalAdapter.deleteUserSessions(ctx.body.userId);
|
|
707
707
|
await ctx.context.internalAdapter.deleteUser(ctx.body.userId);
|
|
708
708
|
return ctx.json({ success: true });
|
|
709
709
|
});
|