mirror of
https://github.com/better-auth/better-auth.git
synced 2026-05-25 16:36:34 -05:00
feat(phone-number): support user additionalFields in signUpOnVerification flow (#7699)
Co-authored-by: Alex Yang <himself65@outlook.com>
This commit is contained in:
@@ -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.
|
||||
</Callout>
|
||||
|
||||
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.
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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<UserWithPhoneNumber>({
|
||||
...additionalFields,
|
||||
email: opts.signUpOnVerification.getTempEmail(
|
||||
ctx.body.phoneNumber,
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user