From 2fbde0456334592f77bdb39adee3563995bd375a Mon Sep 17 00:00:00 2001 From: Tomas Dvorak Date: Tue, 22 Sep 2026 19:46:14 +0200 Subject: [PATCH] fix: don't require email infra on self-hosted, improve login form detection MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - sendTransactionalEmail no-ops (with a warning) when no mail transport is configured instead of throwing — sign-up 500'd on SMTP-less instances - sendVerificationEmail catches transport failures so a broken SMTP can't block account creation; verification stays optional (requireEmailVerification was never set) - login form gains id/name attributes and label for/id pairing so password managers (Proton Pass et al.) detect the fields; email input uses autocomplete=username Authored By: TDvorak Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- apps/auth/src/lib/auth.ts | 18 ++++++++++++------ apps/auth/src/lib/mail.ts | 7 ++++++- apps/frontend/src/App.tsx | 2 +- apps/frontend/src/components/Input.tsx | 5 +++-- apps/frontend/src/pages/LoginPage.tsx | 8 +++++++- 5 files changed, 29 insertions(+), 11 deletions(-) diff --git a/apps/auth/src/lib/auth.ts b/apps/auth/src/lib/auth.ts index b23100c..ff8abab 100644 --- a/apps/auth/src/lib/auth.ts +++ b/apps/auth/src/lib/auth.ts @@ -94,12 +94,18 @@ export const auth = betterAuth({ sendOnSignUp: true, autoSignInAfterVerification: true, async sendVerificationEmail({ user, url }) { - await sendTransactionalEmail({ - to: user.email, - subject: "Verify your Primora email", - text: `Verify your Primora email: ${url}`, - html: `

Verify your Primora email.

Verify email

`, - }); + // Verification is optional — a misconfigured transport must not fail + // sign-up or sign-in. + try { + await sendTransactionalEmail({ + to: user.email, + subject: "Verify your Primora email", + text: `Verify your Primora email: ${url}`, + html: `

Verify your Primora email.

Verify email

`, + }); + } catch (error) { + console.warn(JSON.stringify({ level: "warn", msg: "verification_email_failed", error })); + } }, }, // Enabled unconditionally — better-auth only turns this on when NODE_ENV is diff --git a/apps/auth/src/lib/mail.ts b/apps/auth/src/lib/mail.ts index e8d23ee..5090e69 100644 --- a/apps/auth/src/lib/mail.ts +++ b/apps/auth/src/lib/mail.ts @@ -51,7 +51,12 @@ export async function sendTransactionalEmail(input: { } if (!("smtp" in cfg) || !cfg.smtp) { - throw new Error("No mail transport configured for auth service."); + // Self-hosted instances may run without mail — auth still works, the + // email simply never goes out. + console.warn( + JSON.stringify({ level: "warn", msg: "mail_skipped_no_transport", to: input.to, subject: input.subject }), + ); + return; } await nodemailer.createTransport(cfg.smtp).sendMail({ diff --git a/apps/frontend/src/App.tsx b/apps/frontend/src/App.tsx index 29ebeca..f199d61 100644 --- a/apps/frontend/src/App.tsx +++ b/apps/frontend/src/App.tsx @@ -736,7 +736,7 @@ export default function App() { try { if (mode() === "sign-up") { await authClient.signUp.email({ email: email(), password: password(), name: name() }); - setAuthMessage("Account created. Check Mailpit (or your SMTP provider) for the verification email."); + setAuthMessage("Account created — signing you in."); } else { await authClient.signIn.email({ email: email(), password: password() }); } diff --git a/apps/frontend/src/components/Input.tsx b/apps/frontend/src/components/Input.tsx index 76b80a3..1563198 100644 --- a/apps/frontend/src/components/Input.tsx +++ b/apps/frontend/src/components/Input.tsx @@ -13,16 +13,17 @@ const sizeClasses = { }; export function Input(props: InputProps) { - const [local, rest] = splitProps(props, ["label", "error", "size", "class"]); + const [local, rest] = splitProps(props, ["label", "error", "size", "class", "id"]); const size = () => local.size ?? "md"; return (
- + diff --git a/apps/frontend/src/pages/LoginPage.tsx b/apps/frontend/src/pages/LoginPage.tsx index 91b1d15..37aee47 100644 --- a/apps/frontend/src/pages/LoginPage.tsx +++ b/apps/frontend/src/pages/LoginPage.tsx @@ -124,6 +124,8 @@ export function LoginPage(props: LoginPageProps) { props.onEmailChange(e.currentTarget.value)} - autocomplete="email" + autocomplete="username" required />