better-auth 1.6.16 → 1.6.17
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 +2 -2
- package/dist/api/index.mjs +3 -4
- package/dist/api/middlewares/origin-check.mjs +5 -1
- package/dist/api/rate-limiter/index.mjs +259 -73
- package/dist/api/routes/account.mjs +22 -7
- package/dist/api/routes/callback.mjs +2 -2
- package/dist/api/routes/index.d.mts +1 -1
- package/dist/api/routes/password.mjs +3 -4
- package/dist/api/routes/session.d.mts +12 -1
- package/dist/api/routes/session.mjs +13 -1
- package/dist/api/routes/sign-in.mjs +5 -5
- package/dist/api/routes/sign-up.mjs +2 -2
- package/dist/api/routes/update-session.mjs +2 -3
- package/dist/api/routes/update-user.mjs +10 -12
- package/dist/auth/base.mjs +11 -7
- package/dist/client/equality.d.mts +19 -0
- package/dist/client/equality.mjs +42 -0
- package/dist/client/index.d.mts +5 -4
- package/dist/client/index.mjs +2 -1
- package/dist/client/path-to-object.d.mts +5 -2
- package/dist/client/plugins/index.d.mts +4 -1
- package/dist/client/plugins/index.mjs +4 -1
- package/dist/client/query.d.mts +4 -3
- package/dist/client/query.mjs +27 -17
- package/dist/client/session-atom.mjs +129 -4
- package/dist/client/session-refresh.d.mts +3 -18
- package/dist/client/session-refresh.mjs +38 -49
- package/dist/client/types.d.mts +2 -2
- package/dist/context/create-context.mjs +2 -1
- package/dist/context/store-capabilities.mjs +12 -0
- package/dist/cookies/index.mjs +25 -2
- package/dist/db/internal-adapter.mjs +51 -0
- package/dist/package.mjs +1 -1
- package/dist/plugins/access/access.mjs +49 -19
- package/dist/plugins/admin/routes.mjs +10 -3
- package/dist/plugins/captcha/constants.mjs +8 -1
- package/dist/plugins/captcha/index.mjs +8 -2
- package/dist/plugins/captcha/types.d.mts +21 -0
- package/dist/plugins/captcha/verify-handlers/captchafox.mjs +2 -0
- package/dist/plugins/captcha/verify-handlers/cloudflare-turnstile.mjs +7 -2
- package/dist/plugins/captcha/verify-handlers/google-recaptcha.mjs +7 -2
- package/dist/plugins/captcha/verify-handlers/h-captcha.mjs +2 -0
- package/dist/plugins/device-authorization/routes.mjs +16 -9
- package/dist/plugins/email-otp/routes.mjs +22 -52
- package/dist/plugins/generic-oauth/index.mjs +7 -2
- package/dist/plugins/generic-oauth/routes.mjs +16 -12
- package/dist/plugins/haveibeenpwned/index.d.mts +1 -1
- package/dist/plugins/haveibeenpwned/index.mjs +5 -1
- package/dist/plugins/index.d.mts +5 -1
- package/dist/plugins/index.mjs +4 -1
- package/dist/plugins/jwt/index.mjs +2 -2
- package/dist/plugins/mcp/client/index.mjs +1 -0
- package/dist/plugins/mcp/index.mjs +8 -0
- package/dist/plugins/multi-session/index.mjs +7 -5
- package/dist/plugins/oauth-popup/client.d.mts +82 -0
- package/dist/plugins/oauth-popup/client.mjs +203 -0
- package/dist/plugins/oauth-popup/constants.d.mts +11 -0
- package/dist/plugins/oauth-popup/constants.mjs +11 -0
- package/dist/plugins/oauth-popup/error-codes.d.mts +11 -0
- package/dist/plugins/oauth-popup/error-codes.mjs +10 -0
- package/dist/plugins/oauth-popup/index.d.mts +67 -0
- package/dist/plugins/oauth-popup/index.mjs +227 -0
- package/dist/plugins/oauth-popup/types.d.mts +30 -0
- package/dist/plugins/oauth-proxy/index.mjs +2 -2
- package/dist/plugins/oauth-proxy/utils.mjs +16 -2
- package/dist/plugins/oidc-provider/index.mjs +10 -0
- package/dist/plugins/one-tap/client.mjs +12 -6
- package/dist/plugins/one-tap/index.d.mts +1 -0
- package/dist/plugins/one-tap/index.mjs +9 -5
- package/dist/plugins/one-time-token/index.mjs +1 -3
- package/dist/plugins/open-api/generator.mjs +7 -4
- package/dist/plugins/organization/adapter.d.mts +29 -1
- package/dist/plugins/organization/adapter.mjs +66 -6
- package/dist/plugins/organization/routes/crud-invites.mjs +49 -34
- package/dist/plugins/organization/routes/crud-members.mjs +42 -6
- package/dist/plugins/organization/routes/crud-team.mjs +36 -3
- package/dist/plugins/phone-number/routes.mjs +41 -36
- package/dist/plugins/siwe/index.mjs +2 -3
- package/dist/plugins/two-factor/backup-codes/index.mjs +1 -1
- package/dist/plugins/two-factor/otp/index.mjs +11 -13
- package/dist/plugins/two-factor/totp/index.mjs +1 -1
- package/dist/plugins/two-factor/verify-two-factor.mjs +6 -2
- package/dist/plugins/username/index.mjs +6 -6
- package/package.json +9 -9
|
@@ -158,17 +158,12 @@ const otp2fa = (options) => {
|
|
|
158
158
|
} }
|
|
159
159
|
}, async (ctx) => {
|
|
160
160
|
const { session, key, valid, invalid } = await verifyTwoFactor(ctx);
|
|
161
|
-
const
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
if (toCheckOtp) await ctx.context.internalAdapter.deleteVerificationByIdentifier(`2fa-otp-${key}`);
|
|
165
|
-
throw APIError.from("BAD_REQUEST", TWO_FACTOR_ERROR_CODES.OTP_HAS_EXPIRED);
|
|
166
|
-
}
|
|
161
|
+
const consumed = await ctx.context.internalAdapter.consumeVerificationValue(`2fa-otp-${key}`);
|
|
162
|
+
if (!consumed) throw APIError.from("BAD_REQUEST", TWO_FACTOR_ERROR_CODES.OTP_HAS_EXPIRED);
|
|
163
|
+
const [otp, counter] = consumed.value?.split(":") ?? [];
|
|
167
164
|
const allowedAttempts = options?.allowedAttempts || 5;
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
throw APIError.from("BAD_REQUEST", TWO_FACTOR_ERROR_CODES.TOO_MANY_ATTEMPTS_REQUEST_NEW_CODE);
|
|
171
|
-
}
|
|
165
|
+
const attempts = parseInt(counter, 10) || 0;
|
|
166
|
+
if (attempts >= allowedAttempts) throw APIError.from("BAD_REQUEST", TWO_FACTOR_ERROR_CODES.TOO_MANY_ATTEMPTS_REQUEST_NEW_CODE);
|
|
172
167
|
const [storedValue, inputValue] = await decryptOrHashForComparison(ctx, otp, ctx.body.code);
|
|
173
168
|
if (constantTimeEqual(new TextEncoder().encode(storedValue), new TextEncoder().encode(inputValue))) {
|
|
174
169
|
if (!session.user.twoFactorEnabled) {
|
|
@@ -186,10 +181,13 @@ const otp2fa = (options) => {
|
|
|
186
181
|
});
|
|
187
182
|
}
|
|
188
183
|
return valid(ctx);
|
|
189
|
-
} else {
|
|
190
|
-
await ctx.context.internalAdapter.updateVerificationByIdentifier(`2fa-otp-${key}`, { value: `${otp}:${(parseInt(counter, 10) || 0) + 1}` });
|
|
191
|
-
return invalid("INVALID_CODE");
|
|
192
184
|
}
|
|
185
|
+
await ctx.context.internalAdapter.createVerificationValue({
|
|
186
|
+
value: `${otp}:${attempts + 1}`,
|
|
187
|
+
identifier: `2fa-otp-${key}`,
|
|
188
|
+
expiresAt: consumed.expiresAt
|
|
189
|
+
});
|
|
190
|
+
return invalid("INVALID_CODE");
|
|
193
191
|
})
|
|
194
192
|
}
|
|
195
193
|
};
|
|
@@ -23,12 +23,16 @@ async function verifyTwoFactor(ctx) {
|
|
|
23
23
|
const dontRememberMe = await ctx.getSignedCookie(ctx.context.authCookies.dontRememberToken.name, ctx.context.secret);
|
|
24
24
|
return {
|
|
25
25
|
valid: async (ctx) => {
|
|
26
|
-
const
|
|
26
|
+
const consumed = await ctx.context.internalAdapter.consumeVerificationValue(signedTwoFactorCookie);
|
|
27
|
+
if (!consumed || consumed.value !== user.id) {
|
|
28
|
+
expireCookie(ctx, twoFactorCookie);
|
|
29
|
+
throw APIError.from("UNAUTHORIZED", TWO_FACTOR_ERROR_CODES.INVALID_TWO_FACTOR_COOKIE);
|
|
30
|
+
}
|
|
31
|
+
const session = await ctx.context.internalAdapter.createSession(consumed.value, !!dontRememberMe);
|
|
27
32
|
if (!session) throw APIError.from("INTERNAL_SERVER_ERROR", {
|
|
28
33
|
message: "failed to create session",
|
|
29
34
|
code: "FAILED_TO_CREATE_SESSION"
|
|
30
35
|
});
|
|
31
|
-
await ctx.context.internalAdapter.deleteVerificationByIdentifier(signedTwoFactorCookie);
|
|
32
36
|
await setSessionCookie(ctx, {
|
|
33
37
|
session,
|
|
34
38
|
user
|
|
@@ -145,18 +145,18 @@ const username = (options) => {
|
|
|
145
145
|
} }
|
|
146
146
|
}, async (ctx) => {
|
|
147
147
|
if (!ctx.body.username || !ctx.body.password) {
|
|
148
|
-
ctx.context.logger.
|
|
148
|
+
ctx.context.logger.warn("Username or password not found");
|
|
149
149
|
throw APIError.from("UNAUTHORIZED", USERNAME_ERROR_CODES.INVALID_USERNAME_OR_PASSWORD);
|
|
150
150
|
}
|
|
151
151
|
const username = options?.validationOrder?.username === "pre-normalization" ? normalizer(ctx.body.username) : ctx.body.username;
|
|
152
152
|
const minUsernameLength = options?.minUsernameLength || 3;
|
|
153
153
|
const maxUsernameLength = options?.maxUsernameLength || 30;
|
|
154
154
|
if (username.length < minUsernameLength) {
|
|
155
|
-
ctx.context.logger.
|
|
155
|
+
ctx.context.logger.warn("Username too short");
|
|
156
156
|
throw APIError.from("UNPROCESSABLE_ENTITY", USERNAME_ERROR_CODES.USERNAME_TOO_SHORT);
|
|
157
157
|
}
|
|
158
158
|
if (username.length > maxUsernameLength) {
|
|
159
|
-
ctx.context.logger.
|
|
159
|
+
ctx.context.logger.warn("Username too long");
|
|
160
160
|
throw APIError.from("UNPROCESSABLE_ENTITY", USERNAME_ERROR_CODES.USERNAME_TOO_LONG);
|
|
161
161
|
}
|
|
162
162
|
if (!await (options?.usernameValidator || defaultUsernameValidator)(username)) throw APIError.from("UNPROCESSABLE_ENTITY", USERNAME_ERROR_CODES.INVALID_USERNAME);
|
|
@@ -169,7 +169,7 @@ const username = (options) => {
|
|
|
169
169
|
});
|
|
170
170
|
if (!user) {
|
|
171
171
|
await ctx.context.password.hash(ctx.body.password);
|
|
172
|
-
ctx.context.logger.
|
|
172
|
+
ctx.context.logger.warn("User not found");
|
|
173
173
|
throw APIError.from("UNAUTHORIZED", USERNAME_ERROR_CODES.INVALID_USERNAME_OR_PASSWORD);
|
|
174
174
|
}
|
|
175
175
|
const account = await ctx.context.adapter.findOne({
|
|
@@ -185,14 +185,14 @@ const username = (options) => {
|
|
|
185
185
|
if (!account) throw APIError.from("UNAUTHORIZED", USERNAME_ERROR_CODES.INVALID_USERNAME_OR_PASSWORD);
|
|
186
186
|
const currentPassword = account?.password;
|
|
187
187
|
if (!currentPassword) {
|
|
188
|
-
ctx.context.logger.
|
|
188
|
+
ctx.context.logger.warn("Password not found");
|
|
189
189
|
throw APIError.from("UNAUTHORIZED", USERNAME_ERROR_CODES.INVALID_USERNAME_OR_PASSWORD);
|
|
190
190
|
}
|
|
191
191
|
if (!await ctx.context.password.verify({
|
|
192
192
|
hash: currentPassword,
|
|
193
193
|
password: ctx.body.password
|
|
194
194
|
})) {
|
|
195
|
-
ctx.context.logger.
|
|
195
|
+
ctx.context.logger.warn("Invalid password");
|
|
196
196
|
throw APIError.from("UNAUTHORIZED", USERNAME_ERROR_CODES.INVALID_USERNAME_OR_PASSWORD);
|
|
197
197
|
}
|
|
198
198
|
if (ctx.context.options?.emailAndPassword?.requireEmailVerification && !user.emailVerified) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "better-auth",
|
|
3
|
-
"version": "1.6.
|
|
3
|
+
"version": "1.6.17",
|
|
4
4
|
"description": "The most comprehensive authentication framework for TypeScript.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -480,7 +480,7 @@
|
|
|
480
480
|
},
|
|
481
481
|
"dependencies": {
|
|
482
482
|
"@better-auth/utils": "0.4.1",
|
|
483
|
-
"@better-fetch/fetch": "1.
|
|
483
|
+
"@better-fetch/fetch": "1.3.0",
|
|
484
484
|
"@noble/ciphers": "^2.1.1",
|
|
485
485
|
"@noble/hashes": "^2.0.1",
|
|
486
486
|
"better-call": "1.3.6",
|
|
@@ -489,13 +489,13 @@
|
|
|
489
489
|
"kysely": "^0.28.17 || ^0.29.0",
|
|
490
490
|
"nanostores": "^1.1.1",
|
|
491
491
|
"zod": "^4.3.6",
|
|
492
|
-
"@better-auth/core": "1.6.
|
|
493
|
-
"@better-auth/drizzle-adapter": "1.6.
|
|
494
|
-
"@better-auth/kysely-adapter": "1.6.
|
|
495
|
-
"@better-auth/memory-adapter": "1.6.
|
|
496
|
-
"@better-auth/mongo-adapter": "1.6.
|
|
497
|
-
"@better-auth/prisma-adapter": "1.6.
|
|
498
|
-
"@better-auth/telemetry": "1.6.
|
|
492
|
+
"@better-auth/core": "1.6.17",
|
|
493
|
+
"@better-auth/drizzle-adapter": "1.6.17",
|
|
494
|
+
"@better-auth/kysely-adapter": "1.6.17",
|
|
495
|
+
"@better-auth/memory-adapter": "1.6.17",
|
|
496
|
+
"@better-auth/mongo-adapter": "1.6.17",
|
|
497
|
+
"@better-auth/prisma-adapter": "1.6.17",
|
|
498
|
+
"@better-auth/telemetry": "1.6.17"
|
|
499
499
|
},
|
|
500
500
|
"devDependencies": {
|
|
501
501
|
"@lynx-js/react": "^0.116.3",
|