@aventii/auth (1.0.0)

Published 2026-08-25 09:13:00 +00:00 by Valentin

Installation

@aventii:registry=
npm install @aventii/auth@1.0.0
"@aventii/auth": "1.0.0"

About this package

# @aventii/auth

Noyau générique d'inscription, authentification, credentials et sessions pour les microservices Node.js Aventii.

`@aventii/auth` est extrait du mécanisme Access/PMC sans embarquer les règles propres à Rapidoo.

**Le package sait créer et authentifier un compte. Il ne sait pas ce que ce compte fait dans le produit.**

## Installation

```bash

npm install @aventii/auth

npm install mongodb   # repository Mongo natif

npm install express   # router HTTP fourni

```

Prérequis : Node.js >= 22. Le package est CommonJS.

## Périmètre

La V1 couvre le salt anti-énumération, l'inscription en deux étapes, la validation mail et téléphone, le login, les access/refresh tokens persistants, la rotation refresh, le logout, forgot/reset password, la validation explicite de token, le middleware HTTP, la désactivation de compte et les nettoyages.

Elle ne gère pas le profil, les paiements, les documents d'identité, les fichiers, les règles pays ou les rôles métier.

## API publique

```js

const {

    createAuth,

    createAuthRouter,

    createAuthMiddleware,

    createMongoAuthRepository,

    createVersionedPepperProvider,

    AUTH_ACCOUNT_STATUS,

    AuthError,

    normalizeMail,

    generateRegistrationToken,

    hashOpaqueToken,

    generatePhoneValidationCode

} = require('@aventii/auth');

```

## Architecture

```text

                         microservice

                  /          |           \

        @aventii/db   @aventii/security   métier produit

             |               |                 |

       Mongo repository   primitives       policies/actions

                 \           |               /

                       @aventii/auth

```

Le microservice compose les capacités. Le cœur auth ne dépend pas directement de `@aventii/db`; le repository Mongo est le seul composant qui connaît MongoDB.

## Statuts

```text

active · waitingForMailValidation · disabled

```

Ils sont exposés via `AUTH_ACCOUNT_STATUS` et conservent le schéma historique PMC.

## Identifiants

Cette V1 conserve volontairement les `_id: ObjectId` Mongo classiques. Le repository Mongo convertit les identifiants sérialisés en `ObjectId`. Aucun UUID n'est introduit ici.

## Création du moteur

```js

const auth = createAuth({

    repository,

    tokenSecret,

    fakeSaltSecret,

    pepperProvider,

    generateFingerprint

});

```

Une configuration produit peut aussi injecter `policies`, `actions`, `hooks`, `buildAccessTokenClaims`, `normalizeMail`, `timing` et `enforceFingerprint`.

Le package ne lit jamais `process.env` et ne récupère aucun secret distant lui-même.

## Contrat repository

Le cœur exige :

```text

findUserByMail · findUserById · createUser · updateUser · completeRegistration · prepareMailValidation · consumeMailValidationToken · consumePhoneValidationCode · disableUser · createAccessToken · findAccessTokenById · findReusableAccessToken · invalidateAccessToken · deleteExpiredAccessTokens · createRefreshToken · findReusableRefreshToken · findValidRefreshTokenById · extendRefreshToken · revokeRefreshToken · revokeRefreshTokensByUserId · deleteExpiredRefreshTokens · deleteExpiredIncompleteRegistrations · deleteExpiredUnverifiedMailRegistrations

```

Le cœur ignore le moteur de persistance. La V1 fournit uniquement MongoDB natif.

## Repository Mongo

```js

const repository = createMongoAuthRepository({

    getUsersCollection: () => database.getDb('Auth').collection('Users'),

    getAccessTokensCollection: () => database.getDb('Auth').collection('Tokens'),

    getRefreshTokensCollection: () => database.getDb('Auth').collection('RefreshTokens')

});

```

Les getters sont résolus à chaque opération pour rester compatibles avec un remplacement de connexion par `@aventii/db`.

## Normalisation mail

Le comportement historique est conservé :

```js

normalizeMail('John+promo@Example.com');

// john@example.com

```

Le normaliseur trim, passe en minuscules et retire `+tag` du local-part. Il peut être remplacé via `createAuth({ normalizeMail })`.

## Salt anti-énumération

`register()` génère un salt aléatoire de 16 bytes, soit 32 caractères hexadécimaux.

`getSalt(mail)` renvoie le vrai salt pour un compte existant. Sinon :

```text

HMAC-SHA256(fakeSaltSecret, mail brut)

32 premiers caractères hex

```

Le client reçoit ainsi une réponse de même forme que le compte existe ou non.

## Register

```js

const result = await auth.register({

    mail: 'alice@example.com',

    accountData: {

        firstName: 'Alice',

        nationality: 'CM'

    }

});

```

`accountData` est opaque. Le moteur ajoute ou écrase seulement :

```text

mail · salt · status · registrationTokenHash · registrationTokenExpiresAt · registrationTokenAttempts

```

Le token brut est généré sur 256 bits et seul son SHA-256 est persisté.

## Ce qui reste au produit pendant l'inscription

Le package ne valide pas :

```text

firstName / lastName

dateOfBirth / gender

nationality / pays de service

type de compte

balance initiale

CGU

Mobile Money

règles de téléphone

```

Ces règles sont appliquées avant `auth.register()`.

## Tentatives d'inscription

Valeurs historiques par défaut :

```text

token TTL     60 s · max attempts  5 · penalty from  2 · penalty TTL   3 s

```

Un mauvais token incrémente `registrationTokenAttempts`. À partir de la deuxième erreur l'expiration est raccourcie; à la cinquième hash et expiration sont neutralisés.

L'échec de cette mise à jour secondaire est volontairement ignoré, comme dans le contrôleur extrait.

## Complete registration

```js

await auth.completeRegistration({

    mail,

    clientHash,

    registrationToken

});

```

`clientHash` doit être une chaîne SHA-256 hexadécimale de 64 caractères. Workflow :

```text

validation du token d'inscription

pepper courant

bcrypt(clientHash + pepper)

transition atomique en base

suppression registrationToken*

validation mail éventuelle

```

Work factor par défaut : `12`.

**## Pepper versionné

const pepperProvider = createVersionedPepperProvider({
	currentVersion: () => config.PASSWORD_PEPPER_CURRENT,

	sources: [
		version => config[`PASSWORD_PEPPER_V${version}`],
		version => keyService.get(`SERVER_PEPPER_V${version}`),
		() => hsm.getSecret('AUTH_PEPPER')
	]
});

Le provider reçoit une liste ordonnée et non vide de fonctions sources. Chaque source reçoit systématiquement la version demandée et peut retourner une chaîne ou une Promise<string>.

Toutes les sources sont résolues indépendamment puis leurs valeurs sont combinées dans l'ordre déclaré. La dérivation finale utilise SHA-256 avec un préfixage par longueur de chaque composante afin d'éviter toute ambiguïté de concaténation.

source 0(version)
source 1(version)
...
source N(version)
↓
composantes ordonnées
↓
encodage longueur:valeur
↓
SHA-256
↓
pepper final

Le package ne connaît ni la nature des sources, ni leur localisation, ni leur mécanisme de récupération. Il n'existe plus de notion local, remote, cacheRemote ou clearRemoteCache() dans le provider.

Si une source doit être mise en cache, ce cache appartient à la source elle-même ou à l'infrastructure qui la fournit.

Migration du pepper**

Au login, un credential valide utilisant une ancienne version est automatiquement rehashé avec la version courante :

```text

compare ancien pepper

↓ succès

version ancienne ?

↓ oui

rehash courant

update hash + pepperVersion

```

## Validation mail

Par défaut `mailVerificationRequired: true`, donc le compte naît avec `waitingForMailValidation`.

Après completion, un token e-mail de 256 bits est créé, seul son SHA-256 est persisté, avec un TTL de 24 h.

`actions.deliverMailValidation` est **best-effort** : une panne du mail ne défait pas la completion déjà enregistrée.

## Validate mail

```js

await auth.validateMail(token);

```

Le repository consomme atomiquement le token encore valide puis passe le compte de `waitingForMailValidation` à `active`.

Les champs du token sont supprimés et `hooks.onMailValidated` est exécuté en best-effort.

Si la vérification mail est désactivée, `register()` crée directement un compte `active` et completion déclenche `onRegistrationCompleted`.

## Validation téléphone

```js

await auth.validatePhone({ userId, code });

```

Le repository Mongo utilise par défaut :

```text

phoneValidationToken · verification.verifiedPhone

```

Ces chemins sont configurables via `phoneTokenField` et `phoneVerifiedField`.

`generatePhoneValidationCode()` produit le format historique `xxxx-xxxx`. Le package ne décide pas comment ce code est envoyé.

## Login

```js

const result = await auth.login({

    mail,

    password,

    request,

    context

});

```

`password` désigne la valeur credential fournie par le client; le package ne cherche pas à savoir comment le front l'a dérivée.

Workflow :

```text

find user

policy beforeCredential

hash présent ?

bcrypt.compare(password + pepper)

migration pepper éventuelle

status accepté ?

policy afterCredential

fingerprint

access token

refresh token

```

## Politique login

```js

policies: {

    login: async ({ phase, user, context, request }) => {

        ...

    }

}

```

`phase` vaut `beforeCredential` ou `afterCredential`.

Rapidoo pourra y remettre le contrôle nationalité/pays ou la restriction du portail admin sans contaminer le package.

## Audit login

`hooks.onAuthEvent` reçoit notamment :

```js

{

    eventType: 'login_failed',

    reasonKey: 'WRONG_PASSWORD',

    user,

    request

}

```

Les raisons génériques incluent `USER_NOT_FOUND`, `ACCOUNT_NOT_VALIDATED`, `WRONG_PASSWORD`, `MAIL_NOT_VALIDATED`, `ACCOUNT_DISABLED`.

Le hook est best-effort; le produit décide comment hash l'IP ou persister l'audit.

## Access tokens

La signature/vérification est déléguée à `@aventii/security`. Un document contient au minimum :

```text

userId · issuedAt · expiresAt · deleteAt · deviceFingerprint · passwordVersion

```

Défauts : expiration +1 h, purge +7 j.

## Claims produit

```js

buildAccessTokenClaims: ({ user, context }) => ({

    type: user.type,

    scope: ['read', 'write'],

    issuer: 'authServer@my-product'

})

```

Le package ajoute ensuite ses claims réservés. Il ne connaît ni `pmc-admin`, ni les rôles Rapidoo.

## Réutilisation access token

Un access token non expiré du même `userId + fingerprint` peut être réutilisé.

Compatibilité historique importante : le repository ne filtre pas un éventuel champ `valid` dans `findReusableAccessToken()` ni dans `findAccessTokenById()`.

Cette sémantique est documentée et n'est pas corrigée silencieusement en V1.

## Refresh tokens

Le client conserve l'_id Mongo du refresh token. Le document contient :

```text

userId · deviceFingerprint · passwordVersion · issuedAt · expiresAt · deleteAt · valid

```

Défauts : expiration +15 j, purge +45 j.

Au login, un refresh token `valid:true`, non expiré, du même user+fingerprint+passwordVersion peut être réutilisé et prolongé.

## Rotation refresh

```js

await auth.refresh({

    refreshTokenId,

    request,

    context

});

```

Workflow :

```text

refresh exact valid:true ?

fingerprint identique ?

non expiré ?

user existe ?

non disabled ?

passwordVersion identique ?

révoque ancien

nouveau refresh

nouvel access

```

Le refresh est donc rotatif.

## Password version

Un reset incrémente `passwordVersion`. Les refresh tokens portant une ancienne version sont refusés.

La V1 ne modifie pas la sémantique historique de vérification des access tokens.

## Logout

```js

await auth.logout(refreshTokenId);

```

La révocation est best-effort. Une panne Mongo ne doit pas empêcher le microservice de nettoyer ses cookies.

## Forgot password

```js

await auth.requestPasswordReset(mail);

```

Un compte absent ou disabled produit un résultat accepté sans révéler son existence. Pour un compte actif :

```text

token brut 256 bits

SHA-256 persisté

expiration +15 min

livraison éventuelle

```

## Livraison du reset

`actions.deliverPasswordReset` est **bloquante** lorsqu'elle est fournie.

Le token est persisté avant la tentative de livraison; une panne du mail peut donc remonter une erreur après mutation.

Cette asymétrie avec la validation mail conserve le comportement réel extrait.

## Reset password

```js

await auth.resetPassword({

    mail,

    token,

    newHash

});

```

Le moteur vérifie compte, statut, expiration et token, puis compare le SHA-256 via `timingSafeEqual`. Ensuite :

```text

bcrypt(newHash + currentPepper) · hash remplacé · pepperVersion courante · passwordVersion + 1 · resetToken = null · resetExpires = null

```

## Vérification token

```js

const payload = await auth.verifyAccessToken(token, request);

const { payload, user } = await auth.authenticate(token, request);

```

`validateToken({ token, request, context })` ajoute ensuite une éventuelle `policies.token`.

Le produit peut ainsi contrôler issuer/origine/admin sans code spécifique dans `@aventii/auth`.

## Désactivation

```js

await auth.disableAccount({

    actorUserId,

    targetUserId,

    password,

    context

});

```

Par défaut, `actor == target`. Le credential de l'acteur est vérifié. `policies.disableAccount` peut autoriser un administrateur.

Après succès :

```text

target.status = disabled · target.disabledAt = now · refresh tokens target => valid:false

```

Les access tokens ne sont pas invalidés en masse en V1, conformément au comportement extrait.

## Nettoyage

Le package n'installe aucun cron :

```js

await auth.cleanupExpired();

```

Cela délègue la suppression des access tokens expirés, refresh tokens expirés et inscriptions initiales incomplètes.

Optionnellement :

```js

await auth.cleanupExpired({

    unverifiedMailBefore: new Date(...)

});

```

supprime les comptes en attente de validation mail avant la date donnée.

## Hooks et actions

Hooks best-effort :

```text

onRegistrationCompleted · onMailValidated · onAccountDisabled · onAuthEvent · onHookError

```

Actions :

```text

deliverMailValidation  best-effort · deliverPasswordReset    bloquante

```

Le package ne connaît aucun service mail ou notification concret.

## Middleware HTTP

```js

const authMiddleware = createAuthMiddleware({ auth });

```

Par défaut le token est lu dans `req.cookies.token`. En succès :

```js

req.user = user;

req.auth = { user, payload };

```

Un bypass externe peut être injecté :

```js

bypass: req => isValidInternalToken(req.headers.authorization)

```

Le package ne connaît pas les tokens internes ni les exceptions de routes produit.

## Router Express

```js

const router = createAuthRouter({

    auth,

    resolveRegistration,

    cookieOptionsResolver

});

```

Routes relatives :

```text

GET    /salt

POST   /register

POST   /register/complete

POST   /mail/validate

POST   /login

POST   /refresh

POST   /logout

POST   /phone/validate/:id

POST   /password/forgot

POST   /password/reset

POST   /token/validate

PATCH  /account/disable/:id

```

## resolveRegistration

C'est la frontière principale avec le produit :

```js

resolveRegistration: async req => {

    const accountData = validateMyProductRegistration(req);

    return {

        mail: accountData.mail,

        accountData

    };

}

```

Rapidoo y conserve âge, nationalité, téléphone, CGU, balance, Mobile Money et type de compte.

## Cookies

Le router ne connaît aucun domaine :

```js

cookieOptionsResolver: req => ({

    httpOnly: true,

    secure: true,

    sameSite: 'none',

    domain: resolveDomain(req),

    path: '/'

})

```

Noms par défaut : `token`, `refresh_token`. Durées par défaut : 1 h et 15 j.

Les tokens privés de validation mail et de reset ne sont jamais exposés dans les réponses HTTP du router.

## Timing par défaut

```text

workFactor                       12 · registrationTokenTtlMs           60 s · registrationMaxAttempts          5 · registrationPenaltyAfterAttempts 2 · registrationPenaltyTtlMs         3 s · mailValidationTtlMs             24 h · passwordResetTtlMs               15 min · accessTokenTtlMs                 1 h · accessTokenRetentionMs           7 j · refreshTokenTtlMs                15 j · refreshTokenRetentionMs          45 j · revokedRefreshRetentionMs        30 j

```

## Articulation avec les autres packages

`@aventii/security` fournit les primitives de signature/vérification et le fingerprint est injecté. `enforceFingerprint` vaut `true` par défaut et peut être désactivé explicitement.

`@aventii/db` reste responsable de la connexion; le repository reçoit seulement des getters de collection.

`@aventii/link` peut être utilisé par les actions mail sans dépendance directe.

`@aventii/files` et `@aventii/document-validation` sont totalement indépendants de `@aventii/auth`.

Un futur identity-service pourra donc assembler les trois sans couplage.

## Hors périmètre

Le package ne gère pas :

  • profil public ou avatar ;

  • balance, transactions, top-up ou withdrawal ;

  • signalements ;

  • documents d'identité ou véhicule ;

  • stockage fichier ;

  • notifications métier concrètes ;

  • pays, numérotation, âge, CGU, devise ou Mobile Money ;

  • rôle admin prédéfini ;

  • audit Mongo imposé ;

  • service de clés distant ;

  • `.env`, runtime global, CORS, rate limiting ou Proof of Work.

## Persistance future

Le contrat repository rend une autre persistance possible.

La V1 fournit uniquement MongoDB natif car c'est l'implémentation réellement extraite.

Aucun adapter Mongoose ou SQL spéculatif n'est ajouté.

## Reconnexion de Rapidoo

La future réécriture du MS devra principalement fournir :

```text

resolveRegistration

policies.login

policies.token

policies.disableAccount

buildAccessTokenClaims

deliverMailValidation

deliverPasswordReset

onAuthEvent

onRegistrationCompleted / onMailValidated

cookieOptionsResolver

bypass du middleware

```

Le protocole auth lui-même reste dans le package.

## Erreurs

`AuthError` expose :

```js

{

    name: 'AuthError',

    message: '[auth] ...',

    code,

    status,

    userId,

    details,

    cause

}

```

Les erreurs de contrat de construction utilisent `TypeError`.

## Tests

```bash

npm test

```

La suite utilise `node:test` et `node:assert/strict`.

Aucun Mongo réel, Express réel, bcrypt natif ou service distant n'est nécessaire aux tests.

Les tests couvrent salt, register, tentatives, completion, e-mail, téléphone, login, claims, réutilisation tokens, migration pepper, policies, audit, refresh rotation, passwordVersion, forgot/reset, token validation, disable, logout best-effort, cleanup, repository Mongo, cookies, router, middleware et bypass.

## Résumé

`@aventii/auth` possède :

```text

compte

credential

inscription

sessions

access token

refresh token

validation mail

validation téléphone

forgot/reset

désactivation

```

Le produit possède :

```text

qui peut s'inscrire · avec quelles données · dans quel pays · avec quel rôle · avec quelles conséquences

```

**Auth prouve qu'un compte peut accéder au système. Le microservice compose cette capacité avec le métier du produit.**

Dependencies

Dependencies

ID Version
@aventii/security ^1.2.0
bcrypt ^6.0.0

Peer Dependencies

ID Version
express >=5
mongodb >=6
Details
npm
2026-08-25 09:13:00 +00:00
1
UNLICENSED
latest
22 KiB
Assets (1)
Versions (1) View all
1.0.0 2026-08-25