@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.
- package/README.md +4 -0
- package/dist/_virtual/{_@oxc-project_runtime@0.152.0 → _@oxc-project_runtime@0.155.0}/helpers/esm/decorate.js +1 -2
- package/dist/index.js +15 -20
- package/dist/nodefony/config/config.js +1 -2
- package/dist/nodefony/config/defineModuleConfig.js +1 -2
- package/dist/nodefony/controller/AdminApiController.js +45 -11
- package/dist/nodefony/controller/ApiKeyController.js +18 -10
- package/dist/nodefony/controller/BenchController.js +1 -2
- package/dist/nodefony/controller/IssuerMetadataController.js +1 -2
- package/dist/nodefony/controller/LoginPageController.js +117 -0
- package/dist/nodefony/controller/OAuth2Controller.js +133 -17
- package/dist/nodefony/controller/ProtectedResourceMetadataController.js +1 -2
- package/dist/nodefony/controller/SessionAuthController.js +50 -13
- package/dist/nodefony/controller/TokenAuthController.js +14 -8
- package/dist/nodefony/controller/TotpController.js +7 -8
- package/dist/nodefony/controller/WebAuthnController.js +28 -12
- package/dist/nodefony/controller/oauthAuthority.js +1 -2
- package/dist/nodefony/decorators/routerDecorators.js +268 -65
- 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 +97 -34
- package/dist/nodefony/src/Controller.js +71 -43
- package/dist/nodefony/src/FrameworkAdminApi.js +33 -11
- package/dist/nodefony/src/KernelAdminApi.js +82 -18
- package/dist/nodefony/src/PlaygroundAdminApi.js +72 -3
- package/dist/nodefony/src/RedisIdempotencyStore.js +46 -4
- package/dist/nodefony/src/Resolver.js +105 -36
- package/dist/nodefony/src/ResourceController.js +2 -3
- package/dist/nodefony/src/Route.js +54 -8
- package/dist/nodefony/src/SecurityApiController.js +23 -0
- 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 +39 -23
- package/dist/nodefony/src/idempotency.js +14 -22
- package/dist/nodefony/src/idempotencyGc.js +1 -2
- package/dist/nodefony/src/idempotencyStoreRegistry.js +1 -2
- package/dist/nodefony/src/loginPage.js +430 -0
- package/dist/nodefony/src/requireAuthentication.js +35 -0
- 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 +8 -12
- package/dist/types/nodefony/controller/AdminApiController.d.ts +12 -0
- package/dist/types/nodefony/controller/ApiKeyController.d.ts +2 -2
- package/dist/types/nodefony/controller/LoginPageController.d.ts +32 -0
- package/dist/types/nodefony/controller/OAuth2Controller.d.ts +67 -8
- package/dist/types/nodefony/controller/SessionAuthController.d.ts +46 -8
- package/dist/types/nodefony/controller/TokenAuthController.d.ts +6 -4
- package/dist/types/nodefony/controller/TotpController.d.ts +2 -2
- package/dist/types/nodefony/controller/WebAuthnController.d.ts +12 -4
- package/dist/types/nodefony/decorators/routerDecorators.d.ts +221 -35
- package/dist/types/nodefony/interfaces/IController.d.ts +2 -3
- package/dist/types/nodefony/interfaces/IRoute.d.ts +1 -2
- package/dist/types/nodefony/service/router.d.ts +40 -1
- package/dist/types/nodefony/src/Controller.d.ts +54 -17
- package/dist/types/nodefony/src/PlaygroundAdminApi.d.ts +12 -0
- package/dist/types/nodefony/src/Resolver.d.ts +44 -1
- package/dist/types/nodefony/src/ResourceController.d.ts +1 -1
- package/dist/types/nodefony/src/Route.d.ts +21 -1
- package/dist/types/nodefony/src/SecurityApiController.d.ts +17 -0
- package/dist/types/nodefony/src/docsReader.d.ts +10 -6
- package/dist/types/nodefony/src/idempotency.d.ts +12 -10
- package/dist/types/nodefony/src/loginPage.d.ts +144 -0
- package/dist/types/nodefony/src/requireAuthentication.d.ts +29 -0
- package/dist/types/nodefony/src/routeSchema.d.ts +112 -0
- package/docs/admin.md +83 -78
- package/docs/controller.md +112 -111
- package/docs/decorateurs.md +212 -163
- package/docs/idempotence.md +137 -135
- package/docs/routing.md +96 -95
- package/docs/templates.md +55 -55
- package/package.json +10 -13
- package/dist/_virtual/_@oxc-project_runtime@0.152.0/helpers/esm/decorateMetadata.js +0 -6
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import "reflect-metadata";
|
|
2
|
+
import type { Readable } from "node:stream";
|
|
2
3
|
import { TypeController } from "../service/router.js";
|
|
3
4
|
import { RouteOptions } from "../src/Route.js";
|
|
4
5
|
import Controller from "../src/Controller.js";
|
|
5
6
|
import type { ControllerScope } from "../src/Controller.js";
|
|
6
7
|
import { Module } from "nodefony";
|
|
7
8
|
import { ControllerConstructor } from "../src/Route.js";
|
|
9
|
+
import type { IRouteSchema, RawRouteInput, RouteInput } from "../src/routeSchema.js";
|
|
8
10
|
import type { SessionIntent } from "@nodefony/http";
|
|
9
11
|
type Constructor<T = {}> = new (...args: any[]) => T;
|
|
10
12
|
/**
|
|
@@ -66,7 +68,30 @@ declare function controller(prefix: string): <T extends ControllerConstructor &
|
|
|
66
68
|
* return this.renderJson({ name });
|
|
67
69
|
* }
|
|
68
70
|
*/
|
|
69
|
-
declare function route(name: string, options: RouteOptions
|
|
71
|
+
declare function route(name: string, options: RouteOptions & {
|
|
72
|
+
schema?: undefined;
|
|
73
|
+
bodyStream?: false;
|
|
74
|
+
}): RouteMethodDecorator;
|
|
75
|
+
declare function route<S extends IRouteSchema>(name: string, options: RouteOptionsWithSchema<S> & {
|
|
76
|
+
bodyStream?: false;
|
|
77
|
+
}): SchemaRouteMethodDecorator<S, false>;
|
|
78
|
+
declare function route<S extends IRouteSchema & {
|
|
79
|
+
readonly body?: undefined;
|
|
80
|
+
}>(name: string, options: RouteOptionsWithSchema<S> & {
|
|
81
|
+
bodyStream: true;
|
|
82
|
+
}): SchemaRouteMethodDecorator<S, true>;
|
|
83
|
+
/** Options d'une route qui déclare un schéma. */
|
|
84
|
+
type RouteOptionsWithSchema<S extends IRouteSchema> = RouteOptions & {
|
|
85
|
+
schema: S;
|
|
86
|
+
};
|
|
87
|
+
/** Décorateur de méthode d'une route sans schéma : la signature de l'action est libre. */
|
|
88
|
+
type RouteMethodDecorator = (target: object, propertyKey: string, descriptor: PropertyDescriptor) => PropertyDescriptor;
|
|
89
|
+
/**
|
|
90
|
+
* Décorateur d'une action à schéma : la compilation refuse une action dont
|
|
91
|
+
* l'argument ne reçoit pas `RouteInput<S, Stream>` (champ absent du schéma,
|
|
92
|
+
* type divergent, second argument).
|
|
93
|
+
*/
|
|
94
|
+
type SchemaRouteMethodDecorator<S extends IRouteSchema, Stream extends boolean> = <T extends (input: RouteInput<S, Stream>) => unknown>(target: object, propertyKey: string, descriptor: TypedPropertyDescriptor<T>) => TypedPropertyDescriptor<T>;
|
|
70
95
|
export declare const HTTP_CODE_METADATA = "route:httpCode";
|
|
71
96
|
export declare const HEADERS_METADATA = "route:responseHeaders";
|
|
72
97
|
export declare const REDIRECT_METADATA = "route:redirect";
|
|
@@ -158,19 +183,40 @@ export interface IdempotentMeta {
|
|
|
158
183
|
readonly required: boolean;
|
|
159
184
|
}
|
|
160
185
|
type MethodDecoratorOptions = Omit<RouteOptions, "path" | "method">;
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
186
|
+
/**
|
|
187
|
+
* Un décorateur de méthode HTTP (`@Get`, `@Post`…). Sans schéma, la signature
|
|
188
|
+
* de l'action est libre (décorateurs de paramètre) ; avec `schema`, l'action
|
|
189
|
+
* reçoit un argument unique `RouteInput<S>`, vérifié à la compilation.
|
|
190
|
+
*/
|
|
191
|
+
export interface IHttpMethodDecorator {
|
|
192
|
+
(path?: string, options?: MethodDecoratorOptions & {
|
|
193
|
+
schema?: undefined;
|
|
194
|
+
bodyStream?: false;
|
|
195
|
+
}): RouteMethodDecorator;
|
|
196
|
+
<S extends IRouteSchema>(path: string, options: MethodDecoratorOptions & {
|
|
197
|
+
schema: S;
|
|
198
|
+
bodyStream?: false;
|
|
199
|
+
}): SchemaRouteMethodDecorator<S, false>;
|
|
200
|
+
<S extends IRouteSchema & {
|
|
201
|
+
readonly body?: undefined;
|
|
202
|
+
}>(path: string, options: MethodDecoratorOptions & {
|
|
203
|
+
schema: S;
|
|
204
|
+
bodyStream: true;
|
|
205
|
+
}): SchemaRouteMethodDecorator<S, true>;
|
|
206
|
+
}
|
|
207
|
+
declare const Get: IHttpMethodDecorator;
|
|
208
|
+
declare const Post: IHttpMethodDecorator;
|
|
209
|
+
declare const Put: IHttpMethodDecorator;
|
|
210
|
+
declare const Delete: IHttpMethodDecorator;
|
|
211
|
+
declare const Patch: IHttpMethodDecorator;
|
|
212
|
+
declare const Options: IHttpMethodDecorator;
|
|
213
|
+
declare const Head: IHttpMethodDecorator;
|
|
168
214
|
/**
|
|
169
215
|
* `@All` — route sans restriction de méthode : matche **toutes** les méthodes
|
|
170
216
|
* HTTP (équivalent NestJS `@All()`). N'émet aucun requirement `methods`, donc
|
|
171
217
|
* `Route.matchRequirements` ne lève jamais 405 sur la méthode.
|
|
172
218
|
*/
|
|
173
|
-
declare
|
|
219
|
+
declare const All: IHttpMethodDecorator;
|
|
174
220
|
/**
|
|
175
221
|
* Fixe le code de statut HTTP de la réponse d'une action.
|
|
176
222
|
*
|
|
@@ -470,26 +516,18 @@ declare const Query: (key?: string) => (target: object, propertyKey: string, par
|
|
|
470
516
|
*
|
|
471
517
|
* ## Le corps n'est PAS validé — le type écrit ici ne promet rien
|
|
472
518
|
*
|
|
473
|
-
* `@Body()` injecte le corps **tel qu'il a été
|
|
474
|
-
*
|
|
475
|
-
*
|
|
476
|
-
*
|
|
477
|
-
* taxerait toute requête à paramètres décorés).
|
|
478
|
-
*
|
|
479
|
-
* Où valider, donc :
|
|
519
|
+
* `@Body()` injecte le corps **tel qu'il a été décodé** (objet JSON ou de
|
|
520
|
+
* formulaire, chaîne pour `text/*`, octets `Uint8Array` pour tout autre type) :
|
|
521
|
+
* `@Body() dto: CreateOrder` compile, mais rien ne garantit qu'un `CreateOrder`
|
|
522
|
+
* soit arrivé.
|
|
480
523
|
*
|
|
481
|
-
*
|
|
482
|
-
*
|
|
483
|
-
*
|
|
484
|
-
*
|
|
485
|
-
* - **Un cas isolé** → `schema.parse(body)` en première ligne de l'action, sur le
|
|
486
|
-
* modèle d'`assertPageQuery`. Rien d'autre à écrire : une `ZodError` qui remonte
|
|
487
|
-
* devient un **422** (RFC 9110 §15.5.21) portant `error.fields` — quel champ,
|
|
488
|
-
* quel message, quelle règle.
|
|
524
|
+
* Pour un contrat vérifié, déclarer l'option de route `schema` (ADR-0018) : le
|
|
525
|
+
* corps est validé AVANT l'action — 422 qui nomme chaque champ —, sur HTTP comme
|
|
526
|
+
* sur le pont WebSocket, et l'action le reçoit typé dans `input.body`. Une action
|
|
527
|
+
* à schéma ne prend aucun décorateur de paramètre.
|
|
489
528
|
*
|
|
490
|
-
*
|
|
491
|
-
*
|
|
492
|
-
* ligne de **table** (`id`, horodatages) que le schéma effacera de toute façon.
|
|
529
|
+
* Sans schéma, `schema.parse(body)` en première ligne de l'action reste le geste
|
|
530
|
+
* d'un cas isolé : une `ZodError` qui remonte devient le même **422**.
|
|
493
531
|
*
|
|
494
532
|
* @example
|
|
495
533
|
* ```ts
|
|
@@ -524,20 +562,21 @@ export interface IParamArgContext {
|
|
|
524
562
|
/**
|
|
525
563
|
* Query de l'invocation courante quand elle ne vient PAS de l'URL du
|
|
526
564
|
* transport — pont WS-RPC `api.request` (`Resolver.queryOverride`). Prime
|
|
527
|
-
* sur `request.
|
|
565
|
+
* sur `request.query` pour `@Query` uniquement (`@Req` reste le brut).
|
|
528
566
|
*/
|
|
529
567
|
queryOverride?: Record<string, unknown> | undefined;
|
|
530
568
|
request?: {
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
569
|
+
query?: Record<string, unknown> | undefined;
|
|
570
|
+
/** Corps décodé : objet JSON, champs de formulaire, octets… */
|
|
571
|
+
body?: unknown;
|
|
572
|
+
files?: readonly unknown[] | undefined;
|
|
534
573
|
headers?: Record<string, unknown> | undefined;
|
|
535
574
|
/**
|
|
536
575
|
* P2.9 — `IncomingMessage` brut (un `Readable`) sous-jacent au wrapper
|
|
537
576
|
* `HttpRequest`. Injecté tel quel par `@Body({ stream: true })` (le pipeline
|
|
538
577
|
* n'a pas consommé/parsé ce flux). `undefined` pour les contextes WS.
|
|
539
578
|
*/
|
|
540
|
-
request?:
|
|
579
|
+
request?: Readable | undefined;
|
|
541
580
|
} | null | undefined;
|
|
542
581
|
response?: unknown;
|
|
543
582
|
session?: {
|
|
@@ -545,6 +584,31 @@ export interface IParamArgContext {
|
|
|
545
584
|
} | null | undefined;
|
|
546
585
|
getRequestCookies(name?: string): unknown;
|
|
547
586
|
}
|
|
587
|
+
/**
|
|
588
|
+
* Le corps DÉCODÉ d'une invocation, depuis la requête seule — la règle de
|
|
589
|
+
* {@link readBody}, pour qui n'a pas de contexte de paramètres (porte
|
|
590
|
+
* d'idempotence).
|
|
591
|
+
*
|
|
592
|
+
* @param request - la requête du contexte.
|
|
593
|
+
* @returns le corps posé par le pont WebSocket, sinon le corps HTTP lu.
|
|
594
|
+
*/
|
|
595
|
+
export declare function readInvocationBody(request: IParamArgContext["request"]): unknown;
|
|
596
|
+
/**
|
|
597
|
+
* L'entrée BRUTE d'une action à schéma, lue aux MÊMES sources que les
|
|
598
|
+
* décorateurs de paramètre ({@link readQuery}, {@link readBody}) : pont
|
|
599
|
+
* WebSocket compris. Seule allocation de la voie à schéma : cet objet, propre
|
|
600
|
+
* à l'invocation, que la validation complète en place et que l'action reçoit.
|
|
601
|
+
*
|
|
602
|
+
* Arguments POSITIONNELS, pas un objet de contexte : la voie à schéma n'en
|
|
603
|
+
* alloue aucun de plus.
|
|
604
|
+
*
|
|
605
|
+
* @param paramsMap - variables de route nommées.
|
|
606
|
+
* @param request - la requête du contexte.
|
|
607
|
+
* @param queryOverride - query du chemin invoqué (pont WebSocket), sinon rien.
|
|
608
|
+
* @param bodyStream - `true` : `body` est le flux brut, jamais lu.
|
|
609
|
+
* @returns les quatre parties, brutes.
|
|
610
|
+
*/
|
|
611
|
+
export declare function readRouteInput(paramsMap: Record<string, unknown>, request: IParamArgContext["request"], queryOverride: Record<string, unknown> | undefined, bodyStream: boolean): RawRouteInput;
|
|
548
612
|
/**
|
|
549
613
|
* Résout la valeur d'un unique paramètre décoré depuis le contexte de requête.
|
|
550
614
|
*
|
|
@@ -562,7 +626,79 @@ declare function resolveParamArg(meta: ParamMeta, ctx: IParamArgContext): unknow
|
|
|
562
626
|
* @param ctx - contexte de requête (forme structurelle minimale)
|
|
563
627
|
* @returns arguments positionnels à spread dans l'action
|
|
564
628
|
*/
|
|
565
|
-
declare function buildParamArgs(metas: ParamMeta[], ctx: IParamArgContext): unknown[];
|
|
629
|
+
declare function buildParamArgs(metas: readonly ParamMeta[], ctx: IParamArgContext): unknown[];
|
|
630
|
+
/**
|
|
631
|
+
* Ce qu'un `Context` — HTTP comme WebSocket — offre à la résolution des
|
|
632
|
+
* paramètres décorés. Forme structurelle : le framework n'importe pas les
|
|
633
|
+
* classes de contexte pour la lire.
|
|
634
|
+
*/
|
|
635
|
+
export interface IParamArgSource {
|
|
636
|
+
/** La requête du contexte (HTTP, ou l'upgrade d'une connexion WS). */
|
|
637
|
+
readonly request?: unknown;
|
|
638
|
+
/** La réponse du contexte. */
|
|
639
|
+
readonly response?: unknown;
|
|
640
|
+
/** La session, quand le contexte en porte une. */
|
|
641
|
+
readonly session?: unknown;
|
|
642
|
+
/** Lit un cookie de la requête, ou tous sans argument. */
|
|
643
|
+
getRequestCookies(name?: string): unknown;
|
|
644
|
+
}
|
|
645
|
+
/**
|
|
646
|
+
* Résout les paramètres décorés d'une action depuis un `Context` — la mise en
|
|
647
|
+
* forme UNIQUE, appelée par le Resolver (routes HTTP et WS) comme par les
|
|
648
|
+
* actions `@RealtimeAction`, pour qu'un décorateur ne puisse pas prendre deux
|
|
649
|
+
* sens selon le transport.
|
|
650
|
+
*
|
|
651
|
+
* Le corps d'un appel WebSocket voyage dans l'ALS (`RequestContext` clé
|
|
652
|
+
* `body`) et `@CurrentUser` y lit l'utilisateur : l'appelant pose la bulle,
|
|
653
|
+
* cette fonction n'en crée aucune.
|
|
654
|
+
*
|
|
655
|
+
* @param metas - paramètres décorés de l'action ({@link getParamArgsMeta})
|
|
656
|
+
* @param context - le contexte de la requête ou de la connexion
|
|
657
|
+
* @param paramsMap - variables de route nommées (vide hors route)
|
|
658
|
+
* @param queryOverride - query de l'invocation quand elle ne vient pas de l'URL
|
|
659
|
+
* @returns arguments positionnels à passer à l'action
|
|
660
|
+
*/
|
|
661
|
+
export declare function buildContextParamArgs(metas: readonly ParamMeta[], context: IParamArgSource, paramsMap: Record<string, unknown>, queryOverride?: Record<string, unknown>): unknown[];
|
|
662
|
+
/**
|
|
663
|
+
* Les paramètres décorés d'une méthode (`@Body`, `@CurrentUser`…), ou `null`
|
|
664
|
+
* si elle n'en porte aucun. Lecture `Reflect` : à faire une fois, hors du
|
|
665
|
+
* chemin de chaque requête.
|
|
666
|
+
*
|
|
667
|
+
* @param proto - le prototype de la classe
|
|
668
|
+
* @param method - le nom de la méthode
|
|
669
|
+
* @returns les métadonnées, ou `null` — une action sans décorateur ne paie rien
|
|
670
|
+
*/
|
|
671
|
+
export declare function getParamArgsMeta(proto: object, method: string | symbol): readonly ParamMeta[] | null;
|
|
672
|
+
/**
|
|
673
|
+
* Le schéma d'entrée d'une méthode (option de route `schema`), ou `null`.
|
|
674
|
+
* Lecture `Reflect` : à faire une fois, hors du chemin de chaque requête.
|
|
675
|
+
*
|
|
676
|
+
* @param proto - le prototype de la classe.
|
|
677
|
+
* @param method - le nom de la méthode.
|
|
678
|
+
* @returns le schéma, ou `null` — une action sans schéma ne paie rien.
|
|
679
|
+
* @throws BootConfigurationError si le schéma rangé est mal formé.
|
|
680
|
+
*/
|
|
681
|
+
export declare function getRouteSchema(proto: object, method: string): IRouteSchema | null;
|
|
682
|
+
/**
|
|
683
|
+
* Refuse, au DÉMARRAGE, une action à schéma mal déclarée — appelé par
|
|
684
|
+
* `Router.setController` pour chaque route du contrôleur, pour qu'une faute
|
|
685
|
+
* ne se révèle pas à la première requête :
|
|
686
|
+
*
|
|
687
|
+
* - schéma mal formé (partie inconnue, valeur qui n'est pas un Standard Schema) ;
|
|
688
|
+
* - schéma ET décorateurs de paramètre (`@Body`…) : une action à schéma reçoit
|
|
689
|
+
* un argument UNIQUE, les deux formes ne se combinent pas ;
|
|
690
|
+
* - `bodyStream: true` ET `schema.body` : un flux ne se valide pas.
|
|
691
|
+
*
|
|
692
|
+
* @param routeDef - la route (nom, contrôleur, méthode).
|
|
693
|
+
* @throws BootConfigurationError qui nomme la route.
|
|
694
|
+
*/
|
|
695
|
+
export declare function assertRouteSchemaDeclaration(routeDef: {
|
|
696
|
+
name: string;
|
|
697
|
+
controller?: {
|
|
698
|
+
prototype: object;
|
|
699
|
+
} | null | undefined;
|
|
700
|
+
classMethod?: string | undefined;
|
|
701
|
+
}): void;
|
|
566
702
|
/**
|
|
567
703
|
* P2.9 — Indique si l'action d'une route attend le **flux brut** du body
|
|
568
704
|
* (un paramètre `@Body({ stream:true })`). Le résultat est **mémoïsé** sur
|
|
@@ -588,7 +724,14 @@ declare function routeExpectsBodyStream(routeDef: {
|
|
|
588
724
|
*/
|
|
589
725
|
export interface RouteActionMeta {
|
|
590
726
|
/** Paramètres décorés (`@Param`/`@Body`/`@Query`…) — `null` si aucun. */
|
|
591
|
-
paramsMeta: ParamMeta[] | null;
|
|
727
|
+
paramsMeta: readonly ParamMeta[] | null;
|
|
728
|
+
/**
|
|
729
|
+
* Schéma d'entrée (`schema` de l'option de route) — `null` si l'action n'en
|
|
730
|
+
* déclare pas (cas courant → une comparaison, aucune allocation).
|
|
731
|
+
*/
|
|
732
|
+
schema: IRouteSchema | null;
|
|
733
|
+
/** `bodyStream: true` déclaré en option de route. */
|
|
734
|
+
bodyStream: boolean;
|
|
592
735
|
/** `@Redirect` de l'action — `null` si absent. */
|
|
593
736
|
redirectMeta: RedirectMeta | null;
|
|
594
737
|
/** `@HttpCode` de l'action — `null` si absent. */
|
|
@@ -603,6 +746,8 @@ export interface RouteActionMeta {
|
|
|
603
746
|
* path (ni résolution de service, ni `decide`, ni await). Frozen, partagé.
|
|
604
747
|
*/
|
|
605
748
|
security: SecurityRequirement | null;
|
|
749
|
+
/** `@RequireAuthentication` (fusion au plus strict) — `null` = 0 coût, frozen. */
|
|
750
|
+
authentication: IAuthenticationRequirement | null;
|
|
606
751
|
/**
|
|
607
752
|
* Directives CSP additionnelles (`@Csp`, fusion classe+méthode) — `null` si
|
|
608
753
|
* l'action n'en déclare pas (cas courant → 0 composition CSP). Frozen, partagé.
|
|
@@ -649,4 +794,45 @@ declare function resolveActionMeta(routeDef: {
|
|
|
649
794
|
classMethod?: string | undefined;
|
|
650
795
|
actionMeta?: RouteActionMeta | undefined;
|
|
651
796
|
}): RouteActionMeta;
|
|
652
|
-
export { route, controller, controllers, Get, Post, Put, Delete, Patch, Options, Head, All, Domain, BypassFirewall, IsGranted, RequireScope, Anonymous, Csp, CsrfProtect, CsrfExempt, Idempotent, CurrentUser, Scope, UseSession, resolveSessionIntent, HttpCode, Header, Redirect, Param, Body, Query, Headers, Cookie, Session, Req, Res, UploadedFile, UploadedFiles, resolveParamArg, buildParamArgs, routeExpectsBodyStream, computeActionMeta, resolveActionMeta, extractActionScopes, };
|
|
797
|
+
export { route, controller, controllers, Get, Post, Put, Delete, Patch, Options, Head, All, Domain, BypassFirewall, IsGranted, RequireScope, RequireAuthentication, Anonymous, Csp, CsrfProtect, CsrfExempt, Idempotent, CurrentUser, Scope, UseSession, resolveSessionIntent, HttpCode, Header, Redirect, Param, Body, Query, Headers, Cookie, Session, Req, Res, UploadedFile, UploadedFiles, resolveParamArg, buildParamArgs, routeExpectsBodyStream, computeActionMeta, resolveActionMeta, extractActionScopes, };
|
|
798
|
+
/** Options de {@link RequireAuthentication}. */
|
|
799
|
+
export interface IRequireAuthenticationOptions {
|
|
800
|
+
/** Âge maximal de la connexion, en secondes. Omis = aucune limite d'âge. */
|
|
801
|
+
readonly maxAge?: number;
|
|
802
|
+
/**
|
|
803
|
+
* Moyens d'authentification EXIGÉS, tous présents (RFC 8176) : `"pwd"` mot
|
|
804
|
+
* de passe, `"otp"` code à usage unique, `"pop"` clé d'accès. Omis = aucun.
|
|
805
|
+
*/
|
|
806
|
+
readonly amr?: readonly string[];
|
|
807
|
+
}
|
|
808
|
+
/**
|
|
809
|
+
* Exigence d'authentification **figée** d'une action — fusion classe + méthode
|
|
810
|
+
* au plus strict (âge le plus court, moyens réunis), calculée UNE fois par
|
|
811
|
+
* route. Objet partagé entre requêtes : ne jamais muter.
|
|
812
|
+
*/
|
|
813
|
+
export interface IAuthenticationRequirement {
|
|
814
|
+
/** Âge maximal en secondes, `null` = sans limite. */
|
|
815
|
+
readonly maxAge: number | null;
|
|
816
|
+
/** Moyens exigés, tous présents. */
|
|
817
|
+
readonly amr: readonly string[];
|
|
818
|
+
}
|
|
819
|
+
/**
|
|
820
|
+
* Exige une connexion RÉCENTE et/ou faite avec certains moyens pour l'action
|
|
821
|
+
* (décorateur de méthode) ou tout le contrôleur (décorateur de classe).
|
|
822
|
+
*
|
|
823
|
+
* - `@RequireAuthentication({ maxAge: 300 })` — connexion de moins de 5 minutes.
|
|
824
|
+
* - `@RequireAuthentication({ amr: ["otp"] })` — second facteur exigé.
|
|
825
|
+
* - les deux se combinent ; classe + méthode fusionnent au plus strict.
|
|
826
|
+
*
|
|
827
|
+
* Lit ce que la session a retenu à son ouverture (`readSessionAuthentication`,
|
|
828
|
+
* ADR-0015) et s'évalue dans le `Resolver` APRÈS `@IsGranted` : un rôle absent
|
|
829
|
+
* reste un 403 définitif, une connexion trop ancienne ou trop faible devient un
|
|
830
|
+
* **401** dont le `WWW-Authenticate` porte
|
|
831
|
+
* `error="insufficient_user_authentication"` (RFC 9470) — le client sait qu'il
|
|
832
|
+
* doit redemander une connexion, pas que l'accès lui est fermé. Une session qui
|
|
833
|
+
* ne porte pas ces métadonnées est refusée, jamais acceptée.
|
|
834
|
+
*
|
|
835
|
+
* @param options - âge maximal et moyens exigés.
|
|
836
|
+
* @throws TypeError si `maxAge` n'est pas un entier positif ou si un moyen est vide.
|
|
837
|
+
*/
|
|
838
|
+
declare function RequireAuthentication(options?: IRequireAuthenticationOptions): (target: object, propertyKey?: string, descriptor?: PropertyDescriptor) => any;
|
|
@@ -10,10 +10,9 @@ export interface IController {
|
|
|
10
10
|
context?: ContextType | undefined;
|
|
11
11
|
session?: Session | null;
|
|
12
12
|
method?: HTTPMethod | undefined;
|
|
13
|
-
queryGet: Record<string, unknown>;
|
|
14
13
|
query: Record<string, unknown>;
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
files: readonly unknown[];
|
|
15
|
+
body: Record<string, unknown>;
|
|
17
16
|
module?: Module | undefined;
|
|
18
17
|
setContext(context: ContextType): void;
|
|
19
18
|
setContextJson(encoding?: BufferEncoding): unknown;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { HTTPMethod,
|
|
1
|
+
import type { HTTPMethod, ContextType, IResolvedRoute } from "@nodefony/http";
|
|
2
2
|
import type { ControllerConstructor, RouteRequirements } from "../src/Route.js";
|
|
3
3
|
export interface IRoute extends IResolvedRoute {
|
|
4
4
|
name: string;
|
|
@@ -7,7 +7,6 @@ export interface IRoute extends IResolvedRoute {
|
|
|
7
7
|
classMethod?: string | undefined;
|
|
8
8
|
prefix?: string | undefined;
|
|
9
9
|
method?: HTTPMethod | undefined;
|
|
10
|
-
schemes?: SchemeType | undefined;
|
|
11
10
|
pattern?: RegExp | undefined;
|
|
12
11
|
variables: unknown[];
|
|
13
12
|
defaults: Partial<Record<string, unknown>>;
|
|
@@ -6,7 +6,16 @@ import type { IRequestRouter } from "@nodefony/http";
|
|
|
6
6
|
import Resolver from "../src/Resolver.js";
|
|
7
7
|
import Controller from "../src/Controller.js";
|
|
8
8
|
export type TypeController<T> = new (...args: any[]) => T;
|
|
9
|
+
/**
|
|
10
|
+
* Marque du Router de `@nodefony/framework` — ce que {@link isFrameworkRouter}
|
|
11
|
+
* lit. `Symbol.for` : la même marque pour deux copies installées du framework,
|
|
12
|
+
* là où `instanceof` échouerait en silence. Un faux routeur de test s'en
|
|
13
|
+
* réclame explicitement.
|
|
14
|
+
*/
|
|
15
|
+
export declare const FRAMEWORK_ROUTER: unique symbol;
|
|
9
16
|
declare class Router extends Service implements IRequestRouter {
|
|
17
|
+
/** Marque {@link FRAMEWORK_ROUTER} — sur le prototype, aucun champ par instance. */
|
|
18
|
+
get [FRAMEWORK_ROUTER](): true;
|
|
10
19
|
static routes: Route[];
|
|
11
20
|
routes: Route[];
|
|
12
21
|
private singletonControllers;
|
|
@@ -30,6 +39,19 @@ declare class Router extends Service implements IRequestRouter {
|
|
|
30
39
|
* cache, la requête suivante recrée.
|
|
31
40
|
*/
|
|
32
41
|
getSingletonController<T extends object>(ctor: new (...args: never[]) => T, create: () => MaybePromise<T>): MaybePromise<T>;
|
|
42
|
+
/**
|
|
43
|
+
* Dit si au moins une route déclare le transport `WEBSOCKET` pour ce chemin
|
|
44
|
+
* — contrôle d'avant `101` du pipeline WebSocket (cf `IRequestRouter`).
|
|
45
|
+
*
|
|
46
|
+
* Parcours de la table entière, sans l'index de `resolve` : il est lu une
|
|
47
|
+
* fois par CONNEXION (pas par message), et une route dont les méthodes
|
|
48
|
+
* changent après coup (`addRequirement`) ne laisse ainsi aucun index périmé.
|
|
49
|
+
* Même normalisation que `resolve` (`stripTrailingSlashes`), même motif.
|
|
50
|
+
*
|
|
51
|
+
* @param pathname - chemin de l'URL d'upgrade
|
|
52
|
+
* @returns `true` si une route WebSocket a un motif qui couvre ce chemin
|
|
53
|
+
*/
|
|
54
|
+
servesWebsocket(pathname: string): boolean;
|
|
33
55
|
/**
|
|
34
56
|
* Résout une route pour un contexte donné.
|
|
35
57
|
*
|
|
@@ -42,7 +64,10 @@ declare class Router extends Service implements IRequestRouter {
|
|
|
42
64
|
* WEBSOCKET (pont WS-RPC `api.request` d'une MUTATION) : lève l'ambiguïté
|
|
43
65
|
* GET-via-WS / POST-via-WS sur un même chemin (`context.method` = "WEBSOCKET").
|
|
44
66
|
* `undefined` (GET/HTTP) → match historique sur `context.method`.
|
|
45
|
-
* @returns un `Resolver` (`.resolve === true` si une route a matché)
|
|
67
|
+
* @returns un `Resolver` (`.resolve === true` si une route a matché) — ou,
|
|
68
|
+
* pour un chemin servi en WebSocket SEULEMENT et demandé en HTTP, un
|
|
69
|
+
* `Resolver` dont `exception` porte le `426` (en-têtes `Upgrade` posés) :
|
|
70
|
+
* le pipeline le lève après le pare-feu.
|
|
46
71
|
*/
|
|
47
72
|
resolve(context: ContextType, cleanPathOverride?: string, methodOverride?: string): Resolver;
|
|
48
73
|
resolveController(contex: ContextType, name: string): Resolver;
|
|
@@ -76,4 +101,18 @@ declare class Router extends Service implements IRequestRouter {
|
|
|
76
101
|
*/
|
|
77
102
|
static getRoutesForController(myconstructor: TypeController<Controller>): Route[];
|
|
78
103
|
}
|
|
104
|
+
/**
|
|
105
|
+
* Dit si le service `router` d'un contexte est le Router de
|
|
106
|
+
* `@nodefony/framework` — la garde qui rétrécit le contrat `IRequestRouter` de
|
|
107
|
+
* `@nodefony/http` sans conversion, pour qui lit des membres propres au
|
|
108
|
+
* framework (le Resolver complet que rend `resolve`).
|
|
109
|
+
*
|
|
110
|
+
* Lit la marque {@link FRAMEWORK_ROUTER}, jamais `instanceof` : deux copies
|
|
111
|
+
* installées du framework feraient échouer `instanceof` en silence. Une
|
|
112
|
+
* lecture de propriété.
|
|
113
|
+
*
|
|
114
|
+
* @param router - le routeur lu sur le contexte (`context.router`).
|
|
115
|
+
* @returns `true` si c'est le Router du framework.
|
|
116
|
+
*/
|
|
117
|
+
export declare function isFrameworkRouter(router: IRequestRouter | null | undefined): router is Router;
|
|
79
118
|
export default Router;
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { Service, Module, FileClass } from "nodefony";
|
|
2
|
-
import type { MaybePromise } from "nodefony";
|
|
2
|
+
import type { IRequestUser, MaybePromise } from "nodefony";
|
|
3
3
|
import type { IController } from "../interfaces/index.js";
|
|
4
4
|
import Route from "./Route.js";
|
|
5
5
|
import { contextRequest, HTTPMethod, HttpResponse, Session, ContextType, Http2Response, WebsocketResponse } from "@nodefony/http";
|
|
6
|
+
import type { ISseStreamOptions, SseStream } from "@nodefony/http";
|
|
6
7
|
import { OutgoingHttpHeaders } from "node:http";
|
|
7
8
|
import { ReadStream } from "node:fs";
|
|
8
9
|
import Eta from "../service/Eta.js";
|
|
@@ -94,30 +95,35 @@ declare class Controller extends Service implements IController {
|
|
|
94
95
|
set request(request: contextRequest);
|
|
95
96
|
get response(): HttpResponse | Http2Response | WebsocketResponse | null;
|
|
96
97
|
set response(response: HttpResponse | Http2Response | WebsocketResponse | null);
|
|
98
|
+
/**
|
|
99
|
+
* L'utilisateur CONNECTÉ, ou `null` — anonyme, ou route hors de toute zone
|
|
100
|
+
* du pare-feu. C'est l'identité que le pare-feu a établie pour cette
|
|
101
|
+
* requête : aucun appel de plus. Une page publique qui veut savoir qui la
|
|
102
|
+
* visite se place sous une zone `authenticators: ["session", "anonymous"]`.
|
|
103
|
+
*/
|
|
104
|
+
get user(): IRequestUser | null;
|
|
97
105
|
get method(): HTTPMethod | undefined;
|
|
98
106
|
set method(method: HTTPMethod | undefined);
|
|
99
107
|
/**
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
*
|
|
108
|
+
* Paramètres d'URL de l'appel en cours — jamais le corps, qui reste dans
|
|
109
|
+
* {@link body}. Pour un singleton invoqué par le pont `api.request`,
|
|
110
|
+
* ceux du chemin INVOQUÉ (résolveur d'appel dans l'ALS), pas ceux du
|
|
111
|
+
* handshake de la connexion. Une seule lecture de l'ALS.
|
|
103
112
|
*/
|
|
104
|
-
get queryGet(): Record<string, unknown>;
|
|
105
|
-
set queryGet(value: Record<string, unknown>);
|
|
106
|
-
/** Comme {@link queryGet}, pour la query fusionnée (`query`). */
|
|
107
113
|
get query(): Record<string, unknown>;
|
|
108
114
|
set query(value: Record<string, unknown>);
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
115
|
+
/**
|
|
116
|
+
* Les fichiers reçus dans un corps multipart (`UploadedFile`) ; les champs
|
|
117
|
+
* texte du même corps sont dans {@link body}. Pour une action typée,
|
|
118
|
+
* préférer `@UploadedFiles()`.
|
|
119
|
+
*/
|
|
120
|
+
get files(): readonly unknown[];
|
|
121
|
+
set files(value: readonly unknown[]);
|
|
113
122
|
/**
|
|
114
123
|
* Le CORPS de la requête, parsé — nom universel de l'écosystème (Express,
|
|
115
|
-
* Fastify, NestJS)
|
|
116
|
-
*
|
|
117
|
-
*
|
|
118
|
-
* corps. Pour une action typée, préférer le décorateur `@Body()`.
|
|
119
|
-
*
|
|
120
|
-
* Getter (aucune allocation) : `queryPost` reste la source unique.
|
|
124
|
+
* Fastify, NestJS). Jamais un paramètre d'URL : ceux-là sont dans
|
|
125
|
+
* {@link query}, et les deux ne fusionnent pas. Pour une action typée,
|
|
126
|
+
* préférer le décorateur `@Body()`.
|
|
121
127
|
*/
|
|
122
128
|
get body(): Record<string, unknown>;
|
|
123
129
|
set body(value: Record<string, unknown>);
|
|
@@ -177,6 +183,37 @@ declare class Controller extends Service implements IController {
|
|
|
177
183
|
* @throws de façon SYNCHRONE ce que lève `JSON.stringify` (cycle, BigInt).
|
|
178
184
|
*/
|
|
179
185
|
renderJson(obj: unknown, status?: string | number, headers?: OutgoingHttpHeaders): MaybePromise<Http2Response | HttpResponse | WebsocketResponse>;
|
|
186
|
+
/**
|
|
187
|
+
* Le client demande-t-il un flux d'événements (`Accept: text/event-stream`) ?
|
|
188
|
+
*
|
|
189
|
+
* La route ne change pas de méthode pour autant : un même `POST` répond en
|
|
190
|
+
* JSON ou en flux selon cette réponse. Toujours `false` sur WebSocket.
|
|
191
|
+
*
|
|
192
|
+
* @returns `true` quand `text/event-stream` est accepté avec un poids non nul.
|
|
193
|
+
*/
|
|
194
|
+
acceptsSse(): boolean;
|
|
195
|
+
/**
|
|
196
|
+
* Répond par un flux d'événements serveur, en HTTP/1.1 comme en HTTP/2.
|
|
197
|
+
*
|
|
198
|
+
* L'action garde la main : elle `send()` à son rythme et `close()` quand elle
|
|
199
|
+
* a fini ; le départ du client ferme le flux (`sse.closed`, `sse.onClose`).
|
|
200
|
+
* Rendre le flux depuis l'action est sans effet — la réponse est déjà prise.
|
|
201
|
+
*
|
|
202
|
+
* @example
|
|
203
|
+
* ```ts
|
|
204
|
+
* @Get("/progress")
|
|
205
|
+
* async progress() {
|
|
206
|
+
* const sse = await this.renderSse();
|
|
207
|
+
* for (const step of steps) await sse.send(step, { event: "step" });
|
|
208
|
+
* return sse.close();
|
|
209
|
+
* }
|
|
210
|
+
* ```
|
|
211
|
+
*
|
|
212
|
+
* @param options - battement de cœur, délai `retry:` initial.
|
|
213
|
+
* @returns le flux, ou sa promesse quand une session est à sauver d'abord.
|
|
214
|
+
* @throws HttpError 500 hors HTTP (WebSocket) ou quand la réponse est partie.
|
|
215
|
+
*/
|
|
216
|
+
renderSse(options?: ISseStreamOptions): SseStream | Promise<SseStream>;
|
|
180
217
|
/**
|
|
181
218
|
* Pose la route de la requête courante sur l'instance (per-request).
|
|
182
219
|
*
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { IAdminEndpoint } from "nodefony";
|
|
2
|
+
import type { RoutePart } from "./routeSchema.js";
|
|
2
3
|
/**
|
|
3
4
|
* Endpoints « Playground » du data plane framework — la source de données de la
|
|
4
5
|
* console vivante des controllers dans Studio (`/nodefony/playground`).
|
|
@@ -65,7 +66,18 @@ export interface PlaygroundAction {
|
|
|
65
66
|
variables: string[];
|
|
66
67
|
/** Défauts de variables (hors clé interne `controller`). */
|
|
67
68
|
defaults: Record<string, unknown>;
|
|
69
|
+
/**
|
|
70
|
+
* Entrées de l'action. Pour une action à schéma, déduites du schéma (une
|
|
71
|
+
* entrée par champ de `query`/`headers`/`params`, une pour `body`) : le
|
|
72
|
+
* formulaire se construit pareil, quelle que soit la forme de l'action.
|
|
73
|
+
*/
|
|
68
74
|
params: PlaygroundParam[];
|
|
75
|
+
/**
|
|
76
|
+
* Le contrat d'une action à schéma — JSON Schema d'entrée de chaque partie
|
|
77
|
+
* déclarée (`null` quand la bibliothèque ne sait pas le décrire). `null`
|
|
78
|
+
* pour une action sans schéma.
|
|
79
|
+
*/
|
|
80
|
+
schema: Partial<Record<RoutePart, Record<string, unknown> | null>> | null;
|
|
69
81
|
guards: PlaygroundGuards;
|
|
70
82
|
}
|
|
71
83
|
/** Un controller et ses actions, groupés pour la navigation Studio. */
|
|
@@ -42,7 +42,7 @@ declare class Resolver implements IResolver {
|
|
|
42
42
|
/**
|
|
43
43
|
* Query d'une invocation **par message** (pont WS-RPC `api.request`) — pendant
|
|
44
44
|
* de `cleanPathOverride` pour le `?…` du path invoqué. Le contexte WS étant
|
|
45
|
-
* PARTAGÉ par la connexion (sa `
|
|
45
|
+
* PARTAGÉ par la connexion (sa `query` = celle du handshake), la query
|
|
46
46
|
* per-invocation vit ici (le Resolver est per-invocation → zéro bleed entre
|
|
47
47
|
* requêtes concurrentes d'une même socket). `null` (hot path HTTP) = ignoré.
|
|
48
48
|
* Consommé par `@Query` (`_buildParamArgs`) et copié sur le controller
|
|
@@ -70,6 +70,11 @@ declare class Resolver implements IResolver {
|
|
|
70
70
|
* administrateurs.
|
|
71
71
|
*/
|
|
72
72
|
messageInvocation: boolean;
|
|
73
|
+
/**
|
|
74
|
+
* Les gardes de l'action ont été jugées favorables par {@link authorizeAction}
|
|
75
|
+
* et ne se rejugent pas à la prochaine exécution — consommé par elle.
|
|
76
|
+
*/
|
|
77
|
+
private preAuthorized;
|
|
73
78
|
constructor(context: ContextType);
|
|
74
79
|
match(route: Route, context: ContextType, cleanPath?: string, literal?: boolean): ((string | null)[] & Record<string, unknown>) | null | undefined;
|
|
75
80
|
/**
|
|
@@ -115,6 +120,12 @@ declare class Resolver implements IResolver {
|
|
|
115
120
|
* `instanceof` au message suivant — le hub était RÉINSTANCIÉ (DI +
|
|
116
121
|
* `initialize()`) à chaque frame qui suivait un `api.request`.
|
|
117
122
|
*
|
|
123
|
+
* Ni pour un `forward` sur une connexion WebSocket : son résolveur est neuf,
|
|
124
|
+
* pas une invocation par message, et réécrivait le pointeur que la poignée
|
|
125
|
+
* de main a posé — même réinstanciation à la frame suivante (#574). Une
|
|
126
|
+
* connexion garde le contrôleur qui l'a ouverte. Chemin froid : seulement
|
|
127
|
+
* quand un contrôleur est créé.
|
|
128
|
+
*
|
|
118
129
|
* @returns le controller reçu, pour enchaîner.
|
|
119
130
|
*/
|
|
120
131
|
private _pinController;
|
|
@@ -163,6 +174,25 @@ declare class Resolver implements IResolver {
|
|
|
163
174
|
* d'action) ; un refus d'autorisation rompt la promesse rendue.
|
|
164
175
|
*/
|
|
165
176
|
executeAction(data?: unknown[], reload?: boolean, metaArg?: RouteActionMeta): MaybePromise<ActionOutcome>;
|
|
177
|
+
/**
|
|
178
|
+
* Valide l'entrée d'une action à schéma, puis l'exécute avec cette entrée
|
|
179
|
+
* pour argument unique. Synchrone tant que le validateur l'est.
|
|
180
|
+
*
|
|
181
|
+
* @throws SchemaValidationError (422) quand l'entrée viole le schéma.
|
|
182
|
+
*/
|
|
183
|
+
private _validateAndInvoke;
|
|
184
|
+
/**
|
|
185
|
+
* Juge les gardes de l'action résolue SANS l'exécuter — appelé par le
|
|
186
|
+
* pipeline WebSocket AVANT d'accepter le handshake (#596), pour qu'un
|
|
187
|
+
* connecté sans le rôle reçoive son 403 en statut HTTP au lieu d'une
|
|
188
|
+
* ouverture suivie d'une fermeture. Un verdict favorable vaut pour la
|
|
189
|
+
* PROCHAINE exécution (l'action de handshake), qui ne le rejuge pas ; les
|
|
190
|
+
* messages suivants, eux, sont jugés chacun.
|
|
191
|
+
*
|
|
192
|
+
* @returns `undefined` quand l'action n'est pas gardée ; sinon la promesse du
|
|
193
|
+
* verdict, rompue par un refus
|
|
194
|
+
*/
|
|
195
|
+
authorizeAction(): Promise<void> | undefined;
|
|
166
196
|
/**
|
|
167
197
|
* Les gardes d'autorisation d'une action : `@IsGranted`/`@RequireScope`, puis
|
|
168
198
|
* le filet de la ZONE quand aucune garde n'a décidé du RÔLE.
|
|
@@ -339,5 +369,18 @@ declare class Resolver implements IResolver {
|
|
|
339
369
|
returnController(result: unknown): MaybePromise<unknown>;
|
|
340
370
|
/** Statut courant de la réponse (0 si le transport n'en porte pas). */
|
|
341
371
|
private getResponseStatus;
|
|
372
|
+
/**
|
|
373
|
+
* `@RequireAuthentication` — les gardes d'autorisation d'abord (un rôle
|
|
374
|
+
* absent reste un 403 définitif : inutile de faire redemander une connexion à
|
|
375
|
+
* qui n'aura pas le droit), PUIS l'âge et les moyens de la connexion.
|
|
376
|
+
*
|
|
377
|
+
* Le refus est un 401 « step-up » (RFC 9470) : le `WWW-Authenticate` porte
|
|
378
|
+
* `error="insufficient_user_authentication"` et, quand la route fixe un âge,
|
|
379
|
+
* `max_age` — le client redemande une connexion au lieu de conclure à un
|
|
380
|
+
* accès fermé. Cold path : seules les routes décorées y passent.
|
|
381
|
+
*
|
|
382
|
+
* @throws nodefonyError 401 si la connexion est trop ancienne ou trop faible.
|
|
383
|
+
*/
|
|
384
|
+
private _enforceStepUp;
|
|
342
385
|
}
|
|
343
386
|
export default Resolver;
|
|
@@ -86,7 +86,7 @@ export interface IResourceService<T = unknown> {
|
|
|
86
86
|
* sur `this` doit rétrograder : `static scope = "request"`.
|
|
87
87
|
*
|
|
88
88
|
* Sécurité : aucun critère de requête n'est passé AUTOMATIQUEMENT au service
|
|
89
|
-
* (pas de `find(this.
|
|
89
|
+
* (pas de `find(this.query)` implicite) — exposer un filtrage est une
|
|
90
90
|
* décision EXPLICITE de la sous-classe (deny-by-default ; le scope de
|
|
91
91
|
* sécurité des données se branche au niveau service/criteria, P6).
|
|
92
92
|
*
|