@nodefony/framework 10.0.0-beta.3 → 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 (65) hide show
  1. package/README.md +4 -0
  2. package/dist/_virtual/{_@oxc-project_runtime@0.153.0 → _@oxc-project_runtime@0.155.0}/helpers/esm/decorate.js +1 -2
  3. package/dist/index.js +9 -18
  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 +38 -7
  7. package/dist/nodefony/controller/ApiKeyController.js +16 -8
  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 +4 -11
  11. package/dist/nodefony/controller/OAuth2Controller.js +3 -4
  12. package/dist/nodefony/controller/ProtectedResourceMetadataController.js +1 -2
  13. package/dist/nodefony/controller/SessionAuthController.js +13 -4
  14. package/dist/nodefony/controller/TokenAuthController.js +3 -4
  15. package/dist/nodefony/controller/TotpController.js +2 -3
  16. package/dist/nodefony/controller/WebAuthnController.js +2 -3
  17. package/dist/nodefony/controller/oauthAuthority.js +1 -2
  18. package/dist/nodefony/decorators/routerDecorators.js +155 -53
  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 +49 -15
  23. package/dist/nodefony/src/Controller.js +22 -39
  24. package/dist/nodefony/src/FrameworkAdminApi.js +31 -10
  25. package/dist/nodefony/src/KernelAdminApi.js +7 -9
  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 +66 -34
  29. package/dist/nodefony/src/ResourceController.js +2 -3
  30. package/dist/nodefony/src/Route.js +2 -3
  31. package/dist/nodefony/src/SecurityApiController.js +1 -2
  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 +1 -2
  36. package/dist/nodefony/src/idempotency.js +1 -2
  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 +2 -3
  40. package/dist/nodefony/src/requireAuthentication.js +1 -2
  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 +5 -10
  46. package/dist/types/nodefony/controller/AdminApiController.d.ts +12 -0
  47. package/dist/types/nodefony/controller/SessionAuthController.d.ts +7 -0
  48. package/dist/types/nodefony/decorators/routerDecorators.d.ts +133 -32
  49. package/dist/types/nodefony/interfaces/IController.d.ts +2 -3
  50. package/dist/types/nodefony/service/router.d.ts +27 -1
  51. package/dist/types/nodefony/src/Controller.d.ts +14 -16
  52. package/dist/types/nodefony/src/PlaygroundAdminApi.d.ts +12 -0
  53. package/dist/types/nodefony/src/Resolver.d.ts +25 -1
  54. package/dist/types/nodefony/src/ResourceController.d.ts +1 -1
  55. package/dist/types/nodefony/src/Route.d.ts +3 -0
  56. package/dist/types/nodefony/src/loginPage.d.ts +1 -1
  57. package/dist/types/nodefony/src/routeSchema.d.ts +112 -0
  58. package/docs/admin.md +80 -80
  59. package/docs/controller.md +108 -109
  60. package/docs/decorateurs.md +208 -161
  61. package/docs/idempotence.md +133 -133
  62. package/docs/routing.md +90 -89
  63. package/docs/templates.md +55 -55
  64. package/package.json +8 -11
  65. package/dist/_virtual/_@oxc-project_runtime@0.153.0/helpers/esm/decorateMetadata.js +0 -6
package/README.md CHANGED
@@ -18,6 +18,10 @@ sert **HTTP et WebSocket dans le même contexte**, nativement.
18
18
  instance par requête (par connexion en WebSocket). `RealtimeController` est par connexion.
19
19
  - **Décorateurs** : `@controller`, `@route`, `@Get/@Post/...`, `@Param/@Body/@Query/@Headers`,
20
20
  `@Domain`, etc.
21
+ - **Routes à schéma** : `@Post("/posts", { schema: { body } })` — l'entrée est validée avant
22
+ l'action (422 qui nomme chaque champ, sur HTTP comme par la socket) et reçue typée en un seul
23
+ argument, `input: RouteInput<S>`. Zod, Valibot, ArkType : tout Standard Schema. Détail :
24
+ [`docs/decorateurs.md`](docs/decorateurs.md).
21
25
  - **Data plane admin** : `AdminApiController` + `IAdminBroker` (surface `/nodefony/<module>/api/*`).
22
26
 
23
27
  ## Exemple
@@ -1,9 +1,8 @@
1
- //#region \0@oxc-project+runtime@0.153.0/helpers/esm/decorate.js
1
+ // \0@oxc-project+runtime@0.155.0/helpers/esm/decorate.js
2
2
  function __decorate(decorators, target, key, desc) {
3
3
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
4
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
5
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
6
  return c > 3 && r && Object.defineProperty(target, key, r), r;
7
7
  }
8
- //#endregion
9
8
  export { __decorate as default };
package/dist/index.js CHANGED
@@ -4,12 +4,12 @@ import { getIdempotencyStoreFactory, listIdempotencyBackends, listIdempotencySto
4
4
  import { RedisIdempotencyStore } from "./nodefony/src/RedisIdempotencyStore.js";
5
5
  import { scheduleIdempotencyGc } from "./nodefony/src/idempotencyGc.js";
6
6
  import Route from "./nodefony/src/Route.js";
7
+ import { validateRouteInput } from "./nodefony/src/routeSchema.js";
7
8
  import Controller from "./nodefony/src/Controller.js";
8
9
  import { All, Anonymous, Body, BypassFirewall, Cookie, Csp, CsrfExempt, CsrfProtect, CurrentUser, Delete, Domain, Get, Head, Header, Headers, HttpCode, Idempotent, IsGranted, Options, Param, Patch, Post, Put, Query, Redirect, Req, RequireAuthentication, RequireScope, Res, Scope, Session, UploadedFile, UploadedFiles, UseSession, buildContextParamArgs, controller, controllers, getParamArgsMeta, route, routeExpectsBodyStream } from "./nodefony/decorators/routerDecorators.js";
9
10
  import Resolver from "./nodefony/src/Resolver.js";
10
- import __decorateMetadata from "./_virtual/_@oxc-project_runtime@0.153.0/helpers/esm/decorateMetadata.js";
11
- import __decorate from "./_virtual/_@oxc-project_runtime@0.153.0/helpers/esm/decorate.js";
12
- import router_default from "./nodefony/service/router.js";
11
+ import __decorate from "./_virtual/_@oxc-project_runtime@0.155.0/helpers/esm/decorate.js";
12
+ import router_default, { FRAMEWORK_ROUTER, isFrameworkRouter } from "./nodefony/service/router.js";
13
13
  import ResourceController from "./nodefony/src/ResourceController.js";
14
14
  import AdminApiController from "./nodefony/controller/AdminApiController.js";
15
15
  import AdminBroker_default from "./nodefony/service/AdminBroker.js";
@@ -30,10 +30,8 @@ import { createFrameworkAdminApi } from "./nodefony/src/FrameworkAdminApi.js";
30
30
  import { createSyslogAdminApi } from "./nodefony/src/SyslogAdminApi.js";
31
31
  import Eta from "./nodefony/service/Eta.js";
32
32
  import path from "node:path";
33
- import { AUTO_STORE, EMPTY_INFRA, Kernel, Module, durableStoreRemedy, readStoreConnector, readStoreLocation, resolveAutoStore, runNeedsExternalServices, services } from "nodefony";
34
- import { mergeResolvers, mergeTypeDefs } from "@graphql-tools/merge";
35
- import { makeExecutableSchema, mergeSchemas } from "@graphql-tools/schema";
36
- //#region index.ts
33
+ import { AUTO_STORE, EMPTY_INFRA, Module, durableStoreRemedy, readStoreConnector, readStoreLocation, resolveAutoStore, services, storeConnectivity } from "nodefony";
34
+ // index.ts
37
35
  registerIdempotencyStore("redis", (ctx) => {
38
36
  const redis = ctx.module.kernel?.container?.get("redis");
39
37
  if (!redis) throw new Error("the @nodefony/redis module is not loaded (add use(\"@nodefony/redis\") in nodefony.config.ts)");
@@ -92,13 +90,13 @@ let Framework = class Framework extends Module {
92
90
  let name = configured;
93
91
  let reason = `store explicitement configuré ("${configured}")`;
94
92
  if (name === AUTO_STORE) {
95
- const auto = resolveAutoStore("ephemeral", this.kernel?.infra ?? EMPTY_INFRA, listIdempotencyBackends(), "memory", runNeedsExternalServices(this.kernel));
93
+ const auto = resolveAutoStore("ephemeral", this.kernel?.infra ?? EMPTY_INFRA, listIdempotencyBackends(), "memory", storeConnectivity(this.kernel));
96
94
  name = auto.store;
97
95
  reason = auto.reason;
98
96
  this.log(`idempotency.store "auto" → "${name}" (${auto.reason})`, "INFO");
99
97
  }
100
98
  if (name === "memory") {
101
- if (this.kernel?.environment === "production") this.log("idempotency.store \"memory\" en PRODUCTION — déduplication per-pod uniquement : un rejeu routé vers un autre pod n'est pas dédupliqué (double-effet possible). " + durableStoreRemedy(runNeedsExternalServices(this.kernel)), "WARNING");
99
+ if (this.kernel?.environment === "production") this.log("idempotency.store \"memory\" en PRODUCTION — déduplication per-pod uniquement : un rejeu routé vers un autre pod n'est pas dédupliqué (double-effet possible). " + durableStoreRemedy(storeConnectivity(this.kernel)), "WARNING");
102
100
  this.kernel?.registerStoreResolution({
103
101
  brick: "idempotency",
104
102
  nature: "ephemeral",
@@ -204,13 +202,6 @@ Framework = __decorate([services([
204
202
  Eta,
205
203
  AdminBroker_default,
206
204
  IdempotencyStore_default
207
- ]), __decorateMetadata("design:paramtypes", [typeof Kernel === "undefined" ? Object : Kernel])], Framework);
208
- const graphql = {
209
- mergeSchemas,
210
- makeExecutableSchema,
211
- mergeResolvers,
212
- mergeTypeDefs
213
- };
205
+ ])], Framework);
214
206
  var framework_default = Framework;
215
- //#endregion
216
- export { AdminApiController, AdminBroker_default as AdminBroker, All, Anonymous, ApiKeyController, BenchController, Body, BypassFirewall, Controller, Cookie, Csp, CsrfExempt, CsrfProtect, CurrentUser, Delete, Domain, Eta, Get, Head, Header, Headers, HttpCode, Idempotent, IsGranted, IssuerMetadataController, LoginPageController, IdempotencyStore_default as MemoryIdempotencyStore, OAuth2Controller, Options, Param, Patch, Post, ProtectedResourceMetadataController, Put, Query, Redirect, Req, RequireAuthentication, RequireScope, Res, Resolver, ResourceController, Route, router_default as Router, Scope, Session, SessionAuthController, TokenAuthController, TotpController, UploadedFile, UploadedFiles, UseSession, WebAuthnController, buildContextParamArgs, buildPlaygroundSnapshot, collectProtectedResources, controller, controllers, createFrameworkAdminApi, createKernelAdminApi, createSyslogAdminApi, framework_default as default, defineFrameworkConfig, frameworkConfigJsonSchema, frameworkConfigSchema, getIdempotencyStoreFactory, getParamArgsMeta, graphql, listIdempotencyStores, mountApiKeyRoutes, mountBenchRoutes, mountIssuerMetadataRoutes, mountLoginPage, mountOAuth2Routes, mountProtectedResourceRoutes, mountSessionAuthRoutes, mountTokenAuthRoutes, mountTotpRoutes, mountWebAuthnRoutes, protectedResourceRoutePaths, registerIdempotencyStore, route, routeExpectsBodyStream };
207
+ export { AdminApiController, AdminBroker_default as AdminBroker, All, Anonymous, ApiKeyController, BenchController, Body, BypassFirewall, Controller, Cookie, Csp, CsrfExempt, CsrfProtect, CurrentUser, Delete, Domain, Eta, FRAMEWORK_ROUTER, Get, Head, Header, Headers, HttpCode, Idempotent, IsGranted, IssuerMetadataController, LoginPageController, IdempotencyStore_default as MemoryIdempotencyStore, OAuth2Controller, Options, Param, Patch, Post, ProtectedResourceMetadataController, Put, Query, Redirect, Req, RequireAuthentication, RequireScope, Res, Resolver, ResourceController, Route, router_default as Router, Scope, Session, SessionAuthController, TokenAuthController, TotpController, UploadedFile, UploadedFiles, UseSession, WebAuthnController, buildContextParamArgs, buildPlaygroundSnapshot, collectProtectedResources, controller, controllers, createFrameworkAdminApi, createKernelAdminApi, createSyslogAdminApi, framework_default as default, defineFrameworkConfig, frameworkConfigJsonSchema, frameworkConfigSchema, getIdempotencyStoreFactory, getParamArgsMeta, isFrameworkRouter, listIdempotencyStores, mountApiKeyRoutes, mountBenchRoutes, mountIssuerMetadataRoutes, mountLoginPage, mountOAuth2Routes, mountProtectedResourceRoutes, mountSessionAuthRoutes, mountTokenAuthRoutes, mountTotpRoutes, mountWebAuthnRoutes, protectedResourceRoutePaths, registerIdempotencyStore, route, routeExpectsBodyStream, validateRouteInput };
@@ -1,5 +1,5 @@
1
1
  import { z } from "zod";
2
- //#region nodefony/config/config.ts
2
+ // nodefony/config/config.ts
3
3
  /**
4
4
  * @nodefony/framework — CONFIGURATION DU MODULE (schéma Zod = source unique).
5
5
  *
@@ -58,5 +58,4 @@ var config_default = {
58
58
  }
59
59
  } }
60
60
  };
61
- //#endregion
62
61
  export { config_default as default, frameworkConfigSchema };
@@ -1,7 +1,7 @@
1
1
  import { frameworkConfigSchema } from "./config.js";
2
2
  import { parseModuleConfig } from "nodefony";
3
3
  import { z } from "zod";
4
- //#region nodefony/config/defineModuleConfig.ts
4
+ // nodefony/config/defineModuleConfig.ts
5
5
  /**
6
6
  * Builder type-safe de la configuration de `@nodefony/framework` (PUR — ne
7
7
  * retape JAMAIS un défaut : source unique = `./config.ts`).
@@ -32,5 +32,4 @@ function defineFrameworkConfig(config = {}) {
32
32
  function frameworkConfigJsonSchema() {
33
33
  return z.toJSONSchema(frameworkConfigSchema);
34
34
  }
35
- //#endregion
36
35
  export { defineFrameworkConfig, frameworkConfigJsonSchema };
@@ -1,7 +1,39 @@
1
1
  import Controller from "../src/Controller.js";
2
2
  import { computeFingerprint, evaluateIdempotency, resolveIdempotencyKey, resolveIdentity } from "../src/idempotency.js";
3
3
  import { JsonRpcServerError, RequestContext, RpcError, executeAdminEndpoint } from "nodefony";
4
- //#region nodefony/controller/AdminApiController.ts
4
+ // nodefony/controller/AdminApiController.ts
5
+ /** Vrai pour une chaîne ou un tableau de chaînes — ce que porte {@link IAdminRequest.query}. */
6
+ function isAdminQueryValue(value) {
7
+ if (typeof value === "string") return true;
8
+ if (!Array.isArray(value)) return false;
9
+ for (const item of value) if (typeof item !== "string") return false;
10
+ return true;
11
+ }
12
+ /**
13
+ * Ramène les paramètres d'URL au contrat {@link IAdminRequest.query} : seules
14
+ * les chaînes et les tableaux de chaînes passent. `qs` produit des objets pour
15
+ * `a[b]=1` ; les laisser passer sous un type qui les nie tromperait chaque
16
+ * producteur du plan d'administration.
17
+ *
18
+ * @param source - paramètres d'URL de la requête (`Controller.query`)
19
+ * @returns `source` lui-même quand il respecte déjà le contrat (aucune
20
+ * allocation), sinon une copie sans les valeurs imbriquées
21
+ */
22
+ function toAdminQuery(source) {
23
+ if (source === void 0) return {};
24
+ let conforms = true;
25
+ for (const key of Object.keys(source)) if (!isAdminQueryValue(source[key])) {
26
+ conforms = false;
27
+ break;
28
+ }
29
+ if (conforms) return source;
30
+ const kept = [];
31
+ for (const key of Object.keys(source)) {
32
+ const value = source[key];
33
+ if (isAdminQueryValue(value)) kept.push([key, value]);
34
+ }
35
+ return Object.fromEntries(kept);
36
+ }
5
37
  /**
6
38
  * Controller pont unique du data plane admin (Studio).
7
39
  *
@@ -43,7 +75,7 @@ var AdminApiController = class AdminApiController extends Controller {
43
75
  */
44
76
  async dispatch(...args) {
45
77
  const { status, headers, body } = await this.runAdmin(args);
46
- if (this.context?.type.startsWith("websocket")) {
78
+ if (this.context?.type === "websocket") {
47
79
  if (status >= 400) {
48
80
  const message = body?.error ?? "admin error";
49
81
  throw new RpcError(message, JsonRpcServerError.DEFAULT, {
@@ -114,7 +146,7 @@ var AdminApiController = class AdminApiController extends Controller {
114
146
  request.params,
115
147
  request.body ?? null
116
148
  ]),
117
- isWs: Boolean((this.context?.type)?.startsWith("websocket")),
149
+ isWs: this.context?.type === "websocket",
118
150
  required: false
119
151
  });
120
152
  switch (verdict.kind) {
@@ -143,10 +175,10 @@ var AdminApiController = class AdminApiController extends Controller {
143
175
  }
144
176
  const als = RequestContext.get();
145
177
  const user = als?.user ?? null;
146
- const post = this.queryPost;
178
+ const post = this.body;
147
179
  return {
148
180
  params,
149
- query: this.query ?? {},
181
+ query: toAdminQuery(this.query),
150
182
  body: als?.body !== void 0 ? als.body : post ?? null,
151
183
  user,
152
184
  roles: this.extractRoles(user),
@@ -183,5 +215,4 @@ var AdminApiController = class AdminApiController extends Controller {
183
215
  return [...walker.reachableRoles(roles)];
184
216
  }
185
217
  };
186
- //#endregion
187
- export { AdminApiController as default };
218
+ export { AdminApiController as default, toAdminQuery };
@@ -1,7 +1,7 @@
1
1
  import router_default from "../service/router.js";
2
2
  import SecurityApiController from "../src/SecurityApiController.js";
3
3
  import { collectDeclaredApiScopes } from "../src/scopeCatalog.js";
4
- //#region nodefony/controller/ApiKeyController.ts
4
+ // nodefony/controller/ApiKeyController.ts
5
5
  let mounted = false;
6
6
  /**
7
7
  * Endpoints HTTP de **gestion des clés API personnelles (PAT, P6.12)** — console
@@ -32,15 +32,15 @@ var ApiKeyController = class extends SecurityApiController {
32
32
  async create() {
33
33
  const svc = this.#manager();
34
34
  if (!svc || !svc.isEnabled()) return this.renderJson({ error: "API keys unavailable" }, 503);
35
- const subject = await this.#currentSubject();
36
- if (subject === null) return this.renderJson({ error: "Unauthorized" }, 401);
37
- const body = this.queryPost ?? {};
35
+ const holder = await this.#currentHolder();
36
+ if (holder === null) return this.renderJson({ error: "Unauthorized" }, 401);
37
+ const body = this.body ?? {};
38
38
  try {
39
- const created = await svc.createForSubject(subject, "user", {
39
+ const created = await svc.createForSubject(holder.identifier, "user", {
40
40
  name: body.name,
41
41
  scopes: body.scopes,
42
42
  expiresInDays: body.expiresInDays
43
- });
43
+ }, holder.roles);
44
44
  return await this.renderJson(created, 201);
45
45
  } catch (e) {
46
46
  return this.#renderApiKeyError(e);
@@ -86,10 +86,18 @@ var ApiKeyController = class extends SecurityApiController {
86
86
  }
87
87
  /** Identifiant du porteur courant (session BFF revalidée), ou `null` si non authentifié. */
88
88
  async #currentSubject() {
89
+ return (await this.#currentHolder())?.identifier ?? null;
90
+ }
91
+ async #currentHolder() {
89
92
  const flow = this.#flow();
90
93
  if (!flow) return null;
91
94
  const me = await flow.me(this.context);
92
- return me && typeof me.identifier === "string" ? me.identifier : null;
95
+ if (!me || typeof me.identifier !== "string") return null;
96
+ const roles = Array.isArray(me.roles) ? me.roles.filter((r) => typeof r === "string") : [];
97
+ return {
98
+ identifier: me.identifier,
99
+ roles
100
+ };
93
101
  }
94
102
  #renderApiKeyError(e) {
95
103
  const code = e.code;
@@ -97,6 +105,7 @@ var ApiKeyController = class extends SecurityApiController {
97
105
  const message = e.message;
98
106
  return this.renderJson({ error: typeof message === "string" ? message : "Bad request" }, 400);
99
107
  }
108
+ if (code === 403) return this.renderJson({ error: e instanceof Error ? e.message : "Forbidden" }, 403);
100
109
  if (code === 409) return this.renderJson({ error: "API key limit reached" }, 409);
101
110
  if (code === 503) return this.renderJson({ error: "API keys unavailable" }, 503);
102
111
  throw e;
@@ -149,5 +158,4 @@ function mountApiKeyRoutes(frameworkModule) {
149
158
  if (!Object.prototype.hasOwnProperty.call(ApiKeyController.prototype, "module")) router_default.setController(ApiKeyController, frameworkModule);
150
159
  mounted = true;
151
160
  }
152
- //#endregion
153
161
  export { ApiKeyController as default, mountApiKeyRoutes };
@@ -1,6 +1,6 @@
1
1
  import Controller from "../src/Controller.js";
2
2
  import router_default from "../service/router.js";
3
- //#region nodefony/controller/BenchController.ts
3
+ // nodefony/controller/BenchController.ts
4
4
  /**
5
5
  * Corps servi par la cible de banc — **gelé et partagé** : ce qu'on chronomètre
6
6
  * est le pipeline, pas la construction d'un objet. Le figer évite qu'une
@@ -128,5 +128,4 @@ function mountBenchRoutes(frameworkModule) {
128
128
  if (!Object.prototype.hasOwnProperty.call(BenchController.prototype, "module")) router_default.setController(BenchController, frameworkModule);
129
129
  mounted = true;
130
130
  }
131
- //#endregion
132
131
  export { BenchController as default, mountBenchRoutes };
@@ -2,7 +2,7 @@ import Controller from "../src/Controller.js";
2
2
  import router_default from "../service/router.js";
3
3
  import { askedAuthority, onDeclaredAuthority } from "./oauthAuthority.js";
4
4
  import { JWKS_PATH, authorizationServerMetadataPath, buildAuthorizationServerMetadata } from "nodefony";
5
- //#region nodefony/controller/IssuerMetadataController.ts
5
+ // nodefony/controller/IssuerMetadataController.ts
6
6
  let mounted = false;
7
7
  /**
8
8
  * Les deux documents qui rendent une application Nodefony **découvrable** comme
@@ -144,5 +144,4 @@ function mountIssuerMetadataRoutes(frameworkModule, issuer) {
144
144
  if (!Object.prototype.hasOwnProperty.call(IssuerMetadataController.prototype, "module")) router_default.setController(IssuerMetadataController, frameworkModule);
145
145
  mounted = true;
146
146
  }
147
- //#endregion
148
147
  export { IssuerMetadataController as default, mountIssuerMetadataRoutes };
@@ -1,8 +1,7 @@
1
1
  import { isDevelopment } from "../src/singletonGuard.js";
2
2
  import Controller from "../src/Controller.js";
3
3
  import { Csp } from "../decorators/routerDecorators.js";
4
- import __decorateMetadata from "../../_virtual/_@oxc-project_runtime@0.153.0/helpers/esm/decorateMetadata.js";
5
- import __decorate from "../../_virtual/_@oxc-project_runtime@0.153.0/helpers/esm/decorate.js";
4
+ import __decorate from "../../_virtual/_@oxc-project_runtime@0.155.0/helpers/esm/decorate.js";
6
5
  import router_default from "../service/router.js";
7
6
  import { LOGIN_PAGE_ASSET_FILES, LOGIN_PAGE_TEMPLATE, buildLoginPageView, keepSuccess } from "../src/loginPage.js";
8
7
  import path from "node:path";
@@ -11,7 +10,7 @@ import { createHash } from "node:crypto";
11
10
  import { readFile } from "node:fs/promises";
12
11
  import { fileURLToPath } from "node:url";
13
12
  import { Eta } from "eta";
14
- //#region nodefony/controller/LoginPageController.ts
13
+ // nodefony/controller/LoginPageController.ts
15
14
  const NO_STORE = { "cache-control": "no-store" };
16
15
  let mounted = false;
17
16
  let engine = null;
@@ -69,7 +68,7 @@ var LoginPageController = class extends Controller {
69
68
  async page() {
70
69
  const page = this.get("authFlow")?.describeLoginPage?.() ?? null;
71
70
  if (page === null) return this.renderJson({ error: "Not Found" }, 404, NO_STORE);
72
- const query = this.queryGet;
71
+ const query = this.query;
73
72
  const view = buildLoginPageView(page, {
74
73
  from: query?.from,
75
74
  reason: query?.reason,
@@ -88,12 +87,7 @@ var LoginPageController = class extends Controller {
88
87
  });
89
88
  }
90
89
  };
91
- __decorate([
92
- Csp({ "frame-ancestors": ["'none'"] }),
93
- __decorateMetadata("design:type", Function),
94
- __decorateMetadata("design:paramtypes", []),
95
- __decorateMetadata("design:returntype", Promise)
96
- ], LoginPageController.prototype, "page", null);
90
+ __decorate([Csp({ "frame-ancestors": ["'none'"] })], LoginPageController.prototype, "page", null);
97
91
  /**
98
92
  * Monte la page de connexion par défaut quand security la décrit : la route
99
93
  * sur `loginPage.path`, et ses fichiers sous `LOGIN_PAGE_ASSETS_BASE`.
@@ -120,5 +114,4 @@ function mountLoginPage(frameworkModule) {
120
114
  if (!Object.prototype.hasOwnProperty.call(LoginPageController.prototype, "module")) router_default.setController(LoginPageController, frameworkModule);
121
115
  mounted = true;
122
116
  }
123
- //#endregion
124
117
  export { LoginPageController as default, mountLoginPage };
@@ -1,7 +1,7 @@
1
1
  import router_default from "../service/router.js";
2
2
  import SecurityApiController from "../src/SecurityApiController.js";
3
3
  import { OAUTH2_API_BASE, OAUTH2_FAILED_REASON, safeRedirectPath } from "nodefony";
4
- //#region nodefony/controller/OAuth2Controller.ts
4
+ // nodefony/controller/OAuth2Controller.ts
5
5
  const STATE_KEY = "oauth2:state";
6
6
  const VERIFIER_KEY = "oauth2:verifier";
7
7
  const PROVIDER_KEY = "oauth2:provider";
@@ -191,7 +191,7 @@ var OAuth2Controller = class extends SecurityApiController {
191
191
  const svc = this.#service();
192
192
  if (!svc || !svc.listProviders().includes(provider)) return this.renderJson({ error: "Unknown provider" }, 404, noStore);
193
193
  if (!svc.backchannelLogout) return this.renderJson({ error: "Not implemented" }, 501, noStore);
194
- const token = this.queryPost?.logout_token;
194
+ const token = this.body?.logout_token;
195
195
  if (typeof token !== "string" || token.length === 0) return this.renderJson({ error: "invalid_request" }, 400, noStore);
196
196
  let outcome;
197
197
  try {
@@ -220,7 +220,7 @@ var OAuth2Controller = class extends SecurityApiController {
220
220
  }
221
221
  /** Lit un paramètre de query string (GET), ou `null`. */
222
222
  #queryString(key) {
223
- const v = this.queryGet?.[key];
223
+ const v = this.query?.[key];
224
224
  return typeof v === "string" && v.length > 0 ? v : null;
225
225
  }
226
226
  };
@@ -267,5 +267,4 @@ function mountOAuth2Routes(frameworkModule) {
267
267
  if (!Object.prototype.hasOwnProperty.call(OAuth2Controller.prototype, "module")) router_default.setController(OAuth2Controller, frameworkModule);
268
268
  mounted = true;
269
269
  }
270
- //#endregion
271
270
  export { OAuth2Controller as default, mountOAuth2Routes, oauth2FailureTarget, providerThemeOption };
@@ -2,7 +2,7 @@ import Controller from "../src/Controller.js";
2
2
  import router_default from "../service/router.js";
3
3
  import { askedAuthority, onDeclaredAuthority } from "./oauthAuthority.js";
4
4
  import { buildProtectedResourceMetadata, canonicalResourceUri, protectedResourceMetadataPath } from "nodefony";
5
- //#region nodefony/controller/ProtectedResourceMetadataController.ts
5
+ // nodefony/controller/ProtectedResourceMetadataController.ts
6
6
  let mounted = false;
7
7
  /**
8
8
  * Ce qui a été PUBLIÉ au montage — la même liste qui a décidé des chemins.
@@ -217,5 +217,4 @@ function mountProtectedResourceRoutes(frameworkModule, resources) {
217
217
  mounted = true;
218
218
  return paths.length;
219
219
  }
220
- //#endregion
221
220
  export { collectProtectedResources, ProtectedResourceMetadataController as default, mountProtectedResourceRoutes, protectedResourceRoutePaths };
@@ -1,7 +1,9 @@
1
+ import { CsrfProtect } from "../decorators/routerDecorators.js";
2
+ import __decorate from "../../_virtual/_@oxc-project_runtime@0.155.0/helpers/esm/decorate.js";
1
3
  import router_default from "../service/router.js";
2
4
  import SecurityApiController from "../src/SecurityApiController.js";
3
5
  import { AUTH_API_BASE } from "nodefony";
4
- //#region nodefony/controller/SessionAuthController.ts
6
+ // nodefony/controller/SessionAuthController.ts
5
7
  let mounted = false;
6
8
  /**
7
9
  * Endpoints HTTP du flux de session BFF — adaptateurs MINCES au-dessus du
@@ -42,7 +44,7 @@ var SessionAuthController = class extends SecurityApiController {
42
44
  async login() {
43
45
  const flow = this.#flow();
44
46
  if (!flow) return this.renderJson({ error: "Authentication unavailable" }, 503);
45
- const body = this.queryPost ?? {};
47
+ const body = this.body ?? {};
46
48
  try {
47
49
  const outcome = await flow.login(this.context, body.username, body.password);
48
50
  if (outcome.status === "mfa_required") return await this.renderJson({
@@ -62,7 +64,7 @@ var SessionAuthController = class extends SecurityApiController {
62
64
  async loginTotp() {
63
65
  const flow = this.#flow();
64
66
  if (!flow) return this.renderJson({ error: "Authentication unavailable" }, 503);
65
- const body = this.queryPost ?? {};
67
+ const body = this.body ?? {};
66
68
  try {
67
69
  const user = await flow.completeMfaLogin(this.context, body.code);
68
70
  return await this.renderJson({ user });
@@ -94,6 +96,13 @@ var SessionAuthController = class extends SecurityApiController {
94
96
  * Identité de la session courante, re-résolue (rôles frais). Toujours `200` :
95
97
  * `{ user: null }` quand il n'y a pas de session (ou qu'elle ne mène plus à
96
98
  * un compte actif) — l'absence de session est un état, pas une erreur.
99
+ *
100
+ * **Sème aussi le jeton anti-CSRF** (cookie lisible `csrf-token`) : c'est la
101
+ * seule requête sûre que TOUT client connaît d'avance. Sans elle, une mutation
102
+ * `@CsrfProtect` n'a de jeton à rejouer que si son auteur a AUSSI décoré une
103
+ * lecture — protéger les seules écritures, le geste naturel, rendait alors 403
104
+ * sans issue, au navigateur comme aux tests générés. Le jeton est sans état
105
+ * (HMAC) : celui-ci vaut pour toute route protégée.
97
106
  */
98
107
  async me() {
99
108
  const flow = this.#flow();
@@ -118,6 +127,7 @@ var SessionAuthController = class extends SecurityApiController {
118
127
  throw e;
119
128
  }
120
129
  };
130
+ __decorate([CsrfProtect()], SessionAuthController.prototype, "me", null);
121
131
  /**
122
132
  * Monte les routes du flux de session BFF — appelé par le module framework à
123
133
  * `onKernelReady`, seulement si le service `authFlow` est présent.
@@ -165,5 +175,4 @@ function mountSessionAuthRoutes(frameworkModule) {
165
175
  if (!Object.prototype.hasOwnProperty.call(SessionAuthController.prototype, "module")) router_default.setController(SessionAuthController, frameworkModule);
166
176
  mounted = true;
167
177
  }
168
- //#endregion
169
178
  export { SessionAuthController as default, mountSessionAuthRoutes };
@@ -1,6 +1,6 @@
1
1
  import router_default from "../service/router.js";
2
2
  import SecurityApiController from "../src/SecurityApiController.js";
3
- //#region nodefony/controller/TokenAuthController.ts
3
+ // nodefony/controller/TokenAuthController.ts
4
4
  let mounted = false;
5
5
  /** Normalise le paramètre `scope` (string OAuth « a b c » ou tableau) → string[]. */
6
6
  function parseScope(scope) {
@@ -36,7 +36,7 @@ var TokenAuthController = class extends SecurityApiController {
36
36
  async token() {
37
37
  const svc = this.#issuer();
38
38
  if (!svc || !svc.isEnabled()) return this.renderJson({ error: "Token issuance unavailable" }, 503);
39
- const body = this.queryPost ?? {};
39
+ const body = this.body ?? {};
40
40
  try {
41
41
  const tokens = await svc.issueForCredentials(body.username, body.password, parseScope(body.scope), body.resource, this.context);
42
42
  return await this.renderJson(tokens);
@@ -48,7 +48,7 @@ var TokenAuthController = class extends SecurityApiController {
48
48
  async refresh() {
49
49
  const svc = this.#issuer();
50
50
  if (!svc || !svc.isEnabled()) return this.renderJson({ error: "Token issuance unavailable" }, 503);
51
- const body = this.queryPost ?? {};
51
+ const body = this.body ?? {};
52
52
  try {
53
53
  const tokens = await svc.refresh(body.refresh_token, body.resource);
54
54
  return await this.renderJson(tokens);
@@ -116,5 +116,4 @@ function mountTokenAuthRoutes(frameworkModule) {
116
116
  if (!Object.prototype.hasOwnProperty.call(TokenAuthController.prototype, "module")) router_default.setController(TokenAuthController, frameworkModule);
117
117
  mounted = true;
118
118
  }
119
- //#endregion
120
119
  export { TokenAuthController as default, mountTokenAuthRoutes };
@@ -1,6 +1,6 @@
1
1
  import router_default from "../service/router.js";
2
2
  import SecurityApiController from "../src/SecurityApiController.js";
3
- //#region nodefony/controller/TotpController.ts
3
+ // nodefony/controller/TotpController.ts
4
4
  let mounted = false;
5
5
  /**
6
6
  * Endpoints HTTP **self-service 2FA TOTP (P6.17)** — console « ma sécurité »,
@@ -39,7 +39,7 @@ var TotpController = class extends SecurityApiController {
39
39
  if (!svc) return this.renderJson({ error: "2FA unavailable" }, 503);
40
40
  const subject = await this.#currentSubject();
41
41
  if (subject === null) return this.renderJson({ error: "Unauthorized" }, 401);
42
- const code = this.queryPost?.code;
42
+ const code = this.body?.code;
43
43
  if (typeof code !== "string" || code.length === 0) return this.renderJson({ error: "Invalid code" }, 400);
44
44
  let activation;
45
45
  try {
@@ -128,5 +128,4 @@ function mountTotpRoutes(frameworkModule) {
128
128
  if (!Object.prototype.hasOwnProperty.call(TotpController.prototype, "module")) router_default.setController(TotpController, frameworkModule);
129
129
  mounted = true;
130
130
  }
131
- //#endregion
132
131
  export { TotpController as default, mountTotpRoutes };
@@ -1,7 +1,7 @@
1
1
  import router_default from "../service/router.js";
2
2
  import SecurityApiController from "../src/SecurityApiController.js";
3
3
  import { WEBAUTHN_API_BASE, WEBAUTHN_STATUS_PATH } from "nodefony";
4
- //#region nodefony/controller/WebAuthnController.ts
4
+ // nodefony/controller/WebAuthnController.ts
5
5
  const REG_CHALLENGE = "webauthn:reg:challenge";
6
6
  const AUTH_CHALLENGE = "webauthn:auth:challenge";
7
7
  let mounted = false;
@@ -164,7 +164,7 @@ var WebAuthnController = class extends SecurityApiController {
164
164
  * cérémonie est découvrable (cf {@link WebAuthnController.loginOptions}).
165
165
  */
166
166
  #body() {
167
- return this.queryPost ?? {};
167
+ return this.body ?? {};
168
168
  }
169
169
  /** Origine HTTP de la requête (validée par le service contre le rpID). */
170
170
  #origin() {
@@ -255,5 +255,4 @@ function mountWebAuthnRoutes(frameworkModule) {
255
255
  if (!Object.prototype.hasOwnProperty.call(WebAuthnController.prototype, "module")) router_default.setController(WebAuthnController, frameworkModule);
256
256
  mounted = true;
257
257
  }
258
- //#endregion
259
258
  export { WebAuthnController as default, mountWebAuthnRoutes };
@@ -1,4 +1,4 @@
1
- //#region nodefony/controller/oauthAuthority.ts
1
+ // nodefony/controller/oauthAuthority.ts
2
2
  /**
3
3
  * Une seule règle : un document bien connu ne se sert QUE sur l'autorité dont
4
4
  * il se réclame.
@@ -70,5 +70,4 @@ function askedAuthority(headers) {
70
70
  const asked = Array.isArray(raw) ? raw[0] : raw;
71
71
  return typeof asked === "string" && asked.length > 0 ? asked : void 0;
72
72
  }
73
- //#endregion
74
73
  export { askedAuthority, onDeclaredAuthority };