# @mostajs/auth — fiche LLM
> Authentification complète Next.js : email/password Argon2id, OAuth/OIDC, magic link, MFA TOTP, WebAuthn/Passkeys, refresh tokens rotatifs, rate-limit, lifecycle RGPD.

- Version: 3.3.0 · Licence: AGPL-3.0-or-later · Auteur: Dr Hamid MADANI <drmdh@msn.com>
- Chemin: mostajs/mosta-auth · Statut audit: complet (dist/)

## RÔLE
Couche d'authentification de l'écosystème mostajs, branchée sur NextAuth v5. Fournit 6 méthodes de connexion (email/password, OAuth2/OIDC, magic link, MFA TOTP, WebAuthn/Passkeys, lifecycle RGPD) plus des briques transverses (refresh tokens rotatifs avec détection de replay, rate-limit token-bucket, vocabulaire d'AuthEvent pour l'audit, primitives PKCE). L'autorisation RBAC (rôles/permissions) est déléguée à `@mostajs/rbac`. Le module ne ship pas de DB : chaque fonction reçoit des repos/callbacks à implémenter par le consumer (typiquement via `@mostajs/orm`).

## INSTALLATION
npm i @mostajs/auth @mostajs/rbac next-auth@^5.0.0-beta.25

## EXPORTS
Point d'entrée racine (`.`) — surtout client/RBAC :
- Fonctions: hasPermission, hasAllPermissions, hasAnyPermission, usePermissions
- Composants React: PermissionGuard (default), SessionProvider (default)
- Re-exports Zod schemas (de @mostajs/rbac): UserSchema, RoleSchema, PermissionSchema, PermissionCategorySchema
- Types: UserDTO, RoleDTO, PermissionDTO, PermissionCategoryDTO, PermissionDefinition, RoleDefinition, CategoryDefinition, MostaAuthConfig, RegistrationConfig, VerificationConfig, PasswordResetConfig, ApiKeyProviderConfig, SessionEnrichmentConfig

## EXPORTS PAR SOUS-CHEMIN
Le package expose ~30 sous-chemins via `exports`. Principaux :
- `./server` — agrégat serveur : createAuthHandlers, createAuthChecks, createAuthMiddleware, hashPassword, comparePassword, checkRequest, createRegistrationHandler, createVerificationHandlers, createPasswordResetHandlers, createApiKeyProvider, createCredentialsProvider, createCredentialsVerifyHandler, createRemoteCredentialsProvider, enrichTokenWithPlan, enrichSessionWithPlan + re-exports @mostajs/rbac (seedRBAC, createAdmin, UserRepository, RoleRepository, getPermissionsForRoleFromDB…)
- `./register` — register(registry) : auto-déclaration du module dans un registry mostajs
- `./lib/auth` — createAuthHandlers
- `./lib/auth-check` — createAuthChecks
- `./lib/permissions-server` — getPermissionsForRoleFromDB (re-export rbac)
- `./lib/password` — hashPassword, comparePassword, comparePasswordWithMeta, hashPasswordBcrypt (déprécié)
- `./lib/registration`, `./lib/email-verification`, `./lib/password-reset` — handlers DB-driven
- `./lib/refresh-tokens` — issueRefreshToken, rotateRefreshToken, revokeRefreshToken, revokeAllUserRefreshTokens
- `./lib/auth-rate-limit` — createAuthRateLimiter, MemoryRateLimitStore, RATE_LIMIT_PRESETS
- `./lib/auth-events` — noopAuthEventEmitter, wrapEmitter, types AuthEvent
- `./lib/oauth-primitives` — generateCodeVerifier, deriveCodeChallenge, generateState
- `./lib/oauth-providers`, `./lib/oauth-linking` — OAuth2/OIDC
- `./lib/magic-link` — requestMagicLink, verifyMagicLink
- `./lib/mfa-totp` — enrollTotp, verifyEnrollmentCode, verifyMfaCode, disableTotp, getMfaStatus, generateBackupCodes
- `./lib/webauthn` — startRegistration, finishRegistration, startAuthentication, finishAuthentication, listPasskeys, removePasskey
- `./lib/account-lifecycle` — requestAccountDeletion, confirmAccountDeletion, collectAccountExport, generateDeletionToken, verifyDeletionToken
- `./lib/anon-token`, `./lib/invite-token`, `./lib/passwordless`, `./lib/apikey-provider`, `./lib/credentials-provider`, `./lib/credentials-verify`, `./lib/remote-credentials-provider`, `./lib/check-request`, `./lib/session-enrichment`
- `./middleware/auth-middleware` — createAuthMiddleware
- Composants: `./components/SessionProvider`, `./components/PermissionGuard`, `./components/MfaEnrollDialog`, `./components/MfaChallenge`, `./components/PasskeyRegisterButton`, `./components/PasskeyLoginButton`
- `./hooks/usePermissions`

## API — SIGNATURES
createAuthHandlers(rolePermissions: Record<string,string[]>, config?: MostaAuthConfig): { handlers, auth, signIn, signOut, getServerSession }
createAuthChecks(auth: ()=>Promise<any>, fallbackMap?: Record<string,string[]>): { checkAuth, checkPermission(perm), getUserFromSession }
createAuthMiddleware(options?: AuthMiddlewareOptions): (req: NextRequest)=>NextResponse
hashPassword(plain): Promise<string>  // Argon2id
comparePassword(plain, hashed): Promise<boolean>
comparePasswordWithMeta(plain, hashed): Promise<{ valid, needsRehash }>  // needsRehash si bcrypt legacy
checkRequest(params: CheckRequestParams): Promise<CheckRequestResult>  // autorisation par api-key, transport-agnostique
createCredentialsProvider(config: CredentialsProviderConfig): provider NextAuth (objet plain)
createRegistrationHandler(config: RegistrationConfig): (req: Request)=>Promise<Response>
createVerificationHandlers(config): { sendVerification, verifyEmail, generateVerifyToken }
createPasswordResetHandlers(config): { forgotPassword, resetPassword, generateResetToken }
issueRefreshToken(config, ctx:{userId,ip?,userAgent?}): Promise<IssuedRefreshToken>
rotateRefreshToken(config, tokenPlaintext, ctx?): Promise<RotateResult>  // détection replay
createAuthRateLimiter(config?:{store?,warnIfMemoryFallback?}): AuthRateLimiter  // .tryConsume({key,...opts})
requestMagicLink(config, args:{email,intent?}): Promise<RequestMagicLinkResult>
verifyMagicLink(config, token): Promise<VerifyMagicLinkResult>
enrollTotp(repo, opts: EnrollOptions): Promise<EnrollResult>  // secret + QR + backup codes
verifyMfaCode(repo, {userId,code,encrypter?}): Promise<VerifyResult>
startRegistration / finishRegistration / startAuthentication / finishAuthentication (WebAuthn)
requestAccountDeletion(args), confirmAccountDeletion(args), collectAccountExport({hooks,userId})
usePermissions(adminRole='admin'): { permissions, role, roles, hasPermission, hasAnyPermission, hasRole, isAdmin, canAccess }

## TYPES CLÉS
MostaAuthConfig { extraUserFields?, defaultRoles?, defaultPermissions?, defaultCategories?, pages?, publicPaths?, protectedPrefixes? }
CheckRequestParams { dialect, headers?, query?, ip?, transport, checks?:[{scope,value}], openMode?, fallbackPublicLabel? }
CheckRequestResult { ok, status, body?, ctx?:{transport,apiKey?,projectName?,accountId?,permissions?,...}, apikey? }
RefreshTokenRecord { id, userId, tokenHash(SHA-256), expiresAt, replacedBy?, revokedAt?, ... }
RefreshTokenRepo { insert, findByHash, setReplacedBy, revokeById, revokeAllByUser, deleteExpired }
RateLimitResult { ok, remaining, retryAfter }  · RATE_LIMIT_PRESETS { loginByIp, loginByEmail, registerByIp, resetByEmail, magicLinkByEmail }
AuthEventKind = 'login.success' | 'login.failure' | 'logout' | 'register' | 'refresh.replay_detected' | 'mfa.*' | 'webauthn.*' | 'device_flow.*' | 'pkce.*' | ... (25+ valeurs)
MfaFactorRecord { id, userId, kind:'totp', secret(base32), secretEncrypted?, backupCodesHashed:(string|null)[], enabled, ... }
WebAuthnConfig { rpID(eTLD+1), rpName, expectedOrigins[], challengeTtlSec?, attestationType?, userVerification? }
DataSubjectHook { module, exportUserData(userId), purgeUserData(userId):Promise<{rowsDeleted}> }  // contrat RGPD inter-modules

## PATTERN
```ts
// app/auth.ts — serveur
import { createAuthHandlers, createCredentialsProvider } from '@mostajs/auth/server'
const provider = createCredentialsProvider({ findUserByEmail: (e)=>userRepo.findByEmail(e) })
export const { handlers, auth, signIn, signOut } = createAuthHandlers(rolePermissions, { providers:[provider] })

// route protégée
import { createAuthChecks } from '@mostajs/auth/server'
const { checkPermission } = createAuthChecks(auth)
const { error, session } = await checkPermission('users.read')
if (error) return error

// client
import { usePermissions } from '@mostajs/auth/hooks/usePermissions'
const { hasPermission, isAdmin } = usePermissions()
```

## DÉPEND DE
- @mostajs/rbac (peer >=1.0.0) — rôles, permissions, repositories, seed ; requis.
- @mostajs/config, @mostajs/net — dependencies directes.
- @mostajs/api-keys, @mostajs/orm — utilisés par check-request / refresh-tokens (api-keys et orm en devDependencies ; orm fournit IDialect).
- @mostajs/mailer — **complémentaire** (non importé par auth) : envoi effectif des emails de vérification, reset password, magic link, invite-token, suppression RGPD. auth génère le token/lien, mailer le livre. Cf. README §« Envoi des emails ».
- @mostajs/sms-lite — **complémentaire** (non importé par auth) : livraison du magic link PAR SMS (« magic-sms-link ») + alertes de connexion par SMS. Même principe que mailer : auth génère le token/lien, sms-lite le livre vers un numéro mobile. Cf. README §« Envoi par SMS (magic-sms-link) ».
- Peers : next >=14, next-auth >=5.0.0-beta.25, react >=18.

## LIVRAISON MULTI-CANAL — magic-sms-link
auth ne livre RIEN lui-même (ni email, ni SMS) : il génère le jeton/lien magique, le consumer le livre.
- EMAIL : @mostajs/mailer (`mailer.send`) — chemin historique.
- SMS (« magic-sms-link ») : @mostajs/sms-lite (`smsFromEnv()` → driver Twilio/passerelle) — livre le MÊME magicUrl par texto au numéro mobile connu de l'utilisateur, EN PLUS de l'email. Plus une ALERTE de connexion par SMS après vérification réussie du lien (« login-alert »).
- Activation : le consumer résout le numéro (annuaire/contact), et n'envoie le SMS que si un numéro est connu + canal activé (best-effort : un échec SMS ne casse jamais le flux email/login).
- Référence d'implémentation (composition root) : Hadhinat `incubator/app/auth.mjs` (ports sendEmail/sendSms) + `app/test-scripts/send-magic-sms-link.mjs`.
PIÈGES magic-sms-link :
- Le magicUrl contient un jeton long (~190 car.) → l'URL passe en PLUSIEURS segments SMS. Écrire le corps en GSM-7 STRICT (sans accents, sans em-dash « — ») : un seul caractère accentué force l'encodage UCS-2 (70 car./segment au lieu de 160) et multiplie les segments.
- Comptes Twilio TRIAL : destinataire devant être VÉRIFIÉ + préfixe « Sent from your Twilio trial account - » ajouté → un corps UCS-2 + URL longue déclenche l'erreur 30044 « Trial Message Length Exceeded ». Solution : GSM-7 (translittération) ou compte payant. Alternative à fort volume : envoyer un code OTP court plutôt que l'URL.

## PIÈGES
- Le module est DB-agnostique : presque toutes les fonctions exigent un repo/callback (RefreshTokenRepo, MfaFactorRepo, WebAuthnCredentialRepo, RegistrationConfig.createAccount, etc.). Rien n'est persisté tout seul.
- MFA/WebAuthn : enrollTotp/startRegistration créent un record `enabled:false` ; il faut un second appel (verifyEnrollmentCode / finishRegistration) pour activer.
- rotateRefreshToken : si l'ancien token a déjà un `replacedBy`, c'est un replay (token volé) → onReplayDetected déclenché ; prévoir révocation de chaîne + alerte audit.
- MemoryRateLimitStore est process-local : inadapté multi-instance, brancher un store partagé (Redis).
- requestMagicLink renvoie `unknown_email` quand le compte n'existe pas — le caller doit convertir en HTTP 200 neutre pour éviter l'énumération de comptes.
- hashPasswordBcrypt est déprécié (test/audit seulement) ; la voie de prod est hashPassword (Argon2id).
- comparePasswordWithMeta.needsRehash=true signale un hash bcrypt legacy : l'app doit re-hasher en Argon2id et persister.
- AuthEventEmitter du consumer doit être enveloppé par wrapEmitter pour qu'une panne d'audit ne casse jamais le flow auth.
- auth n'envoie AUCUN email : il génère les tokens/liens (vérification, reset, magic link, invite, suppression RGPD), l'envoi est à brancher via `@mostajs/mailer` (`createMailerFromEnv()` + `mailer.send(...)`). Les exemples du README l'utilisent.

## RÉFÉRENCES
- README.md (à la racine du module) — capacités détaillées, 6 lots
- docs/HOWTO-invite-token.md
- LICENSE (AGPL-3.0-or-later)
