@nodefony/framework 10.0.0-beta.2 → 10.0.0-beta.4

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 (76) hide show
  1. package/README.md +4 -0
  2. package/dist/_virtual/{_@oxc-project_runtime@0.152.0 → _@oxc-project_runtime@0.155.0}/helpers/esm/decorate.js +1 -2
  3. package/dist/index.js +15 -20
  4. package/dist/nodefony/config/config.js +1 -2
  5. package/dist/nodefony/config/defineModuleConfig.js +1 -2
  6. package/dist/nodefony/controller/AdminApiController.js +45 -11
  7. package/dist/nodefony/controller/ApiKeyController.js +18 -10
  8. package/dist/nodefony/controller/BenchController.js +1 -2
  9. package/dist/nodefony/controller/IssuerMetadataController.js +1 -2
  10. package/dist/nodefony/controller/LoginPageController.js +117 -0
  11. package/dist/nodefony/controller/OAuth2Controller.js +133 -17
  12. package/dist/nodefony/controller/ProtectedResourceMetadataController.js +1 -2
  13. package/dist/nodefony/controller/SessionAuthController.js +50 -13
  14. package/dist/nodefony/controller/TokenAuthController.js +14 -8
  15. package/dist/nodefony/controller/TotpController.js +7 -8
  16. package/dist/nodefony/controller/WebAuthnController.js +28 -12
  17. package/dist/nodefony/controller/oauthAuthority.js +1 -2
  18. package/dist/nodefony/decorators/routerDecorators.js +268 -65
  19. package/dist/nodefony/service/AdminBroker.js +4 -6
  20. package/dist/nodefony/service/Eta.js +1 -2
  21. package/dist/nodefony/service/IdempotencyStore.js +4 -6
  22. package/dist/nodefony/service/router.js +97 -34
  23. package/dist/nodefony/src/Controller.js +71 -43
  24. package/dist/nodefony/src/FrameworkAdminApi.js +33 -11
  25. package/dist/nodefony/src/KernelAdminApi.js +82 -18
  26. package/dist/nodefony/src/PlaygroundAdminApi.js +72 -3
  27. package/dist/nodefony/src/RedisIdempotencyStore.js +46 -4
  28. package/dist/nodefony/src/Resolver.js +105 -36
  29. package/dist/nodefony/src/ResourceController.js +2 -3
  30. package/dist/nodefony/src/Route.js +54 -8
  31. package/dist/nodefony/src/SecurityApiController.js +23 -0
  32. package/dist/nodefony/src/SyslogAdminApi.js +1 -2
  33. package/dist/nodefony/src/Template.js +1 -2
  34. package/dist/nodefony/src/configMutation.js +1 -2
  35. package/dist/nodefony/src/docsReader.js +39 -23
  36. package/dist/nodefony/src/idempotency.js +14 -22
  37. package/dist/nodefony/src/idempotencyGc.js +1 -2
  38. package/dist/nodefony/src/idempotencyStoreRegistry.js +1 -2
  39. package/dist/nodefony/src/loginPage.js +430 -0
  40. package/dist/nodefony/src/requireAuthentication.js +35 -0
  41. package/dist/nodefony/src/routeSchema.js +81 -0
  42. package/dist/nodefony/src/scopeCatalog.js +1 -2
  43. package/dist/nodefony/src/singletonGuard.js +1 -2
  44. package/dist/nodefony/src/syslogFilters.js +1 -2
  45. package/dist/types/index.d.ts +8 -12
  46. package/dist/types/nodefony/controller/AdminApiController.d.ts +12 -0
  47. package/dist/types/nodefony/controller/ApiKeyController.d.ts +2 -2
  48. package/dist/types/nodefony/controller/LoginPageController.d.ts +32 -0
  49. package/dist/types/nodefony/controller/OAuth2Controller.d.ts +67 -8
  50. package/dist/types/nodefony/controller/SessionAuthController.d.ts +46 -8
  51. package/dist/types/nodefony/controller/TokenAuthController.d.ts +6 -4
  52. package/dist/types/nodefony/controller/TotpController.d.ts +2 -2
  53. package/dist/types/nodefony/controller/WebAuthnController.d.ts +12 -4
  54. package/dist/types/nodefony/decorators/routerDecorators.d.ts +221 -35
  55. package/dist/types/nodefony/interfaces/IController.d.ts +2 -3
  56. package/dist/types/nodefony/interfaces/IRoute.d.ts +1 -2
  57. package/dist/types/nodefony/service/router.d.ts +40 -1
  58. package/dist/types/nodefony/src/Controller.d.ts +54 -17
  59. package/dist/types/nodefony/src/PlaygroundAdminApi.d.ts +12 -0
  60. package/dist/types/nodefony/src/Resolver.d.ts +44 -1
  61. package/dist/types/nodefony/src/ResourceController.d.ts +1 -1
  62. package/dist/types/nodefony/src/Route.d.ts +21 -1
  63. package/dist/types/nodefony/src/SecurityApiController.d.ts +17 -0
  64. package/dist/types/nodefony/src/docsReader.d.ts +10 -6
  65. package/dist/types/nodefony/src/idempotency.d.ts +12 -10
  66. package/dist/types/nodefony/src/loginPage.d.ts +144 -0
  67. package/dist/types/nodefony/src/requireAuthentication.d.ts +29 -0
  68. package/dist/types/nodefony/src/routeSchema.d.ts +112 -0
  69. package/docs/admin.md +83 -78
  70. package/docs/controller.md +112 -111
  71. package/docs/decorateurs.md +212 -163
  72. package/docs/idempotence.md +137 -135
  73. package/docs/routing.md +96 -95
  74. package/docs/templates.md +55 -55
  75. package/package.json +10 -13
  76. package/dist/_virtual/_@oxc-project_runtime@0.152.0/helpers/esm/decorateMetadata.js +0 -6
@@ -0,0 +1,430 @@
1
+ import { LOGIN_PAGE_ASSETS_BASE, LOGIN_PAGE_LAYOUTS, LOGIN_PAGE_SKINS, LOGIN_PAGE_SKIN_SPECS, LOGIN_PAGE_THEME_KEY, OAUTH2_CANCELLED_REASON, isLoginPageSkin, loginPageSkinFile, oauth2AuthorizePath, safeRedirectPath } from "nodefony";
2
+ // nodefony/src/loginPage.ts
3
+ /**
4
+ * Mémoïse une ressource chargée de façon asynchrone : le SUCCÈS est gardé, un
5
+ * ÉCHEC est oublié. Garder la promesse rejetée figerait la panne — la page
6
+ * répondrait 500 jusqu'au redémarrage, même une fois le fichier corrigé.
7
+ *
8
+ * @param load - charge la ressource
9
+ * @returns la fonction qui la rend, chargée une seule fois tant qu'elle réussit
10
+ */
11
+ function keepSuccess(load) {
12
+ let pending = null;
13
+ return () => pending ??= load().catch((error) => {
14
+ pending = null;
15
+ throw error;
16
+ });
17
+ }
18
+ /** Feuille, script et logo de la page, servis sous {@link LOGIN_PAGE_ASSETS_BASE}. */
19
+ const LOGIN_PAGE_ASSET_FILES = {
20
+ style: "login.css",
21
+ script: "login.js",
22
+ logo: "nodefony-logo.svg"
23
+ };
24
+ const DEFAULT_TITLE = "Se connecter";
25
+ /**
26
+ * Fournisseurs dont la MARQUE est le mot que l'utilisateur connaît : il a un
27
+ * compte GitHub, pas un « fournisseur OpenID Connect ». Tout autre fournisseur
28
+ * est un serveur d'identité d'organisation (Keycloak, Okta, un OIDC
29
+ * d'entreprise) dont le nom ne dit rien à qui se connecte.
30
+ */
31
+ const PUBLIC_ACCOUNT_PROVIDERS = /* @__PURE__ */ new Set([
32
+ "apple",
33
+ "bitbucket",
34
+ "discord",
35
+ "facebook",
36
+ "github",
37
+ "gitlab",
38
+ "google",
39
+ "linkedin",
40
+ "microsoft",
41
+ "slack"
42
+ ]);
43
+ /** Libellé d'un fournisseur d'organisation : ce qu'il ouvre, pas son logiciel. */
44
+ const ORGANIZATION_ACCOUNT_LABEL = "Compte de l'organisation";
45
+ /** Annonce d'une page sans aucun moyen de connexion. */
46
+ const LOGIN_PAGE_EMPTY_NOTICE = "Aucun moyen de connexion n'est disponible pour le moment. Prévenez l'administrateur du site.";
47
+ /**
48
+ * Ligne sous le titre, déduite des moyens proposés quand la configuration n'en
49
+ * donne pas : elle annonce ce que la carte contient, jamais autre chose.
50
+ */
51
+ function leadFor(password, passkey, sso) {
52
+ if (password && sso) return "Compte de l'organisation ou identifiant local";
53
+ if (password && passkey) return "Identifiant et mot de passe, ou passkey";
54
+ if (password) return "Identifiant et mot de passe";
55
+ if (sso) return "Avec le compte de votre organisation";
56
+ if (passkey) return "Avec votre passkey";
57
+ return "Aucun moyen de connexion disponible";
58
+ }
59
+ /** Annonce d'un retour du fournisseur sans connexion, hors annulation. */
60
+ const PROVIDER_FAILED_NOTICE = "La connexion par le fournisseur n'a pas abouti. Réessayez, ou choisissez une autre méthode.";
61
+ /** Dit si une valeur de requête nomme une mise en page connue. */
62
+ function isLayout(value) {
63
+ return LOGIN_PAGE_LAYOUTS.includes(value);
64
+ }
65
+ /**
66
+ * Habillage et mise en page servis : la configuration, sauf aperçu demandé en
67
+ * développement. Un `?skin=` seul prend la mise en page de son habillage ; une
68
+ * valeur inconnue est ignorée.
69
+ */
70
+ function resolveSkin(page, request) {
71
+ if (request.preview !== true) return {
72
+ skin: page.skin,
73
+ layout: page.layout
74
+ };
75
+ const skin = isLoginPageSkin(request.skin) ? request.skin : page.skin;
76
+ return {
77
+ skin,
78
+ layout: isLayout(request.layout) ? request.layout : skin === page.skin ? page.layout : LOGIN_PAGE_SKIN_SPECS[skin].layout
79
+ };
80
+ }
81
+ /**
82
+ * Prépare les données du gabarit à partir de la description de la page
83
+ * (security) et de la requête.
84
+ *
85
+ * Fonction pure : `from` repasse par `safeRedirectPath` (un chemin hors de
86
+ * l'origine retombe sur `/`), `theme` n'accepte que `light` ou `dark`,
87
+ * `skin` et `layout` ne sont lus qu'en aperçu et contre leur liste fermée.
88
+ *
89
+ * @param page - description rendue par `authFlow.describeLoginPage()`
90
+ * @param request - valeurs de la requête, non filtrées
91
+ * @returns les données du gabarit
92
+ */
93
+ function buildLoginPageView(page, request) {
94
+ const from = safeRedirectPath(request.from);
95
+ const asset = (file) => `${LOGIN_PAGE_ASSETS_BASE}/${file}?v=${encodeURIComponent(request.assetsVersion)}`;
96
+ const logoHref = asset(LOGIN_PAGE_ASSET_FILES.logo);
97
+ const providerFrom = from === "/" ? void 0 : from;
98
+ const { skin, layout } = resolveSkin(page, request);
99
+ const skinFile = loginPageSkinFile(skin);
100
+ const sso = page.providers.length > 0;
101
+ return {
102
+ brand: page.title ?? request.projectName,
103
+ title: page.heading ?? DEFAULT_TITLE,
104
+ subtitle: page.subtitle,
105
+ hero: page.hero,
106
+ footer: page.footer,
107
+ logo: page.logo ?? logoHref,
108
+ skin,
109
+ layout,
110
+ backdrop: LOGIN_PAGE_SKIN_SPECS[skin].backdrop,
111
+ theme: request.theme === "light" || request.theme === "dark" ? request.theme : null,
112
+ ssoFirst: page.providersFirst && (sso || page.passkey),
113
+ password: page.password,
114
+ passkey: page.passkey,
115
+ empty: !page.password && !page.passkey && !sso,
116
+ lead: page.subtitle ?? leadFor(page.password, page.passkey, sso),
117
+ providers: page.providers.map(({ name, label, icon }) => ({
118
+ ...PUBLIC_ACCOUNT_PROVIDERS.has(name.toLowerCase()) ? {
119
+ label,
120
+ caption: null
121
+ } : {
122
+ label: ORGANIZATION_ACCOUNT_LABEL,
123
+ caption: `SSO · ${label}`
124
+ },
125
+ href: oauth2AuthorizePath(name, providerFrom, page.path),
126
+ icon: name === "github" ? "m-github" : "i-key",
127
+ image: icon
128
+ })),
129
+ from,
130
+ picker: request.preview === true && page.previewPicker ? {
131
+ skins: LOGIN_PAGE_SKINS,
132
+ layouts: LOGIN_PAGE_LAYOUTS,
133
+ hook: page.previewHook ?? null
134
+ } : null,
135
+ notice: typeof request.reason === "string" && request.reason.length > 0 && request.reason !== OAUTH2_CANCELLED_REASON ? PROVIDER_FAILED_NOTICE : null,
136
+ styleHref: asset(LOGIN_PAGE_ASSET_FILES.style),
137
+ skinHref: skinFile === null ? null : asset(skinFile),
138
+ stylesheetHref: page.stylesheet,
139
+ scriptHref: asset(LOGIN_PAGE_ASSET_FILES.script),
140
+ logoHref,
141
+ nonce: request.nonce
142
+ };
143
+ }
144
+ /**
145
+ * Gabarit Eta de la page de connexion par défaut (balisage F03, feuille
146
+ * `nodefony/login.css`, script `nodefony/login.js`).
147
+ *
148
+ * En chaîne dans le code : un `.eta` posé à côté ne serait pas publié
149
+ * (`files` du paquet = `dist`, `docs`). Compilé une fois par processus.
150
+ * Tout passe par `<%= %>` (échappé) ; aucune sortie brute. L'icône
151
+ * d'empreinte (`i-finger`) reprend `IconFingerprint` de Tabler Icons (MIT),
152
+ * celle du bouton de Studio : les deux écrans montrent le même geste.
153
+ */
154
+ const LOGIN_PAGE_TEMPLATE = `<!doctype html>
155
+ <html lang="fr"<% if (it.theme) { %> data-theme="<%= it.theme %>"<% } %>>
156
+ <head>
157
+ <meta charset="utf-8" />
158
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
159
+ <meta name="color-scheme" content="dark light" />
160
+ <meta name="robots" content="noindex" />
161
+ <title><%= it.title %> — <%= it.brand %></title>
162
+ <link rel="icon" href="<%= it.logoHref %>" type="image/svg+xml" />
163
+ <% if (!it.theme) { %>
164
+ <script nonce="<%= it.nonce %>">try{var t=localStorage.getItem("${LOGIN_PAGE_THEME_KEY}");if(t==="light"||t==="dark")document.documentElement.dataset.theme=t}catch(e){}<\/script>
165
+ <% } %>
166
+ <link rel="stylesheet" href="<%= it.styleHref %>" />
167
+ <% if (it.skinHref) { %>
168
+ <link rel="stylesheet" href="<%= it.skinHref %>" />
169
+ <% } %>
170
+ <% if (it.stylesheetHref) { %>
171
+ <link rel="stylesheet" href="<%= it.stylesheetHref %>" />
172
+ <% } %>
173
+ <script type="module" src="<%= it.scriptHref %>" nonce="<%= it.nonce %>"><\/script>
174
+ </head>
175
+ <body data-skin="<%= it.skin %>" data-layout="<%= it.layout %>"<% if (it.backdrop) { %> data-backdrop<% } %><% if (it.ssoFirst) { %> data-sso-first<% } %>>
176
+ <svg width="0" height="0" style="position: absolute" aria-hidden="true">
177
+ <symbol id="i-user" viewBox="0 0 24 24"><circle cx="12" cy="8" r="3.5" /><path d="M5.5 20a6.5 6.5 0 0 1 13 0" /></symbol>
178
+ <symbol id="i-lock" viewBox="0 0 24 24"><rect x="5" y="10.5" width="14" height="10" rx="2" /><path d="M8.5 10.5V7.5a3.5 3.5 0 0 1 7 0v3" /></symbol>
179
+ <symbol id="i-eye" viewBox="0 0 24 24"><path d="M2.5 12S6 5.5 12 5.5 21.5 12 21.5 12 18 18.5 12 18.5 2.5 12 2.5 12Z" /><circle cx="12" cy="12" r="2.8" /></symbol>
180
+ <symbol id="i-arrow" viewBox="0 0 24 24"><path d="M5 12h14M13 6l6 6-6 6" /></symbol>
181
+ <symbol id="i-key" viewBox="0 0 24 24"><circle cx="8" cy="15" r="3.8" /><path d="M10.8 12.2 19.5 3.5M16.5 6.5l2.5 2.5M14 9l2 2" /></symbol>
182
+ <symbol id="i-check" viewBox="0 0 24 24"><path d="M5 12.5l4.5 4.5L19 7.5" /></symbol>
183
+ <symbol id="i-finger" viewBox="0 0 24 24"><path d="M18.9 7a8 8 0 0 1 1.1 5v1a6 6 0 0 0 .8 3" /><path d="M8 11a4 4 0 0 1 8 0v1a10 10 0 0 0 2 6" /><path d="M12 11v2a14 14 0 0 0 2.5 8" /><path d="M8 15a18 18 0 0 0 1.8 6" /><path d="M4.9 19a22 22 0 0 1-.9-7v-1a8 8 0 0 1 12-6.95" /></symbol>
184
+ <symbol id="i-sun" viewBox="0 0 24 24"><circle cx="12" cy="12" r="4" /><path d="M12 2.5v2M12 19.5v2M4.6 4.6l1.4 1.4M18 18l1.4 1.4M2.5 12h2M19.5 12h2M4.6 19.4 6 18M18 6l1.4-1.4" /></symbol>
185
+ <symbol id="i-moon" viewBox="0 0 24 24"><path d="M20 14.5A8 8 0 0 1 9.5 4a8 8 0 1 0 10.5 10.5Z" /></symbol>
186
+ <symbol id="i-shield" viewBox="0 0 24 24"><path d="M12 3l7.5 3v5.5c0 4.6-3.2 8-7.5 9.5-4.3-1.5-7.5-4.9-7.5-9.5V6z" /></symbol>
187
+ <symbol id="m-github" viewBox="0 0 16 16"><path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27s1.36.09 2 .27c1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.01 8.01 0 0 0 16 8c0-4.42-3.58-8-8-8Z" /></symbol>
188
+ </svg>
189
+ <header class="nf-strip">
190
+ <div class="nf-strip-brand"><img class="nf-logo" src="<%= it.logo %>" alt="" /><%= it.brand %></div>
191
+ </header>
192
+ <div class="nf-layout">
193
+ <aside class="nf-hero" aria-hidden="true">
194
+ <div class="nf-hero-brand"><img class="nf-logo" src="<%= it.logo %>" alt="" /><%= it.brand %></div>
195
+ <% if (it.hero === null) { %>
196
+ <div class="nf-home">
197
+ <img
198
+ class="nf-home-logo"
199
+ src="<%= it.logoHref %>"
200
+ alt=""
201
+ />
202
+ <h2>Le framework Node.js fullstack</h2>
203
+ <p class="sub">
204
+ Temps réel natif · développement agentic-ready · socle TypeScript
205
+ isomorphe
206
+ </p>
207
+ <p class="sig">
208
+ Une action de contrôleur. Deux transports. La même session, la même
209
+ sécurité, le même code.
210
+ </p>
211
+ <ul class="badges">
212
+ <li class="agentic">Agentic ready</li>
213
+ <li>Node.js ≥ 24.14</li>
214
+ <li>TypeScript strict</li>
215
+ <li>ESM only</li>
216
+ <li>Licence Apache 2.0</li>
217
+ </ul>
218
+ <div
219
+ class="install"
220
+ role="img"
221
+ aria-label="Commande d'installation : npx nodefony create app"
222
+ >
223
+ <span class="p">$</span><span>npx nodefony create app</span>
224
+ </div>
225
+ <div class="features">
226
+ <div class="feature">
227
+ <span class="ic"
228
+ ><svg
229
+ viewBox="0 0 24 24"
230
+ fill="none"
231
+ stroke="currentColor"
232
+ stroke-width="2"
233
+ stroke-linecap="round"
234
+ stroke-linejoin="round"
235
+ aria-hidden="true"
236
+ >
237
+ <polygon
238
+ points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"
239
+ /></svg
240
+ ></span>
241
+ <h3>Temps réel natif</h3>
242
+ <p>
243
+ Le WebSocket n'est pas un ajout : même pipeline, même table de
244
+ routes, même sécurité que le HTTP.
245
+ </p>
246
+ </div>
247
+ <div class="feature">
248
+ <span class="ic"
249
+ ><svg
250
+ viewBox="0 0 24 24"
251
+ fill="none"
252
+ stroke="currentColor"
253
+ stroke-width="2"
254
+ stroke-linecap="round"
255
+ stroke-linejoin="round"
256
+ aria-hidden="true"
257
+ >
258
+ <path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z" />
259
+ <path d="m9 12 2 2 4-4" /></svg
260
+ ></span>
261
+ <h3>Sécurité par conception</h3>
262
+ <p>
263
+ Pare-feu applicatif par zones, Zero Trust, CSRF Fetch Metadata,
264
+ sessions hybrides, WebAuthn.
265
+ </p>
266
+ </div>
267
+ <div class="feature">
268
+ <span class="ic"
269
+ ><svg
270
+ viewBox="0 0 24 24"
271
+ fill="none"
272
+ stroke="currentColor"
273
+ stroke-width="2"
274
+ stroke-linecap="round"
275
+ stroke-linejoin="round"
276
+ aria-hidden="true"
277
+ >
278
+ <rect x="3" y="3" width="7" height="7" rx="1" />
279
+ <rect x="14" y="3" width="7" height="7" rx="1" />
280
+ <rect x="3" y="14" width="7" height="7" rx="1" />
281
+ <rect x="14" y="14" width="7" height="7" rx="1" /></svg
282
+ ></span>
283
+ <h3>DI & modules</h3>
284
+ <p>
285
+ Injection de dépendances par décorateurs, modules autonomes,
286
+ configuration validée au boot.
287
+ </p>
288
+ </div>
289
+ <div class="feature">
290
+ <span class="ic"
291
+ ><svg
292
+ viewBox="0 0 24 24"
293
+ fill="none"
294
+ stroke="currentColor"
295
+ stroke-width="2"
296
+ stroke-linecap="round"
297
+ stroke-linejoin="round"
298
+ aria-hidden="true"
299
+ >
300
+ <circle cx="12" cy="12" r="9" />
301
+ <path
302
+ d="M3 12h18M12 3c2.5 2.6 4 5.7 4 9s-1.5 6.4-4 9c-2.5-2.6-4-5.7-4-9s1.5-6.4 4-9z"
303
+ /></svg
304
+ ></span>
305
+ <h3>Socle isomorphe</h3>
306
+ <p>
307
+ Le même paquet côté serveur et côté navigateur : client temps
308
+ réel, règles et types écrits une fois.
309
+ </p>
310
+ </div>
311
+ </div>
312
+ </div>
313
+ <small>Propulsé par Nodefony</small>
314
+ <% } else if (it.hero) { %>
315
+ <div class="nf-home">
316
+ <h2><%= it.hero.heading %></h2>
317
+ <% if (it.hero.text) { %>
318
+ <p class="sub"><%= it.hero.text %></p>
319
+ <% } %>
320
+ </div>
321
+ <% } %>
322
+ </aside>
323
+ <main class="nf-stage" data-nf-login data-from="<%= it.from %>">
324
+ <button type="button" class="nf-theme" data-theme-toggle aria-label="Basculer entre thème clair et thème sombre"><svg class="nf-icon t-sun" aria-hidden="true"><use href="#i-sun" /></svg><svg class="nf-icon t-moon" aria-hidden="true"><use href="#i-moon" /></svg></button>
325
+ <section class="nf-card" aria-labelledby="nf-title">
326
+ <div class="nf-head">
327
+ <div class="nf-brand"><img class="nf-logo" src="<%= it.logo %>" alt="" /><%= it.brand %></div>
328
+ <h1 id="nf-title"><%= it.title %></h1>
329
+ <p class="nf-sub" id="nf-sub"><%= it.lead %></p>
330
+ </div>
331
+ <div class="nf-body">
332
+ <% if (it.password) { %>
333
+ <ol class="nf-steps" aria-label="Étapes">
334
+ <li data-tab="identifier" aria-current="step">Identifiant</li>
335
+ <li data-tab="password">Mot de passe</li>
336
+ <li data-tab="mfa" hidden>Vérification</li>
337
+ </ol>
338
+ <form data-step="identifier">
339
+ <div class="nf-field">
340
+ <label for="nf-username">Identifiant ou adresse e-mail</label>
341
+ <div class="nf-input">
342
+ <svg class="nf-icon" aria-hidden="true"><use href="#i-user" /></svg>
343
+ <input id="nf-username" name="username" autocomplete="username" autocapitalize="none" spellcheck="false" required />
344
+ </div>
345
+ </div>
346
+ <button class="nf-btn primary" type="submit">Continuer<svg class="nf-icon" aria-hidden="true"><use href="#i-arrow" /></svg></button>
347
+ </form>
348
+ <form data-step="password" hidden>
349
+ <!-- Compte de l'étape précédente, masqué : le gestionnaire de mots de passe
350
+ associe le mot de passe à CE compte (HTML, jeton « username »). -->
351
+ <input name="username" autocomplete="username" hidden readonly tabindex="-1" />
352
+ <div class="nf-account">
353
+ <span class="nf-avatar" aria-hidden="true"></span><span class="who"></span>
354
+ <button type="button" class="nf-link" data-go="identifier">Changer</button>
355
+ </div>
356
+ <div class="nf-field">
357
+ <div class="nf-label-row"><label for="nf-password">Mot de passe</label></div>
358
+ <div class="nf-input">
359
+ <svg class="nf-icon" aria-hidden="true"><use href="#i-lock" /></svg>
360
+ <input id="nf-password" name="password" type="password" autocomplete="current-password" required />
361
+ <button type="button" class="nf-reveal" data-reveal aria-label="Afficher le mot de passe" aria-pressed="false"><svg class="nf-icon" aria-hidden="true"><use href="#i-eye" /></svg></button>
362
+ </div>
363
+ </div>
364
+ <button class="nf-btn primary" type="submit">Se connecter<svg class="nf-icon" aria-hidden="true"><use href="#i-arrow" /></svg></button>
365
+ </form>
366
+ <form data-step="mfa" hidden>
367
+ <div class="nf-field">
368
+ <label id="nf-otp-label">Code à 6 chiffres</label>
369
+ <div class="nf-otp" role="group" aria-labelledby="nf-otp-label">
370
+ <input inputmode="numeric" maxlength="1" autocomplete="one-time-code" aria-label="Chiffre 1" /><input inputmode="numeric" maxlength="1" aria-label="Chiffre 2" /><input inputmode="numeric" maxlength="1" aria-label="Chiffre 3" /><input inputmode="numeric" maxlength="1" aria-label="Chiffre 4" /><input inputmode="numeric" maxlength="1" aria-label="Chiffre 5" /><input inputmode="numeric" maxlength="1" aria-label="Chiffre 6" />
371
+ </div>
372
+ </div>
373
+ <p class="nf-hint">Le code affiché par votre application d'authentification.</p>
374
+ <button class="nf-btn primary" type="submit">Vérifier</button>
375
+ </form>
376
+ <% } %>
377
+ <% if (it.password || it.passkey) { %>
378
+ <div data-step="authenticated" hidden>
379
+ <div class="nf-done" role="status">
380
+ <span class="ring" aria-hidden="true"><svg class="nf-icon"><use href="#i-check" /></svg></span>
381
+ <strong>Session ouverte</strong><span class="dest"></span>
382
+ <span class="nf-progress" aria-hidden="true"><span></span></span>
383
+ </div>
384
+ </div>
385
+ <% } %>
386
+ <% if (it.providers.length > 0 || it.passkey) { %>
387
+ <div data-alt>
388
+ <% if (it.password) { %>
389
+ <div class="nf-or">ou</div>
390
+ <% } %>
391
+ <div class="nf-alt">
392
+ <% if (it.passkey) { %>
393
+ <button type="button" class="nf-btn span" data-passkey hidden><svg class="nf-icon" aria-hidden="true"><use href="#i-finger" /></svg>Passkey / empreinte digitale</button>
394
+ <% } %>
395
+ <% it.providers.forEach(function (provider) { %>
396
+ <a class="nf-btn" href="<%= provider.href %>"><% if (provider.image) { %><img class="nf-icon" src="<%= provider.image %>" alt="" /><% } else { %><svg class="<%= provider.icon === 'm-github' ? 'nf-mark' : 'nf-icon' %>" aria-hidden="true"><use href="#<%= provider.icon %>" /></svg><% } %><% if (provider.caption) { %><span class="nf-label"><%= provider.label %><small><%= provider.caption %></small></span><% } else { %><%= provider.label %><% } %></a>
397
+ <% }) %>
398
+ </div>
399
+ </div>
400
+ <% } %>
401
+ <div class="nf-message" aria-live="polite" data-message><% if (it.empty) { %><p class="nf-alert" role="alert"><span class="code">—</span><span>${LOGIN_PAGE_EMPTY_NOTICE}</span></p><% } else if (it.notice) { %><p class="nf-alert" role="alert"><span class="code">SSO</span><span><%= it.notice %></span></p><% } %></div>
402
+ <% if (it.password || it.passkey) { %>
403
+ <noscript><p class="nf-hint">Cette page a besoin de JavaScript pour vous connecter.</p></noscript>
404
+ <% } %>
405
+ </div>
406
+ </section>
407
+ <% if (it.picker) { %>
408
+ <form class="nf-picker" method="get" data-preview-picker<% if (it.picker.hook) { %> data-preview-hook="<%= it.picker.hook %>"<% } %> aria-label="Aperçu de la page (développement)">
409
+ <input type="hidden" name="from" value="<%= it.from %>" />
410
+ <label>Habillage <select name="skin"><% it.picker.skins.forEach(function (skin) { %><option value="<%= skin %>"<% if (skin === it.skin) { %> selected<% } %>><%= skin %></option><% }) %></select></label>
411
+ <label>Mise en page <select name="layout"><% it.picker.layouts.forEach(function (layout) { %><option value="<%= layout %>"<% if (layout === it.layout) { %> selected<% } %>><%= layout %></option><% }) %></select></label>
412
+ <noscript><button type="submit">Appliquer</button></noscript>
413
+ </form>
414
+ <% } %>
415
+ <% if (it.footer) { %>
416
+ <div class="nf-foot">
417
+ <ul class="nf-facts" aria-label="Protection de la session">
418
+ <li><svg class="nf-icon" aria-hidden="true"><use href="#i-shield" /></svg>cookie HttpOnly</li>
419
+ <li><svg class="nf-icon" aria-hidden="true"><use href="#i-shield" /></svg>anti-CSRF</li>
420
+ <li><svg class="nf-icon" aria-hidden="true"><use href="#i-shield" /></svg>CSP stricte</li>
421
+ </ul>
422
+ <a class="nf-powered" href="https://github.com/nodefony/nodefony-core" rel="noopener noreferrer"><img class="nf-logo nf-logo-mono" src="<%= it.logoHref %>" alt="" />Propulsé par Nodefony</a>
423
+ </div>
424
+ <% } %>
425
+ </main>
426
+ </div>
427
+ </body>
428
+ </html>
429
+ `;
430
+ export { LOGIN_PAGE_ASSET_FILES, LOGIN_PAGE_EMPTY_NOTICE, LOGIN_PAGE_TEMPLATE, ORGANIZATION_ACCOUNT_LABEL, buildLoginPageView, keepSuccess };
@@ -0,0 +1,35 @@
1
+ import { readSessionAuthentication } from "nodefony";
2
+ // nodefony/src/requireAuthentication.ts
3
+ /**
4
+ * Ce qui manque à une session pour satisfaire un `@RequireAuthentication` —
5
+ * fonction PURE : la session, l'exigence et l'heure lui sont données.
6
+ *
7
+ * Lit `authAt`/`amr` par `readSessionAuthentication` (contrat du cœur), jamais
8
+ * le sac de métadonnées à la main. Une session absente, ou qui ne porte pas ces
9
+ * métadonnées, est REFUSÉE : ne pas savoir n'est pas une preuve.
10
+ *
11
+ * @param session - la session de la requête, ou `null`.
12
+ * @param requirement - l'exigence figée de la route.
13
+ * @param now - l'heure de la décision, en millisecondes epoch.
14
+ * @returns la cause du refus en clair, ou `null` quand la session suffit.
15
+ */
16
+ function authenticationShortfall(session, requirement, now) {
17
+ const auth = readSessionAuthentication(session);
18
+ if (auth === null) return "la session de cette requête ne dit ni quand ni comment elle a été ouverte (aucune connexion, ou route hors d'une zone à session)";
19
+ if (requirement.maxAge !== null && now - auth.at > requirement.maxAge * 1e3) return `la connexion date de plus de ${requirement.maxAge} s`;
20
+ const missing = requirement.amr.filter((m) => !auth.amr.includes(m));
21
+ if (missing.length > 0) return `la connexion n'a pas employé ${missing.join(", ")} (employés : ${auth.amr.join(", ") || "aucun moyen déclaré"})`;
22
+ return null;
23
+ }
24
+ /**
25
+ * Le défi d'un refus « step-up » (RFC 9470 §3) : le schéma de session du
26
+ * pare-feu, l'erreur normée et l'âge maximal quand la route en fixe un.
27
+ * `error_description` reste en ASCII, sans guillemet (RFC 6750 §3).
28
+ *
29
+ * @param requirement - l'exigence de la route.
30
+ * @returns la valeur de `WWW-Authenticate`.
31
+ */
32
+ function stepUpChallenge(requirement) {
33
+ return "Session realm=\"nodefony\", error=\"insufficient_user_authentication\", error_description=\"A more recent or stronger authentication is required\"" + (requirement.maxAge === null ? "" : `, max_age="${requirement.maxAge}"`);
34
+ }
35
+ export { authenticationShortfall, stepUpChallenge };
@@ -0,0 +1,81 @@
1
+ import { BootConfigurationError, SchemaValidationError, isStandardSchema, toSchemaValidationIssues } from "nodefony";
2
+ // nodefony/src/routeSchema.ts
3
+ const ROUTE_PARTS = [
4
+ "params",
5
+ "query",
6
+ "body",
7
+ "headers"
8
+ ];
9
+ /** Les noms des parties, pour reconnaître une clé lue sur un objet inconnu. */
10
+ const ROUTE_PART_NAMES = new Set(ROUTE_PARTS);
11
+ /**
12
+ * Valide l'entrée d'une action contre son schéma — synchrone tant que chaque
13
+ * validateur l'est, asynchrone seulement si l'un d'eux rend une promesse.
14
+ *
15
+ * Les quatre parties sont toutes validées avant de conclure : un échec rend
16
+ * en une fois TOUTES les anomalies, chacune rattachée à sa partie. La valeur
17
+ * d'une partie validée est remplacée par la SORTIE de son schéma (valeurs par
18
+ * défaut, coercitions) ; la valeur reçue n'est ni copiée ni sérialisée — des
19
+ * octets décodés par le transport arrivent tels quels au validateur.
20
+ *
21
+ * @param schema - le schéma de la route.
22
+ * @param raw - l'entrée brute, mutée en place (objet propre à la requête).
23
+ * @returns l'entrée validée, ou sa promesse.
24
+ * @throws SchemaValidationError (statut 422) quand une partie est invalide —
25
+ * de façon synchrone, ou par la promesse rendue.
26
+ */
27
+ function validateRouteInput(schema, raw) {
28
+ let failures = null;
29
+ let pending = null;
30
+ for (let i = 0; i < ROUTE_PARTS.length; i++) {
31
+ const part = ROUTE_PARTS[i];
32
+ const partSchema = part === void 0 ? void 0 : schema[part];
33
+ if (part === void 0 || partSchema === void 0) continue;
34
+ const result = partSchema["~standard"].validate(raw[part]);
35
+ if (result instanceof Promise) {
36
+ pending ??= [];
37
+ pending.push(result.then((settled) => failures = applyPartResult(raw, part, i, settled, failures)));
38
+ } else failures = applyPartResult(raw, part, i, result, failures);
39
+ }
40
+ if (pending === null) return concludeRouteInput(raw, failures);
41
+ return Promise.all(pending).then(() => concludeRouteInput(raw, failures));
42
+ }
43
+ /**
44
+ * Applique le résultat d'une partie : sa sortie remplace la valeur brute, ou
45
+ * ses anomalies sont rangées à SON rang — l'erreur les rend dans l'ordre des
46
+ * parties, que leurs validateurs aient attendu ou non.
47
+ */
48
+ function applyPartResult(raw, part, rank, result, failures) {
49
+ if (result.issues === void 0) {
50
+ raw[part] = result.value;
51
+ return failures;
52
+ }
53
+ const ranked = failures ?? [];
54
+ ranked[rank] = toSchemaValidationIssues(result.issues, part);
55
+ return ranked;
56
+ }
57
+ /** Rend l'entrée validée, ou lève l'erreur unique qui porte toutes les anomalies. */
58
+ function concludeRouteInput(raw, failures) {
59
+ if (failures === null) return raw;
60
+ const issues = [];
61
+ for (const partIssues of failures) if (partIssues !== void 0) issues.push(...partIssues);
62
+ throw new SchemaValidationError(issues);
63
+ }
64
+ /**
65
+ * Refuse, au démarrage, un schéma de route mal formé : partie inconnue (une
66
+ * faute de frappe — `bodies` — serait sinon ignorée sans un mot), ou valeur
67
+ * qui n'implémente pas Standard Schema (un appelant JavaScript peut passer
68
+ * n'importe quoi).
69
+ *
70
+ * @param schema - le schéma déclaré.
71
+ * @param routeName - la route, nommée dans l'erreur.
72
+ * @throws BootConfigurationError sur la première faute trouvée.
73
+ */
74
+ function assertRouteSchemaShape(schema, routeName) {
75
+ if (typeof schema !== "object" || schema === null) throw new BootConfigurationError(`Route "${routeName}" : l'option « schema » doit être un objet { params, query, body, headers }.`);
76
+ for (const [part, value] of Object.entries(schema)) {
77
+ if (!ROUTE_PART_NAMES.has(part)) throw new BootConfigurationError(`Route "${routeName}" : partie de schéma inconnue « ${part} » (attendu : ${ROUTE_PARTS.join(", ")}).`);
78
+ if (value !== void 0 && !isStandardSchema(value)) throw new BootConfigurationError(`Route "${routeName}" : « schema.${part} » n'implémente pas Standard Schema (Zod, Valibot, ArkType…).`);
79
+ }
80
+ }
81
+ export { assertRouteSchemaShape, validateRouteInput };
@@ -1,6 +1,6 @@
1
1
  import { extractActionScopes } from "../decorators/routerDecorators.js";
2
2
  import router_default from "../service/router.js";
3
- //#region nodefony/src/scopeCatalog.ts
3
+ // nodefony/src/scopeCatalog.ts
4
4
  /**
5
5
  * P6.8 — **Découverte au boot** : scanne TOUTES les routes montées (`Router.routes`)
6
6
  * et agrège les scopes déclarés par `@RequireScope`, **regroupés par API** (préfixe
@@ -36,5 +36,4 @@ function collectDeclaredApiScopes() {
36
36
  scopes: [...byApi.get(api) ?? []].sort()
37
37
  }));
38
38
  }
39
- //#endregion
40
39
  export { collectDeclaredApiScopes, collectDeclaredApiScopes as default };
@@ -1,4 +1,4 @@
1
- //#region nodefony/src/singletonGuard.ts
1
+ // nodefony/src/singletonGuard.ts
2
2
  /**
3
3
  * Filets d'un contrôleur **singleton** — le défaut : UNE instance par classe,
4
4
  * partagée par toutes les requêtes, concurrentes comprises.
@@ -72,5 +72,4 @@ function guardSingletonState(controller, name) {
72
72
  }
73
73
  Object.preventExtensions(controller);
74
74
  }
75
- //#endregion
76
75
  export { guardSingletonState, isDevelopment, singletonWriteMessage };
@@ -1,5 +1,5 @@
1
1
  import { FLOW_STEPS, SEVERITY_NAMES } from "nodefony";
2
- //#region nodefony/src/syslogFilters.ts
2
+ // nodefony/src/syslogFilters.ts
3
3
  /**
4
4
  * Ce que le journal sait TRIER — un seul axe, le temps.
5
5
  *
@@ -47,5 +47,4 @@ const SYSLOG_FILTERS = {
47
47
  /** Borne haute d'horodatage (epoch ms, incluse). */
48
48
  to: "int"
49
49
  };
50
- //#endregion
51
50
  export { SYSLOG_FILTERS, SYSLOG_SORTABLE };
@@ -1,7 +1,7 @@
1
1
  import { Kernel, Module } from "nodefony";
2
2
  import type { IFrameworkConfigInput, IFrameworkConfig } from "./nodefony/config/config.js";
3
3
  import { getIdempotencyStoreFactory, registerIdempotencyStore, listIdempotencyStores } from "./nodefony/src/idempotencyStoreRegistry.js";
4
- import Router from "./nodefony/service/router.js";
4
+ import Router, { FRAMEWORK_ROUTER, isFrameworkRouter } from "./nodefony/service/router.js";
5
5
  import Route from "./nodefony/src/Route.js";
6
6
  import Controller from "./nodefony/src/Controller.js";
7
7
  import ResourceController from "./nodefony/src/ResourceController.js";
@@ -10,6 +10,7 @@ import AdminBroker from "./nodefony/service/AdminBroker.js";
10
10
  import MemoryIdempotencyStore from "./nodefony/service/IdempotencyStore.js";
11
11
  import AdminApiController from "./nodefony/controller/AdminApiController.js";
12
12
  import SessionAuthController, { mountSessionAuthRoutes } from "./nodefony/controller/SessionAuthController.js";
13
+ import LoginPageController, { mountLoginPage } from "./nodefony/controller/LoginPageController.js";
13
14
  import TokenAuthController, { mountTokenAuthRoutes } from "./nodefony/controller/TokenAuthController.js";
14
15
  import IssuerMetadataController, { mountIssuerMetadataRoutes } from "./nodefony/controller/IssuerMetadataController.js";
15
16
  import ProtectedResourceMetadataController, { mountProtectedResourceRoutes, protectedResourceRoutePaths, collectProtectedResources } from "./nodefony/controller/ProtectedResourceMetadataController.js";
@@ -23,9 +24,10 @@ import { createFrameworkAdminApi } from "./nodefony/src/FrameworkAdminApi.js";
23
24
  import { buildPlaygroundSnapshot } from "./nodefony/src/PlaygroundAdminApi.js";
24
25
  import { createSyslogAdminApi } from "./nodefony/src/SyslogAdminApi.js";
25
26
  import Eta from "./nodefony/service/Eta.js";
26
- import { mergeResolvers, mergeTypeDefs } from "@graphql-tools/merge";
27
- import { mergeSchemas, makeExecutableSchema } from "@graphql-tools/schema";
28
- import { controllers, route, controller, Get, Post, Put, Delete, Patch, Options, Head, All, Domain, BypassFirewall, IsGranted, RequireScope, Anonymous, Csp, CsrfProtect, CsrfExempt, Idempotent, CurrentUser, Scope, UseSession, HttpCode, Header, Redirect, Param, Body, Query, Headers, Cookie, Session, Req, Res, UploadedFile, UploadedFiles, routeExpectsBodyStream } from "./nodefony/decorators/routerDecorators.js";
27
+ import { controllers, route, controller, Get, Post, Put, Delete, Patch, Options, Head, All, Domain, BypassFirewall, IsGranted, RequireScope, RequireAuthentication, Anonymous, Csp, CsrfProtect, CsrfExempt, Idempotent, CurrentUser, Scope, UseSession, HttpCode, Header, Redirect, Param, Body, Query, Headers, Cookie, Session, Req, Res, UploadedFile, UploadedFiles, routeExpectsBodyStream, buildContextParamArgs, getParamArgsMeta } from "./nodefony/decorators/routerDecorators.js";
28
+ export type { IHttpMethodDecorator } from "./nodefony/decorators/routerDecorators.js";
29
+ export { validateRouteInput } from "./nodefony/src/routeSchema.js";
30
+ export type { IRouteSchema, RouteInput, RoutePart, RawRouteInput, RawRouteParams, RawRouteQuery, RawRouteHeaders, } from "./nodefony/src/routeSchema.js";
29
31
  declare module "nodefony" {
30
32
  interface NodefonyModuleConfig {
31
33
  "@nodefony/framework": IFrameworkConfigInput;
@@ -76,17 +78,11 @@ declare class Framework extends Module<IFrameworkConfig> {
76
78
  */
77
79
  onKernelReady(): Promise<this>;
78
80
  }
79
- declare const graphql: {
80
- mergeSchemas: typeof mergeSchemas;
81
- makeExecutableSchema: typeof makeExecutableSchema;
82
- mergeResolvers: typeof mergeResolvers;
83
- mergeTypeDefs: typeof mergeTypeDefs;
84
- };
85
81
  export default Framework;
86
- export { Controller, ResourceController, Route, Router, Resolver, AdminBroker, MemoryIdempotencyStore, AdminApiController, SessionAuthController, mountSessionAuthRoutes, TokenAuthController, mountTokenAuthRoutes, IssuerMetadataController, mountIssuerMetadataRoutes, ProtectedResourceMetadataController, mountProtectedResourceRoutes, protectedResourceRoutePaths, collectProtectedResources, WebAuthnController, mountWebAuthnRoutes, OAuth2Controller, mountOAuth2Routes, BenchController, mountBenchRoutes, ApiKeyController, mountApiKeyRoutes, TotpController, mountTotpRoutes, createKernelAdminApi, createFrameworkAdminApi, createSyslogAdminApi, buildPlaygroundSnapshot, Eta, route, controller, controllers, Get, Post, Put, Delete, Patch, Options, Head, All, Domain, BypassFirewall, IsGranted, RequireScope, Anonymous, Csp, CsrfProtect, CsrfExempt, Idempotent, CurrentUser, Scope, UseSession, HttpCode, Header, Redirect, Param, Body, Query, Headers, Cookie, Session, Req, Res, UploadedFile, UploadedFiles, routeExpectsBodyStream, graphql, registerIdempotencyStore, getIdempotencyStoreFactory, listIdempotencyStores, };
82
+ export { Controller, ResourceController, Route, Router, FRAMEWORK_ROUTER, isFrameworkRouter, Resolver, AdminBroker, MemoryIdempotencyStore, AdminApiController, SessionAuthController, mountSessionAuthRoutes, LoginPageController, mountLoginPage, TokenAuthController, mountTokenAuthRoutes, IssuerMetadataController, mountIssuerMetadataRoutes, ProtectedResourceMetadataController, mountProtectedResourceRoutes, protectedResourceRoutePaths, collectProtectedResources, WebAuthnController, mountWebAuthnRoutes, OAuth2Controller, mountOAuth2Routes, BenchController, mountBenchRoutes, ApiKeyController, mountApiKeyRoutes, TotpController, mountTotpRoutes, createKernelAdminApi, createFrameworkAdminApi, createSyslogAdminApi, buildPlaygroundSnapshot, Eta, route, controller, controllers, Get, Post, Put, Delete, Patch, Options, Head, All, Domain, BypassFirewall, IsGranted, RequireScope, RequireAuthentication, Anonymous, Csp, CsrfProtect, CsrfExempt, Idempotent, CurrentUser, Scope, UseSession, HttpCode, Header, Redirect, Param, Body, Query, Headers, Cookie, Session, Req, Res, UploadedFile, UploadedFiles, routeExpectsBodyStream, buildContextParamArgs, getParamArgsMeta, registerIdempotencyStore, getIdempotencyStoreFactory, listIdempotencyStores, };
87
83
  export { frameworkConfigSchema } from "./nodefony/config/config.js";
88
84
  export type { IdempotencyStoreFactory, IIdempotencyStoreFactoryContext, } from "./nodefony/src/idempotencyStoreRegistry.js";
89
- export type { SecurityClause, SecurityRequirement, CspDirectives, } from "./nodefony/decorators/routerDecorators.js";
85
+ export type { SecurityClause, SecurityRequirement, IAuthenticationRequirement, IRequireAuthenticationOptions, CspDirectives, IParamArgSource, ParamMeta, } from "./nodefony/decorators/routerDecorators.js";
90
86
  export type { ControllerScope } from "./nodefony/src/Controller.js";
91
87
  export type { FrameworkAdminApiOptions } from "./nodefony/src/FrameworkAdminApi.js";
92
88
  export type { PlaygroundAction, PlaygroundController, PlaygroundGuards, PlaygroundParam, } from "./nodefony/src/PlaygroundAdminApi.js";