feat(auth): add user invitation flow with account setup
- add `createAccountSetup`, `verifyAccountSetupToken`, `deleteAccountSetupToken` to verifications core - add `completeAccountSetup` function to auth core for password creation on invite - add `InvitationEmail` template for sending invite links - add `SetupAccountPage` client page for invited users to set their password - add `UserCreateModal` admin component to invite new users - wire invitation action and API endpoint in auth feature - update admin `UsersPage` to include user creation modal - update auth and admin README docs
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
'use server';
|
||||
|
||||
import { register, login, requestPasswordReset, resetPassword, verifyUserEmail } from './auth.js';
|
||||
import { register, login, requestPasswordReset, resetPassword, verifyUserEmail, completeAccountSetup } from './auth.js';
|
||||
import { validateSession, deleteSession } from './session.js';
|
||||
import { verifyEmailToken, verifyResetToken, sendVerificationEmail, sendPasswordResetEmail } from './email.js';
|
||||
import { fail } from '@zen/core/shared/logger';
|
||||
@@ -323,6 +323,42 @@ export async function resetPasswordAction(formData) {
|
||||
}
|
||||
}
|
||||
|
||||
export async function setupAccountAction(formData) {
|
||||
try {
|
||||
const ip = await getClientIp();
|
||||
const rl = enforceRateLimit(ip, 'setup_account');
|
||||
if (rl && !rl.allowed) {
|
||||
return { success: false, error: `Trop de tentatives. Réessayez dans ${formatRetryAfter(rl.retryAfterMs)}.` };
|
||||
}
|
||||
|
||||
const email = formData.get('email');
|
||||
const token = formData.get('token');
|
||||
const newPassword = formData.get('newPassword');
|
||||
const confirmPassword = formData.get('confirmPassword');
|
||||
|
||||
if (!newPassword || !confirmPassword) {
|
||||
throw new UserFacingError('Les deux champs de mot de passe sont requis');
|
||||
}
|
||||
|
||||
if (newPassword !== confirmPassword) {
|
||||
throw new UserFacingError('Les mots de passe ne correspondent pas');
|
||||
}
|
||||
|
||||
await completeAccountSetup({ email, token, password: newPassword });
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: 'Mot de passe créé avec succès. Vous pouvez maintenant vous connecter.'
|
||||
};
|
||||
} catch (error) {
|
||||
if (error instanceof UserFacingError) {
|
||||
return { success: false, error: error.message };
|
||||
}
|
||||
fail(`Auth: setupAccountAction error: ${error.message}`);
|
||||
return { success: false, error: 'Une erreur interne est survenue. Veuillez réessayer.' };
|
||||
}
|
||||
}
|
||||
|
||||
export async function verifyEmailAction(formData) {
|
||||
try {
|
||||
const ip = await getClientIp();
|
||||
|
||||
Reference in New Issue
Block a user