mirror of
https://github.com/Dvorinka/Primora.git
synced 2026-10-28 15:51:23 +00:00
fix: don't require email infra on self-hosted, improve login form detection
- 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 <[email protected]> Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
1 parent
9c118d41c2
commit
2fbde04563
5 files changed
+29
-11
No files matched your search
@@ -94,12 +94,18 @@ export const auth = betterAuth({
|
|||||||
sendOnSignUp: true,
|
sendOnSignUp: true,
|
||||||
autoSignInAfterVerification: true,
|
autoSignInAfterVerification: true,
|
||||||
async sendVerificationEmail({ user, url }) {
|
async sendVerificationEmail({ user, url }) {
|
||||||
await sendTransactionalEmail({
|
// Verification is optional — a misconfigured transport must not fail
|
||||||
to: user.email,
|
// sign-up or sign-in.
|
||||||
subject: "Verify your Primora email",
|
try {
|
||||||
text: `Verify your Primora email: ${url}`,
|
await sendTransactionalEmail({
|
||||||
html: `<p>Verify your Primora email.</p><p><a href="${url}">Verify email</a></p>`,
|
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
|
// Enabled unconditionally — better-auth only turns this on when NODE_ENV is
|
||||||
|
|||||||
@@ -51,7 +51,12 @@ export async function sendTransactionalEmail(input: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!("smtp" in cfg) || !cfg.smtp) {
|
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({
|
await nodemailer.createTransport(cfg.smtp).sendMail({
|
||||||
|
|||||||
@@ -736,7 +736,7 @@ export default function App() {
|
|||||||
try {
|
try {
|
||||||
if (mode() === "sign-up") {
|
if (mode() === "sign-up") {
|
||||||
await authClient.signUp.email({ email: email(), password: password(), name: name() });
|
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 {
|
} else {
|
||||||
await authClient.signIn.email({ email: email(), password: password() });
|
await authClient.signIn.email({ email: email(), password: password() });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,16 +13,17 @@ const sizeClasses = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export function Input(props: InputProps) {
|
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";
|
const size = () => local.size ?? "md";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class="w-full">
|
<div class="w-full">
|
||||||
<Show when={local.label}>
|
<Show when={local.label}>
|
||||||
<label class="label">{local.label}</label>
|
<label class="label" for={local.id}>{local.label}</label>
|
||||||
</Show>
|
</Show>
|
||||||
<input
|
<input
|
||||||
|
id={local.id}
|
||||||
class={`input ${sizeClasses[size()]} ${local.error ? "border-error focus:border-error focus:ring-error/20" : ""} ${local.class ?? ""}`}
|
class={`input ${sizeClasses[size()]} ${local.error ? "border-error focus:border-error focus:ring-error/20" : ""} ${local.class ?? ""}`}
|
||||||
{...rest}
|
{...rest}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -124,6 +124,8 @@ export function LoginPage(props: LoginPageProps) {
|
|||||||
<Show when={!isSignIn()}>
|
<Show when={!isSignIn()}>
|
||||||
<Input
|
<Input
|
||||||
label="Name"
|
label="Name"
|
||||||
|
id="name"
|
||||||
|
name="name"
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Ada Lovelace"
|
placeholder="Ada Lovelace"
|
||||||
value={props.name}
|
value={props.name}
|
||||||
@@ -134,15 +136,19 @@ export function LoginPage(props: LoginPageProps) {
|
|||||||
</Show>
|
</Show>
|
||||||
<Input
|
<Input
|
||||||
label="Email"
|
label="Email"
|
||||||
|
id="email"
|
||||||
|
name="email"
|
||||||
type="email"
|
type="email"
|
||||||
placeholder="[email protected]"
|
placeholder="[email protected]"
|
||||||
value={props.email}
|
value={props.email}
|
||||||
onInput={(e) => props.onEmailChange(e.currentTarget.value)}
|
onInput={(e) => props.onEmailChange(e.currentTarget.value)}
|
||||||
autocomplete="email"
|
autocomplete="username"
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
<Input
|
<Input
|
||||||
label="Password"
|
label="Password"
|
||||||
|
id="password"
|
||||||
|
name="password"
|
||||||
type="password"
|
type="password"
|
||||||
placeholder="••••••••"
|
placeholder="••••••••"
|
||||||
value={props.password}
|
value={props.password}
|
||||||
|
|||||||
Reference in new issue
Block a user