better-auth 1.7.7 → 1.7.9
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.mjs +6 -2
- package/dist/api/routes/password.mjs +5 -11
- package/dist/api/routes/sign-in.mjs +0 -1
- package/dist/api/routes/sign-up.mjs +1 -1
- package/dist/api/routes/update-user.mjs +10 -5
- package/dist/cookies/index.d.mts +5 -5
- package/dist/db/get-migration.mjs +13 -11
- package/dist/db/get-schema.mjs +27 -11
- package/dist/db/internal-adapter.mjs +73 -56
- package/dist/db/to-zod.d.mts +8 -6
- package/dist/db/to-zod.mjs +12 -2
- package/dist/index.mjs +0 -1
- package/dist/package.mjs +1 -1
- package/dist/plugins/custom-session/index.mjs +6 -13
- package/dist/plugins/email-otp/routes.mjs +19 -10
- package/dist/plugins/generic-oauth/index.d.mts +13 -0
- package/dist/plugins/generic-oauth/index.mjs +232 -189
- package/dist/plugins/last-login-method/index.mjs +3 -3
- package/dist/plugins/magic-link/index.mjs +6 -12
- package/dist/plugins/open-api/index.mjs +0 -1
- package/dist/plugins/organization/routes/crud-members.d.mts +1 -42
- package/dist/plugins/organization/routes/crud-members.mjs +1 -1
- package/dist/plugins/phone-number/routes.mjs +33 -16
- package/dist/utils/password.mjs +33 -1
- package/package.json +13 -13
- package/dist/utils/index.mjs +0 -5
|
@@ -4,8 +4,7 @@ import { generateRandomString } from "../../crypto/random.mjs";
|
|
|
4
4
|
import { setSessionCookie } from "../../cookies/index.mjs";
|
|
5
5
|
import { getSessionFromCtx } from "../../api/routes/session.mjs";
|
|
6
6
|
import { HIDE_METADATA } from "../../utils/hide-metadata.mjs";
|
|
7
|
-
import { assertPasswordNotTooLong, assertPasswordNotTooShort } from "../../utils/password.mjs";
|
|
8
|
-
import "../../utils/index.mjs";
|
|
7
|
+
import { assertPasswordNotTooLong, assertPasswordNotTooShort, resetCredentialPassword } from "../../utils/password.mjs";
|
|
9
8
|
import { PHONE_NUMBER_ERROR_CODES } from "./error-codes.mjs";
|
|
10
9
|
import { APIError, BASE_ERROR_CODES } from "@better-auth/core/error";
|
|
11
10
|
import { createAuthEndpoint } from "@better-auth/core/api";
|
|
@@ -176,15 +175,19 @@ const sendPhoneNumberOTP = (opts) => createAuthEndpoint("/phone-number/send-otp"
|
|
|
176
175
|
async function verifyAndConsumePhoneNumberOTP(ctx, opts, body) {
|
|
177
176
|
const { phoneNumber, code } = body;
|
|
178
177
|
if (opts.verifyOTP) {
|
|
179
|
-
|
|
180
|
-
phoneNumber,
|
|
181
|
-
code
|
|
182
|
-
}, ctx)) throw APIError.from("BAD_REQUEST", PHONE_NUMBER_ERROR_CODES.INVALID_OTP);
|
|
183
|
-
await ctx.context.internalAdapter.deleteVerificationByIdentifier(phoneNumber);
|
|
178
|
+
await verifyWithCustomVerifier(ctx, opts.verifyOTP, phoneNumber, code, phoneNumber);
|
|
184
179
|
return;
|
|
185
180
|
}
|
|
186
181
|
await verifyPhoneNumberOTP(ctx, opts, phoneNumber, code);
|
|
187
182
|
}
|
|
183
|
+
/** Checks the code with `verifyOTP`, then drops the code stored under `identifier`. */
|
|
184
|
+
async function verifyWithCustomVerifier(ctx, verifyOTP, phoneNumber, code, identifier) {
|
|
185
|
+
if (!await verifyOTP({
|
|
186
|
+
phoneNumber,
|
|
187
|
+
code
|
|
188
|
+
}, ctx)) throw APIError.from("BAD_REQUEST", PHONE_NUMBER_ERROR_CODES.INVALID_OTP);
|
|
189
|
+
await ctx.context.internalAdapter.deleteVerificationByIdentifier(identifier);
|
|
190
|
+
}
|
|
188
191
|
/**
|
|
189
192
|
* ### API Methods
|
|
190
193
|
*
|
|
@@ -460,7 +463,17 @@ const resetPasswordPhoneNumber = (opts) => createAuthEndpoint("/phone-number/res
|
|
|
460
463
|
} }
|
|
461
464
|
} }
|
|
462
465
|
}, async (ctx) => {
|
|
463
|
-
|
|
466
|
+
const phoneResetIdentifier = `${ctx.body.phoneNumber}-request-password-reset`;
|
|
467
|
+
let otpConsumed = false;
|
|
468
|
+
if (opts.verifyOTP) {
|
|
469
|
+
assertPasswordNotTooShort(ctx, ctx.body.newPassword);
|
|
470
|
+
assertPasswordNotTooLong(ctx, ctx.body.newPassword);
|
|
471
|
+
await verifyWithCustomVerifier(ctx, opts.verifyOTP, ctx.body.phoneNumber, ctx.body.otp, phoneResetIdentifier);
|
|
472
|
+
otpConsumed = true;
|
|
473
|
+
} else if (!await matchesUsableStoredPhoneNumberOTP(ctx, opts, phoneResetIdentifier, ctx.body.otp)) {
|
|
474
|
+
await verifyPhoneNumberOTP(ctx, opts, phoneResetIdentifier, ctx.body.otp);
|
|
475
|
+
otpConsumed = true;
|
|
476
|
+
}
|
|
464
477
|
const user = await ctx.context.adapter.findOne({
|
|
465
478
|
model: "user",
|
|
466
479
|
where: [{
|
|
@@ -472,15 +485,8 @@ const resetPasswordPhoneNumber = (opts) => createAuthEndpoint("/phone-number/res
|
|
|
472
485
|
assertPasswordNotTooShort(ctx, ctx.body.newPassword);
|
|
473
486
|
assertPasswordNotTooLong(ctx, ctx.body.newPassword);
|
|
474
487
|
const hashedPassword = await ctx.context.password.hash(ctx.body.newPassword);
|
|
475
|
-
|
|
476
|
-
userId: user.id,
|
|
477
|
-
providerId: "credential",
|
|
478
|
-
accountId: user.id,
|
|
479
|
-
password: hashedPassword
|
|
480
|
-
});
|
|
481
|
-
else await ctx.context.internalAdapter.updatePassword(user.id, hashedPassword);
|
|
488
|
+
await resetCredentialPassword(ctx, user.id, hashedPassword, otpConsumed ? void 0 : () => verifyPhoneNumberOTP(ctx, opts, phoneResetIdentifier, ctx.body.otp));
|
|
482
489
|
if (ctx.context.options.emailAndPassword?.onPasswordReset) await ctx.context.options.emailAndPassword.onPasswordReset({ user }, ctx.request);
|
|
483
|
-
if (ctx.context.options.emailAndPassword?.revokeSessionsOnPasswordReset) await ctx.context.internalAdapter.deleteUserSessions(user.id);
|
|
484
490
|
return ctx.json({ status: true });
|
|
485
491
|
});
|
|
486
492
|
/**
|
|
@@ -519,6 +525,17 @@ async function verifyPhoneNumberOTP(ctx, opts, identifier, providedCode) {
|
|
|
519
525
|
throw APIError.from("BAD_REQUEST", PHONE_NUMBER_ERROR_CODES.INVALID_OTP);
|
|
520
526
|
}
|
|
521
527
|
}
|
|
528
|
+
/**
|
|
529
|
+
* Reports whether the provided code matches a stored, unexpired record with
|
|
530
|
+
* attempts left, without consuming it or counting an attempt.
|
|
531
|
+
*/
|
|
532
|
+
async function matchesUsableStoredPhoneNumberOTP(ctx, opts, identifier, providedCode) {
|
|
533
|
+
const existing = await ctx.context.internalAdapter.findVerificationValue(identifier);
|
|
534
|
+
if (!existing || existing.expiresAt < /* @__PURE__ */ new Date()) return false;
|
|
535
|
+
const [otpValue, rawAttempts] = existing.value.split(":");
|
|
536
|
+
if (parseVerificationAttempts(rawAttempts) >= (opts?.allowedAttempts ?? 3)) return false;
|
|
537
|
+
return otpValue === providedCode;
|
|
538
|
+
}
|
|
522
539
|
function parseVerificationAttempts(value) {
|
|
523
540
|
const attempts = Number(value ?? 0);
|
|
524
541
|
return Number.isSafeInteger(attempts) && attempts > 0 ? attempts : 0;
|
package/dist/utils/password.mjs
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { runWithTransaction } from "@better-auth/core/context";
|
|
1
2
|
import { APIError, BASE_ERROR_CODES } from "@better-auth/core/error";
|
|
2
3
|
//#region src/utils/password.ts
|
|
3
4
|
function assertPasswordNotTooShort(ctx, password) {
|
|
@@ -12,6 +13,37 @@ function assertPasswordNotTooLong(ctx, password) {
|
|
|
12
13
|
throw APIError.from("BAD_REQUEST", BASE_ERROR_CODES.PASSWORD_TOO_LONG);
|
|
13
14
|
}
|
|
14
15
|
}
|
|
16
|
+
/**
|
|
17
|
+
* Sets a user's credential password after a reset, revoking their sessions
|
|
18
|
+
* when `emailAndPassword.revokeSessionsOnPasswordReset` is enabled.
|
|
19
|
+
*
|
|
20
|
+
* `consumeResetToken` spends the single-use reset token or OTP and throws when
|
|
21
|
+
* it is no longer valid. It runs first inside the same transaction as
|
|
22
|
+
* revocation and the password write, so a failure in any of them leaves all
|
|
23
|
+
* three unchanged and the user can retry. Verification values held only in
|
|
24
|
+
* secondary storage are consumed with `getAndDelete`, which can't roll back,
|
|
25
|
+
* so a later failure there still spends the token. Secondary-storage sessions are
|
|
26
|
+
* removed by an after-commit hook, so they can't roll back with the write.
|
|
27
|
+
* Sessions are revoked before the write so that on an adapter without
|
|
28
|
+
* transactions a failure still keeps the old password rather than committing a
|
|
29
|
+
* new one while existing sessions stay valid.
|
|
30
|
+
*/
|
|
31
|
+
async function resetCredentialPassword(ctx, userId, passwordHash, consumeResetToken) {
|
|
32
|
+
await runWithTransaction(ctx.context.adapter, async () => {
|
|
33
|
+
await consumeResetToken?.();
|
|
34
|
+
if (ctx.context.options.emailAndPassword?.revokeSessionsOnPasswordReset) await ctx.context.internalAdapter.deleteUserSessions(userId);
|
|
35
|
+
if (!await ctx.context.internalAdapter.findCredentialAccount(userId)) {
|
|
36
|
+
await ctx.context.internalAdapter.createAccount({
|
|
37
|
+
userId,
|
|
38
|
+
providerId: "credential",
|
|
39
|
+
accountId: userId,
|
|
40
|
+
password: passwordHash
|
|
41
|
+
});
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
await ctx.context.internalAdapter.updatePassword(userId, passwordHash);
|
|
45
|
+
});
|
|
46
|
+
}
|
|
15
47
|
async function validatePassword(ctx, data) {
|
|
16
48
|
assertPasswordNotTooLong(ctx, data.password);
|
|
17
49
|
const credentialAccount = await ctx.context.internalAdapter.findCredentialAccount(data.userId);
|
|
@@ -43,4 +75,4 @@ async function shouldRequirePassword(ctx, userId, allowPasswordless) {
|
|
|
43
75
|
return Boolean(credentialAccount?.password);
|
|
44
76
|
}
|
|
45
77
|
//#endregion
|
|
46
|
-
export { assertPasswordNotTooLong, assertPasswordNotTooShort, checkPassword, shouldRequirePassword, validatePassword };
|
|
78
|
+
export { assertPasswordNotTooLong, assertPasswordNotTooShort, checkPassword, resetCredentialPassword, shouldRequirePassword, validatePassword };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "better-auth",
|
|
3
|
-
"version": "1.7.
|
|
3
|
+
"version": "1.7.9",
|
|
4
4
|
"description": "The most comprehensive authentication framework for TypeScript.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -463,23 +463,23 @@
|
|
|
463
463
|
}
|
|
464
464
|
},
|
|
465
465
|
"dependencies": {
|
|
466
|
-
"@better-auth/utils": "0.
|
|
466
|
+
"@better-auth/utils": "0.5.0",
|
|
467
467
|
"@better-fetch/fetch": "1.3.2",
|
|
468
468
|
"@noble/ciphers": "^2.2.0",
|
|
469
469
|
"@noble/hashes": "^2.2.0",
|
|
470
|
-
"better-call": "1.4.
|
|
470
|
+
"better-call": "1.4.2",
|
|
471
471
|
"defu": "^6.1.4",
|
|
472
472
|
"jose": "^6.2.3",
|
|
473
473
|
"kysely": "^0.28.17 || ^0.29.0",
|
|
474
474
|
"nanostores": "^1.3.0",
|
|
475
475
|
"zod": "^4.5.4",
|
|
476
|
-
"@better-auth/core": "1.7.
|
|
477
|
-
"@better-auth/drizzle-adapter": "1.7.
|
|
478
|
-
"@better-auth/kysely-adapter": "1.7.
|
|
479
|
-
"@better-auth/memory-adapter": "1.7.
|
|
480
|
-
"@better-auth/mongo-adapter": "1.7.
|
|
481
|
-
"@better-auth/prisma-adapter": "1.7.
|
|
482
|
-
"@better-auth/telemetry": "1.7.
|
|
476
|
+
"@better-auth/core": "1.7.9",
|
|
477
|
+
"@better-auth/drizzle-adapter": "1.7.9",
|
|
478
|
+
"@better-auth/kysely-adapter": "1.7.9",
|
|
479
|
+
"@better-auth/memory-adapter": "1.7.9",
|
|
480
|
+
"@better-auth/mongo-adapter": "1.7.9",
|
|
481
|
+
"@better-auth/prisma-adapter": "1.7.9",
|
|
482
|
+
"@better-auth/telemetry": "1.7.9"
|
|
483
483
|
},
|
|
484
484
|
"devDependencies": {
|
|
485
485
|
"@lynx-js/react": "^0.121.2",
|
|
@@ -487,8 +487,8 @@
|
|
|
487
487
|
"@opentelemetry/sdk-trace-base": "^2.10.0",
|
|
488
488
|
"@opentelemetry/sdk-trace-node": "^2.10.0",
|
|
489
489
|
"@sveltejs/kit": "^2.70.1",
|
|
490
|
-
"@tanstack/react-start": "^1.168.
|
|
491
|
-
"@tanstack/solid-start": "^1.168.
|
|
490
|
+
"@tanstack/react-start": "^1.168.60",
|
|
491
|
+
"@tanstack/solid-start": "^1.168.57",
|
|
492
492
|
"@types/bun": "^1.3.14",
|
|
493
493
|
"@types/google.accounts": "^0.0.18",
|
|
494
494
|
"@types/pg": "^8.20.0",
|
|
@@ -511,7 +511,7 @@
|
|
|
511
511
|
"peerDependencies": {
|
|
512
512
|
"@lynx-js/react": "*",
|
|
513
513
|
"@prisma/client": "^5.0.0 || ^6.0.0 || ^7.0.0",
|
|
514
|
-
"@sveltejs/kit": "^2.0.0",
|
|
514
|
+
"@sveltejs/kit": "^2.0.0 || ^3.0.0",
|
|
515
515
|
"@tanstack/react-start": "^1.0.0",
|
|
516
516
|
"@tanstack/solid-start": "^1.0.0",
|
|
517
517
|
"drizzle-kit": ">=0.31.4 || >=1.0.0-beta.1",
|