/** * Email Verification and Password Reset * Handles email verification tokens and password reset tokens */ import { create, findOne, deleteWhere } from '../../../core/database/crud.js'; import { generateToken, generateId } from './password.js'; import { sendAuthEmail } from '../../../core/email/index.js'; import { renderVerificationEmail, renderPasswordResetEmail, renderPasswordChangedEmail } from '../../../core/email/templates/index.js'; /** * Create an email verification token * @param {string} email - User email * @returns {Promise} Verification object with token */ async function createEmailVerification(email) { const token = generateToken(32); const verificationId = generateId(); // Token expires in 24 hours const expiresAt = new Date(); expiresAt.setHours(expiresAt.getHours() + 24); // Delete any existing verification tokens for this email await deleteWhere('zen_auth_verifications', { identifier: 'email_verification', value: email }); const verification = await create('zen_auth_verifications', { id: verificationId, identifier: 'email_verification', value: email, token, expires_at: expiresAt, updated_at: new Date() }); return { ...verification, token }; } /** * Verify an email token * @param {string} email - User email * @param {string} token - Verification token * @returns {Promise} True if valid, false otherwise */ async function verifyEmailToken(email, token) { const verification = await findOne('zen_auth_verifications', { identifier: 'email_verification', value: email }); if (!verification) return false; // Verify token matches if (verification.token !== token) return false; // Check if token is expired if (new Date(verification.expires_at) < new Date()) { await deleteWhere('zen_auth_verifications', { id: verification.id }); return false; } // Delete the verification token after use await deleteWhere('zen_auth_verifications', { id: verification.id }); return true; } /** * Create a password reset token * @param {string} email - User email * @returns {Promise} Reset object with token */ async function createPasswordReset(email) { const token = generateToken(32); const resetId = generateId(); // Token expires in 1 hour const expiresAt = new Date(); expiresAt.setHours(expiresAt.getHours() + 1); // Delete any existing reset tokens for this email await deleteWhere('zen_auth_verifications', { identifier: 'password_reset', value: email }); const reset = await create('zen_auth_verifications', { id: resetId, identifier: 'password_reset', value: email, token, expires_at: expiresAt, updated_at: new Date() }); return { ...reset, token }; } /** * Verify a password reset token * @param {string} email - User email * @param {string} token - Reset token * @returns {Promise} True if valid, false otherwise */ async function verifyResetToken(email, token) { const reset = await findOne('zen_auth_verifications', { identifier: 'password_reset', value: email }); if (!reset) return false; // Verify token matches if (reset.token !== token) return false; // Check if token is expired if (new Date(reset.expires_at) < new Date()) { await deleteWhere('zen_auth_verifications', { id: reset.id }); return false; } return true; } /** * Delete a password reset token * @param {string} email - User email * @returns {Promise} Number of deleted tokens */ async function deleteResetToken(email) { return await deleteWhere('zen_auth_verifications', { identifier: 'password_reset', value: email }); } /** * Send verification email using Resend * @param {string} email - User email * @param {string} token - Verification token * @param {string} baseUrl - Base URL of the application */ async function sendVerificationEmail(email, token, baseUrl) { const verificationUrl = `${baseUrl}/auth/confirm?email=${encodeURIComponent(email)}&token=${token}`; const appName = process.env.ZEN_NAME || 'ZEN'; const html = await renderVerificationEmail(verificationUrl, email, appName); const result = await sendAuthEmail({ to: email, subject: `Confirmez votre adresse courriel – ${appName}`, html }); if (!result.success) { console.error(`[ZEN AUTH] Failed to send verification email to ${email}:`, result.error); throw new Error('Failed to send verification email'); } console.log(`[ZEN AUTH] Verification email sent to ${email}`); return result; } /** * Send password reset email using Resend * @param {string} email - User email * @param {string} token - Reset token * @param {string} baseUrl - Base URL of the application */ async function sendPasswordResetEmail(email, token, baseUrl) { const resetUrl = `${baseUrl}/auth/reset?email=${encodeURIComponent(email)}&token=${token}`; const appName = process.env.ZEN_NAME || 'ZEN'; const html = await renderPasswordResetEmail(resetUrl, email, appName); const result = await sendAuthEmail({ to: email, subject: `Réinitialisation du mot de passe – ${appName}`, html }); if (!result.success) { console.error(`[ZEN AUTH] Failed to send password reset email to ${email}:`, result.error); throw new Error('Failed to send password reset email'); } console.log(`[ZEN AUTH] Password reset email sent to ${email}`); return result; } /** * Send password changed confirmation email using Resend * @param {string} email - User email */ async function sendPasswordChangedEmail(email) { const appName = process.env.ZEN_NAME || 'ZEN'; const html = await renderPasswordChangedEmail(email, appName); const result = await sendAuthEmail({ to: email, subject: `Mot de passe modifié – ${appName}`, html }); if (!result.success) { console.error(`[ZEN AUTH] Failed to send password changed email to ${email}:`, result.error); throw new Error('Failed to send password changed email'); } console.log(`[ZEN AUTH] Password changed email sent to ${email}`); return result; } export { createEmailVerification, verifyEmailToken, createPasswordReset, verifyResetToken, deleteResetToken, sendVerificationEmail, sendPasswordResetEmail, sendPasswordChangedEmail };