@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,141 @@
1
+ /**
2
+ * Tous les messages d’erreur d’Express AuthKit, au même endroit.
3
+ *
4
+ * Les messages sont rédigés en français. Les applications ne doivent pas s’y
5
+ * fier pour leur logique : elles utilisent le `code` stable de chaque erreur.
6
+ * Centraliser les textes garantit un ton cohérent et facilitera une future
7
+ * traduction.
8
+ *
9
+ * @module core/messages
10
+ */
11
+ /**
12
+ * Messages publics : ils peuvent être renvoyés tels quels aux clients HTTP.
13
+ * Ils ne révèlent jamais d’information interne.
14
+ */
15
+ export declare const PUBLIC_MESSAGES: Readonly<{
16
+ invalidCredentials: "Identifiants invalides.";
17
+ tokenMissing: "Authentification requise.";
18
+ tokenInvalid: "Jeton d’accès invalide.";
19
+ tokenExpired: "Jeton d’accès expiré.";
20
+ refreshTokenInvalid: "Jeton de rafraîchissement invalide ou expiré. Reconnectez-vous.";
21
+ accessDenied: "Accès refusé : vous n’avez pas les droits nécessaires.";
22
+ credentialsNotObject: "Les identifiants doivent être un objet contenant « identifier » et « password ».";
23
+ missingJsonBody: "Le corps de la requête doit être un objet JSON : envoyez l’en-tête « Content-Type: application/json ».";
24
+ malformedJsonBody: "Le corps de la requête n’est pas un JSON valide.";
25
+ payloadTooLarge: "Le corps de la requête est trop volumineux.";
26
+ refreshTokenMalformed: "Le champ « refreshToken » est obligatoire et doit être un texte non vide.";
27
+ userIdInvalid: "L’identifiant d’utilisateur doit être un texte non vide, un entier ou un ObjectId MongoDB.";
28
+ }>;
29
+ /**
30
+ * @param {string} field
31
+ * @returns {string}
32
+ */
33
+ export declare function fieldRequired(field: string): string;
34
+ /**
35
+ * @param {string} field
36
+ * @param {number} maxLength
37
+ * @returns {string}
38
+ */
39
+ export declare function fieldTooLong(field: string, maxLength: number): string;
40
+ /**
41
+ * Messages destinés aux développeurs (configuration, contrats, pannes).
42
+ * Ils expliquent toujours comment corriger le problème.
43
+ */
44
+ export declare const DEVELOPER_MESSAGES: Readonly<{
45
+ unsupportedPasswordHash: string;
46
+ usersRequired: "L’option « users » est obligatoire : passez un objet qui possède les méthodes findByIdentifier() et findById().";
47
+ jwtOrTokenProvider: "Utilisez soit l’option « jwt », soit « providers.token », mais pas les deux : l’une des deux serait ignorée.";
48
+ jwtRequired: "L’option « jwt » est obligatoire (secret, issuer, audience).";
49
+ passwordOrPasswordProvider: "Utilisez soit l’option « password », soit « providers.password », mais pas les deux : l’une des deux serait ignorée.";
50
+ secretRequired: "L’option « jwt.secret » est obligatoire. Générez-en un avec : npx express-authkit secret (ou : node -e \"console.log(require('node:crypto').randomBytes(48).toString('base64url'))\").";
51
+ secretWrongType: "L’option « jwt.secret » doit être un texte ou un Uint8Array.";
52
+ secretPlaceholder: "L’option « jwt.secret » ressemble à une valeur d’exemple. Générez-en un avec : npx express-authkit secret (ou : node -e \"console.log(require('node:crypto').randomBytes(48).toString('base64url'))\").";
53
+ secretRepetitive: "L’option « jwt.secret » est trop répétitive pour être aléatoire. Générez-en un avec : npx express-authkit secret (ou : node -e \"console.log(require('node:crypto').randomBytes(48).toString('base64url'))\").";
54
+ invalidRealm: "L’option « realm » doit contenir de 1 à 64 caractères : lettres, chiffres, espaces, points, tirets ou soulignés.";
55
+ invalidIdentifierField: "L’option « identifierField » doit être un simple nom de champ, par exemple \"email\" ou \"username\".";
56
+ repositoryRecordNotObject: "Le repository utilisateur doit renvoyer un objet, null ou undefined.";
57
+ repositoryRecordId: "L’utilisateur renvoyé par le repository doit avoir un « id » : texte non vide, entier ou ObjectId MongoDB.";
58
+ repositoryRecordPasswordHash: string;
59
+ repositoryRecordIsActive: string;
60
+ repositoryIdMismatch: "UserRepository.findById() a renvoyé un utilisateur dont l’« id » diffère de celui demandé.";
61
+ tokenClaimsWithoutSubject: "TokenProvider.verify() doit renvoyer des informations contenant un « subject » non vide.";
62
+ refreshStoreConsumeInvalid: string;
63
+ refreshStoreRequired: string;
64
+ refreshTokensDisabled: string;
65
+ authorizeRequirementInvalid: string;
66
+ authorizationCheckNotBoolean: "La fonction « check » de authorize() doit renvoyer true ou false (éventuellement via une promesse).";
67
+ issuedTokenInvalid: string;
68
+ }>;
69
+ /**
70
+ * @param {number} maxLength
71
+ * @returns {string}
72
+ */
73
+ export declare function subjectInvalid(maxLength: number): string;
74
+ /**
75
+ * @param {string} name Nom de l’option, par exemple « jwt » ou « createAuthKit(options) ».
76
+ * @returns {string}
77
+ */
78
+ export declare function notPlainObject(name: string): string;
79
+ /**
80
+ * @param {string} name
81
+ * @param {string[]} unknownKeys
82
+ * @param {readonly string[]} allowedKeys
83
+ * @returns {string}
84
+ */
85
+ export declare function unknownOptions(name: string, unknownKeys: string[], allowedKeys: readonly string[]): string;
86
+ /**
87
+ * @param {string} name
88
+ * @returns {string}
89
+ */
90
+ export declare function optionRequiredString(name: string): string;
91
+ /**
92
+ * @param {string} name
93
+ * @param {number} maxLength
94
+ * @returns {string}
95
+ */
96
+ export declare function optionTooLong(name: string, maxLength: number): string;
97
+ /**
98
+ * @param {string} name
99
+ * @param {number} min
100
+ * @param {number} max
101
+ * @returns {string}
102
+ */
103
+ export declare function optionIntegerRange(name: string, min: number, max: number): string;
104
+ /**
105
+ * @param {readonly string[]} algorithms
106
+ * @returns {string}
107
+ */
108
+ export declare function unsupportedAlgorithm(algorithms: readonly string[]): string;
109
+ /**
110
+ * @param {number} minBytes
111
+ * @returns {string}
112
+ */
113
+ export declare function secretTooShort(minBytes: number): string;
114
+ /**
115
+ * @param {string} contractName
116
+ * @returns {string}
117
+ */
118
+ export declare function contractNotObject(contractName: string): string;
119
+ /**
120
+ * @param {string} contractName
121
+ * @param {string[]} missingMethods
122
+ * @returns {string}
123
+ */
124
+ export declare function contractMissingMethods(contractName: string, missingMethods: string[]): string;
125
+ /**
126
+ * @param {string} dependency Par exemple « UserRepository.findById ».
127
+ * @returns {string}
128
+ */
129
+ export declare function dependencyFailed(dependency: string): string;
130
+ /**
131
+ * @param {string} method Par exemple « authenticate ».
132
+ * @returns {string}
133
+ */
134
+ export declare function middlewareFactoryMisuse(method: string): string;
135
+ /**
136
+ * @param {string} field « roles » ou « permissions ».
137
+ * @param {number} maxItems
138
+ * @param {number} maxLength
139
+ * @returns {string}
140
+ */
141
+ export declare function repositoryRecordAuthorities(field: string, maxItems: number, maxLength: number): string;
@@ -0,0 +1,31 @@
1
+ export type RefreshTokenStore = import("./contracts/refresh-token-store.js").RefreshTokenStore;
2
+ export type ConsumedRefreshToken = import("./contracts/refresh-token-store.js").ConsumedRefreshToken;
3
+ /**
4
+ * @typedef {import("./contracts/refresh-token-store.js").RefreshTokenStore} RefreshTokenStore
5
+ * @typedef {import("./contracts/refresh-token-store.js").ConsumedRefreshToken} ConsumedRefreshToken
6
+ */
7
+ /**
8
+ * Gère le cycle de vie des jetons de rafraîchissement : émission, rotation,
9
+ * détection de réutilisation et révocation.
10
+ *
11
+ * Les jetons sont des valeurs aléatoires opaques (pas des JWT) : ils ne
12
+ * contiennent aucune information et ne valent que par leur présence dans le
13
+ * stockage, ce qui permet de les révoquer à tout moment.
14
+ *
15
+ * @param {object} options
16
+ * @param {RefreshTokenStore} options.store
17
+ * @param {number} options.expiresInSeconds
18
+ */
19
+ export declare function createRefreshTokenService({ store, expiresInSeconds }: {
20
+ store: RefreshTokenStore;
21
+ expiresInSeconds: number;
22
+ }): Readonly<{
23
+ issue: (userId: string, familyId?: string) => Promise<{
24
+ token: string;
25
+ expiresIn: number;
26
+ }>;
27
+ consume: (token: unknown) => Promise<ConsumedRefreshToken>;
28
+ revokeSessionOf: (token: unknown) => Promise<void>;
29
+ revokeFamily: (familyId: string) => Promise<void>;
30
+ revokeUser: (userId: string) => Promise<void>;
31
+ }>;
@@ -0,0 +1,52 @@
1
+ export type PasswordApi = Readonly<{
2
+ hash: (password: string) => Promise<string>;
3
+ verify: (password: string, hash: string) => Promise<boolean>;
4
+ }>;
5
+ export type AuthKit = Readonly<{
6
+ login: (credentials: import("../core/contracts/authentication-service.js").LoginCredentials) => Promise<import("../core/contracts/authentication-service.js").LoginResult>;
7
+ refresh: (refreshToken: string) => Promise<import("../core/contracts/authentication-service.js").LoginResult>;
8
+ logout: (refreshToken: string) => Promise<void>;
9
+ revokeAllSessions: (userId: import("../core/contracts/authentication-service.js").UserIdInput) => Promise<void>;
10
+ verifyAccessToken: (token: string) => Promise<import("../core/contracts/auth-principal.js").AuthPrincipal>;
11
+ password: PasswordApi;
12
+ }>;
13
+ /**
14
+ * Fonctions liées aux mots de passe, exposées dans `auth.password`.
15
+ *
16
+ * @typedef {Readonly<{
17
+ * hash: (password: string) => Promise<string>,
18
+ * verify: (password: string, hash: string) => Promise<boolean>
19
+ * }>} PasswordApi
20
+ */
21
+ /**
22
+ * Moteur d’authentification renvoyé par {@link createAuthKit}.
23
+ *
24
+ * @typedef {Readonly<{
25
+ * login: (credentials: import("../core/contracts/authentication-service.js").LoginCredentials) => Promise<import("../core/contracts/authentication-service.js").LoginResult>,
26
+ * refresh: (refreshToken: string) => Promise<import("../core/contracts/authentication-service.js").LoginResult>,
27
+ * logout: (refreshToken: string) => Promise<void>,
28
+ * revokeAllSessions: (userId: import("../core/contracts/authentication-service.js").UserIdInput) => Promise<void>,
29
+ * verifyAccessToken: (token: string) => Promise<import("../core/contracts/auth-principal.js").AuthPrincipal>,
30
+ * password: PasswordApi
31
+ * }>} AuthKit
32
+ */
33
+ /**
34
+ * Crée et configure un moteur d’authentification.
35
+ *
36
+ * Toutes les options et dépendances injectées sont validées ici. Une mauvaise
37
+ * configuration arrête donc l’application au démarrage, et non à la première
38
+ * requête. L’objet renvoyé ne conserve aucun état par utilisateur et peut être
39
+ * partagé dans tout le processus.
40
+ *
41
+ * @example
42
+ * const auth = createAuthKit({
43
+ * users: userRepository,
44
+ * jwt: { secret: process.env.AUTH_SECRET, issuer: "my-api", audience: "my-client" }
45
+ * });
46
+ *
47
+ * @param {import("../config/auth-kit-options.js").AuthKitOptions} options
48
+ * @returns {AuthKit}
49
+ * @throws {import("../core/errors.js").ConfigurationError} Si les options manquent ou ne sont pas sûres.
50
+ * @throws {import("../core/errors.js").ContractViolationError} Si une dépendance injectée ne respecte pas son contrat.
51
+ */
52
+ export declare function createAuthKit(options: import("../config/auth-kit-options.js").AuthKitOptions): AuthKit;
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Express AuthKit — point d’entrée indépendant du framework.
3
+ *
4
+ * API publique. Les modules non réexportés ici sont internes et peuvent
5
+ * évoluer sans préavis.
6
+ *
7
+ * @module @kourouma/express-authkit
8
+ */
9
+ export { createAuthKit } from "./factory/create-auth-kit.js";
10
+ export { isAuthorized } from "./core/authorization.js";
11
+ export { createArgon2PasswordProvider } from "./providers/argon2-password-provider.js";
12
+ export { createJwtTokenProvider } from "./providers/jwt-token-provider.js";
13
+ export { createInMemoryRefreshTokenStore } from "./providers/in-memory-refresh-token-store.js";
14
+ export { assertUserRepository } from "./core/contracts/user-repository.js";
15
+ export { assertPasswordProvider } from "./core/contracts/password-provider.js";
16
+ export { assertTokenProvider } from "./core/contracts/token-provider.js";
17
+ export { assertRefreshTokenStore } from "./core/contracts/refresh-token-store.js";
18
+ export { AUTH_ERROR_CODES, AUTH_FAILURE_REASONS, AccessDeniedError, AuthKitError, ConfigurationError, ContractViolationError, DependencyFailureError, InvalidCredentialsError, InvalidInputError, InvalidPasswordHashError, InvalidRefreshTokenError, InvalidTokenError, TokenExpiredError, TokenMissingError, isAuthKitError } from "./core/errors.js";
19
+ export type AuthKit = import("./factory/create-auth-kit.js").AuthKit;
20
+ export type AuthKitOptions = import("./config/auth-kit-options.js").AuthKitOptions;
21
+ export type JwtOptions = import("./config/jwt-options.js").JwtOptions;
22
+ export type PasswordOptions = import("./config/password-options.js").PasswordOptions;
23
+ export type UserRepository = import("./core/contracts/user-repository.js").UserRepository;
24
+ export type AuthUserRecord = import("./core/contracts/user-repository.js").AuthUserRecord;
25
+ export type PasswordProvider = import("./core/contracts/password-provider.js").PasswordProvider;
26
+ export type TokenProvider = import("./core/contracts/token-provider.js").TokenProvider;
27
+ export type IssuedToken = import("./core/contracts/token-provider.js").IssuedToken;
28
+ export type VerifiedTokenClaims = import("./core/contracts/token-provider.js").VerifiedTokenClaims;
29
+ export type AuthenticationService = import("./core/contracts/authentication-service.js").AuthenticationService;
30
+ export type LoginCredentials = import("./core/contracts/authentication-service.js").LoginCredentials;
31
+ export type LoginResult = import("./core/contracts/authentication-service.js").LoginResult;
32
+ export type AuthPrincipal = import("./core/contracts/auth-principal.js").AuthPrincipal;
33
+ export type UserIdInput = import("./core/contracts/authentication-service.js").UserIdInput;
34
+ export type AuthorizationRequirement = import("./core/authorization.js").AuthorizationRequirement;
35
+ export type RefreshTokenOptions = import("./config/refresh-token-options.js").RefreshTokenOptions;
36
+ export type RefreshTokenStore = import("./core/contracts/refresh-token-store.js").RefreshTokenStore;
37
+ export type RefreshTokenRecord = import("./core/contracts/refresh-token-store.js").RefreshTokenRecord;
38
+ export type ConsumedRefreshToken = import("./core/contracts/refresh-token-store.js").ConsumedRefreshToken;
39
+ /**
40
+ * @typedef {import("./factory/create-auth-kit.js").AuthKit} AuthKit
41
+ * @typedef {import("./config/auth-kit-options.js").AuthKitOptions} AuthKitOptions
42
+ * @typedef {import("./config/jwt-options.js").JwtOptions} JwtOptions
43
+ * @typedef {import("./config/password-options.js").PasswordOptions} PasswordOptions
44
+ * @typedef {import("./core/contracts/user-repository.js").UserRepository} UserRepository
45
+ * @typedef {import("./core/contracts/user-repository.js").AuthUserRecord} AuthUserRecord
46
+ * @typedef {import("./core/contracts/password-provider.js").PasswordProvider} PasswordProvider
47
+ * @typedef {import("./core/contracts/token-provider.js").TokenProvider} TokenProvider
48
+ * @typedef {import("./core/contracts/token-provider.js").IssuedToken} IssuedToken
49
+ * @typedef {import("./core/contracts/token-provider.js").VerifiedTokenClaims} VerifiedTokenClaims
50
+ * @typedef {import("./core/contracts/authentication-service.js").AuthenticationService} AuthenticationService
51
+ * @typedef {import("./core/contracts/authentication-service.js").LoginCredentials} LoginCredentials
52
+ * @typedef {import("./core/contracts/authentication-service.js").LoginResult} LoginResult
53
+ * @typedef {import("./core/contracts/auth-principal.js").AuthPrincipal} AuthPrincipal
54
+ * @typedef {import("./core/contracts/authentication-service.js").UserIdInput} UserIdInput
55
+ * @typedef {import("./core/authorization.js").AuthorizationRequirement} AuthorizationRequirement
56
+ * @typedef {import("./config/refresh-token-options.js").RefreshTokenOptions} RefreshTokenOptions
57
+ * @typedef {import("./core/contracts/refresh-token-store.js").RefreshTokenStore} RefreshTokenStore
58
+ * @typedef {import("./core/contracts/refresh-token-store.js").RefreshTokenRecord} RefreshTokenRecord
59
+ * @typedef {import("./core/contracts/refresh-token-store.js").ConsumedRefreshToken} ConsumedRefreshToken
60
+ */
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Crée un {@link import("../core/contracts/password-provider.js").PasswordProvider}
3
+ * basé sur le paquet `argon2` (implémentation C de référence, variante Argon2id).
4
+ *
5
+ * La bibliothèque génère le sel avec un générateur aléatoire sécurisé et le
6
+ * conserve avec les paramètres dans le hash. Modifier les coûts plus tard ne
7
+ * rend donc pas les hash existants invalides.
8
+ *
9
+ * @param {import("../config/password-options.js").PasswordOptions} [options]
10
+ * @returns {import("../core/contracts/password-provider.js").PasswordProvider}
11
+ * @throws {import("../core/errors.js").ConfigurationError}
12
+ */
13
+ export declare function createArgon2PasswordProvider(options?: import("../config/password-options.js").PasswordOptions): import("../core/contracts/password-provider.js").PasswordProvider;
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Stockage des jetons de rafraîchissement en mémoire.
3
+ *
4
+ * ⚠️ Réservé au développement et aux tests : les jetons sont perdus au
5
+ * redémarrage et ne sont pas partagés entre plusieurs processus ou serveurs.
6
+ * En production, implémentez le contrat `RefreshTokenStore` avec votre base
7
+ * de données (voir docs/refresh-tokens.md).
8
+ *
9
+ * @returns {import("../core/contracts/refresh-token-store.js").RefreshTokenStore}
10
+ */
11
+ export declare function createInMemoryRefreshTokenStore(): import("../core/contracts/refresh-token-store.js").RefreshTokenStore;
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Crée un {@link import("../core/contracts/token-provider.js").TokenProvider}
3
+ * qui émet et vérifie des jetons d’accès JWT signés en HMAC avec `jose`.
4
+ *
5
+ * Les jetons ne contiennent que des revendications standard (`sub`, `iss`,
6
+ * `aud`, `iat`, `exp`, `jti`) : aucune donnée personnelle ou sensible n’est
7
+ * placée dans la charge utile, qui est encodée mais non chiffrée.
8
+ *
9
+ * @param {import("../config/jwt-options.js").JwtOptions} options
10
+ * @returns {import("../core/contracts/token-provider.js").TokenProvider}
11
+ * @throws {import("../core/errors.js").ConfigurationError}
12
+ */
13
+ export declare function createJwtTokenProvider(options: import("../config/jwt-options.js").JwtOptions): import("../core/contracts/token-provider.js").TokenProvider;