Merge pull request #13 from Dvorinka/fix/selfhosted-email-optional
CI / Backend Tests (push) Canceled after 0s
CI / Frontend Tests (push) Canceled after 0s
CI / Build All Services (push) Canceled after 0s
CI / Docker Build (auth) (push) Canceled after 0s
CI / Docker Build (backend) (push) Canceled after 0s
CI / Docker Build (frontend) (push) Canceled after 0s
CI / Migration Up/Down (push) Canceled after 0s
CI / Compose E2E Smoke (push) Canceled after 0s
CI / Lint (push) Canceled after 0s
CI / Publish edge images (auth) (push) Canceled after 0s
CI / Publish edge images (backend) (push) Canceled after 0s
CI / Publish edge images (frontend) (push) Canceled after 0s

fix: self-hosted auth without mail transport, password-manager-friendly login
This commit is contained in:
Tomáš Dvořák authored and GitHub committed 2026-09-22 19:50:30 +02:00
commit 61ede5ee99
5 files changed
+29 -11

No files matched your search

+12 -6
View File
@@ -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: `<p>Verify your Primora email.</p><p><a href="${url}">Verify email</a></p>`,
});
// 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: `<p>Verify your Primora email.</p><p><a href="${url}">Verify email</a></p>`,
});
} 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
+6 -1
View File
@@ -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({
+1 -1
View File
@@ -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() });
}
+3 -2
View File
@@ -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 (
<div class="w-full">
<Show when={local.label}>
<label class="label">{local.label}</label>
<label class="label" for={local.id}>{local.label}</label>
</Show>
<input
id={local.id}
class={`input ${sizeClasses[size()]} ${local.error ? "border-error focus:border-error focus:ring-error/20" : ""} ${local.class ?? ""}`}
{...rest}
/>
+7 -1
View File
@@ -124,6 +124,8 @@ export function LoginPage(props: LoginPageProps) {
<Show when={!isSignIn()}>
<Input
label="Name"
id="name"
name="name"
type="text"
placeholder="Ada Lovelace"
value={props.name}
@@ -134,15 +136,19 @@ export function LoginPage(props: LoginPageProps) {
</Show>
<Input
label="Email"
id="email"
name="email"
type="email"
placeholder="[email protected]"
value={props.email}
onInput={(e) => props.onEmailChange(e.currentTarget.value)}
autocomplete="email"
autocomplete="username"
required
/>
<Input
label="Password"
id="password"
name="password"
type="password"
placeholder="••••••••"
value={props.password}