feat(auth): enable email verification for new user registrations

- Add sendVerificationEmail function in email.service.ts
- Enable requireEmailVerification in Better Auth config
- New users must verify their email before logging in

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Till-JS 2026-01-26 15:35:25 +01:00
parent 4973cf8cf9
commit 5098250364
2 changed files with 67 additions and 5 deletions

View file

@ -22,7 +22,11 @@ import { getDb } from '../db/connection';
import { organizations, members, invitations } from '../db/schema/organizations.schema';
import { users, sessions, accounts, verificationTokens, jwks } from '../db/schema/auth.schema';
import type { JWTPayloadContext } from './types/better-auth.types';
import { sendPasswordResetEmail, sendInvitationEmail } from '../email/email.service';
import {
sendPasswordResetEmail,
sendInvitationEmail,
sendVerificationEmail,
} from '../email/email.service';
/**
* JWT Custom Payload Interface
@ -81,19 +85,29 @@ export function createBetterAuth(databaseUrl: string) {
},
}),
// Email/password authentication with password reset
// Email/password authentication with email verification and password reset
emailAndPassword: {
enabled: true,
requireEmailVerification: false, // Can enable later
requireEmailVerification: true,
minPasswordLength: 8,
maxPasswordLength: 128,
/**
* Email Verification
*
* Sends verification email when user registers.
* User must verify email before they can log in.
*/
sendVerificationEmail: async ({ user, url }) => {
await sendVerificationEmail(user.email, url, user.name);
},
/**
* Password Reset Configuration
*
* Better Auth provides password reset via:
* - auth.api.forgetPassword({ email }) - Sends reset email
* - auth.api.resetPassword({ newPassword, token }) - Resets password
* - auth.api.requestPasswordReset({ body: { email } }) - Sends reset email
* - auth.api.resetPassword({ body: { newPassword, token } }) - Resets password
*
* @see https://www.better-auth.com/docs/authentication/email-password#password-reset
*/

View file

@ -176,6 +176,54 @@ export async function sendInvitationEmail(
});
}
/**
* Send email verification email
*/
export async function sendVerificationEmail(
email: string,
verificationUrl: string,
userName?: string
): Promise<boolean> {
const name = userName || email.split('@')[0];
return sendEmail({
to: email,
subject: 'E-Mail bestätigen - ManaCore',
html: `
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; line-height: 1.6; color: #333; max-width: 600px; margin: 0 auto; padding: 20px;">
<div style="text-align: center; margin-bottom: 30px;">
<h1 style="color: #2563eb; margin: 0;">ManaCore</h1>
</div>
<p>Hallo ${name},</p>
<p>Willkommen bei ManaCore! Bitte bestätige deine E-Mail-Adresse, um deinen Account zu aktivieren:</p>
<div style="text-align: center; margin: 30px 0;">
<a href="${verificationUrl}" style="background-color: #2563eb; color: white; padding: 12px 30px; text-decoration: none; border-radius: 6px; font-weight: 500; display: inline-block;">E-Mail bestätigen</a>
</div>
<p style="color: #666; font-size: 14px;">Dieser Link ist 24 Stunden gültig. Falls du dich nicht bei ManaCore registriert hast, kannst du diese E-Mail ignorieren.</p>
<hr style="border: none; border-top: 1px solid #eee; margin: 30px 0;">
<p style="color: #999; font-size: 12px; text-align: center;">
Diese E-Mail wurde automatisch von ManaCore gesendet.<br>
Falls der Button nicht funktioniert, kopiere diesen Link in deinen Browser:<br>
<a href="${verificationUrl}" style="color: #2563eb; word-break: break-all;">${verificationUrl}</a>
</p>
</body>
</html>
`,
});
}
/**
* Send welcome/verification email
*/