Files
core/src/features/auth/email.js
T
hykocx 66c862cf73 feat(admin): add email change flow with confirmation for users
- add `ConfirmEmailChangePage.client.js` for email change token confirmation
- add `emailChange.js` core utility to generate and verify email change tokens
- add `EmailChangeConfirmEmail.js` and `EmailChangeNotifyEmail.js` email templates
- update `UserEditModal` to handle email changes with password verification for self-edits
- update `ProfilePage` to support email change initiation
- update `UsersPage` to pass `currentUserId` to `UserEditModal`
- add email change API endpoints in `auth/api.js` and `auth/email.js`
- register `ConfirmEmailChangePage` in `AdminPage.client.js`
2026-04-24 15:04:36 -04:00

97 lines
5.0 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { render } from '@react-email/components';
import { fail, info } from '@zen/core/shared/logger';
import { sendEmail } from '@zen/core/email';
import { VerificationEmail } from './templates/VerificationEmail.js';
import { PasswordResetEmail } from './templates/PasswordResetEmail.js';
import { PasswordChangedEmail } from './templates/PasswordChangedEmail.js';
import { EmailChangeConfirmEmail } from './templates/EmailChangeConfirmEmail.js';
import { EmailChangeNotifyEmail } from './templates/EmailChangeNotifyEmail.js';
export { createEmailVerification, verifyEmailToken, createPasswordReset, verifyResetToken, deleteResetToken }
from '../../core/users/verifications.js';
export { createEmailChangeToken, verifyEmailChangeToken, applyEmailChange }
from '../../core/users/emailChange.js';
async function sendVerificationEmail(email, token, baseUrl) {
const appName = process.env.ZEN_NAME || 'ZEN';
const verificationUrl = `${baseUrl}/auth/confirm?email=${encodeURIComponent(email)}&token=${token}`;
const html = await render(<VerificationEmail verificationUrl={verificationUrl} companyName={appName} />);
const result = await sendEmail({ to: email, subject: `Confirmez votre adresse courriel ${appName}`, html });
if (!result.success) {
fail(`Auth: failed to send verification email to ${email}: ${result.error}`);
throw new Error('Failed to send verification email');
}
info(`Auth: verification email sent to ${email}`);
return result;
}
async function sendPasswordResetEmail(email, token, baseUrl) {
const appName = process.env.ZEN_NAME || 'ZEN';
const resetUrl = `${baseUrl}/auth/reset?email=${encodeURIComponent(email)}&token=${token}`;
const html = await render(<PasswordResetEmail resetUrl={resetUrl} companyName={appName} />);
const result = await sendEmail({ to: email, subject: `Réinitialisation du mot de passe ${appName}`, html });
if (!result.success) {
fail(`Auth: failed to send password reset email to ${email}: ${result.error}`);
throw new Error('Failed to send password reset email');
}
info(`Auth: password reset email sent to ${email}`);
return result;
}
async function sendPasswordChangedEmail(email) {
const appName = process.env.ZEN_NAME || 'ZEN';
const html = await render(<PasswordChangedEmail email={email} companyName={appName} />);
const result = await sendEmail({ to: email, subject: `Mot de passe modifié ${appName}`, html });
if (!result.success) {
fail(`Auth: failed to send password changed email to ${email}: ${result.error}`);
throw new Error('Failed to send password changed email');
}
info(`Auth: password changed email sent to ${email}`);
return result;
}
async function sendEmailChangeConfirmEmail(newEmail, token, baseUrl) {
const appName = process.env.ZEN_NAME || 'ZEN';
const confirmUrl = `${baseUrl}/admin/confirm-email-change?token=${encodeURIComponent(token)}`;
const html = await render(<EmailChangeConfirmEmail confirmUrl={confirmUrl} newEmail={newEmail} companyName={appName} />);
const result = await sendEmail({ to: newEmail, subject: `Confirmez votre nouvelle adresse courriel ${appName}`, html });
if (!result.success) {
fail(`Auth: failed to send email change confirmation to ${newEmail}: ${result.error}`);
throw new Error('Failed to send email change confirmation');
}
info(`Auth: email change confirmation sent to ${newEmail}`);
return result;
}
async function sendEmailChangeOldNotifyEmail(oldEmail, newEmail, variant) {
const appName = process.env.ZEN_NAME || 'ZEN';
const subjects = {
pending: `Demande de modification de courriel ${appName}`,
changed: `Votre adresse courriel a été modifiée ${appName}`,
};
const subject = subjects[variant] || subjects.changed;
const html = await render(<EmailChangeNotifyEmail oldEmail={oldEmail} newEmail={newEmail} variant={variant} companyName={appName} />);
const result = await sendEmail({ to: oldEmail, subject, html });
if (!result.success) {
fail(`Auth: failed to send email change notification to ${oldEmail}: ${result.error}`);
throw new Error('Failed to send email change notification');
}
info(`Auth: email change notification (${variant}) sent to ${oldEmail}`);
return result;
}
async function sendEmailChangeNewNotifyEmail(newEmail, oldEmail) {
const appName = process.env.ZEN_NAME || 'ZEN';
const html = await render(<EmailChangeNotifyEmail oldEmail={oldEmail} newEmail={newEmail} variant="admin_new" companyName={appName} />);
const result = await sendEmail({ to: newEmail, subject: `Votre compte est maintenant associé à cette adresse ${appName}`, html });
if (!result.success) {
fail(`Auth: failed to send email change welcome to ${newEmail}: ${result.error}`);
throw new Error('Failed to send email change welcome');
}
info(`Auth: email change welcome sent to ${newEmail}`);
return result;
}
export { sendVerificationEmail, sendPasswordResetEmail, sendPasswordChangedEmail, sendEmailChangeConfirmEmail, sendEmailChangeOldNotifyEmail, sendEmailChangeNewNotifyEmail };