diff --git a/docs/content/docs/plugins/phone-number.mdx b/docs/content/docs/plugins/phone-number.mdx index 3e2689596d..524a309f6f 100644 --- a/docs/content/docs/plugins/phone-number.mdx +++ b/docs/content/docs/plugins/phone-number.mdx @@ -135,6 +135,16 @@ export const auth = betterAuth({ We highly recommend not awaiting the `sendOTP` function. If you await it, it'll slow down the request and could cause timing attacks. For serverless platforms, you can use `waitUntil` to ensure the OTP is sent. +If you have additional required fields in your user schema, you can pass them in the verify request body: + +```ts title="auth-client.ts" +await authClient.phoneNumber.verify({ + phoneNumber: "+1234567890", + code: "123456", + customField: "custom-value", // additional field [!code highlight] +}) +``` + ### Sign In with Phone Number In addition to signing in a user using send-verify flow, you can also use phone number as an identifier and sign in a user using phone number and password. 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 6e37cc344b..c155208bca 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 @@ -594,6 +594,65 @@ describe("updateUser phone number update prevention", async () => { }); }); +describe("signUpOnVerification with additionalFields", async () => { + let otp = ""; + + const { client } = await getTestInstance( + { + user: { + additionalFields: { + lastName: { + type: "string", + required: true, + }, + dateOfBirth: { + type: "date", + required: false, + }, + }, + }, + plugins: [ + phoneNumber({ + async sendOTP({ code }) { + otp = code; + }, + signUpOnVerification: { + getTempEmail(phoneNumber) { + return `temp-${phoneNumber}@example.com`; + }, + }, + }), + ], + }, + { + disableTestUser: true, + clientOptions: { + plugins: [phoneNumberClient()], + }, + }, + ); + + const testPhoneNumber = "+1234567890"; + + it("should create user with additional fields from body", async () => { + await client.phoneNumber.sendOtp({ + phoneNumber: testPhoneNumber, + }); + + const res = await client.phoneNumber.verify({ + phoneNumber: testPhoneNumber, + code: otp, + lastName: "Doe", + }); + + expect(res.error).toBe(null); + expect(res.data?.status).toBe(true); + expect(res.data?.user).not.toBe(null); + // @ts-expect-error - additionalFields not yet inferred in plugin response type + expect(res.data?.user.lastName).toBe("Doe"); + }); +}); + describe("custom verifyOTP", async () => { const mockVerifyOTP = vi.fn(); diff --git a/packages/better-auth/src/plugins/phone-number/routes.ts b/packages/better-auth/src/plugins/phone-number/routes.ts index 5083fe78dc..c184fdfbda 100644 --- a/packages/better-auth/src/plugins/phone-number/routes.ts +++ b/packages/better-auth/src/plugins/phone-number/routes.ts @@ -4,6 +4,7 @@ import * as z from "zod"; import { getSessionFromCtx } from "../../api"; import { setSessionCookie } from "../../cookies"; import { generateRandomString } from "../../crypto/random"; +import { parseUserInput } from "../../db"; import { parseUserOutput } from "../../db/schema"; import type { User } from "../../types"; import { getDate } from "../../utils/date"; @@ -292,42 +293,44 @@ export const sendPhoneNumberOTP = (opts: RequiredPhoneNumberOptions) => }, ); -const verifyPhoneNumberBodySchema = z.object({ - /** - * Phone number - */ - phoneNumber: z.string().meta({ - description: 'Phone number to verify. Eg: "+1234567890"', - }), - /** - * OTP code - */ - code: z.string().meta({ - description: 'OTP code. Eg: "123456"', - }), - /** - * Disable session creation after verification - * @default false - */ - disableSession: z - .boolean() - .meta({ - description: "Disable session creation after verification. Eg: false", - }) - .optional(), - /** - * This checks if there is a session already - * and updates the phone number with the provided - * phone number - */ - updatePhoneNumber: z - .boolean() - .meta({ - description: - "Check if there is a session and update the phone number. Eg: true", - }) - .optional(), -}); +const verifyPhoneNumberBodySchema = z + .object({ + /** + * Phone number + */ + phoneNumber: z.string().meta({ + description: 'Phone number to verify. Eg: "+1234567890"', + }), + /** + * OTP code + */ + code: z.string().meta({ + description: 'OTP code. Eg: "123456"', + }), + /** + * Disable session creation after verification + * @default false + */ + disableSession: z + .boolean() + .meta({ + description: "Disable session creation after verification. Eg: false", + }) + .optional(), + /** + * This checks if there is a session already + * and updates the phone number with the provided + * phone number + */ + updatePhoneNumber: z + .boolean() + .meta({ + description: + "Check if there is a session and update the phone number. Eg: true", + }) + .optional(), + }) + .and(z.record(z.string(), z.any())); /** * ### Endpoint @@ -559,8 +562,21 @@ export const verifyPhoneNumber = (opts: RequiredPhoneNumberOptions) => }); if (!user) { if (opts?.signUpOnVerification) { + const { + phoneNumber, + code, + disableSession, + updatePhoneNumber, + ...rest + } = ctx.body; + const additionalFields = parseUserInput( + ctx.context.options, + rest, + "create", + ); user = await ctx.context.internalAdapter.createUser({ + ...additionalFields, email: opts.signUpOnVerification.getTempEmail( ctx.body.phoneNumber, ),