From 8fe651400a2cd03c427874beb55f74446ec6a9e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A9l=20Solano?= Date: Thu, 19 Feb 2026 06:19:45 +0100 Subject: [PATCH] fix(phone-number): call callback on password reset (#8046) --- .../plugins/phone-number/phone-number.test.ts | 76 +++++++++++++++++++ .../src/plugins/phone-number/routes.ts | 11 ++- 2 files changed, 85 insertions(+), 2 deletions(-) diff --git a/packages/better-auth/src/plugins/phone-number/phone-number.test.ts b/packages/better-auth/src/plugins/phone-number/phone-number.test.ts index 9e21eabb4d..24376ebfa9 100644 --- a/packages/better-auth/src/plugins/phone-number/phone-number.test.ts +++ b/packages/better-auth/src/plugins/phone-number/phone-number.test.ts @@ -506,6 +506,82 @@ describe("reset password session revocation", async () => { }); }); +describe("reset password onPasswordReset callback", async () => { + let otp = ""; + let resetOtp = ""; + const onPasswordReset = vi.fn(); + + const { client, sessionSetter } = await getTestInstance( + { + emailAndPassword: { + enabled: true, + onPasswordReset, + }, + plugins: [ + phoneNumber({ + async sendOTP({ code }) { + otp = code; + }, + sendPasswordResetOTP(data) { + resetOtp = data.code; + }, + signUpOnVerification: { + getTempEmail(phoneNumber) { + return `temp-${phoneNumber}`; + }, + }, + }), + ], + }, + { + clientOptions: { + plugins: [phoneNumberClient()], + }, + }, + ); + + const testPhoneNumber = "+251911999888"; + + it("should call onPasswordReset after phone number password reset", async () => { + const headers = new Headers(); + + await client.phoneNumber.sendOtp({ + phoneNumber: testPhoneNumber, + }); + await client.phoneNumber.verify( + { + phoneNumber: testPhoneNumber, + code: otp, + }, + { + onSuccess: sessionSetter(headers), + }, + ); + + await client.phoneNumber.requestPasswordReset({ + phoneNumber: testPhoneNumber, + }); + + const res = await client.phoneNumber.resetPassword({ + phoneNumber: testPhoneNumber, + otp: resetOtp, + newPassword: "new-password-123", + }); + + expect(res.error).toBe(null); + expect(res.data?.status).toBe(true); + expect(onPasswordReset).toHaveBeenCalledOnce(); + expect(onPasswordReset).toHaveBeenCalledWith( + expect.objectContaining({ + user: expect.objectContaining({ + phoneNumber: testPhoneNumber, + }), + }), + expect.anything(), + ); + }); +}); + describe("phone number verification requirement", async () => { let otp = ""; const { client } = await getTestInstance( diff --git a/packages/better-auth/src/plugins/phone-number/routes.ts b/packages/better-auth/src/plugins/phone-number/routes.ts index ddeb237686..9d2f833db6 100644 --- a/packages/better-auth/src/plugins/phone-number/routes.ts +++ b/packages/better-auth/src/plugins/phone-number/routes.ts @@ -6,7 +6,7 @@ import { setSessionCookie } from "../../cookies"; import { generateRandomString } from "../../crypto/random"; import { parseUserInput } from "../../db"; import { parseUserOutput } from "../../db/schema"; -import type { Account, User } from "../../types"; +import type { Account } from "../../types"; import { getDate } from "../../utils/date"; import { PHONE_NUMBER_ERROR_CODES } from "./error-codes"; import type { PhoneNumberOptions, UserWithPhoneNumber } from "./types"; @@ -811,7 +811,7 @@ export const resetPasswordPhoneNumber = (opts: RequiredPhoneNumberOptions) => ); } const userRes = await ctx.context.adapter.findOne< - User & { account: Account[] | undefined } + UserWithPhoneNumber & { account: Account[] | undefined } >({ model: "user", where: [ @@ -862,6 +862,13 @@ export const resetPasswordPhoneNumber = (opts: RequiredPhoneNumberOptions) => verification.id, ); + if (ctx.context.options.emailAndPassword?.onPasswordReset) { + await ctx.context.options.emailAndPassword.onPasswordReset( + { user }, + ctx.request, + ); + } + if (ctx.context.options.emailAndPassword?.revokeSessionsOnPasswordReset) { await ctx.context.internalAdapter.deleteSessions(user.id); }