Utilisateurs
Comptes, sessions, rôles, permissions et tokens de vérification, la couche de données auth du core.
core/users gère les utilisateurs, l'authentification par identifiants, les sessions, les rôles et les permissions. C'est la couche de données : les features l'appellent, il ne connaît pas les features.
Import
import {
register, login, requestPasswordReset, resetPassword, verifyUserEmail, updateUser,
isRegistrationOpen, needsFirstRun, createFirstAdmin, resendEmailVerification,
EmailNotVerifiedError, FIRST_RUN_DONE_MESSAGE,
createSession, validateSession, deleteSession, deleteUserSessions, refreshSession,
getUserById, getUserByEmail, countUsers, listUsers, updateUserById,
createRole, updateRole, deleteRole, listRoles, getRoleById,
getUserRoles, assignUserRole, revokeUserRole,
hasPermission, getUserPermissions,
registerPermission, registerPermissions, getRegisteredPermissions, getRegisteredPermissionKeys,
createEmailVerification, verifyEmailToken, createPasswordReset, verifyResetToken, deleteResetToken,
hashPassword, verifyPassword, generateToken, generateId,
} from '@lrvo/core/users';
// Sous-entrée client-safe : constantes seules, aucun import serveur
import { PERMISSIONS, PERMISSION_DEFINITIONS, getPermissionGroups } from '@lrvo/core/users/constants';Le barrel @lrvo/core/users est serveur. Depuis un fichier 'use client', importer uniquement @lrvo/core/users/constants.
Fichiers
| Fichier | Rôle |
|---|---|
auth.js | register, createFirstAdmin, login, mot de passe, vérification d'adresse. |
session.js | Création, validation, suppression de sessions. |
queries.js | Lecture et mise à jour des utilisateurs. |
roles.js | CRUD des rôles et assignation. |
permissions.js | hasPermission, getUserPermissions. |
permissions-registry.js | Enregistrement des permissions contribuées par les modules. |
constants.js | PERMISSIONS, PERMISSION_DEFINITIONS, getPermissionGroups. |
verifications.js | Tokens de vérification d'adresse et de réinitialisation. |
emailChange.js | Tokens de changement d'adresse. |
password.js | hashPassword, verifyPassword, generateToken, generateId. |
Comptes
register(userData, options?)
Crée un compte après vérification des contraintes de mot de passe. Le premier utilisateur enregistré reçoit le rôle admin. Retourne { user, verificationToken }.
Lève quand les inscriptions sont fermées, sauf pour ce premier compte. Voir isRegistrationOpen() ci-dessous.
const { user, verificationToken } = await register(
{ email: '[email protected]', password: 'Secret1', name: 'Alice' },
{
onEmailVerification: async (email, token) => {
await sendVerificationEmail({ to: email, token });
},
}
);| Paramètre | Contrainte |
|---|---|
email | 254 caractères maximum |
password | 8 à 128 caractères, au moins une majuscule, une minuscule et un chiffre |
name | 100 caractères maximum |
onEmailVerification | async (email, token) => void, appelé après création |
isRegistrationOpen()
Retourne false quand LRVO_AUTH_REGISTRATION=false et qu'au moins un utilisateur existe. La base vide passe toujours, sinon une installation neuve configurée ainsi ne pourrait plus créer son admin.
register() applique la même condition et lève Les inscriptions sont actuellement fermées. quand elle n'est pas remplie. Appeler isRegistrationOpen() avant permet de renvoyer un message clair plutôt que de traiter une exception. La création de comptes par un admin ne passe pas par register() et reste donc possible.
if (!(await isRegistrationOpen())) {
return { success: false, error: 'Les inscriptions sont actuellement fermées.' };
}needsFirstRun()
Retourne true tant que lrvo_auth_users est vide. C'est le test qui fait afficher le formulaire de première configuration.
createFirstAdmin(userData, sessionOptions?)
Crée le premier administrateur et sa session. Retourne { user, session }. Mêmes contraintes de champs que register().
Le compte a email_verified = true, le rôle système admin, et aucun token de vérification n'est créé. Le comptage et les insertions se font dans une transaction qui prend le verrou consultatif 4919420002 : deux appels simultanés se sérialisent, et le second lève FIRST_RUN_DONE_MESSAGE (« Le compte administrateur existe déjà. ») au lieu de créer un second admin. Il lève le même message dès qu'un compte existe.
const { user, session } = await createFirstAdmin(
{ email, password, name },
{ ipAddress, userAgent }
);Lève aussi quand le rôle admin est absent de la base, ce qui signale un npx lrvo-db init qui n'a pas tourné.
login(credentials, sessionOptions?)
Vérifie les identifiants et crée une session. Retourne { user, session }. Lève si les identifiants sont incorrects.
Un compte non vérifié ne se connecte pas. Quand le mot de passe est correct mais email_verified est faux, login() lève EmailNotVerifiedError et ne crée aucune session. Le contrôle vient après le mot de passe pour ne rien révéler à qui ne le connaît pas. Il vit ici plutôt que dans l'action de connexion pour s'appliquer à tout appelant.
const { user, session } = await login(
{ email, password },
{ ipAddress: req.ip, userAgent: req.headers['user-agent'] }
);try {
await login({ email, password });
} catch (error) {
if (error instanceof EmailNotVerifiedError) { /* proposer le renvoi du lien */ }
}resendEmailVerification(email)
Remplace le token de vérification d'un compte non vérifié et retourne { token }. Retourne { token: null } si le compte n'existe pas ou est déjà vérifié : l'appelant doit répondre de la même façon dans les deux cas, pour ne pas permettre l'énumération des comptes.
requestPasswordReset(email)
Génère un token valide une heure. Retourne { success: true, token } même si l'adresse est inconnue, pour ne pas permettre l'énumération des comptes.
resetPassword(data, options?)
Valide le token et met à jour le mot de passe. Marque aussi l'adresse comme vérifiée : le lien est arrivé dans cette boîte, ce qui prouve sa possession autant que le lien de vérification.
await resetPassword(
{ email, token, newPassword: 'NewSecret1' },
{ onPasswordChanged: async (email) => sendConfirmation(email) }
);verifyUserEmail(userId)
Marque l'adresse comme vérifiée.
updateUser(userId, data)
Met à jour les champs de profil autorisés : name, image, language.
Sessions
createSession(userId, options?)
Crée une session valide 30 jours.
const session = await createSession(user.id, { ipAddress: '127.0.0.1', userAgent });validateSession(token)
Valide un token. Renouvelle automatiquement la session si elle expire dans moins de 20 jours. Retourne { session, user, sessionRefreshed }, ou null si le token est invalide ou expiré.
const result = await validateSession(token);
if (!result) return null;
if (result.sessionRefreshed) await writeCookie(result.session.token);deleteSession(token) et deleteUserSessions(userId)
Supprime une session, ou toutes celles d'un utilisateur.
refreshSession(token)
Prolonge explicitement une session sans attendre le seuil des 20 jours.
Lecture
getUserById(id) et getUserByEmail(email)
Retournent l'utilisateur, ou null.
listUsers(options?)
Liste paginée et triée. Retourne { users, pagination }.
| Paramètre | Défaut | Valeurs |
|---|---|---|
page | 1 | — |
limit | 10 | 100 maximum |
sortBy | 'created_at' | id, email, name, role, email_verified, created_at |
sortOrder | 'desc' | 'asc' | 'desc' |
countUsers()
Nombre total de comptes.
updateUserById(id, fields)
Met à jour les champs autorisés : name, role, email_verified, image, language.
Rôles
const roles = await listRoles();
const role = await getRoleById(id);
const created = await createRole({
name: 'Modérateur',
description: 'Peut gérer les utilisateurs',
color: '#3b82f6',
});
await updateRole(roleId, {
name: 'Modérateur',
permissionKeys: [PERMISSIONS.USERS_VIEW, PERMISSIONS.USERS_MANAGE],
});
await deleteRole(roleId); // impossible sur un rôle système
const userRoles = await getUserRoles(userId);
await assignUserRole(userId, roleId);
await revokeUserRole(userId, roleId);Les rôles système (is_system = true) peuvent être renommés, mais leurs permissions ne sont pas modifiables et ils ne sont pas supprimables.
Permissions
const canManage = await hasPermission(userId, PERMISSIONS.ROLES_MANAGE);
const keys = await getUserPermissions(userId);PERMISSIONS contient toutes les clés. PERMISSION_DEFINITIONS expose le label, la description et le groupe de chacune. getPermissionGroups() retourne les permissions groupées par group_name.
| Groupe | Clés |
|---|---|
| Administration | admin.access |
| Utilisateurs | users.view, users.manage |
| Rôles | roles.view, roles.manage |
CORE_PERMISSION_GROUP_ORDER définit l'ordre d'affichage canonique des sections core dans l'UI. Le handler /lrvo/api/permissions réserve ces sections en tête de la réponse, puis ajoute les groupes contribués par les modules dans leur ordre d'enregistrement. L'ordre des appels à registerPermissions() n'a donc aucun effet sur l'ordre des sections core.
Voir Permissions et rôles pour en déclarer depuis un module.
Changement d'adresse
emailChange.js n'a pas de sous-chemin public. createEmailChangeToken, verifyEmailChangeToken et applyEmailChange restent internes : ils sont appelés par les handlers de features/auth, qui portent les règles du flux (mot de passe courant exigé, unicité de la nouvelle adresse, notification de l'ancienne).
Un projet pilote donc le changement d'adresse par les routes REST :
| Méthode | Route | Effet |
|---|---|---|
POST | /lrvo/api/users/profile/email | Vérifie le mot de passe et l'unicité, crée le token, envoie la confirmation à la nouvelle adresse et une notification à l'ancienne. |
GET | /lrvo/api/users/email/confirm?token=… | Consomme le token et applique le changement sur lrvo_auth_users et lrvo_auth_accounts. |
Le token vit dans lrvo_auth_verifications sous l'identifiant email_change:<userId> et expire après 24 heures. Il est comparé en temps constant, puis supprimé qu'il soit valide ou expiré : un token ne sert jamais deux fois.
Tokens de vérification
Ces fonctions écrivent dans lrvo_auth_verifications. Elles sont bas niveau : les features s'en servent pour composer un flux, avec l'envoi de courriel qui va avec.
| Fonction | Rôle |
|---|---|
createEmailVerification(email) | Token de confirmation d'adresse, valide 24 heures. Remplace le token précédent de la même adresse. |
verifyEmailToken(email, token) | Valide le token. Retourne null s'il est inconnu ou expiré, auquel cas l'enregistrement est supprimé. |
createPasswordReset(email) | Token de réinitialisation, valide 1 heure. |
verifyResetToken(email, token) | Même contrat que verifyEmailToken. |
deleteResetToken(email) | Supprime le token de réinitialisation, après usage. |
Les tokens d'invitation (account_setup, 48 heures) et de changement d'adresse restent internes à features/auth : leur cycle de vie est indissociable des règles du flux.
Registre de permissions
Un module déclare ses permissions dans son manifest, ce qui suffit dans la quasi-totalité des cas. Le registre reste accessible pour les déclarer à la main, depuis un projet consommateur par exemple.
| Fonction | Rôle |
|---|---|
registerPermissions(list) | Ajoute plusieurs permissions au catalogue. C'est la forme normale. |
registerPermission({ key, name, description, group_name }) | Ajoute une permission. Une clé déjà enregistrée n'est pas dupliquée. |
getRegisteredPermissions() | Retourne les permissions contribuées, hors core. |
getRegisteredPermissionKeys() | Les clés seules, forme pratique pour un contrôle d'appartenance. |
Le catalogue est en mémoire : npx lrvo-db init le persiste et attribue les clés au rôle admin. Voir Permissions et rôles.
Mots de passe et identifiants
| Fonction | Rôle |
|---|---|
hashPassword(plain) | Hache un mot de passe. |
verifyPassword(plain, hash) | Vérifie en temps constant. |
generateToken() | Token aléatoire pour les vérifications. |
generateId() | Identifiant d'enregistrement. |
Tables
| Table | Contenu |
|---|---|
lrvo_auth_users | Comptes utilisateurs. |
lrvo_auth_accounts | Identifiants par provider (credential). |
lrvo_auth_sessions | Sessions actives, avec IP et user-agent. |
lrvo_auth_verifications | Tokens de vérification, réinitialisation, changement d'adresse, invitation. |
lrvo_auth_roles | Rôles. |
lrvo_auth_role_permissions | Permissions associées aux rôles. |
lrvo_auth_user_roles | Rôles assignés aux utilisateurs. |
Notes
Erreurs. register, createFirstAdmin, login et resetPassword lèvent des Error typées avec un message en français destiné à l'utilisateur. Les fonctions de lecture retournent null quand l'entrée n'existe pas.
Callbacks non bloquants. onEmailVerification et onPasswordChanged s'exécutent sans bloquer le flux principal : une erreur dedans est journalisée mais n'annule pas l'opération.
Auto-révocation de rôle. L'endpoint DELETE /lrvo/api/users/:id/roles/:roleId refuse qu'un utilisateur se retire son dernier rôle accordant users.manage. La règle vit dans le handler, pas dans revokeUserRole, qui reste une opération de bas niveau.