@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.
- package/README.md +4 -0
- package/dist/_virtual/{_@oxc-project_runtime@0.153.0 → _@oxc-project_runtime@0.155.0}/helpers/esm/decorate.js +1 -2
- package/dist/index.js +9 -18
- package/dist/nodefony/config/config.js +1 -2
- package/dist/nodefony/config/defineModuleConfig.js +1 -2
- package/dist/nodefony/controller/AdminApiController.js +38 -7
- package/dist/nodefony/controller/ApiKeyController.js +16 -8
- package/dist/nodefony/controller/BenchController.js +1 -2
- package/dist/nodefony/controller/IssuerMetadataController.js +1 -2
- package/dist/nodefony/controller/LoginPageController.js +4 -11
- package/dist/nodefony/controller/OAuth2Controller.js +3 -4
- package/dist/nodefony/controller/ProtectedResourceMetadataController.js +1 -2
- package/dist/nodefony/controller/SessionAuthController.js +13 -4
- package/dist/nodefony/controller/TokenAuthController.js +3 -4
- package/dist/nodefony/controller/TotpController.js +2 -3
- package/dist/nodefony/controller/WebAuthnController.js +2 -3
- package/dist/nodefony/controller/oauthAuthority.js +1 -2
- package/dist/nodefony/decorators/routerDecorators.js +155 -53
- package/dist/nodefony/service/AdminBroker.js +4 -6
- package/dist/nodefony/service/Eta.js +1 -2
- package/dist/nodefony/service/IdempotencyStore.js +4 -6
- package/dist/nodefony/service/router.js +49 -15
- package/dist/nodefony/src/Controller.js +22 -39
- package/dist/nodefony/src/FrameworkAdminApi.js +31 -10
- package/dist/nodefony/src/KernelAdminApi.js +7 -9
- package/dist/nodefony/src/PlaygroundAdminApi.js +72 -3
- package/dist/nodefony/src/RedisIdempotencyStore.js +46 -4
- package/dist/nodefony/src/Resolver.js +66 -34
- package/dist/nodefony/src/ResourceController.js +2 -3
- package/dist/nodefony/src/Route.js +2 -3
- package/dist/nodefony/src/SecurityApiController.js +1 -2
- package/dist/nodefony/src/SyslogAdminApi.js +1 -2
- package/dist/nodefony/src/Template.js +1 -2
- package/dist/nodefony/src/configMutation.js +1 -2
- package/dist/nodefony/src/docsReader.js +1 -2
- package/dist/nodefony/src/idempotency.js +1 -2
- package/dist/nodefony/src/idempotencyGc.js +1 -2
- package/dist/nodefony/src/idempotencyStoreRegistry.js +1 -2
- package/dist/nodefony/src/loginPage.js +2 -3
- package/dist/nodefony/src/requireAuthentication.js +1 -2
- package/dist/nodefony/src/routeSchema.js +81 -0
- package/dist/nodefony/src/scopeCatalog.js +1 -2
- package/dist/nodefony/src/singletonGuard.js +1 -2
- package/dist/nodefony/src/syslogFilters.js +1 -2
- package/dist/types/index.d.ts +5 -10
- package/dist/types/nodefony/controller/AdminApiController.d.ts +12 -0
- package/dist/types/nodefony/controller/SessionAuthController.d.ts +7 -0
- package/dist/types/nodefony/decorators/routerDecorators.d.ts +133 -32
- package/dist/types/nodefony/interfaces/IController.d.ts +2 -3
- package/dist/types/nodefony/service/router.d.ts +27 -1
- package/dist/types/nodefony/src/Controller.d.ts +14 -16
- package/dist/types/nodefony/src/PlaygroundAdminApi.d.ts +12 -0
- package/dist/types/nodefony/src/Resolver.d.ts +25 -1
- package/dist/types/nodefony/src/ResourceController.d.ts +1 -1
- package/dist/types/nodefony/src/Route.d.ts +3 -0
- package/dist/types/nodefony/src/loginPage.d.ts +1 -1
- package/dist/types/nodefony/src/routeSchema.d.ts +112 -0
- package/docs/admin.md +80 -80
- package/docs/controller.md +108 -109
- package/docs/decorateurs.md +208 -161
- package/docs/idempotence.md +133 -133
- package/docs/routing.md +90 -89
- package/docs/templates.md +55 -55
- package/package.json +8 -11
- 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
|
-
|
|
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
|
|
11
|
-
import
|
|
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,
|
|
34
|
-
|
|
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",
|
|
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(
|
|
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
|
-
])
|
|
208
|
-
const graphql = {
|
|
209
|
-
mergeSchemas,
|
|
210
|
-
makeExecutableSchema,
|
|
211
|
-
mergeResolvers,
|
|
212
|
-
mergeTypeDefs
|
|
213
|
-
};
|
|
205
|
+
])], Framework);
|
|
214
206
|
var framework_default = Framework;
|
|
215
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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:
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
36
|
-
if (
|
|
37
|
-
const body = this.
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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 };
|