diff --git a/packages/backend/src/settings/email-templates.ts b/packages/backend/src/settings/email-templates.ts index e74a6ef11..15234d842 100644 --- a/packages/backend/src/settings/email-templates.ts +++ b/packages/backend/src/settings/email-templates.ts @@ -68,7 +68,6 @@ export function verificationEmail( input: { code: string; verifyUrl: string }, ctx: EmailBrandContext, ): RenderedEmail { - const shown = /^\d{6}$/.test(input.code) ? `${input.code.slice(0, 3)} ${input.code.slice(3)}` : input.code; return build({ // Code first: it is what the inbox preview shows, and what iOS reads to // offer it above the keyboard (autocomplete one-time-code). @@ -77,7 +76,11 @@ export function verificationEmail( bodyHtml: h1('Confirm your email,', 'and you are in.') + p('Enter this code in the AnythingMCP window you just left. It is valid for 15 minutes.') + - codeBox(shown) + + // The code exactly as it is typed: no space between the two halves. The + // box's letter-spacing already spaces the digits out to read, while a + // real space was copied along with them, so a pasted "504 452" no + // longer fitted the six-character field (October 2026). + codeBox(input.code) + p('Or confirm with one click:', { size: 15, muted: true, mb: 4 }) + button(input.verifyUrl, 'Verify my email') + small("Didn't sign up? You can ignore this email; the address stays unverified without the code."), diff --git a/packages/backend/src/settings/email.service.spec.ts b/packages/backend/src/settings/email.service.spec.ts index 81139c5b7..94cf040e9 100644 --- a/packages/backend/src/settings/email.service.spec.ts +++ b/packages/backend/src/settings/email.service.spec.ts @@ -59,7 +59,9 @@ describe('EmailService', () => { await expect(service.sendVerificationEmail('a@b.com', '123456', 'https://cloud.anythingmcp.com/v?t=1')).resolves.toBe(true); const mail = sent(); expect(mail.subject).toBe('123456 is your AnythingMCP verification code'); - expect(mail.html).toContain('123 456'); + // Shown exactly as typed: a space between the halves was copied with them. + expect(mail.html).toContain('>123456<'); + expect(mail.html).not.toContain('123 456'); expect(mail.text).toContain('123456'); expect(mail.headers).toBeUndefined(); expect(mail.html).not.toContain('Unsubscribe'); diff --git a/packages/frontend/src/app/login/page.tsx b/packages/frontend/src/app/login/page.tsx index a8bc4d159..cba531111 100644 --- a/packages/frontend/src/app/login/page.tsx +++ b/packages/frontend/src/app/login/page.tsx @@ -491,9 +491,12 @@ function LoginForm() { // Mail) or a message right above the keyboard. autoComplete="one-time-code" name="one-time-code" - maxLength={6} + // No maxLength: the browser cuts a paste to that length before + // onChange runs, so "504 452" (a space, as older emails showed + // it) arrived as "504 45" and the code could not be submitted. + // Keep the digits of whatever is pasted, then the first six. value={verificationCode} - onChange={(e) => setVerificationCode(e.target.value.replace(/\D/g, ''))} + onChange={(e) => setVerificationCode(e.target.value.replace(/\D/g, '').slice(0, 6))} placeholder="000000" className="w-full text-center text-2xl tracking-[0.5em] font-mono h-12 rounded-[9px] border border-[var(--border)] bg-[var(--surface)] px-3 text-[var(--text)] placeholder:text-[var(--text-3)] outline-none transition-colors focus:border-[var(--brand)] focus:ring-2 focus:ring-[var(--brand-ring)]" autoFocus