fix(webapp): limit account email address length (#4330)

## Summary

Limits user account email addresses to 254 characters in profile
settings and onboarding. Oversized values are rejected before the
uniqueness lookup, and the form fields enforce the same limit in the
browser.

## Fix

Both email update flows use a shared bounded email schema. Basic
validation completes before the uniqueness lookup runs.
This commit is contained in:
Chris Arderne
2026-07-22 10:29:30 +01:00
committed by GitHub
parent a9815f745c
commit 7a14188663
4 changed files with 28 additions and 11 deletions
@@ -0,0 +1,6 @@
---
area: webapp
type: fix
---
Limit account settings email input to 254 characters.
@@ -22,6 +22,7 @@ import { useUser } from "~/hooks/useUser";
import { redirectWithSuccessMessage } from "~/models/message.server";
import { updateUser } from "~/models/user.server";
import { requireUserId } from "~/services/session.server";
import { emailSchema, MAX_EMAIL_LENGTH } from "~/utils/emailValidation";
import { accountPath } from "~/utils/pathBuilder";
export const meta: MetaFunction = () => {
@@ -42,10 +43,8 @@ function createSchema(
.string({ required_error: "You must enter a name" })
.min(2, "Your name must be at least 2 characters long")
.max(50),
email: z
.string()
.email()
.superRefine((email, ctx) => {
email: emailSchema.pipe(
z.string().superRefine((email, ctx) => {
if (constraints.isEmailUnique === undefined) {
//client-side validation skips this
ctx.addIssue({
@@ -65,7 +64,8 @@ function createSchema(
});
});
}
}),
})
),
marketingEmails: z.preprocess((value) => value === "on", z.boolean()),
});
}
@@ -177,6 +177,7 @@ export default function Page() {
<div className="flex w-56 flex-none flex-col gap-1">
<Input
{...getInputProps(email, { type: "text" })}
maxLength={MAX_EMAIL_LENGTH}
placeholder="Your email"
defaultValue={user?.email ?? ""}
/>
@@ -27,6 +27,7 @@ import { useUser } from "~/hooks/useUser";
import { redirectWithSuccessMessage } from "~/models/message.server";
import { updateUser } from "~/models/user.server";
import { requireUserId } from "~/services/session.server";
import { emailSchema, MAX_EMAIL_LENGTH } from "~/utils/emailValidation";
import { rootPath } from "~/utils/pathBuilder";
import { getVercelInstallParams } from "~/v3/vercel";
@@ -72,10 +73,8 @@ function createSchema(
return z
.object({
name: z.string().min(3, "Your name must be at least 3 characters").max(50),
email: z
.string()
.email()
.superRefine((email, ctx) => {
email: emailSchema.pipe(
z.string().superRefine((email, ctx) => {
if (constraints.isEmailUnique === undefined) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
@@ -93,8 +92,9 @@ function createSchema(
});
});
}
}),
confirmEmail: z.string(),
})
),
confirmEmail: emailSchema,
referralSource: z.string().optional(),
referralSourceOther: z.string().optional(),
role: z.string().optional(),
@@ -290,6 +290,7 @@ export default function Page() {
</Label>
<Input
{...getInputProps(email, { type: "email" })}
maxLength={MAX_EMAIL_LENGTH}
defaultValue={enteredEmail}
onChange={(e) => {
setEnteredEmail(e.target.value);
@@ -306,6 +307,7 @@ export default function Page() {
<Label htmlFor={confirmEmail.id}>Confirm email</Label>
<Input
{...getInputProps(confirmEmail, { type: "email" })}
maxLength={MAX_EMAIL_LENGTH}
placeholder="Your email, again"
icon={EnvelopeIcon}
spellCheck={false}
+8
View File
@@ -0,0 +1,8 @@
import { z } from "zod";
export const MAX_EMAIL_LENGTH = 254;
export const emailSchema = z
.string()
.email()
.max(MAX_EMAIL_LENGTH, `Email must be ${MAX_EMAIL_LENGTH} characters or fewer`);