@seifer-webapp-factory/authentication 0.1.0

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 (113) hide show
  1. package/README.md +8 -0
  2. package/backend/templates/config/config-fragment.ts +73 -0
  3. package/backend/templates/mail/templates.ts +84 -0
  4. package/backend/templates/nestjs/auth.controller.ts +274 -0
  5. package/backend/templates/nestjs/auth.module.ts +207 -0
  6. package/backend/templates/nestjs/tokens.ts +24 -0
  7. package/backend/templates/persistence/migrations/0001_auth.sql +36 -0
  8. package/backend/templates/persistence/migrations/index.ts +75 -0
  9. package/backend/templates/persistence/pg-single-use-store.ts +64 -0
  10. package/backend/templates/persistence/pg-token-store.ts +75 -0
  11. package/backend/templates/persistence/pg-user-store.ts +53 -0
  12. package/backend/templates/security/cookies.ts +89 -0
  13. package/backend/templates/security/csrf.ts +44 -0
  14. package/backend/templates/security/headers.ts +30 -0
  15. package/backend/templates/security/redaction.ts +38 -0
  16. package/dist/backend/src/errors.d.ts +12 -0
  17. package/dist/backend/src/errors.d.ts.map +1 -0
  18. package/dist/backend/src/errors.js +55 -0
  19. package/dist/backend/src/errors.js.map +1 -0
  20. package/dist/backend/src/index.d.ts +9 -0
  21. package/dist/backend/src/index.d.ts.map +1 -0
  22. package/dist/backend/src/index.js +8 -0
  23. package/dist/backend/src/index.js.map +1 -0
  24. package/dist/backend/src/ports.d.ts +60 -0
  25. package/dist/backend/src/ports.d.ts.map +1 -0
  26. package/dist/backend/src/ports.js +2 -0
  27. package/dist/backend/src/ports.js.map +1 -0
  28. package/dist/backend/src/services.d.ts +49 -0
  29. package/dist/backend/src/services.d.ts.map +1 -0
  30. package/dist/backend/src/services.js +178 -0
  31. package/dist/backend/src/services.js.map +1 -0
  32. package/dist/contract/endpoints.d.ts +259 -0
  33. package/dist/contract/endpoints.d.ts.map +1 -0
  34. package/dist/contract/endpoints.js +42 -0
  35. package/dist/contract/endpoints.js.map +1 -0
  36. package/dist/contract/errors.d.ts +23 -0
  37. package/dist/contract/errors.d.ts.map +1 -0
  38. package/dist/contract/errors.js +31 -0
  39. package/dist/contract/errors.js.map +1 -0
  40. package/dist/contract/events.d.ts +40 -0
  41. package/dist/contract/events.d.ts.map +1 -0
  42. package/dist/contract/events.js +14 -0
  43. package/dist/contract/events.js.map +1 -0
  44. package/dist/contract/index.d.ts +9 -0
  45. package/dist/contract/index.d.ts.map +1 -0
  46. package/dist/contract/index.js +9 -0
  47. package/dist/contract/index.js.map +1 -0
  48. package/dist/contract/schemas.d.ts +150 -0
  49. package/dist/contract/schemas.d.ts.map +1 -0
  50. package/dist/contract/schemas.js +43 -0
  51. package/dist/contract/schemas.js.map +1 -0
  52. package/dist/frontend/src/client.d.ts +38 -0
  53. package/dist/frontend/src/client.d.ts.map +1 -0
  54. package/dist/frontend/src/client.js +88 -0
  55. package/dist/frontend/src/client.js.map +1 -0
  56. package/dist/frontend/src/composables.d.ts +46 -0
  57. package/dist/frontend/src/composables.d.ts.map +1 -0
  58. package/dist/frontend/src/composables.js +111 -0
  59. package/dist/frontend/src/composables.js.map +1 -0
  60. package/dist/frontend/src/guards.d.ts +10 -0
  61. package/dist/frontend/src/guards.d.ts.map +1 -0
  62. package/dist/frontend/src/guards.js +9 -0
  63. package/dist/frontend/src/guards.js.map +1 -0
  64. package/dist/frontend/src/index.d.ts +12 -0
  65. package/dist/frontend/src/index.d.ts.map +1 -0
  66. package/dist/frontend/src/index.js +9 -0
  67. package/dist/frontend/src/index.js.map +1 -0
  68. package/dist/manifest.d.ts +80 -0
  69. package/dist/manifest.d.ts.map +1 -0
  70. package/dist/manifest.js +126 -0
  71. package/dist/manifest.js.map +1 -0
  72. package/dist/scaffolder/core/config.d.ts +213 -0
  73. package/dist/scaffolder/core/config.d.ts.map +1 -0
  74. package/dist/scaffolder/core/config.js +132 -0
  75. package/dist/scaffolder/core/config.js.map +1 -0
  76. package/dist/scaffolder/core/errors.d.ts +37 -0
  77. package/dist/scaffolder/core/errors.d.ts.map +1 -0
  78. package/dist/scaffolder/core/errors.js +46 -0
  79. package/dist/scaffolder/core/errors.js.map +1 -0
  80. package/dist/scaffolder/core/extend.d.ts +115 -0
  81. package/dist/scaffolder/core/extend.d.ts.map +1 -0
  82. package/dist/scaffolder/core/extend.js +116 -0
  83. package/dist/scaffolder/core/extend.js.map +1 -0
  84. package/dist/scaffolder/core/materialize.d.ts +71 -0
  85. package/dist/scaffolder/core/materialize.d.ts.map +1 -0
  86. package/dist/scaffolder/core/materialize.js +47 -0
  87. package/dist/scaffolder/core/materialize.js.map +1 -0
  88. package/dist/scaffolder/core/ports.d.ts +39 -0
  89. package/dist/scaffolder/core/ports.d.ts.map +1 -0
  90. package/dist/scaffolder/core/ports.js +33 -0
  91. package/dist/scaffolder/core/ports.js.map +1 -0
  92. package/dist/scaffolder/core/three-way-merge.d.ts +113 -0
  93. package/dist/scaffolder/core/three-way-merge.d.ts.map +1 -0
  94. package/dist/scaffolder/core/three-way-merge.js +184 -0
  95. package/dist/scaffolder/core/three-way-merge.js.map +1 -0
  96. package/dist/scaffolder/index.d.ts +21 -0
  97. package/dist/scaffolder/index.d.ts.map +1 -0
  98. package/dist/scaffolder/index.js +20 -0
  99. package/dist/scaffolder/index.js.map +1 -0
  100. package/frontend/templates/components/AuthField.vue +68 -0
  101. package/frontend/templates/i18n/en.json +70 -0
  102. package/frontend/templates/i18n/nl.json +70 -0
  103. package/frontend/templates/middleware/auth.ts +25 -0
  104. package/frontend/templates/middleware/guest.ts +25 -0
  105. package/frontend/templates/pages/forgot-password.vue +89 -0
  106. package/frontend/templates/pages/login.vue +90 -0
  107. package/frontend/templates/pages/logout.vue +46 -0
  108. package/frontend/templates/pages/register.vue +100 -0
  109. package/frontend/templates/pages/reset-password.vue +105 -0
  110. package/frontend/templates/pages/verify-email.vue +76 -0
  111. package/frontend/templates/plugins/auth.client.ts +111 -0
  112. package/frontend/templates/runtime.ts +60 -0
  113. package/package.json +71 -0
@@ -0,0 +1,60 @@
1
+ /**
2
+ * De poorten & de dependency-bundle van de authentication-mechanism-laag.
3
+ *
4
+ * De flow-services zijn framework-vrij en krijgen ALLE afhankelijkheden via één {@link AuthModuleDeps}-
5
+ * bundel. We hergebruiken de poorten van de `auth`-kit (UserStore, Mailer, PasswordHasher, Clock,
6
+ * TokenStore) i.p.v. eigen varianten te definiëren. Voor de token- en single-use-service typen we tegen
7
+ * smalle *structurele* poorten (`TokenServicePort`/`SingleUseServicePort`): de kit exporteert deze als
8
+ * klassen met `#private`-velden — die zijn nominaal en dus niet fakebaar — terwijl een structurele
9
+ * interface zowel de echte kit-service als een in-memory fake accepteert.
10
+ */
11
+ import type { Clock, IssuedTokens, Mailer, PasswordHasher, UserStore, VerifiedAccess } from '@seifer-webapp-factory/kits/backend/auth';
12
+ import type { AuthEventSink } from '../../contract/index.js';
13
+ /**
14
+ * Structurele poort die de publieke API van de kit-`TokenService` weerspiegelt (JWT + roterend
15
+ * refresh). `revokeRefresh` retourneert het subject uitsluitend wanneer het token nú werd ingetrokken
16
+ * (emit-once); `revokeAllForSubject` trekt alle sessies van een gebruiker in (bv. na wachtwoord-reset).
17
+ */
18
+ export interface TokenServicePort {
19
+ issue(subject: string, claims?: Record<string, unknown>): Promise<IssuedTokens>;
20
+ verify(accessToken: string): Promise<VerifiedAccess>;
21
+ refresh(refreshToken: string): Promise<IssuedTokens>;
22
+ revokeAccess(accessToken: string): Promise<void>;
23
+ revokeRefresh(refreshToken: string): Promise<string | null>;
24
+ revokeAllForSubject(subject: string): Promise<void>;
25
+ }
26
+ /** Structurele poort die de publieke API van de kit-`SingleUseTokenService` weerspiegelt. */
27
+ export interface SingleUseServicePort {
28
+ issue(purpose: string, payload: unknown): Promise<string>;
29
+ consume(token: string, purpose: string): Promise<unknown>;
30
+ }
31
+ /** Wachtwoord-beleid (US-A0603) — details komen uit config, niet uit het schema. */
32
+ export interface PasswordPolicy {
33
+ minLength: number;
34
+ maxLength: number;
35
+ }
36
+ /** Purposes voor de single-use-tokens; sluiten aan op de kit-defaults maar zijn configureerbaar. */
37
+ export interface AuthPurposes {
38
+ emailVerify: string;
39
+ passwordReset: string;
40
+ }
41
+ /** Configuratie van de mechanism-laag (geen secrets hier — die zitten in de geïnjecteerde services). */
42
+ export interface AuthModuleConfig {
43
+ purposes: AuthPurposes;
44
+ password: PasswordPolicy;
45
+ }
46
+ /**
47
+ * De volledige afhankelijkheids-bundel die in elke flow-service wordt geïnjecteerd. Geen module-globale
48
+ * state; determinisme loopt via de geïnjecteerde {@link Clock}.
49
+ */
50
+ export interface AuthModuleDeps {
51
+ userStore: UserStore;
52
+ mailer: Mailer;
53
+ hasher: PasswordHasher;
54
+ tokenService: TokenServicePort;
55
+ singleUseTokenService: SingleUseServicePort;
56
+ events: AuthEventSink;
57
+ clock: Clock;
58
+ config: AuthModuleConfig;
59
+ }
60
+ //# sourceMappingURL=ports.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ports.d.ts","sourceRoot":"","sources":["../../../backend/src/ports.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,KAAK,EACV,KAAK,EACL,YAAY,EACZ,MAAM,EACN,cAAc,EACd,SAAS,EACT,cAAc,EACf,MAAM,0CAA0C,CAAC;AAClD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAE7D;;;;GAIG;AACH,MAAM,WAAW,gBAAgB;IAC/B,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC;IAChF,MAAM,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC,CAAC;IACrD,OAAO,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC;IACrD,YAAY,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACjD,aAAa,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC5D,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACrD;AAED,6FAA6F;AAC7F,MAAM,WAAW,oBAAoB;IACnC,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC1D,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CAC3D;AAED,oFAAoF;AACpF,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,oGAAoG;AACpG,MAAM,WAAW,YAAY;IAC3B,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,wGAAwG;AACxG,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,YAAY,CAAC;IACvB,QAAQ,EAAE,cAAc,CAAC;CAC1B;AAED;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,SAAS,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,cAAc,CAAC;IACvB,YAAY,EAAE,gBAAgB,CAAC;IAC/B,qBAAqB,EAAE,oBAAoB,CAAC;IAC5C,MAAM,EAAE,aAAa,CAAC;IACtB,KAAK,EAAE,KAAK,CAAC;IACb,MAAM,EAAE,gBAAgB,CAAC;CAC1B"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=ports.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ports.js","sourceRoot":"","sources":["../../../backend/src/ports.ts"],"names":[],"mappings":""}
@@ -0,0 +1,49 @@
1
+ import type { Acknowledgement, ForgotPasswordRequest, LoginRequest, RegisterRequest, ResetPasswordRequest, SessionResponse } from '../../contract/index.js';
2
+ import type { AuthModuleDeps } from './ports.js';
3
+ /**
4
+ * Resultaat van login/refresh. `session` is de contract-body; `refreshToken` is het opaque, roterende
5
+ * refresh-token dat de surface-laag via een httpOnly-cookie zet (US-A0703) — het hoort NIET in de body.
6
+ */
7
+ export interface SessionResult {
8
+ session: SessionResponse;
9
+ refreshToken: string;
10
+ }
11
+ /**
12
+ * Registreer een gebruiker: hash + opslaan, single-use verificatietoken uitgeven, verificatiemail sturen,
13
+ * `user.registered` emitten. Fouten: `weak_password`, `email_taken`.
14
+ */
15
+ export declare function registerUser(deps: AuthModuleDeps, req: RegisterRequest): Promise<Acknowledgement>;
16
+ /**
17
+ * Verifieer een e-mailadres: verbruik het single-use verificatietoken (exact één keer), markeer de
18
+ * gebruiker als geverifieerd en emit `user.verified`. Fouten: `token_invalid`, `token_expired`.
19
+ */
20
+ export declare function verifyEmail(deps: AuthModuleDeps, token: string): Promise<Acknowledgement>;
21
+ /**
22
+ * Log in: gebruiker zoeken, wachtwoord verifiëren, geverifieerd e-mailadres eisen, tokens uitgeven en
23
+ * `user.logged_in` emitten. No-enumeration: onbekend e-mailadres én verkeerd wachtwoord geven BEIDE
24
+ * `invalid_credentials`. `email_not_verified` wordt pas ná geslaagde wachtwoordcontrole onthuld.
25
+ */
26
+ export declare function login(deps: AuthModuleDeps, req: LoginRequest): Promise<SessionResult>;
27
+ /**
28
+ * Ververs de sessie: roteer het refresh-token via de kit (met hergebruik-detectie), emit
29
+ * `session.refreshed`. Fouten: `unauthenticated`, `token_invalid`, `token_expired`.
30
+ */
31
+ export declare function refresh(deps: AuthModuleDeps, refreshToken: string | undefined): Promise<SessionResult>;
32
+ /**
33
+ * Log uit: trek het refresh-token in en emit `user.logged_out`. Idempotent — een onbekend of reeds
34
+ * ingetrokken token geeft geen fout en emit geen dubbel event.
35
+ */
36
+ export declare function logout(deps: AuthModuleDeps, refreshToken: string | undefined): Promise<Acknowledgement>;
37
+ /**
38
+ * Start een wachtwoord-reset. No-enumeration: retourneert ALTIJD dezelfde generieke bevestiging. Alleen
39
+ * als de gebruiker bestaat wordt een reset-token uitgegeven, een mail gestuurd en `password.reset_requested`
40
+ * geëmit.
41
+ */
42
+ export declare function forgotPassword(deps: AuthModuleDeps, req: ForgotPasswordRequest): Promise<Acknowledgement>;
43
+ /**
44
+ * Reset het wachtwoord: verbruik het single-use reset-token (exact één keer), zet de nieuwe hash, trek
45
+ * ALLE sessies van de gebruiker in en emit `password.reset`. Fouten: `weak_password`, `token_invalid`,
46
+ * `token_expired`.
47
+ */
48
+ export declare function resetPassword(deps: AuthModuleDeps, req: ResetPasswordRequest): Promise<Acknowledgement>;
49
+ //# sourceMappingURL=services.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"services.d.ts","sourceRoot":"","sources":["../../../backend/src/services.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EACV,eAAe,EACf,qBAAqB,EACrB,YAAY,EAEZ,eAAe,EACf,oBAAoB,EACpB,eAAe,EAChB,MAAM,yBAAyB,CAAC;AAEjC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAEjD;;;GAGG;AACH,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,eAAe,CAAC;IACzB,YAAY,EAAE,MAAM,CAAC;CACtB;AAMD;;;GAGG;AACH,wBAAsB,YAAY,CAAC,IAAI,EAAE,cAAc,EAAE,GAAG,EAAE,eAAe,GAAG,OAAO,CAAC,eAAe,CAAC,CAuBvG;AAED;;;GAGG;AACH,wBAAsB,WAAW,CAAC,IAAI,EAAE,cAAc,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC,CAS/F;AAED;;;;GAIG;AACH,wBAAsB,KAAK,CAAC,IAAI,EAAE,cAAc,EAAE,GAAG,EAAE,YAAY,GAAG,OAAO,CAAC,aAAa,CAAC,CAiB3F;AAED;;;GAGG;AACH,wBAAsB,OAAO,CAAC,IAAI,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC,aAAa,CAAC,CAgB5G;AAED;;;GAGG;AACH,wBAAsB,MAAM,CAAC,IAAI,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC,eAAe,CAAC,CAU7G;AAED;;;;GAIG;AACH,wBAAsB,cAAc,CAAC,IAAI,EAAE,cAAc,EAAE,GAAG,EAAE,qBAAqB,GAAG,OAAO,CAAC,eAAe,CAAC,CAY/G;AAED;;;;GAIG;AACH,wBAAsB,aAAa,CAAC,IAAI,EAAE,cAAc,EAAE,GAAG,EAAE,oBAAoB,GAAG,OAAO,CAAC,eAAe,CAAC,CAe7G"}
@@ -0,0 +1,178 @@
1
+ /**
2
+ * De backend-mechanism-laag: framework-vrije flow-services voor authentication. Elke functie krijgt de
3
+ * {@link AuthModuleDeps}-bundel + het gevalideerde request, componeert de `auth`-kit-services, mapt
4
+ * fouten naar de contract-taxonomie, emit het juiste domein-event en handhaaft de security-invarianten
5
+ * (no-enumeration, single-use + revoke, idempotente logout). NestJS-controllers zijn *surface* en horen
6
+ * hier niet thuis.
7
+ */
8
+ import { newId } from '@seifer-webapp-factory/kits/backend/auth';
9
+ import { AuthModuleError, asModuleError } from './errors.js';
10
+ const OK = { ok: true };
11
+ // --- Services ---
12
+ /**
13
+ * Registreer een gebruiker: hash + opslaan, single-use verificatietoken uitgeven, verificatiemail sturen,
14
+ * `user.registered` emitten. Fouten: `weak_password`, `email_taken`.
15
+ */
16
+ export async function registerUser(deps, req) {
17
+ assertStrongPassword(deps, req.password);
18
+ const existing = await deps.userStore.findByIdentifier(req.email);
19
+ if (existing)
20
+ throw new AuthModuleError('email_taken');
21
+ const passwordHash = await deps.hasher.hash(req.password);
22
+ const user = await deps.userStore.save({
23
+ id: newId(),
24
+ identifier: req.email,
25
+ passwordHash,
26
+ verified: false,
27
+ });
28
+ const token = await deps.singleUseTokenService.issue(deps.config.purposes.emailVerify, { userId: user.id });
29
+ await deps.mailer.send({
30
+ to: req.email,
31
+ subject: 'Bevestig je account',
32
+ data: { token, purpose: deps.config.purposes.emailVerify },
33
+ });
34
+ deps.events.emit({ name: 'user.registered', occurredAt: nowIso(deps), userId: user.id, email: req.email });
35
+ return OK;
36
+ }
37
+ /**
38
+ * Verifieer een e-mailadres: verbruik het single-use verificatietoken (exact één keer), markeer de
39
+ * gebruiker als geverifieerd en emit `user.verified`. Fouten: `token_invalid`, `token_expired`.
40
+ */
41
+ export async function verifyEmail(deps, token) {
42
+ const payload = await consume(deps, token, deps.config.purposes.emailVerify);
43
+ const user = await deps.userStore.findById(payload.userId);
44
+ if (!user)
45
+ throw new AuthModuleError('token_invalid');
46
+ const saved = await deps.userStore.save({ ...user, verified: true });
47
+ deps.events.emit({ name: 'user.verified', occurredAt: nowIso(deps), userId: saved.id });
48
+ return OK;
49
+ }
50
+ /**
51
+ * Log in: gebruiker zoeken, wachtwoord verifiëren, geverifieerd e-mailadres eisen, tokens uitgeven en
52
+ * `user.logged_in` emitten. No-enumeration: onbekend e-mailadres én verkeerd wachtwoord geven BEIDE
53
+ * `invalid_credentials`. `email_not_verified` wordt pas ná geslaagde wachtwoordcontrole onthuld.
54
+ */
55
+ export async function login(deps, req) {
56
+ const user = await deps.userStore.findByIdentifier(req.email);
57
+ if (!user || !user.passwordHash)
58
+ throw new AuthModuleError('invalid_credentials');
59
+ const { valid, needsRehash } = await deps.hasher.verify(req.password, user.passwordHash);
60
+ if (!valid)
61
+ throw new AuthModuleError('invalid_credentials');
62
+ if (!user.verified)
63
+ throw new AuthModuleError('email_not_verified');
64
+ if (needsRehash) {
65
+ const passwordHash = await deps.hasher.hash(req.password);
66
+ await deps.userStore.save({ ...user, passwordHash });
67
+ }
68
+ const tokens = await deps.tokenService.issue(user.id, { identifier: user.identifier });
69
+ deps.events.emit({ name: 'user.logged_in', occurredAt: nowIso(deps), userId: user.id });
70
+ return { session: toSession(user, tokens.accessToken, tokens.expiresInSeconds), refreshToken: tokens.refreshToken };
71
+ }
72
+ /**
73
+ * Ververs de sessie: roteer het refresh-token via de kit (met hergebruik-detectie), emit
74
+ * `session.refreshed`. Fouten: `unauthenticated`, `token_invalid`, `token_expired`.
75
+ */
76
+ export async function refresh(deps, refreshToken) {
77
+ if (!refreshToken)
78
+ throw new AuthModuleError('unauthenticated');
79
+ let tokens;
80
+ try {
81
+ tokens = await deps.tokenService.refresh(refreshToken);
82
+ }
83
+ catch (err) {
84
+ throw asModuleError(err) ?? err;
85
+ }
86
+ const verified = await deps.tokenService.verify(tokens.accessToken);
87
+ const user = await deps.userStore.findById(verified.subject);
88
+ if (!user)
89
+ throw new AuthModuleError('unauthenticated');
90
+ deps.events.emit({ name: 'session.refreshed', occurredAt: nowIso(deps), userId: user.id });
91
+ return { session: toSession(user, tokens.accessToken, tokens.expiresInSeconds), refreshToken: tokens.refreshToken };
92
+ }
93
+ /**
94
+ * Log uit: trek het refresh-token in en emit `user.logged_out`. Idempotent — een onbekend of reeds
95
+ * ingetrokken token geeft geen fout en emit geen dubbel event.
96
+ */
97
+ export async function logout(deps, refreshToken) {
98
+ if (!refreshToken)
99
+ return OK;
100
+ // De kit trekt idempotent in en geeft het subject alleen terug wanneer dit token nú werd
101
+ // ingetrokken — dus emitten we `user.logged_out` precies één keer, zonder eigen lookup.
102
+ const subject = await deps.tokenService.revokeRefresh(refreshToken);
103
+ if (subject !== null) {
104
+ deps.events.emit({ name: 'user.logged_out', occurredAt: nowIso(deps), userId: subject });
105
+ }
106
+ return OK;
107
+ }
108
+ /**
109
+ * Start een wachtwoord-reset. No-enumeration: retourneert ALTIJD dezelfde generieke bevestiging. Alleen
110
+ * als de gebruiker bestaat wordt een reset-token uitgegeven, een mail gestuurd en `password.reset_requested`
111
+ * geëmit.
112
+ */
113
+ export async function forgotPassword(deps, req) {
114
+ const user = await deps.userStore.findByIdentifier(req.email);
115
+ if (user) {
116
+ const token = await deps.singleUseTokenService.issue(deps.config.purposes.passwordReset, { userId: user.id });
117
+ await deps.mailer.send({
118
+ to: req.email,
119
+ subject: 'Reset je wachtwoord',
120
+ data: { token, purpose: deps.config.purposes.passwordReset },
121
+ });
122
+ deps.events.emit({ name: 'password.reset_requested', occurredAt: nowIso(deps), userId: user.id });
123
+ }
124
+ return OK;
125
+ }
126
+ /**
127
+ * Reset het wachtwoord: verbruik het single-use reset-token (exact één keer), zet de nieuwe hash, trek
128
+ * ALLE sessies van de gebruiker in en emit `password.reset`. Fouten: `weak_password`, `token_invalid`,
129
+ * `token_expired`.
130
+ */
131
+ export async function resetPassword(deps, req) {
132
+ // Beleid vóór verbruik controleren: een zwak wachtwoord mag geen geldig token opbranden.
133
+ assertStrongPassword(deps, req.password);
134
+ const payload = await consume(deps, req.token, deps.config.purposes.passwordReset);
135
+ const user = await deps.userStore.findById(payload.userId);
136
+ if (!user)
137
+ throw new AuthModuleError('token_invalid');
138
+ const passwordHash = await deps.hasher.hash(req.password);
139
+ await deps.userStore.save({ ...user, passwordHash });
140
+ await deps.tokenService.revokeAllForSubject(user.id);
141
+ deps.events.emit({ name: 'password.reset', occurredAt: nowIso(deps), userId: user.id });
142
+ return OK;
143
+ }
144
+ // --- Interne helpers ---
145
+ /** Verbruik een single-use-token en normaliseer de payload; kit-fouten → contract-taxonomie. */
146
+ async function consume(deps, token, purpose) {
147
+ let payload;
148
+ try {
149
+ payload = await deps.singleUseTokenService.consume(token, purpose);
150
+ }
151
+ catch (err) {
152
+ throw asModuleError(err) ?? err;
153
+ }
154
+ if (!payload || typeof payload !== 'object' || typeof payload.userId !== 'string') {
155
+ throw new AuthModuleError('token_invalid');
156
+ }
157
+ return payload;
158
+ }
159
+ /** Handhaaf het wachtwoord-beleid uit config; gooit `weak_password`. */
160
+ function assertStrongPassword(deps, password) {
161
+ const { minLength, maxLength } = deps.config.password;
162
+ if (password.length < minLength || password.length > maxLength) {
163
+ throw new AuthModuleError('weak_password');
164
+ }
165
+ }
166
+ /** ISO-8601 tijdstip uit de geïnjecteerde klok (deterministisch, geen `Date.now`). */
167
+ function nowIso(deps) {
168
+ return new Date(deps.clock()).toISOString();
169
+ }
170
+ /** Map een kit-`UserRecord` naar de veilige contract-`PublicUser` (geen credential-materiaal). */
171
+ function toPublicUser(user) {
172
+ return { id: user.id, email: user.identifier, emailVerified: user.verified === true };
173
+ }
174
+ /** Bouw de contract-`SessionResponse` (access-token in de body; refresh gaat via cookie). */
175
+ function toSession(user, accessToken, expiresIn) {
176
+ return { user: toPublicUser(user), accessToken, expiresIn };
177
+ }
178
+ //# sourceMappingURL=services.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"services.js","sourceRoot":"","sources":["../../../backend/src/services.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,259 @@
1
+ import type { z } from 'zod';
2
+ import type { AuthErrorCode } from './errors.js';
3
+ /**
4
+ * US-A0104/US-A0105 — de machine-leesbare endpoint-catalogus. Backend leidt hier route + validatie
5
+ * uit af; frontend leidt hier zijn getypte client uit af. Eén bron, geen drift.
6
+ */
7
+ export interface EndpointDescriptor<Req extends z.ZodTypeAny | null, Res extends z.ZodTypeAny> {
8
+ readonly method: 'POST' | 'GET';
9
+ readonly path: string;
10
+ readonly request: Req;
11
+ readonly response: Res;
12
+ /** Foutcodes die dit endpoint kan retourneren (uit de gedeelde taxonomie). */
13
+ readonly errors: readonly AuthErrorCode[];
14
+ /** Vereist een geldig CSRF-token (cookie-transport, US-A0703). */
15
+ readonly csrf: boolean;
16
+ /** Vereist een geauthenticeerde sessie. */
17
+ readonly auth: boolean;
18
+ }
19
+ export declare const AUTH_ENDPOINTS: {
20
+ readonly register: {
21
+ readonly method: "POST";
22
+ readonly path: "/auth/register";
23
+ readonly request: z.ZodObject<{
24
+ email: z.ZodString;
25
+ password: z.ZodString;
26
+ }, "strip", z.ZodTypeAny, {
27
+ email: string;
28
+ password: string;
29
+ }, {
30
+ email: string;
31
+ password: string;
32
+ }>;
33
+ readonly response: z.ZodObject<{
34
+ ok: z.ZodLiteral<true>;
35
+ }, "strip", z.ZodTypeAny, {
36
+ ok: true;
37
+ }, {
38
+ ok: true;
39
+ }>;
40
+ readonly errors: readonly ["email_taken", "weak_password", "validation_failed", "rate_limited"];
41
+ readonly csrf: true;
42
+ readonly auth: false;
43
+ };
44
+ readonly verifyEmail: {
45
+ readonly method: "POST";
46
+ readonly path: "/auth/verify-email";
47
+ readonly request: z.ZodObject<{
48
+ token: z.ZodString;
49
+ }, "strip", z.ZodTypeAny, {
50
+ token: string;
51
+ }, {
52
+ token: string;
53
+ }>;
54
+ readonly response: z.ZodObject<{
55
+ ok: z.ZodLiteral<true>;
56
+ }, "strip", z.ZodTypeAny, {
57
+ ok: true;
58
+ }, {
59
+ ok: true;
60
+ }>;
61
+ readonly errors: readonly ["token_invalid", "token_expired"];
62
+ readonly csrf: true;
63
+ readonly auth: false;
64
+ };
65
+ readonly login: {
66
+ readonly method: "POST";
67
+ readonly path: "/auth/login";
68
+ readonly request: z.ZodObject<{
69
+ email: z.ZodString;
70
+ password: z.ZodString;
71
+ }, "strip", z.ZodTypeAny, {
72
+ email: string;
73
+ password: string;
74
+ }, {
75
+ email: string;
76
+ password: string;
77
+ }>;
78
+ readonly response: z.ZodObject<{
79
+ user: z.ZodObject<{
80
+ id: z.ZodString;
81
+ email: z.ZodString;
82
+ emailVerified: z.ZodBoolean;
83
+ }, "strip", z.ZodTypeAny, {
84
+ email: string;
85
+ id: string;
86
+ emailVerified: boolean;
87
+ }, {
88
+ email: string;
89
+ id: string;
90
+ emailVerified: boolean;
91
+ }>;
92
+ accessToken: z.ZodString;
93
+ expiresIn: z.ZodNumber;
94
+ }, "strip", z.ZodTypeAny, {
95
+ user: {
96
+ email: string;
97
+ id: string;
98
+ emailVerified: boolean;
99
+ };
100
+ accessToken: string;
101
+ expiresIn: number;
102
+ }, {
103
+ user: {
104
+ email: string;
105
+ id: string;
106
+ emailVerified: boolean;
107
+ };
108
+ accessToken: string;
109
+ expiresIn: number;
110
+ }>;
111
+ readonly errors: readonly ["invalid_credentials", "email_not_verified", "rate_limited", "csrf_failed"];
112
+ readonly csrf: true;
113
+ readonly auth: false;
114
+ };
115
+ readonly refresh: {
116
+ readonly method: "POST";
117
+ readonly path: "/auth/refresh";
118
+ readonly request: null;
119
+ readonly response: z.ZodObject<{
120
+ user: z.ZodObject<{
121
+ id: z.ZodString;
122
+ email: z.ZodString;
123
+ emailVerified: z.ZodBoolean;
124
+ }, "strip", z.ZodTypeAny, {
125
+ email: string;
126
+ id: string;
127
+ emailVerified: boolean;
128
+ }, {
129
+ email: string;
130
+ id: string;
131
+ emailVerified: boolean;
132
+ }>;
133
+ accessToken: z.ZodString;
134
+ expiresIn: z.ZodNumber;
135
+ }, "strip", z.ZodTypeAny, {
136
+ user: {
137
+ email: string;
138
+ id: string;
139
+ emailVerified: boolean;
140
+ };
141
+ accessToken: string;
142
+ expiresIn: number;
143
+ }, {
144
+ user: {
145
+ email: string;
146
+ id: string;
147
+ emailVerified: boolean;
148
+ };
149
+ accessToken: string;
150
+ expiresIn: number;
151
+ }>;
152
+ readonly errors: readonly ["unauthenticated", "token_invalid", "token_expired"];
153
+ readonly csrf: true;
154
+ readonly auth: false;
155
+ };
156
+ readonly logout: {
157
+ readonly method: "POST";
158
+ readonly path: "/auth/logout";
159
+ readonly request: null;
160
+ readonly response: z.ZodObject<{
161
+ ok: z.ZodLiteral<true>;
162
+ }, "strip", z.ZodTypeAny, {
163
+ ok: true;
164
+ }, {
165
+ ok: true;
166
+ }>;
167
+ readonly errors: readonly [];
168
+ readonly csrf: true;
169
+ readonly auth: false;
170
+ };
171
+ readonly forgotPassword: {
172
+ readonly method: "POST";
173
+ readonly path: "/auth/forgot-password";
174
+ readonly request: z.ZodObject<{
175
+ email: z.ZodString;
176
+ }, "strip", z.ZodTypeAny, {
177
+ email: string;
178
+ }, {
179
+ email: string;
180
+ }>;
181
+ readonly response: z.ZodObject<{
182
+ ok: z.ZodLiteral<true>;
183
+ }, "strip", z.ZodTypeAny, {
184
+ ok: true;
185
+ }, {
186
+ ok: true;
187
+ }>;
188
+ readonly errors: readonly ["rate_limited", "validation_failed"];
189
+ readonly csrf: true;
190
+ readonly auth: false;
191
+ };
192
+ readonly resetPassword: {
193
+ readonly method: "POST";
194
+ readonly path: "/auth/reset-password";
195
+ readonly request: z.ZodObject<{
196
+ token: z.ZodString;
197
+ password: z.ZodString;
198
+ }, "strip", z.ZodTypeAny, {
199
+ password: string;
200
+ token: string;
201
+ }, {
202
+ password: string;
203
+ token: string;
204
+ }>;
205
+ readonly response: z.ZodObject<{
206
+ ok: z.ZodLiteral<true>;
207
+ }, "strip", z.ZodTypeAny, {
208
+ ok: true;
209
+ }, {
210
+ ok: true;
211
+ }>;
212
+ readonly errors: readonly ["token_invalid", "token_expired", "weak_password"];
213
+ readonly csrf: true;
214
+ readonly auth: false;
215
+ };
216
+ readonly me: {
217
+ readonly method: "GET";
218
+ readonly path: "/auth/me";
219
+ readonly request: null;
220
+ readonly response: z.ZodObject<{
221
+ user: z.ZodObject<{
222
+ id: z.ZodString;
223
+ email: z.ZodString;
224
+ emailVerified: z.ZodBoolean;
225
+ }, "strip", z.ZodTypeAny, {
226
+ email: string;
227
+ id: string;
228
+ emailVerified: boolean;
229
+ }, {
230
+ email: string;
231
+ id: string;
232
+ emailVerified: boolean;
233
+ }>;
234
+ roles: z.ZodArray<z.ZodString, "many">;
235
+ permissions: z.ZodArray<z.ZodString, "many">;
236
+ }, "strip", z.ZodTypeAny, {
237
+ user: {
238
+ email: string;
239
+ id: string;
240
+ emailVerified: boolean;
241
+ };
242
+ roles: string[];
243
+ permissions: string[];
244
+ }, {
245
+ user: {
246
+ email: string;
247
+ id: string;
248
+ emailVerified: boolean;
249
+ };
250
+ roles: string[];
251
+ permissions: string[];
252
+ }>;
253
+ readonly errors: readonly ["unauthenticated"];
254
+ readonly csrf: false;
255
+ readonly auth: true;
256
+ };
257
+ };
258
+ export type AuthEndpointName = keyof typeof AUTH_ENDPOINTS;
259
+ //# sourceMappingURL=endpoints.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"endpoints.d.ts","sourceRoot":"","sources":["../../contract/endpoints.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAW7B,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAEjD;;;GAGG;AACH,MAAM,WAAW,kBAAkB,CACjC,GAAG,SAAS,CAAC,CAAC,UAAU,GAAG,IAAI,EAC/B,GAAG,SAAS,CAAC,CAAC,UAAU;IAExB,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,KAAK,CAAC;IAChC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,GAAG,CAAC;IACtB,QAAQ,CAAC,QAAQ,EAAE,GAAG,CAAC;IACvB,8EAA8E;IAC9E,QAAQ,CAAC,MAAM,EAAE,SAAS,aAAa,EAAE,CAAC;IAC1C,kEAAkE;IAClE,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,2CAA2C;IAC3C,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;CACxB;AAED,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAuC+D,CAAC;AAE3F,MAAM,MAAM,gBAAgB,GAAG,MAAM,OAAO,cAAc,CAAC"}
@@ -0,0 +1,42 @@
1
+ import { registerRequestSchema, verifyEmailRequestSchema, loginRequestSchema, forgotPasswordRequestSchema, resetPasswordRequestSchema, sessionResponseSchema, meResponseSchema, acknowledgementSchema, } from './schemas.js';
2
+ export const AUTH_ENDPOINTS = {
3
+ register: {
4
+ method: 'POST', path: '/auth/register', request: registerRequestSchema,
5
+ response: acknowledgementSchema, errors: ['email_taken', 'weak_password', 'validation_failed', 'rate_limited'],
6
+ csrf: true, auth: false,
7
+ },
8
+ verifyEmail: {
9
+ method: 'POST', path: '/auth/verify-email', request: verifyEmailRequestSchema,
10
+ response: acknowledgementSchema, errors: ['token_invalid', 'token_expired'],
11
+ csrf: true, auth: false,
12
+ },
13
+ login: {
14
+ method: 'POST', path: '/auth/login', request: loginRequestSchema,
15
+ response: sessionResponseSchema, errors: ['invalid_credentials', 'email_not_verified', 'rate_limited', 'csrf_failed'],
16
+ csrf: true, auth: false,
17
+ },
18
+ refresh: {
19
+ method: 'POST', path: '/auth/refresh', request: null,
20
+ response: sessionResponseSchema, errors: ['unauthenticated', 'token_invalid', 'token_expired'],
21
+ csrf: true, auth: false,
22
+ },
23
+ logout: {
24
+ method: 'POST', path: '/auth/logout', request: null,
25
+ response: acknowledgementSchema, errors: [], csrf: true, auth: false,
26
+ },
27
+ forgotPassword: {
28
+ method: 'POST', path: '/auth/forgot-password', request: forgotPasswordRequestSchema,
29
+ response: acknowledgementSchema, errors: ['rate_limited', 'validation_failed'],
30
+ csrf: true, auth: false,
31
+ },
32
+ resetPassword: {
33
+ method: 'POST', path: '/auth/reset-password', request: resetPasswordRequestSchema,
34
+ response: acknowledgementSchema, errors: ['token_invalid', 'token_expired', 'weak_password'],
35
+ csrf: true, auth: false,
36
+ },
37
+ me: {
38
+ method: 'GET', path: '/auth/me', request: null,
39
+ response: meResponseSchema, errors: ['unauthenticated'], csrf: false, auth: true,
40
+ },
41
+ };
42
+ //# sourceMappingURL=endpoints.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"endpoints.js","sourceRoot":"","sources":["../../contract/endpoints.ts"],"names":[],"mappings":"AACA,OAAO,EACL,qBAAqB,EACrB,wBAAwB,EACxB,kBAAkB,EAClB,2BAA2B,EAC3B,0BAA0B,EAC1B,qBAAqB,EACrB,gBAAgB,EAChB,qBAAqB,GACtB,MAAM,cAAc,CAAC;AAuBtB,MAAM,CAAC,MAAM,cAAc,GAAG;IAC5B,QAAQ,EAAE;QACR,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,gBAAgB,EAAE,OAAO,EAAE,qBAAqB;QACtE,QAAQ,EAAE,qBAAqB,EAAE,MAAM,EAAE,CAAC,aAAa,EAAE,eAAe,EAAE,mBAAmB,EAAE,cAAc,CAAC;QAC9G,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK;KACxB;IACD,WAAW,EAAE;QACX,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,oBAAoB,EAAE,OAAO,EAAE,wBAAwB;QAC7E,QAAQ,EAAE,qBAAqB,EAAE,MAAM,EAAE,CAAC,eAAe,EAAE,eAAe,CAAC;QAC3E,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK;KACxB;IACD,KAAK,EAAE;QACL,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,aAAa,EAAE,OAAO,EAAE,kBAAkB;QAChE,QAAQ,EAAE,qBAAqB,EAAE,MAAM,EAAE,CAAC,qBAAqB,EAAE,oBAAoB,EAAE,cAAc,EAAE,aAAa,CAAC;QACrH,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK;KACxB;IACD,OAAO,EAAE;QACP,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,eAAe,EAAE,OAAO,EAAE,IAAI;QACpD,QAAQ,EAAE,qBAAqB,EAAE,MAAM,EAAE,CAAC,iBAAiB,EAAE,eAAe,EAAE,eAAe,CAAC;QAC9F,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK;KACxB;IACD,MAAM,EAAE;QACN,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,cAAc,EAAE,OAAO,EAAE,IAAI;QACnD,QAAQ,EAAE,qBAAqB,EAAE,MAAM,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK;KACrE;IACD,cAAc,EAAE;QACd,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,uBAAuB,EAAE,OAAO,EAAE,2BAA2B;QACnF,QAAQ,EAAE,qBAAqB,EAAE,MAAM,EAAE,CAAC,cAAc,EAAE,mBAAmB,CAAC;QAC9E,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK;KACxB;IACD,aAAa,EAAE;QACb,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,sBAAsB,EAAE,OAAO,EAAE,0BAA0B;QACjF,QAAQ,EAAE,qBAAqB,EAAE,MAAM,EAAE,CAAC,eAAe,EAAE,eAAe,EAAE,eAAe,CAAC;QAC5F,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK;KACxB;IACD,EAAE,EAAE;QACF,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,UAAU,EAAE,OAAO,EAAE,IAAI;QAC9C,QAAQ,EAAE,gBAAgB,EAAE,MAAM,EAAE,CAAC,iBAAiB,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI;KACjF;CACuF,CAAC"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * US-A0102 — Error-taxonomie + i18n-sleutels. Eén gedeelde foutcodelijst die beide helften (backend
3
+ * mapping → HTTP, frontend weergave → i18n) uit het contract afleiden. Codes lekken nooit of een
4
+ * e-mail bestaat of waarom een token precies faalde (no-enumeration, security).
5
+ */
6
+ export declare const AUTH_ERROR_CODES: readonly ["invalid_credentials", "email_taken", "weak_password", "token_invalid", "token_expired", "email_not_verified", "rate_limited", "csrf_failed", "unauthenticated", "validation_failed"];
7
+ export type AuthErrorCode = (typeof AUTH_ERROR_CODES)[number];
8
+ export interface AuthErrorDescriptor {
9
+ /** HTTP-status waarnaar de backend deze fout mapt. */
10
+ readonly httpStatus: number;
11
+ /** i18n-sleutel die de frontend toont (nooit rauwe details). */
12
+ readonly i18nKey: string;
13
+ }
14
+ /** Enige bron van de code→HTTP + code→i18n-mapping. Bewust generiek gehouden (no-enumeration). */
15
+ export declare const AUTH_ERROR_TAXONOMY: Readonly<Record<AuthErrorCode, AuthErrorDescriptor>>;
16
+ /** Contract-vorm van een foutrespons (body). Bevat nooit stacktraces of secret-materiaal. */
17
+ export interface AuthErrorBody {
18
+ readonly code: AuthErrorCode;
19
+ readonly message: string;
20
+ /** Optioneel: veld-gebonden validatiefouten (paden), zonder ingevoerde waarden. */
21
+ readonly fields?: Readonly<Record<string, string[]>>;
22
+ }
23
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../contract/errors.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,eAAO,MAAM,gBAAgB,iMAWnB,CAAC;AAEX,MAAM,MAAM,aAAa,GAAG,CAAC,OAAO,gBAAgB,CAAC,CAAC,MAAM,CAAC,CAAC;AAE9D,MAAM,WAAW,mBAAmB;IAClC,sDAAsD;IACtD,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,gEAAgE;IAChE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED,kGAAkG;AAClG,eAAO,MAAM,mBAAmB,EAAE,QAAQ,CAAC,MAAM,CAAC,aAAa,EAAE,mBAAmB,CAAC,CAWnF,CAAC;AAEH,6FAA6F;AAC7F,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC;IAC7B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,mFAAmF;IACnF,QAAQ,CAAC,MAAM,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC;CACtD"}