@kourouma/express-authkit 0.0.0-stage → 0.1.0-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/CHANGELOG.md +78 -0
  2. package/LICENSE +21 -0
  3. package/README.md +218 -2
  4. package/SECURITY.md +30 -0
  5. package/bin/express-authkit.js +30 -0
  6. package/package.json +93 -4
  7. package/src/adapters/express/bearer-token.js +35 -0
  8. package/src/adapters/express/http-errors.js +89 -0
  9. package/src/adapters/express/index.js +405 -0
  10. package/src/config/assertions.js +80 -0
  11. package/src/config/auth-kit-options.js +61 -0
  12. package/src/config/defaults.js +66 -0
  13. package/src/config/jwt-options.js +86 -0
  14. package/src/config/password-options.js +38 -0
  15. package/src/config/refresh-token-options.js +49 -0
  16. package/src/config/secret-strength.js +112 -0
  17. package/src/core/authentication-service.js +278 -0
  18. package/src/core/authorization.js +156 -0
  19. package/src/core/contracts/assert-contract.js +26 -0
  20. package/src/core/contracts/auth-principal.js +36 -0
  21. package/src/core/contracts/authentication-service.js +44 -0
  22. package/src/core/contracts/password-provider.js +27 -0
  23. package/src/core/contracts/refresh-token-store.js +80 -0
  24. package/src/core/contracts/token-provider.js +45 -0
  25. package/src/core/contracts/user-repository.js +157 -0
  26. package/src/core/errors.js +258 -0
  27. package/src/core/input-limits.js +26 -0
  28. package/src/core/messages.js +239 -0
  29. package/src/core/refresh-token-service.js +149 -0
  30. package/src/factory/create-auth-kit.js +90 -0
  31. package/src/index.js +61 -0
  32. package/src/providers/argon2-password-provider.js +68 -0
  33. package/src/providers/in-memory-refresh-token-store.js +51 -0
  34. package/src/providers/jwt-token-provider.js +112 -0
  35. package/types/adapters/express/bearer-token.d.ts +11 -0
  36. package/types/adapters/express/http-errors.d.ts +30 -0
  37. package/types/adapters/express/index.d.ts +57 -0
  38. package/types/config/assertions.d.ts +40 -0
  39. package/types/config/auth-kit-options.d.ts +41 -0
  40. package/types/config/defaults.d.ts +55 -0
  41. package/types/config/jwt-options.d.ts +42 -0
  42. package/types/config/password-options.d.ts +23 -0
  43. package/types/config/refresh-token-options.d.ts +21 -0
  44. package/types/config/secret-strength.d.ts +7 -0
  45. package/types/core/authentication-service.d.ts +37 -0
  46. package/types/core/authorization.d.ts +40 -0
  47. package/types/core/contracts/assert-contract.d.ts +13 -0
  48. package/types/core/contracts/auth-principal.d.ts +38 -0
  49. package/types/core/contracts/authentication-service.d.ts +102 -0
  50. package/types/core/contracts/password-provider.d.ts +32 -0
  51. package/types/core/contracts/refresh-token-store.d.ts +105 -0
  52. package/types/core/contracts/token-provider.d.ts +79 -0
  53. package/types/core/contracts/user-repository.d.ts +112 -0
  54. package/types/core/errors.d.ts +213 -0
  55. package/types/core/input-limits.d.ts +20 -0
  56. package/types/core/messages.d.ts +141 -0
  57. package/types/core/refresh-token-service.d.ts +31 -0
  58. package/types/factory/create-auth-kit.d.ts +52 -0
  59. package/types/index.d.ts +60 -0
  60. package/types/providers/argon2-password-provider.d.ts +13 -0
  61. package/types/providers/in-memory-refresh-token-store.d.ts +11 -0
  62. package/types/providers/jwt-token-provider.d.ts +13 -0
@@ -0,0 +1,112 @@
1
+ import { SignJWT, errors as joseErrors, jwtVerify } from "jose";
2
+
3
+ import { resolveJwtOptions } from "../config/jwt-options.js";
4
+ import { InvalidInputError, InvalidTokenError, TokenExpiredError } from "../core/errors.js";
5
+ import { MAX_TOKEN_LENGTH } from "../core/input-limits.js";
6
+ import { subjectInvalid } from "../core/messages.js";
7
+
8
+ /**
9
+ * Type de média RFC 9068 pour les jetons d’accès JWT. L’exiger lors de la
10
+ * vérification empêche d’autres JWT signés avec la même clé (jetons d’identité,
11
+ * liens de confirmation d’e-mail…) d’être acceptés comme jetons d’accès.
12
+ */
13
+ const ACCESS_TOKEN_TYPE = "at+jwt";
14
+ const REQUIRED_CLAIMS = Object.freeze(["sub", "iss", "aud", "exp", "iat", "jti"]);
15
+ const MAX_SUBJECT_LENGTH = 256;
16
+
17
+ /**
18
+ * Crée un {@link import("../core/contracts/token-provider.js").TokenProvider}
19
+ * qui émet et vérifie des jetons d’accès JWT signés en HMAC avec `jose`.
20
+ *
21
+ * Les jetons ne contiennent que des revendications standard (`sub`, `iss`,
22
+ * `aud`, `iat`, `exp`, `jti`) : aucune donnée personnelle ou sensible n’est
23
+ * placée dans la charge utile, qui est encodée mais non chiffrée.
24
+ *
25
+ * @param {import("../config/jwt-options.js").JwtOptions} options
26
+ * @returns {import("../core/contracts/token-provider.js").TokenProvider}
27
+ * @throws {import("../core/errors.js").ConfigurationError}
28
+ */
29
+ export function createJwtTokenProvider(options) {
30
+ const config = resolveJwtOptions(options);
31
+
32
+ const verifyOptions = Object.freeze({
33
+ algorithms: [config.algorithm],
34
+ issuer: config.issuer,
35
+ audience: config.audience,
36
+ typ: ACCESS_TOKEN_TYPE,
37
+ clockTolerance: config.clockToleranceSeconds,
38
+ // Also makes jose reject tokens whose `iat` lies in the future, and tokens
39
+ // older than the configured lifetime (e.g. after shortening it).
40
+ maxTokenAge: config.expiresInSeconds,
41
+ requiredClaims: [...REQUIRED_CLAIMS]
42
+ });
43
+
44
+ /**
45
+ * @param {{ subject: string }} input
46
+ * @returns {Promise<import("../core/contracts/token-provider.js").IssuedToken>}
47
+ */
48
+ async function issue(input) {
49
+ const subject = input?.subject;
50
+ if (typeof subject !== "string" || subject.length === 0 || subject.length > MAX_SUBJECT_LENGTH) {
51
+ throw new InvalidInputError(subjectInvalid(MAX_SUBJECT_LENGTH));
52
+ }
53
+
54
+ const issuedAt = Math.floor(Date.now() / 1000);
55
+ const expiresAt = issuedAt + config.expiresInSeconds;
56
+ const tokenId = globalThis.crypto.randomUUID();
57
+
58
+ const token = await new SignJWT({})
59
+ .setProtectedHeader({ alg: config.algorithm, typ: ACCESS_TOKEN_TYPE })
60
+ .setSubject(subject)
61
+ .setIssuer(config.issuer)
62
+ .setAudience(config.audience)
63
+ .setIssuedAt(issuedAt)
64
+ .setExpirationTime(expiresAt)
65
+ .setJti(tokenId)
66
+ .sign(config.key);
67
+
68
+ return { token, tokenId, issuedAt, expiresAt };
69
+ }
70
+
71
+ /**
72
+ * @param {string} token
73
+ * @returns {Promise<import("../core/contracts/token-provider.js").VerifiedTokenClaims>}
74
+ */
75
+ async function verify(token) {
76
+ if (typeof token !== "string" || token.length === 0 || token.length > MAX_TOKEN_LENGTH) {
77
+ throw new InvalidTokenError();
78
+ }
79
+
80
+ let payload;
81
+ try {
82
+ ({ payload } = await jwtVerify(token, config.key, verifyOptions));
83
+ } catch (error) {
84
+ throw toTokenError(error);
85
+ }
86
+
87
+ const { sub, jti, iat, exp } = payload;
88
+ if (typeof sub !== "string" || sub.length === 0 || sub.length > MAX_SUBJECT_LENGTH || typeof jti !== "string") {
89
+ throw new InvalidTokenError();
90
+ }
91
+ return { subject: sub, tokenId: jti, issuedAt: /** @type {number} */ (iat), expiresAt: /** @type {number} */ (exp) };
92
+ }
93
+
94
+ return Object.freeze({ issue, verify });
95
+ }
96
+
97
+ /**
98
+ * Convertit les erreurs de `jose` en erreurs de la bibliothèque. Toute erreur
99
+ * qui ne vient pas de JOSE est inattendue et est propagée sans modification.
100
+ *
101
+ * @param {unknown} error
102
+ * @returns {unknown}
103
+ */
104
+ function toTokenError(error) {
105
+ if (error instanceof joseErrors.JWTExpired) {
106
+ return new TokenExpiredError({ cause: error });
107
+ }
108
+ if (error instanceof joseErrors.JOSEError) {
109
+ return new InvalidTokenError({ cause: error });
110
+ }
111
+ return error;
112
+ }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Extrait le jeton d’accès d’un en-tête `Authorization`.
3
+ *
4
+ * Un en-tête utilisant un autre schéma (par exemple `Basic`) est traité comme
5
+ * une absence de jeton ; une valeur `Bearer` mal formée est invalide.
6
+ *
7
+ * @param {string | undefined} headerValue
8
+ * @returns {string}
9
+ * @throws {TokenMissingError | InvalidTokenError}
10
+ */
11
+ export declare function extractBearerToken(headerValue: string | undefined): string;
@@ -0,0 +1,30 @@
1
+ /**
2
+ * @param {unknown} error
3
+ * @returns {number | undefined} Statut à envoyer, ou `undefined` si ce n’est pas une erreur client.
4
+ */
5
+ export declare function getClientErrorStatus(error: unknown): number | undefined;
6
+ /**
7
+ * Comme {@link getClientErrorStatus}, mais reconnaît aussi les erreurs du
8
+ * parseur JSON d’Express (`express.json()`) : un JSON mal formé ou trop
9
+ * volumineux reçoit une réponse JSON propre au lieu de la page d’erreur HTML
10
+ * d’Express, qui peut afficher la pile d’appels en développement.
11
+ *
12
+ * @param {unknown} error
13
+ * @returns {{ error: import("../../core/errors.js").AuthKitError, status: number } | undefined}
14
+ */
15
+ export declare function resolveClientError(error: unknown): {
16
+ error: import("../../core/errors.js").AuthKitError;
17
+ status: number;
18
+ } | undefined;
19
+ /**
20
+ * Envoie une erreur d’authentification client au format JSON.
21
+ *
22
+ * Seuls le `code` stable et le `message` public sont envoyés : jamais la pile,
23
+ * la cause ou la raison interne de l’échec.
24
+ *
25
+ * @param {import("express").Response} res
26
+ * @param {import("../../core/errors.js").AuthKitError} error
27
+ * @param {number} status
28
+ * @param {string} realm
29
+ */
30
+ export declare function sendClientError(res: import("express").Response, error: import("../../core/errors.js").AuthKitError, status: number, realm: string): void;
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Express AuthKit — adaptateur Express.js (`@kourouma/express-authkit/express`).
3
+ *
4
+ * Ce module n’importe pas Express : il produit uniquement des middlewares
5
+ * standard `(req, res, next)`, compatibles avec Express 4 et 5.
6
+ *
7
+ * @module @kourouma/express-authkit/express
8
+ */
9
+ export type ExpressAuthOptions = {
10
+ /**
11
+ * Domaine annoncé dans l’en-tête `WWW-Authenticate`.
12
+ */
13
+ realm?: string;
14
+ };
15
+ export type ExpressAuthorizationRequirement = string | {
16
+ anyRole?: string[];
17
+ allPermissions?: string[];
18
+ check?: (user: import("../../core/contracts/auth-principal.js").AuthPrincipal, req: import("express").Request) => boolean | Promise<boolean>;
19
+ };
20
+ export type LoginHandlerOptions = {
21
+ /**
22
+ * Champ du corps JSON contenant l’identifiant (par exemple `"email"`).
23
+ */
24
+ identifierField?: string;
25
+ };
26
+ export type ExpressAuth = Readonly<{
27
+ authenticate: () => import("express").RequestHandler;
28
+ authorize: (requirement: ExpressAuthorizationRequirement) => import("express").RequestHandler;
29
+ login: (options?: LoginHandlerOptions) => import("express").RequestHandler;
30
+ refresh: () => import("express").RequestHandler;
31
+ logout: () => import("express").RequestHandler;
32
+ errorHandler: () => import("express").ErrorRequestHandler;
33
+ }>;
34
+ /**
35
+ * Crée l’intégration Express pour un moteur construit avec `createAuthKit()`.
36
+ *
37
+ * @example
38
+ * const expressAuth = createExpressAuth(auth);
39
+ * app.post("/login", express.json(), expressAuth.login());
40
+ * app.get("/profile", expressAuth.authenticate(), (req, res) => {
41
+ * res.json({ userId: req.auth.userId });
42
+ * });
43
+ *
44
+ * @param {import("../../factory/create-auth-kit.js").AuthKit} auth
45
+ * @param {ExpressAuthOptions} [options]
46
+ * @returns {ExpressAuth}
47
+ */
48
+ export declare function createExpressAuth(auth: import("../../factory/create-auth-kit.js").AuthKit, options?: ExpressAuthOptions): ExpressAuth;
49
+
50
+ declare global {
51
+ namespace Express {
52
+ interface Request {
53
+ /** Utilisateur authentifié. Présent après `expressAuth.authenticate()`. */
54
+ auth?: import("../../core/contracts/auth-principal.js").AuthPrincipal;
55
+ }
56
+ }
57
+ }
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Petites fonctions de vérification partagées par les validateurs de configuration.
3
+ *
4
+ * @module config/assertions
5
+ */
6
+ /**
7
+ * @param {unknown} value
8
+ * @returns {value is Record<string, unknown>}
9
+ */
10
+ export declare function isPlainObject(value: unknown): value is Record<string, unknown>;
11
+ /**
12
+ * @param {unknown} value
13
+ * @param {string} name
14
+ * @returns {asserts value is Record<string, unknown>}
15
+ */
16
+ export declare function assertPlainObject(value: unknown, name: string): asserts value is Record<string, unknown>;
17
+ /**
18
+ * Refuse les clés inconnues afin qu’une faute de frappe (par exemple
19
+ * `expiresIn` au lieu de `expiresInSeconds`) ne passe pas inaperçue.
20
+ *
21
+ * @param {Record<string, unknown>} object
22
+ * @param {readonly string[]} allowedKeys
23
+ * @param {string} name
24
+ */
25
+ export declare function assertKnownKeys(object: Record<string, unknown>, allowedKeys: readonly string[], name: string): void;
26
+ /**
27
+ * @param {unknown} value
28
+ * @param {string} name
29
+ * @param {number} maxLength
30
+ * @returns {asserts value is string}
31
+ */
32
+ export declare function assertNonEmptyString(value: unknown, name: string, maxLength: number): asserts value is string;
33
+ /**
34
+ * @param {unknown} value
35
+ * @param {string} name
36
+ * @param {number} min
37
+ * @param {number} max
38
+ * @returns {asserts value is number}
39
+ */
40
+ export declare function assertIntegerInRange(value: unknown, name: string, min: number, max: number): asserts value is number;
@@ -0,0 +1,41 @@
1
+ export type ProviderOverrides = {
2
+ /**
3
+ * Remplace le fournisseur Argon2id. Les options `password` doivent alors être absentes.
4
+ */
5
+ password?: import("../core/contracts/password-provider.js").PasswordProvider;
6
+ /**
7
+ * Remplace le fournisseur JWT. Les options `jwt` doivent alors être absentes.
8
+ */
9
+ token?: import("../core/contracts/token-provider.js").TokenProvider;
10
+ };
11
+ export type AuthKitOptions = {
12
+ /**
13
+ * Dépôt d’utilisateurs de l’application.
14
+ */
15
+ users: import("../core/contracts/user-repository.js").UserRepository;
16
+ /**
17
+ * Obligatoire sans `providers.token`.
18
+ */
19
+ jwt?: import("./jwt-options.js").JwtOptions;
20
+ /**
21
+ * Paramètres de coût Argon2id.
22
+ */
23
+ password?: import("./password-options.js").PasswordOptions;
24
+ /**
25
+ * Active les jetons de rafraîchissement (désactivés par défaut).
26
+ */
27
+ refreshTokens?: import("./refresh-token-options.js").RefreshTokenOptions;
28
+ /**
29
+ * Implémentations personnalisées des fournisseurs.
30
+ */
31
+ providers?: ProviderOverrides;
32
+ };
33
+ /**
34
+ * Valide la structure principale des options. Les valeurs imbriquées sont
35
+ * validées par leurs résolveurs et leurs vérifications de contrat dédiés.
36
+ *
37
+ * @param {unknown} options
38
+ * @returns {AuthKitOptions}
39
+ * @throws {ConfigurationError}
40
+ */
41
+ export declare function validateAuthKitOptions(options: unknown): AuthKitOptions;
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Valeurs sûres par défaut et limites acceptées.
3
+ *
4
+ * @module config/defaults
5
+ */
6
+ /**
7
+ * Algorithmes JWT acceptés pour une signature par secret partagé, avec la taille
8
+ * minimale de clé exigée par la RFC 7518 §3.2.
9
+ *
10
+ * @type {Readonly<Record<string, number>>}
11
+ */
12
+ export declare const JWT_ALGORITHM_MIN_KEY_BYTES: Readonly<Record<string, number>>;
13
+ export declare const JWT_DEFAULTS: Readonly<{
14
+ algorithm: "HS256";
15
+ /** 15 minutes : une courte durée réduit l’impact d’un jeton divulgué. */
16
+ expiresInSeconds: 900;
17
+ clockToleranceSeconds: 0;
18
+ }>;
19
+ export declare const JWT_LIMITS: Readonly<{
20
+ minExpiresInSeconds: 30;
21
+ /** Les jetons d’accès ne sont pas révocables : une durée de plus d’un jour est refusée. */
22
+ maxExpiresInSeconds: 86400;
23
+ maxClockToleranceSeconds: 60;
24
+ maxClaimLength: 256;
25
+ }>;
26
+ /**
27
+ * Valeurs Argon2id par défaut : « deuxième option recommandée » de la RFC 9106
28
+ * (64 Mio, t=3, p=4), plus forte que le minimum OWASP.
29
+ */
30
+ export declare const ARGON2_DEFAULTS: Readonly<{
31
+ memoryCost: 65536;
32
+ timeCost: 3;
33
+ parallelism: 4;
34
+ }>;
35
+ /**
36
+ * Seuils en dessous desquels Argon2id est refusé : au moins 19 Mio de mémoire
37
+ * et 2 itérations, selon OWASP.
38
+ */
39
+ export declare const ARGON2_LIMITS: Readonly<{
40
+ minMemoryCost: 19456;
41
+ maxMemoryCost: 4194304;
42
+ minTimeCost: 2;
43
+ maxTimeCost: 20;
44
+ minParallelism: 1;
45
+ maxParallelism: 16;
46
+ }>;
47
+ export declare const REFRESH_TOKEN_DEFAULTS: Readonly<{
48
+ /** 14 jours : l’utilisateur reste connecté s’il revient au moins toutes les deux semaines. */
49
+ expiresInSeconds: 1209600;
50
+ }>;
51
+ export declare const REFRESH_TOKEN_LIMITS: Readonly<{
52
+ minExpiresInSeconds: 300;
53
+ /** 90 jours au plus : au-delà, une session oubliée reste exploitable trop longtemps. */
54
+ maxExpiresInSeconds: 7776000;
55
+ }>;
@@ -0,0 +1,42 @@
1
+ export type JwtOptions = {
2
+ /**
3
+ * Clé de signature partagée produite par un générateur sûr (≥ 32 octets pour HS256). Une valeur absente (par exemple une variable d’environnement non définie) est acceptée par les types mais arrête createAuthKit() avec un message clair.
4
+ */
5
+ secret: string | Uint8Array | undefined;
6
+ /**
7
+ * Valeur de la revendication `iss`, contrôlée à la vérification.
8
+ */
9
+ issuer: string;
10
+ /**
11
+ * Valeur de la revendication `aud`, contrôlée à la vérification.
12
+ */
13
+ audience: string;
14
+ /**
15
+ * Durée de vie du jeton (30 à 86 400 secondes).
16
+ */
17
+ expiresInSeconds?: number;
18
+ /**
19
+ * Seul algorithme accepté à la vérification.
20
+ */
21
+ algorithm?: "HS256" | "HS384" | "HS512";
22
+ /**
23
+ * Décalage d’horloge accepté entre instances (0 à 60 secondes).
24
+ */
25
+ clockToleranceSeconds?: number;
26
+ };
27
+ export type ResolvedJwtOptions = Readonly<{
28
+ key: Uint8Array;
29
+ issuer: string;
30
+ audience: string;
31
+ expiresInSeconds: number;
32
+ algorithm: string;
33
+ clockToleranceSeconds: number;
34
+ }>;
35
+ /**
36
+ * Valide les options JWT, applique des valeurs sûres et refuse les valeurs dangereuses.
37
+ *
38
+ * @param {unknown} options
39
+ * @returns {ResolvedJwtOptions}
40
+ * @throws {ConfigurationError}
41
+ */
42
+ export declare function resolveJwtOptions(options: unknown): ResolvedJwtOptions;
@@ -0,0 +1,23 @@
1
+ export type PasswordOptions = {
2
+ /**
3
+ * Mémoire en Kio (minimum 19 456, soit 19 Mio).
4
+ */
5
+ memoryCost?: number;
6
+ /**
7
+ * Nombre d’itérations (minimum 2).
8
+ */
9
+ timeCost?: number;
10
+ /**
11
+ * Niveau de parallélisme (1 à 16).
12
+ */
13
+ parallelism?: number;
14
+ };
15
+ export type ResolvedPasswordOptions = Readonly<Required<PasswordOptions>>;
16
+ /**
17
+ * Valide les options Argon2id et refuse les paramètres sous le seuil OWASP.
18
+ *
19
+ * @param {unknown} [options]
20
+ * @returns {ResolvedPasswordOptions}
21
+ * @throws {import("../core/errors.js").ConfigurationError}
22
+ */
23
+ export declare function resolvePasswordOptions(options?: unknown): ResolvedPasswordOptions;
@@ -0,0 +1,21 @@
1
+ export type RefreshTokenOptions = {
2
+ /**
3
+ * Stockage des jetons (votre base de données, ou `createInMemoryRefreshTokenStore()` en développement).
4
+ */
5
+ store: import("../core/contracts/refresh-token-store.js").RefreshTokenStore;
6
+ /**
7
+ * Durée de vie d’un jeton de rafraîchissement
8
+ * (14 jours par défaut, de 5 minutes à 90 jours). Chaque rotation repart de zéro.
9
+ */
10
+ expiresInSeconds?: number;
11
+ };
12
+ export type ResolvedRefreshTokenOptions = Readonly<{
13
+ store: import("../core/contracts/refresh-token-store.js").RefreshTokenStore;
14
+ expiresInSeconds: number;
15
+ }>;
16
+ /**
17
+ * @param {unknown} options
18
+ * @returns {ResolvedRefreshTokenOptions}
19
+ * @throws {ConfigurationError | import("../core/errors.js").ContractViolationError}
20
+ */
21
+ export declare function resolveRefreshTokenOptions(options: unknown): ResolvedRefreshTokenOptions;
@@ -0,0 +1,7 @@
1
+ /**
2
+ * @param {unknown} secret
3
+ * @param {number} minBytes
4
+ * @returns {Uint8Array} Secret encodé en octets, prêt pour le fournisseur JWT.
5
+ * @throws {ConfigurationError}
6
+ */
7
+ export declare function assertStrongSecret(secret: unknown, minBytes: number): Uint8Array;
@@ -0,0 +1,37 @@
1
+ export type UserRepository = import("./contracts/user-repository.js").UserRepository;
2
+ export type PasswordProvider = import("./contracts/password-provider.js").PasswordProvider;
3
+ export type TokenProvider = import("./contracts/token-provider.js").TokenProvider;
4
+ export type AuthenticationService = import("./contracts/authentication-service.js").AuthenticationService;
5
+ export type LoginCredentials = import("./contracts/authentication-service.js").LoginCredentials;
6
+ export type LoginResult = import("./contracts/authentication-service.js").LoginResult;
7
+ export type AuthPrincipal = import("./contracts/auth-principal.js").AuthPrincipal;
8
+ /**
9
+ * @typedef {import("./contracts/user-repository.js").UserRepository} UserRepository
10
+ * @typedef {import("./contracts/password-provider.js").PasswordProvider} PasswordProvider
11
+ * @typedef {import("./contracts/token-provider.js").TokenProvider} TokenProvider
12
+ * @typedef {import("./contracts/authentication-service.js").AuthenticationService} AuthenticationService
13
+ * @typedef {import("./contracts/authentication-service.js").LoginCredentials} LoginCredentials
14
+ * @typedef {import("./contracts/authentication-service.js").LoginResult} LoginResult
15
+ * @typedef {import("./contracts/auth-principal.js").AuthPrincipal} AuthPrincipal
16
+ */
17
+ /**
18
+ * Crée le service d’authentification indépendant du framework.
19
+ *
20
+ * La fabrique doit avoir validé les dépendances au préalable. Ce service ne
21
+ * conserve aucun état utilisateur : il peut être partagé entre les requêtes
22
+ * et entre plusieurs processus derrière un répartiteur de charge.
23
+ *
24
+ * @param {object} dependencies
25
+ * @param {UserRepository} dependencies.users
26
+ * @param {PasswordProvider} dependencies.passwordProvider
27
+ * @param {TokenProvider} dependencies.tokenProvider
28
+ * @param {ReturnType<typeof import("./refresh-token-service.js").createRefreshTokenService>} [dependencies.refreshTokens]
29
+ * Service des jetons de rafraîchissement ; absent si la fonctionnalité n’est pas activée.
30
+ * @returns {AuthenticationService}
31
+ */
32
+ export declare function createAuthenticationService({ users, passwordProvider, tokenProvider, refreshTokens }: {
33
+ users: UserRepository;
34
+ passwordProvider: PasswordProvider;
35
+ tokenProvider: TokenProvider;
36
+ refreshTokens?: ReturnType<typeof import("./refresh-token-service.js").createRefreshTokenService>;
37
+ }): AuthenticationService;
@@ -0,0 +1,40 @@
1
+ export type AuthorizationRequirement = string | {
2
+ anyRole?: string[];
3
+ allPermissions?: string[];
4
+ check?: (principal: import("./contracts/auth-principal.js").AuthPrincipal, context?: unknown) => boolean | Promise<boolean>;
5
+ };
6
+ export type NormalizedAuthorizationRequirement = Readonly<{
7
+ anyRole: readonly string[];
8
+ allPermissions: readonly string[];
9
+ check: ((principal: import("./contracts/auth-principal.js").AuthPrincipal, context?: unknown) => boolean | Promise<boolean>) | undefined;
10
+ }>;
11
+ /**
12
+ * Valide une règle d’accès au démarrage, pour qu’une faute (rôle vide, clé
13
+ * mal orthographiée…) soit signalée avant la première requête.
14
+ *
15
+ * @param {unknown} requirement
16
+ * @returns {NormalizedAuthorizationRequirement}
17
+ * @throws {ConfigurationError}
18
+ */
19
+ export declare function normalizeAuthorizationRequirement(requirement: unknown): NormalizedAuthorizationRequirement;
20
+ /**
21
+ * Indique si un utilisateur authentifié respecte une règle d’accès.
22
+ *
23
+ * - `anyRole` : l’utilisateur doit avoir **au moins un** de ces rôles ;
24
+ * - `allPermissions` : il doit avoir **toutes** ces permissions ;
25
+ * - `check` : fonction personnalisée, appelée en dernier, avec `context`
26
+ * (la requête HTTP avec l’adaptateur Express).
27
+ *
28
+ * @example
29
+ * await isAuthorized(principal, "admin");
30
+ * await isAuthorized(principal, { allPermissions: ["orders:write"] });
31
+ * await isAuthorized(principal, { check: (user, req) => user.userId === req.params.id }, req);
32
+ *
33
+ * @param {import("./contracts/auth-principal.js").AuthPrincipal} principal
34
+ * @param {AuthorizationRequirement | NormalizedAuthorizationRequirement} requirement
35
+ * @param {unknown} [context] Information transmise à `check` (par exemple la requête).
36
+ * @returns {Promise<boolean>}
37
+ * @throws {ConfigurationError} Si la règle est invalide.
38
+ * @throws {ContractViolationError} Si `check` ne renvoie pas un booléen.
39
+ */
40
+ export declare function isAuthorized(principal: import("./contracts/auth-principal.js").AuthPrincipal, requirement: AuthorizationRequirement | NormalizedAuthorizationRequirement, context?: unknown): Promise<boolean>;
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Vérifie que `candidate` expose toutes les méthodes de `methodNames`.
3
+ *
4
+ * JavaScript n’a pas d’interfaces : les contrats sont vérifiés par leur forme
5
+ * au démarrage afin de signaler clairement une dépendance mal configurée.
6
+ *
7
+ * @param {unknown} candidate Dépendance injectée.
8
+ * @param {string} contractName Nom utilisé dans les messages d’erreur.
9
+ * @param {readonly string[]} methodNames Méthodes exigées par le contrat.
10
+ * @returns {void}
11
+ * @throws {ContractViolationError}
12
+ */
13
+ export declare function assertImplements(candidate: unknown, contractName: string, methodNames: readonly string[]): void;
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Identité validée d’un utilisateur authentifié.
3
+ *
4
+ * Le principal ne contient que des données non sensibles issues d’un jeton
5
+ * vérifié. Il ne contient jamais le hash du mot de passe ni le jeton brut.
6
+ * Il est figé pour empêcher les middlewares suivants de le modifier.
7
+ *
8
+ * Les rôles et permissions viennent du repository, relu à chaque requête :
9
+ * ils ne sont jamais écrits dans le jeton, et un changement de rôle
10
+ * s’applique immédiatement.
11
+ *
12
+ * @typedef {Readonly<{
13
+ * userId: string,
14
+ * tokenId: string,
15
+ * issuedAt: number,
16
+ * expiresAt: number,
17
+ * roles: readonly string[],
18
+ * permissions: readonly string[]
19
+ * }>} AuthPrincipal
20
+ */
21
+ export type AuthPrincipal = Readonly<{
22
+ userId: string;
23
+ tokenId: string;
24
+ issuedAt: number;
25
+ expiresAt: number;
26
+ roles: readonly string[];
27
+ permissions: readonly string[];
28
+ }>;
29
+ /**
30
+ * @param {import("./token-provider.js").VerifiedTokenClaims | import("./token-provider.js").IssuedToken} claims
31
+ * @param {{ id: string, roles: readonly string[], permissions: readonly string[] }} user
32
+ * @returns {AuthPrincipal}
33
+ */
34
+ export declare function createAuthPrincipal(claims: import("./token-provider.js").VerifiedTokenClaims | import("./token-provider.js").IssuedToken, user: {
35
+ id: string;
36
+ roles: readonly string[];
37
+ permissions: readonly string[];
38
+ }): AuthPrincipal;