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.
Files changed (84) hide show
  1. package/dist/api/index.d.mts +2 -2
  2. package/dist/api/index.mjs +3 -4
  3. package/dist/api/middlewares/origin-check.mjs +5 -1
  4. package/dist/api/rate-limiter/index.mjs +259 -73
  5. package/dist/api/routes/account.mjs +22 -7
  6. package/dist/api/routes/callback.mjs +2 -2
  7. package/dist/api/routes/index.d.mts +1 -1
  8. package/dist/api/routes/password.mjs +3 -4
  9. package/dist/api/routes/session.d.mts +12 -1
  10. package/dist/api/routes/session.mjs +13 -1
  11. package/dist/api/routes/sign-in.mjs +5 -5
  12. package/dist/api/routes/sign-up.mjs +2 -2
  13. package/dist/api/routes/update-session.mjs +2 -3
  14. package/dist/api/routes/update-user.mjs +10 -12
  15. package/dist/auth/base.mjs +11 -7
  16. package/dist/client/equality.d.mts +19 -0
  17. package/dist/client/equality.mjs +42 -0
  18. package/dist/client/index.d.mts +5 -4
  19. package/dist/client/index.mjs +2 -1
  20. package/dist/client/path-to-object.d.mts +5 -2
  21. package/dist/client/plugins/index.d.mts +4 -1
  22. package/dist/client/plugins/index.mjs +4 -1
  23. package/dist/client/query.d.mts +4 -3
  24. package/dist/client/query.mjs +27 -17
  25. package/dist/client/session-atom.mjs +129 -4
  26. package/dist/client/session-refresh.d.mts +3 -18
  27. package/dist/client/session-refresh.mjs +38 -49
  28. package/dist/client/types.d.mts +2 -2
  29. package/dist/context/create-context.mjs +2 -1
  30. package/dist/context/store-capabilities.mjs +12 -0
  31. package/dist/cookies/index.mjs +25 -2
  32. package/dist/db/internal-adapter.mjs +51 -0
  33. package/dist/package.mjs +1 -1
  34. package/dist/plugins/access/access.mjs +49 -19
  35. package/dist/plugins/admin/routes.mjs +10 -3
  36. package/dist/plugins/captcha/constants.mjs +8 -1
  37. package/dist/plugins/captcha/index.mjs +8 -2
  38. package/dist/plugins/captcha/types.d.mts +21 -0
  39. package/dist/plugins/captcha/verify-handlers/captchafox.mjs +2 -0
  40. package/dist/plugins/captcha/verify-handlers/cloudflare-turnstile.mjs +7 -2
  41. package/dist/plugins/captcha/verify-handlers/google-recaptcha.mjs +7 -2
  42. package/dist/plugins/captcha/verify-handlers/h-captcha.mjs +2 -0
  43. package/dist/plugins/device-authorization/routes.mjs +16 -9
  44. package/dist/plugins/email-otp/routes.mjs +22 -52
  45. package/dist/plugins/generic-oauth/index.mjs +7 -2
  46. package/dist/plugins/generic-oauth/routes.mjs +16 -12
  47. package/dist/plugins/haveibeenpwned/index.d.mts +1 -1
  48. package/dist/plugins/haveibeenpwned/index.mjs +5 -1
  49. package/dist/plugins/index.d.mts +5 -1
  50. package/dist/plugins/index.mjs +4 -1
  51. package/dist/plugins/jwt/index.mjs +2 -2
  52. package/dist/plugins/mcp/client/index.mjs +1 -0
  53. package/dist/plugins/mcp/index.mjs +8 -0
  54. package/dist/plugins/multi-session/index.mjs +7 -5
  55. package/dist/plugins/oauth-popup/client.d.mts +82 -0
  56. package/dist/plugins/oauth-popup/client.mjs +203 -0
  57. package/dist/plugins/oauth-popup/constants.d.mts +11 -0
  58. package/dist/plugins/oauth-popup/constants.mjs +11 -0
  59. package/dist/plugins/oauth-popup/error-codes.d.mts +11 -0
  60. package/dist/plugins/oauth-popup/error-codes.mjs +10 -0
  61. package/dist/plugins/oauth-popup/index.d.mts +67 -0
  62. package/dist/plugins/oauth-popup/index.mjs +227 -0
  63. package/dist/plugins/oauth-popup/types.d.mts +30 -0
  64. package/dist/plugins/oauth-proxy/index.mjs +2 -2
  65. package/dist/plugins/oauth-proxy/utils.mjs +16 -2
  66. package/dist/plugins/oidc-provider/index.mjs +10 -0
  67. package/dist/plugins/one-tap/client.mjs +12 -6
  68. package/dist/plugins/one-tap/index.d.mts +1 -0
  69. package/dist/plugins/one-tap/index.mjs +9 -5
  70. package/dist/plugins/one-time-token/index.mjs +1 -3
  71. package/dist/plugins/open-api/generator.mjs +7 -4
  72. package/dist/plugins/organization/adapter.d.mts +29 -1
  73. package/dist/plugins/organization/adapter.mjs +66 -6
  74. package/dist/plugins/organization/routes/crud-invites.mjs +49 -34
  75. package/dist/plugins/organization/routes/crud-members.mjs +42 -6
  76. package/dist/plugins/organization/routes/crud-team.mjs +36 -3
  77. package/dist/plugins/phone-number/routes.mjs +41 -36
  78. package/dist/plugins/siwe/index.mjs +2 -3
  79. package/dist/plugins/two-factor/backup-codes/index.mjs +1 -1
  80. package/dist/plugins/two-factor/otp/index.mjs +11 -13
  81. package/dist/plugins/two-factor/totp/index.mjs +1 -1
  82. package/dist/plugins/two-factor/verify-two-factor.mjs +6 -2
  83. package/dist/plugins/username/index.mjs +6 -6
  84. 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 toCheckOtp = await ctx.context.internalAdapter.findVerificationValue(`2fa-otp-${key}`);
162
- const [otp, counter] = toCheckOtp?.value?.split(":") ?? [];
163
- if (!toCheckOtp || toCheckOtp.expiresAt < /* @__PURE__ */ new Date()) {
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
- if (parseInt(counter) >= allowedAttempts) {
169
- await ctx.context.internalAdapter.deleteVerificationByIdentifier(`2fa-otp-${key}`);
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
  };
@@ -28,7 +28,7 @@ const totp2fa = (options) => {
28
28
  id: "totp",
29
29
  version: PACKAGE_VERSION,
30
30
  endpoints: {
31
- generateTOTP: createAuthEndpoint({
31
+ generateTOTP: createAuthEndpoint.serverOnly({
32
32
  method: "POST",
33
33
  body: generateTOTPBodySchema,
34
34
  metadata: { openapi: {
@@ -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 session = await ctx.context.internalAdapter.createSession(verificationToken.value, !!dontRememberMe);
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.error("Username or password not found");
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.error("Username too short", { username });
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.error("Username too long", { username });
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.error("User not found", { username });
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.error("Password not found", { username });
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.error("Invalid password");
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.16",
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.2.2",
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.16",
493
- "@better-auth/drizzle-adapter": "1.6.16",
494
- "@better-auth/kysely-adapter": "1.6.16",
495
- "@better-auth/memory-adapter": "1.6.16",
496
- "@better-auth/mongo-adapter": "1.6.16",
497
- "@better-auth/prisma-adapter": "1.6.16",
498
- "@better-auth/telemetry": "1.6.16"
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",