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

FichierRôle
auth.jsregister, createFirstAdmin, login, mot de passe, vérification d'adresse.
session.jsCréation, validation, suppression de sessions.
queries.jsLecture et mise à jour des utilisateurs.
roles.jsCRUD des rôles et assignation.
permissions.jshasPermission, getUserPermissions.
permissions-registry.jsEnregistrement des permissions contribuées par les modules.
constants.jsPERMISSIONS, PERMISSION_DEFINITIONS, getPermissionGroups.
verifications.jsTokens de vérification d'adresse et de réinitialisation.
emailChange.jsTokens de changement d'adresse.
password.jshashPassword, 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ètreContrainte
email254 caractères maximum
password8 à 128 caractères, au moins une majuscule, une minuscule et un chiffre
name100 caractères maximum
onEmailVerificationasync (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ètreDéfautValeurs
page1—
limit10100 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.

GroupeClés
Administrationadmin.access
Utilisateursusers.view, users.manage
Rôlesroles.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éthodeRouteEffet
POST/lrvo/api/users/profile/emailVé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.

FonctionRô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.

FonctionRô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

FonctionRô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

TableContenu
lrvo_auth_usersComptes utilisateurs.
lrvo_auth_accountsIdentifiants par provider (credential).
lrvo_auth_sessionsSessions actives, avec IP et user-agent.
lrvo_auth_verificationsTokens de vérification, réinitialisation, changement d'adresse, invitation.
lrvo_auth_rolesRôles.
lrvo_auth_role_permissionsPermissions associées aux rôles.
lrvo_auth_user_rolesRô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.

Sur cette page