From 317d95a603d7981e9641336382929c5377425793 Mon Sep 17 00:00:00 2001 From: Jithin Raj Date: Sat, 4 Jul 2026 01:23:15 +0530 Subject: [PATCH 1/2] fix(core): allow valid international phone numbers and reject empty SMS codes The phone number schema capped input at 10 digits, but that field holds the national number only (the dial code is prepended later by formatPhoneNumber), so valid 11-digit numbers such as China and Germany mobiles were rejected in the browser. Raise the cap to 15, the ITU-T E.164 maximum. The phone verification schema allowed an empty code to pass via a "!val ||" short-circuit, inconsistent with the TOTP verification schema. Drop the guard so an empty code fails. --- packages/core/src/schemas.test.ts | 56 ++++++++++++++++++++++++++++--- packages/core/src/schemas.ts | 7 ++-- 2 files changed, 57 insertions(+), 6 deletions(-) diff --git a/packages/core/src/schemas.test.ts b/packages/core/src/schemas.test.ts index 45b07c75b..82bf6a0fe 100644 --- a/packages/core/src/schemas.test.ts +++ b/packages/core/src/schemas.test.ts @@ -261,10 +261,9 @@ describe("createPhoneAuthNumberFormSchema", () => { const schema = createPhoneAuthNumberFormSchema(mockUI); - // Cause the schema to fail... - // TODO(ehesp): If no value is provided, the schema error is just "Required" - should this also be translated? + // 16 digits exceeds the ITU-T E.164 maximum of 15. const result = schema.safeParse({ - phoneNumber: "12345678901", + phoneNumber: "1234567890123456", }); expect(result.success).toBe(false); @@ -272,6 +271,27 @@ describe("createPhoneAuthNumberFormSchema", () => { expect(result.error?.issues[0]?.message).toBe("createPhoneAuthNumberFormSchema + invalidPhoneNumber"); }); + + it("should accept a valid 11-digit national number (e.g. China / Germany mobile)", () => { + const mockUI = createMockUI({ + locale: registerLocale("test", { + errors: { + missingPhoneNumber: "missing", + invalidPhoneNumber: "invalid", + }, + }), + }); + + const schema = createPhoneAuthNumberFormSchema(mockUI); + + // Previously rejected by the .max(10) cap. The dial code is added later by + // formatPhoneNumber, so this national number must be accepted here. + const result = schema.safeParse({ + phoneNumber: "13800138000", + }); + + expect(result.success).toBe(true); + }); }); describe("createPhoneAuthVerifyFormSchema", () => { @@ -325,6 +345,33 @@ describe("createPhoneAuthVerifyFormSchema", () => { ) ).toBe(true); }); + + it("should reject an empty verification code", () => { + const testLocale = registerLocale("test", { + errors: { + invalidVerificationCode: "createPhoneAuthVerifyFormSchema + invalidVerificationCode", + }, + }); + + const mockUI = createMockUI({ + locale: testLocale, + }); + + const schema = createPhoneAuthVerifyFormSchema(mockUI); + + const result = schema.safeParse({ + verificationId: "test-verification-id", + verificationCode: "", + }); + + expect(result.success).toBe(false); + expect(result.error).toBeDefined(); + expect( + result.error?.issues.some( + (issue) => issue.message === "createPhoneAuthVerifyFormSchema + invalidVerificationCode" + ) + ).toBe(true); + }); }); describe("createMultiFactorPhoneAuthAssertionFormSchema", () => { @@ -363,8 +410,9 @@ describe("createMultiFactorPhoneAuthAssertionFormSchema", () => { const schema = createMultiFactorPhoneAuthAssertionFormSchema(mockUI); + // 16 digits exceeds the ITU-T E.164 maximum of 15. const result = schema.safeParse({ - phoneNumber: "12345678901", + phoneNumber: "1234567890123456", }); expect(result.success).toBe(false); diff --git a/packages/core/src/schemas.ts b/packages/core/src/schemas.ts index 255b8ab6f..c0de278df 100644 --- a/packages/core/src/schemas.ts +++ b/packages/core/src/schemas.ts @@ -97,7 +97,10 @@ export function createPhoneAuthNumberFormSchema(ui: FirebaseUI) { phoneNumber: z .string() .min(1, getTranslation(ui, "errors", "missingPhoneNumber")) - .max(10, getTranslation(ui, "errors", "invalidPhoneNumber")), + // National number only (dial code is added later by formatPhoneNumber). 15 is the + // ITU-T E.164 digit maximum; the previous cap of 10 rejected valid numbers such as + // 11-digit China and Germany mobiles. + .max(15, getTranslation(ui, "errors", "invalidPhoneNumber")), }); } @@ -112,7 +115,7 @@ export function createPhoneAuthNumberFormSchema(ui: FirebaseUI) { export function createPhoneAuthVerifyFormSchema(ui: FirebaseUI) { return z.object({ verificationId: z.string().min(1, getTranslation(ui, "errors", "missingVerificationId")), - verificationCode: z.string().refine((val) => !val || val.length >= 6, { + verificationCode: z.string().refine((val) => val.length >= 6, { error: getTranslation(ui, "errors", "invalidVerificationCode"), }), }); From 719136b351768e43bbe58e417201efbb453a1ca3 Mon Sep 17 00:00:00 2001 From: Jithin Raj Date: Sat, 4 Jul 2026 01:35:23 +0530 Subject: [PATCH 2/2] refactor(core): use built-in z.min for verification code length Matches the password/display-name validations in this file and is more idiomatic than a custom refine check. No behavior change. --- packages/core/src/schemas.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/core/src/schemas.ts b/packages/core/src/schemas.ts index c0de278df..5a013a26b 100644 --- a/packages/core/src/schemas.ts +++ b/packages/core/src/schemas.ts @@ -115,9 +115,7 @@ export function createPhoneAuthNumberFormSchema(ui: FirebaseUI) { export function createPhoneAuthVerifyFormSchema(ui: FirebaseUI) { return z.object({ verificationId: z.string().min(1, getTranslation(ui, "errors", "missingVerificationId")), - verificationCode: z.string().refine((val) => val.length >= 6, { - error: getTranslation(ui, "errors", "invalidVerificationCode"), - }), + verificationCode: z.string().min(6, getTranslation(ui, "errors", "invalidVerificationCode")), }); }