@nodefony/framework 10.0.0-alpha.1
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/LICENSE +544 -0
- package/README.md +50 -0
- package/dist/_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorate.js +9 -0
- package/dist/_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorateMetadata.js +6 -0
- package/dist/index.js +211 -0
- package/dist/nodefony/config/config.js +61 -0
- package/dist/nodefony/config/defineModuleConfig.js +36 -0
- package/dist/nodefony/controller/AdminApiController.js +163 -0
- package/dist/nodefony/controller/ApiKeyController.js +151 -0
- package/dist/nodefony/controller/BenchController.js +132 -0
- package/dist/nodefony/controller/IssuerMetadataController.js +148 -0
- package/dist/nodefony/controller/OAuth2Controller.js +133 -0
- package/dist/nodefony/controller/ProtectedResourceMetadataController.js +221 -0
- package/dist/nodefony/controller/SessionAuthController.js +141 -0
- package/dist/nodefony/controller/TokenAuthController.js +113 -0
- package/dist/nodefony/controller/TotpController.js +129 -0
- package/dist/nodefony/controller/WebAuthnController.js +242 -0
- package/dist/nodefony/controller/oauthAuthority.js +74 -0
- package/dist/nodefony/decorators/routerDecorators.js +967 -0
- package/dist/nodefony/interfaces/IAdminBroker.js +1 -0
- package/dist/nodefony/interfaces/IController.js +1 -0
- package/dist/nodefony/interfaces/IIdempotencyStore.js +1 -0
- package/dist/nodefony/interfaces/IResolver.js +1 -0
- package/dist/nodefony/interfaces/IRoute.js +1 -0
- package/dist/nodefony/interfaces/index.js +1 -0
- package/dist/nodefony/service/AdminBroker.js +106 -0
- package/dist/nodefony/service/Eta.js +68 -0
- package/dist/nodefony/service/IdempotencyStore.js +136 -0
- package/dist/nodefony/service/router.js +243 -0
- package/dist/nodefony/src/Controller.js +515 -0
- package/dist/nodefony/src/FrameworkAdminApi.js +268 -0
- package/dist/nodefony/src/KernelAdminApi.js +1243 -0
- package/dist/nodefony/src/PlaygroundAdminApi.js +97 -0
- package/dist/nodefony/src/RedisIdempotencyStore.js +254 -0
- package/dist/nodefony/src/Resolver.js +416 -0
- package/dist/nodefony/src/ResourceController.js +148 -0
- package/dist/nodefony/src/Route.js +476 -0
- package/dist/nodefony/src/SyslogAdminApi.js +466 -0
- package/dist/nodefony/src/Template.js +15 -0
- package/dist/nodefony/src/configMutation.js +186 -0
- package/dist/nodefony/src/docsReader.js +929 -0
- package/dist/nodefony/src/idempotency.js +137 -0
- package/dist/nodefony/src/idempotencyGc.js +32 -0
- package/dist/nodefony/src/idempotencyStoreRegistry.js +36 -0
- package/dist/nodefony/src/scopeCatalog.js +40 -0
- package/dist/nodefony/src/syslogFilters.js +51 -0
- package/dist/types/index.d.ts +96 -0
- package/dist/types/nodefony/config/config.d.ts +41 -0
- package/dist/types/nodefony/config/defineModuleConfig.d.ts +27 -0
- package/dist/types/nodefony/controller/AdminApiController.d.ts +70 -0
- package/dist/types/nodefony/controller/ApiKeyController.d.ts +49 -0
- package/dist/types/nodefony/controller/BenchController.d.ts +45 -0
- package/dist/types/nodefony/controller/IssuerMetadataController.d.ts +86 -0
- package/dist/types/nodefony/controller/OAuth2Controller.d.ts +81 -0
- package/dist/types/nodefony/controller/ProtectedResourceMetadataController.d.ts +147 -0
- package/dist/types/nodefony/controller/SessionAuthController.d.ts +74 -0
- package/dist/types/nodefony/controller/TokenAuthController.d.ts +56 -0
- package/dist/types/nodefony/controller/TotpController.d.ts +42 -0
- package/dist/types/nodefony/controller/WebAuthnController.d.ts +123 -0
- package/dist/types/nodefony/controller/oauthAuthority.d.ts +54 -0
- package/dist/types/nodefony/decorators/routerDecorators.d.ts +632 -0
- package/dist/types/nodefony/interfaces/IAdminBroker.d.ts +79 -0
- package/dist/types/nodefony/interfaces/IController.d.ts +38 -0
- package/dist/types/nodefony/interfaces/IIdempotencyStore.d.ts +1 -0
- package/dist/types/nodefony/interfaces/IResolver.d.ts +25 -0
- package/dist/types/nodefony/interfaces/IRoute.d.ts +31 -0
- package/dist/types/nodefony/interfaces/index.d.ts +5 -0
- package/dist/types/nodefony/service/AdminBroker.d.ts +37 -0
- package/dist/types/nodefony/service/Eta.d.ts +25 -0
- package/dist/types/nodefony/service/IdempotencyStore.d.ts +45 -0
- package/dist/types/nodefony/service/router.d.ts +53 -0
- package/dist/types/nodefony/src/Controller.d.ts +193 -0
- package/dist/types/nodefony/src/FrameworkAdminApi.d.ts +35 -0
- package/dist/types/nodefony/src/KernelAdminApi.d.ts +71 -0
- package/dist/types/nodefony/src/PlaygroundAdminApi.d.ts +98 -0
- package/dist/types/nodefony/src/RedisIdempotencyStore.d.ts +104 -0
- package/dist/types/nodefony/src/Resolver.d.ts +165 -0
- package/dist/types/nodefony/src/ResourceController.d.ts +171 -0
- package/dist/types/nodefony/src/Route.d.ts +192 -0
- package/dist/types/nodefony/src/SyslogAdminApi.d.ts +38 -0
- package/dist/types/nodefony/src/Template.d.ts +8 -0
- package/dist/types/nodefony/src/configMutation.d.ts +109 -0
- package/dist/types/nodefony/src/docsReader.d.ts +369 -0
- package/dist/types/nodefony/src/idempotency.d.ts +96 -0
- package/dist/types/nodefony/src/idempotencyGc.d.ts +30 -0
- package/dist/types/nodefony/src/idempotencyStoreRegistry.d.ts +59 -0
- package/dist/types/nodefony/src/scopeCatalog.d.ts +26 -0
- package/dist/types/nodefony/src/syslogFilters.d.ts +52 -0
- package/docs/admin.md +451 -0
- package/docs/controller.md +645 -0
- package/docs/decorateurs.md +845 -0
- package/docs/idempotence.md +741 -0
- package/docs/index.md +151 -0
- package/docs/routing.md +648 -0
- package/docs/templates.md +380 -0
- package/package.json +83 -0
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import type { Module } from "nodefony";
|
|
2
|
+
import type { ContextType } from "@nodefony/http";
|
|
3
|
+
import Controller from "../src/Controller.js";
|
|
4
|
+
/**
|
|
5
|
+
* Vue MINIMALE du service d'émission de jetons, côté **publication**
|
|
6
|
+
* (`tokenService`, posé au container par `@nodefony/security`). Contrat
|
|
7
|
+
* structurel local : framework ne dépend JAMAIS de security — couplage par nom
|
|
8
|
+
* de service, comme `authFlow`/`adminBroker`.
|
|
9
|
+
*
|
|
10
|
+
* Les deux méthodes disent l'essentiel du partage des rôles : security DÉCIDE
|
|
11
|
+
* (peut-on se déclarer émetteur ?) et FOURNIT la matière (les clés publiques) ;
|
|
12
|
+
* framework se contente d'ouvrir la porte.
|
|
13
|
+
*/
|
|
14
|
+
export interface IIssuerPublisher {
|
|
15
|
+
/** Émetteur canonique publiable, ou `null` si rien ne doit l'être. */
|
|
16
|
+
publishedIssuer(): string | null;
|
|
17
|
+
/** Jeu de clés PUBLIQUES de signature (jamais de clé privée). */
|
|
18
|
+
getPublicJWKS(): Promise<unknown>;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Les deux documents qui rendent une application Nodefony **découvrable** comme
|
|
22
|
+
* émetteur de jetons — RFC 8414 :
|
|
23
|
+
*
|
|
24
|
+
* - `GET /.well-known/oauth-authorization-server` — métadonnées d'émetteur
|
|
25
|
+
* (`issuer`, `jwks_uri`) ; c'est le seul document qu'un tiers sait trouver,
|
|
26
|
+
* puisqu'il ne le lit pas mais le CONSTRUIT depuis l'identifiant d'émetteur
|
|
27
|
+
* par insertion de chemin (§3.1).
|
|
28
|
+
* - `GET /.well-known/jwks.json` — le jeu de clés publiques lui-même.
|
|
29
|
+
*
|
|
30
|
+
* ## Pourquoi ces routes existent
|
|
31
|
+
*
|
|
32
|
+
* Sans elles, `getPublicJWKS()` n'a qu'un usage interne : Nodefony vérifie ses
|
|
33
|
+
* propres jetons en mémoire. Un tiers — une autre application Nodefony, un
|
|
34
|
+
* agent, un service — ne peut donc PAS valider une signature émise ici, même en
|
|
35
|
+
* connaissant l'URL des clés : il n'y en a pas. C'est le symétrique exact du
|
|
36
|
+
* rôle serveur de ressource : là, on refusait en disant où aller ; ici, on
|
|
37
|
+
* permet à quelqu'un d'autre de vérifier ce qu'on a signé.
|
|
38
|
+
*
|
|
39
|
+
* ## Ce qui les monte, et ce qui les retient
|
|
40
|
+
*
|
|
41
|
+
* Montées par le module framework UNIQUEMENT si `tokenService.publishedIssuer()`
|
|
42
|
+
* rend une valeur — c'est-à-dire si le JWT est actif, `security.jwt.jwks` vrai,
|
|
43
|
+
* et l'émetteur écrit sous forme d'URL https. Sinon les routes **n'existent
|
|
44
|
+
* pas** (`404`, zéro surface) : un document creux apprendrait à un client qu'il
|
|
45
|
+
* y a quelque chose à découvrir sans lui donner de quoi le faire.
|
|
46
|
+
*
|
|
47
|
+
* Le montage ne suffit pas : servir encore exige que la requête entre par
|
|
48
|
+
* **l'autorité de l'émetteur** ({@link IssuerMetadataController.metadata}). Un
|
|
49
|
+
* serveur écoute plusieurs adresses ; l'émetteur n'en désigne qu'une.
|
|
50
|
+
*
|
|
51
|
+
* `bypassFirewall: true` : ces documents sont publics par construction. Un JWKS
|
|
52
|
+
* derrière une authentification serait un non-sens — il sert précisément à
|
|
53
|
+
* vérifier les jetons de ceux qui ne sont pas encore authentifiés. Ils ne
|
|
54
|
+
* révèlent rien de secret : des clés PUBLIQUES et un identifiant, jamais `d`.
|
|
55
|
+
*
|
|
56
|
+
* @remarks Le chemin des métadonnées est **dérivé** de l'émetteur
|
|
57
|
+
* (`authorizationServerMetadataPath`) et non écrit en dur : un émetteur porteur
|
|
58
|
+
* d'un chemin se publie SOUS ce chemin (RFC 8414 §3.1), et c'est la même
|
|
59
|
+
* fonction qui sert au lecteur, dans `@nodefony/security`.
|
|
60
|
+
*/
|
|
61
|
+
declare class IssuerMetadataController extends Controller {
|
|
62
|
+
#private;
|
|
63
|
+
constructor(context: ContextType);
|
|
64
|
+
/**
|
|
65
|
+
* `GET /.well-known/oauth-authorization-server` — métadonnées d'émetteur.
|
|
66
|
+
*
|
|
67
|
+
* @returns le document RFC 8414, ou `404` si la publication a été coupée
|
|
68
|
+
* après le montage des routes
|
|
69
|
+
*/
|
|
70
|
+
metadata(): Promise<import("@nodefony/http").Http2Response | import("@nodefony/http").HttpResponse | import("@nodefony/http").WebsocketResponse>;
|
|
71
|
+
/**
|
|
72
|
+
* `GET /.well-known/jwks.json` — clés publiques de signature.
|
|
73
|
+
*
|
|
74
|
+
* @returns le JWK Set (paramètres publics seuls)
|
|
75
|
+
*/
|
|
76
|
+
jwks(): Promise<import("@nodefony/http").Http2Response | import("@nodefony/http").HttpResponse | import("@nodefony/http").WebsocketResponse>;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Monte les deux documents d'émetteur — appelé par le module framework à
|
|
80
|
+
* `onKernelReady`, seulement si `tokenService.publishedIssuer()` répond.
|
|
81
|
+
*
|
|
82
|
+
* @param frameworkModule - module porteur des routes
|
|
83
|
+
* @param issuer - émetteur canonique, qui DÉTERMINE le chemin des métadonnées
|
|
84
|
+
*/
|
|
85
|
+
export declare function mountIssuerMetadataRoutes(frameworkModule: Module, issuer: string): void;
|
|
86
|
+
export default IssuerMetadataController;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import type { Module } from "nodefony";
|
|
2
|
+
import type { ContextType } from "@nodefony/http";
|
|
3
|
+
import Controller from "../src/Controller.js";
|
|
4
|
+
/**
|
|
5
|
+
* Vue MINIMALE du service `oauth2` (`@nodefony/security`) — couplage par NOM
|
|
6
|
+
* (framework ne dépend ni de security ni d'arctic). Contrat structurel imposé par
|
|
7
|
+
* cast (`this.get<…>`), aucune liaison de build.
|
|
8
|
+
*/
|
|
9
|
+
export interface IOAuth2Service {
|
|
10
|
+
isEnabled(): boolean;
|
|
11
|
+
listProviders(): string[];
|
|
12
|
+
getRedirects(provider?: string): {
|
|
13
|
+
success: string;
|
|
14
|
+
failure: string;
|
|
15
|
+
};
|
|
16
|
+
createAuthorization(provider: string): Promise<{
|
|
17
|
+
url: string;
|
|
18
|
+
state: string;
|
|
19
|
+
codeVerifier: string | null;
|
|
20
|
+
}>;
|
|
21
|
+
exchangeAndProvision(provider: string, code: string, codeVerifier: string | null, returnedIss: string | null): Promise<{
|
|
22
|
+
identifier: string;
|
|
23
|
+
}>;
|
|
24
|
+
}
|
|
25
|
+
/** Vue minimale d'une session — porte l'état du flux OAuth (anti-CSRF/anti-replay). */
|
|
26
|
+
export interface IOAuth2Session {
|
|
27
|
+
get(key: string): unknown;
|
|
28
|
+
set(key: string, value: unknown): unknown;
|
|
29
|
+
save(): Promise<unknown>;
|
|
30
|
+
}
|
|
31
|
+
/** Vue minimale du flux de session BFF (`authFlow`) consommée ici. */
|
|
32
|
+
export interface IOAuth2BffFlow {
|
|
33
|
+
/**
|
|
34
|
+
* @param reason - facteur d'authentification journalisé par l'audit
|
|
35
|
+
* (`"oauth"` ici). Omis, il retombe sur `"federated"`, qui ne distingue plus
|
|
36
|
+
* un login social d'une passkey dans le journal.
|
|
37
|
+
*/
|
|
38
|
+
establishSessionFor(context: ContextType, identifier: string, reason?: string): Promise<unknown>;
|
|
39
|
+
/** Garantit une session (anonyme) pour porter `state`/`code_verifier`. */
|
|
40
|
+
ensureSession(context: ContextType): Promise<IOAuth2Session | null>;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Endpoints HTTP du **social login OAuth 2.0** (P6 J9) — adaptateurs MINCES
|
|
44
|
+
* au-dessus du service `oauth2` (`@nodefony/security`) :
|
|
45
|
+
*
|
|
46
|
+
* - `GET /nodefony/security/api/oauth2/{provider}/authorize` — démarre le flux :
|
|
47
|
+
* pose `state`+`code_verifier` en session (anonyme), redirige (302) vers le
|
|
48
|
+
* fournisseur.
|
|
49
|
+
* - `GET /nodefony/security/api/oauth2/{provider}/callback` — valide le `state`
|
|
50
|
+
* (anti-CSRF), échange le `code`, provisionne le Shadow User et OUVRE la
|
|
51
|
+
* session BFF (302 vers `successRedirect`).
|
|
52
|
+
*
|
|
53
|
+
* Montés UNIQUEMENT si le service `oauth2` existe (social login activé) — 404
|
|
54
|
+
* sinon, zéro surface.
|
|
55
|
+
*
|
|
56
|
+
* @remarks `bypassFirewall` : ces routes SONT (ou précèdent) le mécanisme d'auth
|
|
57
|
+
* (l'utilisateur est anonyme pendant tout l'aller-retour). Le firewall, sur l'aire
|
|
58
|
+
* data plane, déclencherait un deadlock identique au login BFF / WebAuthn login.
|
|
59
|
+
* La session anonyme ne porte que `state`/`verifier` ; `establishSessionFor`
|
|
60
|
+
* régénère l'ID (anti-fixation) à la promotion.
|
|
61
|
+
*/
|
|
62
|
+
declare class OAuth2Controller extends Controller {
|
|
63
|
+
#private;
|
|
64
|
+
constructor(context: ContextType);
|
|
65
|
+
/**
|
|
66
|
+
* Liste PUBLIQUE des fournisseurs activés (configurés ET connus du registre).
|
|
67
|
+
* Consommé par l'UI de login pour n'afficher QUE les boutons opérationnels —
|
|
68
|
+
* jamais de bouton mort. Aucun secret n'est exposé (uniquement les noms).
|
|
69
|
+
*/
|
|
70
|
+
providers(): Promise<import("@nodefony/http").Http2Response | import("@nodefony/http").HttpResponse | import("@nodefony/http").WebsocketResponse>;
|
|
71
|
+
/** Démarre le flux : URL d'autorisation + état anti-replay en session, 302. */
|
|
72
|
+
authorize(provider: string): Promise<void | import("@nodefony/http").HttpResponse | import("@nodefony/http").WebsocketResponse>;
|
|
73
|
+
/** Valide `state`, échange le `code`, ouvre la session BFF (302). */
|
|
74
|
+
callback(provider: string): Promise<void | import("@nodefony/http").HttpResponse | import("@nodefony/http").WebsocketResponse>;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Monte les routes du social login OAuth — appelé par le module framework à
|
|
78
|
+
* `onKernelReady`, seulement si le service `oauth2` est présent.
|
|
79
|
+
*/
|
|
80
|
+
export declare function mountOAuth2Routes(frameworkModule: Module): void;
|
|
81
|
+
export default OAuth2Controller;
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import type { Module } from "nodefony";
|
|
2
|
+
import type { IProtectedResourceInput } from "nodefony";
|
|
3
|
+
import type { ContextType } from "@nodefony/http";
|
|
4
|
+
import Controller from "../src/Controller.js";
|
|
5
|
+
/**
|
|
6
|
+
* Vue MINIMALE du service qui sait ce que l'application PROTÈGE, côté
|
|
7
|
+
* **publication**. Contrat structurel local : framework ne dépend JAMAIS de
|
|
8
|
+
* `@nodefony/security` — couplage par nom de service (`firewall`), comme
|
|
9
|
+
* `tokenService`/`authFlow`/`adminBroker`.
|
|
10
|
+
*
|
|
11
|
+
* Le partage des rôles est le même que pour l'émetteur : security DÉCIDE (quelles
|
|
12
|
+
* zones déclarent une ressource, quels serveurs d'autorisation les servent) et
|
|
13
|
+
* framework se contente d'ouvrir la porte.
|
|
14
|
+
*/
|
|
15
|
+
export interface IProtectedResourcePublisher {
|
|
16
|
+
/**
|
|
17
|
+
* Ressources protégées à publier — vide si l'application n'en protège aucune.
|
|
18
|
+
*
|
|
19
|
+
* Chaque entrée décrit une ressource telle qu'un client l'atteint, et les
|
|
20
|
+
* serveurs d'autorisation capables d'émettre un jeton pour elle.
|
|
21
|
+
*/
|
|
22
|
+
publishedProtectedResources(): readonly IProtectedResourceInput[];
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Le document qui rend un refus APPRENABLE — RFC 9728.
|
|
26
|
+
*
|
|
27
|
+
* ## Le trou que ce controller ferme
|
|
28
|
+
*
|
|
29
|
+
* Un `401` d'une zone qui déclare sa ressource porte désormais un défi complet :
|
|
30
|
+
*
|
|
31
|
+
* ```
|
|
32
|
+
* WWW-Authenticate: Bearer resource_metadata="https://app/.well-known/oauth-protected-resource/api"
|
|
33
|
+
* ```
|
|
34
|
+
*
|
|
35
|
+
* Sans ce controller, cette URL rendait **404** : un pointeur syntaxiquement
|
|
36
|
+
* conforme qui ne mène nulle part. Le client le lit, le suit, trouve une erreur,
|
|
37
|
+
* et conclut qu'il n'y a pas d'autorisation ici — c'est-à-dire exactement
|
|
38
|
+
* l'inverse de ce que le refus voulait lui apprendre. Seul `@nodefony/devkit`
|
|
39
|
+
* publiait un document, et uniquement pour SA porte MCP.
|
|
40
|
+
*
|
|
41
|
+
* ## Une route par CHEMIN, un document par AUTORITÉ
|
|
42
|
+
*
|
|
43
|
+
* La RFC 9728 §3.1 **insère** le chemin de la ressource dans l'URL bien connue
|
|
44
|
+
* (« Using path components enables supporting multiple resources per host ») :
|
|
45
|
+
* deux ressources de chemins distincts ont donc deux URL distinctes, et l'on
|
|
46
|
+
* monte une route par chemin. Deux ressources qui partagent le chemin mais pas
|
|
47
|
+
* l'autorité (`https://a.example/api` et `https://b.example/api`) partagent en
|
|
48
|
+
* revanche la même route : c'est alors l'autorité demandée qui départage, à la
|
|
49
|
+
* requête.
|
|
50
|
+
*
|
|
51
|
+
* ## Ce qui retient la publication
|
|
52
|
+
*
|
|
53
|
+
* Rien n'est monté si le service `firewall` est absent, ou s'il ne déclare
|
|
54
|
+
* aucune ressource : **`404`, zéro surface**. Un document creux apprendrait à un
|
|
55
|
+
* client qu'il y a quelque chose à découvrir sans lui donner de quoi le faire —
|
|
56
|
+
* et la spécification MCP l'interdit explicitement (« MUST include […] at least
|
|
57
|
+
* one authorization server »).
|
|
58
|
+
*
|
|
59
|
+
* `bypassFirewall: true` : ce document est public par construction. Le placer
|
|
60
|
+
* derrière l'authentification serait circulaire — il sert précisément à
|
|
61
|
+
* expliquer comment s'authentifier à qui ne l'est pas encore. Il ne révèle rien
|
|
62
|
+
* de secret : une URI publique, des émetteurs, des scopes.
|
|
63
|
+
*
|
|
64
|
+
* @see references/rfc/ietf/rfc9728.txt — métadonnées de la ressource protégée
|
|
65
|
+
* @see references/rfc/ietf/rfc8707.txt — l'audience, qui LIE un jeton à CE service
|
|
66
|
+
*/
|
|
67
|
+
declare class ProtectedResourceMetadataController extends Controller {
|
|
68
|
+
#private;
|
|
69
|
+
constructor(context: ContextType);
|
|
70
|
+
/**
|
|
71
|
+
* `GET /.well-known/oauth-protected-resource/<chemin>` — RFC 9728 §3.
|
|
72
|
+
*
|
|
73
|
+
* 🔴 Le document n'est servi que sur l'autorité de SA ressource. Un client
|
|
74
|
+
* conforme rejette un document dont la `resource` ne correspond pas à l'URI
|
|
75
|
+
* qu'il interrogeait (§3.3) — et un client réel s'arrête là au lieu de
|
|
76
|
+
* continuer sans authentification. C'est la faille déjà vécue sur le document
|
|
77
|
+
* d'émetteur, transposée : la règle est partagée (`onDeclaredAuthority`).
|
|
78
|
+
*
|
|
79
|
+
* @returns le document JSON, ou `404` si aucune ressource déclarée ne
|
|
80
|
+
* correspond au chemin ET à l'autorité demandés
|
|
81
|
+
*/
|
|
82
|
+
metadata(): Promise<import("@nodefony/http").Http2Response | import("@nodefony/http").HttpResponse | import("@nodefony/http").WebsocketResponse>;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Vue minimale du conteneur de services — assez pour balayer, pas plus.
|
|
86
|
+
*
|
|
87
|
+
* Le type complet vit dans `nodefony` mais l'importer ici lierait ce fichier au
|
|
88
|
+
* cœur pour une seule opération de lecture.
|
|
89
|
+
*/
|
|
90
|
+
export interface IServiceScan {
|
|
91
|
+
keys(): string[];
|
|
92
|
+
get<T = unknown>(name: string): T | null;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Rassemble ce que TOUS les services du conteneur déclarent protéger.
|
|
96
|
+
*
|
|
97
|
+
* ⭐ **Pourquoi balayer plutôt que demander à un service nommé.** Les ressources
|
|
98
|
+
* protégées d'une application n'ont pas une source unique : le pare-feu en
|
|
99
|
+
* déclare (une zone qui exige un jeton tiers), et un module peut en déclarer une
|
|
100
|
+
* de son propre chef — la porte MCP de `@nodefony/devkit` en est le premier cas.
|
|
101
|
+
* Interroger le seul `firewall` aurait laissé chaque autre module monter SON
|
|
102
|
+
* document, c'est-à-dire recopier cette règle autant de fois qu'il y a de
|
|
103
|
+
* sources, avec la collision de chemin en prime : `Router.createRoute` empile
|
|
104
|
+
* sans vérifier, et la seconde route ne serait jamais atteinte.
|
|
105
|
+
*
|
|
106
|
+
* Un **registre** aurait fait le même travail, au prix d'une dépendance d'ordre :
|
|
107
|
+
* un module qui s'enregistre après le `onKernelReady` du framework ne serait
|
|
108
|
+
* jamais publié, et rien ne le dirait. Le balayage n'a pas ce défaut — les
|
|
109
|
+
* services sont tous en place bien avant, et la question se pose au moment où
|
|
110
|
+
* l'on monte.
|
|
111
|
+
*
|
|
112
|
+
* Le contrat est **structurel** : porter la méthode suffit, aucun import, aucune
|
|
113
|
+
* interface à implémenter formellement — le même couplage que `tokenService` ou
|
|
114
|
+
* `adminBroker`.
|
|
115
|
+
*
|
|
116
|
+
* @param container - conteneur de services de l'application
|
|
117
|
+
* @returns les ressources déclarées, dans l'ordre des services
|
|
118
|
+
*/
|
|
119
|
+
export declare function collectProtectedResources(container: IServiceScan): readonly IProtectedResourceInput[];
|
|
120
|
+
/**
|
|
121
|
+
* Chemins bien connus à monter pour un jeu de ressources déclarées.
|
|
122
|
+
*
|
|
123
|
+
* Fonction **pure**, exportée pour être éprouvée sans serveur : c'est elle qui
|
|
124
|
+
* porte les deux décisions qui font la différence entre un document servi et un
|
|
125
|
+
* `404` — la dérivation du chemin (insertion RFC 9728 §3.1, jamais une
|
|
126
|
+
* concaténation) et la déduplication.
|
|
127
|
+
*
|
|
128
|
+
* Deux zones peuvent parfaitement déclarer la même ressource (typiquement une
|
|
129
|
+
* zone HTTP et son pendant WebSocket) ; deux hôtes virtuels peuvent en déclarer
|
|
130
|
+
* deux différentes sous le même chemin. Dans les deux cas il ne faut monter
|
|
131
|
+
* qu'**une** route : `Router.createRoute` empile sans vérifier, et la seconde
|
|
132
|
+
* ne serait jamais atteinte — une collision parfaitement silencieuse.
|
|
133
|
+
*
|
|
134
|
+
* @param resources - ressources déclarées par le publieur
|
|
135
|
+
* @returns les chemins distincts à servir, dans l'ordre de déclaration
|
|
136
|
+
*/
|
|
137
|
+
export declare function protectedResourceRoutePaths(resources: readonly IProtectedResourceInput[]): string[];
|
|
138
|
+
/**
|
|
139
|
+
* Monte les documents de ressource protégée — appelé par le module framework à
|
|
140
|
+
* `onKernelReady`, seulement si un publieur déclare au moins une ressource.
|
|
141
|
+
*
|
|
142
|
+
* @param frameworkModule - module porteur des routes
|
|
143
|
+
* @param resources - ressources déclarées, qui DÉTERMINENT les chemins montés
|
|
144
|
+
* @returns le nombre de routes montées
|
|
145
|
+
*/
|
|
146
|
+
export declare function mountProtectedResourceRoutes(frameworkModule: Module, resources: readonly IProtectedResourceInput[]): number;
|
|
147
|
+
export default ProtectedResourceMetadataController;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import type { Module } from "nodefony";
|
|
2
|
+
import type { ContextType } from "@nodefony/http";
|
|
3
|
+
import Controller from "../src/Controller.js";
|
|
4
|
+
/**
|
|
5
|
+
* Vue MINIMALE du flux de session BFF que consomme ce controller — le service
|
|
6
|
+
* `authFlow` est posé au container par `@nodefony/security` (P6 J3). Contrat
|
|
7
|
+
* structurel local : framework ne dépend JAMAIS de security (c'est http, sous
|
|
8
|
+
* framework, que security décore) ; le couplage se fait par nom de service,
|
|
9
|
+
* comme `adminBroker`.
|
|
10
|
+
*/
|
|
11
|
+
/** Issue d'un login BFF — identité établie, ou second facteur (2FA) requis. */
|
|
12
|
+
type ILoginOutcome = {
|
|
13
|
+
status: "authenticated";
|
|
14
|
+
user: unknown;
|
|
15
|
+
} | {
|
|
16
|
+
status: "mfa_required";
|
|
17
|
+
methods: string[];
|
|
18
|
+
};
|
|
19
|
+
export interface ISessionAuthFlow {
|
|
20
|
+
login(context: ContextType, identifier: unknown, password: unknown): Promise<ILoginOutcome>;
|
|
21
|
+
completeMfaLogin(context: ContextType, code: unknown): Promise<unknown>;
|
|
22
|
+
logout(context: ContextType): Promise<boolean>;
|
|
23
|
+
me(context: ContextType): Promise<unknown | null>;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Endpoints HTTP du flux de session BFF — adaptateurs MINCES au-dessus du
|
|
27
|
+
* service `authFlow` (`@nodefony/security`) :
|
|
28
|
+
*
|
|
29
|
+
* - `POST /nodefony/security/api/auth/login` — body JSON `{username, password}`
|
|
30
|
+
* - `POST /nodefony/security/api/auth/logout` — idempotent
|
|
31
|
+
* - `GET /nodefony/security/api/auth/me` — identité de la session courante
|
|
32
|
+
*
|
|
33
|
+
* Montés par le module framework UNIQUEMENT si le service `authFlow` existe
|
|
34
|
+
* (module security chargé) — sans lui, les routes n'existent pas (404, zéro
|
|
35
|
+
* surface). Remplace les mocks `/nodefony/studio/api/auth/*` de Studio.
|
|
36
|
+
*
|
|
37
|
+
* Erreurs : mappées par DUCK-TYPING sur `code` (401/429) — framework ne peut
|
|
38
|
+
* pas importer les classes d'erreur de security. Un 429 reporte le
|
|
39
|
+
* `Retry-After` (RFC 6585) posé par le throttler NIST ; tout le reste remonte
|
|
40
|
+
* au pipeline d'erreurs standard (500, détail loggé jamais fuité).
|
|
41
|
+
*
|
|
42
|
+
* @remarks Dérogation RFC 7235 §3.1 DÉLIBÉRÉE : les 401 de `login`/`me` ne
|
|
43
|
+
* portent PAS de `WWW-Authenticate` — aucun scheme HTTP ne décrit un cookie de
|
|
44
|
+
* session, et un challenge `Basic` mensonger déclencherait le popup natif du
|
|
45
|
+
* navigateur (l'anti-pattern que le BFF élimine). Les zones du firewall, elles,
|
|
46
|
+
* restent strictement conformes (challenge du premier authenticator qui en
|
|
47
|
+
* déclare un).
|
|
48
|
+
*/
|
|
49
|
+
declare class SessionAuthController extends Controller {
|
|
50
|
+
#private;
|
|
51
|
+
constructor(context: ContextType);
|
|
52
|
+
/** Login BFF : credential JSON présenté UNE fois → cookie de session opaque. */
|
|
53
|
+
login(): Promise<import("@nodefony/http").Http2Response | import("@nodefony/http").HttpResponse | import("@nodefony/http").WebsocketResponse>;
|
|
54
|
+
/**
|
|
55
|
+
* Second facteur (TOTP ou code de récupération) après un `login` ayant répondu
|
|
56
|
+
* `202 mfaRequired`. Body JSON `{ code }`. Succès → 200 + identité ; code
|
|
57
|
+
* absent/invalide → 401 (uniforme) ; trop de tentatives → 429 (Retry-After).
|
|
58
|
+
*/
|
|
59
|
+
loginTotp(): Promise<import("@nodefony/http").Http2Response | import("@nodefony/http").HttpResponse | import("@nodefony/http").WebsocketResponse>;
|
|
60
|
+
/** Détruit la session (storage + cookie). Toujours 200 (idempotent). */
|
|
61
|
+
logout(): Promise<import("@nodefony/http").Http2Response | import("@nodefony/http").HttpResponse | import("@nodefony/http").WebsocketResponse>;
|
|
62
|
+
/** Identité de la session courante, re-résolue (rôles frais), ou 401. */
|
|
63
|
+
me(): Promise<import("@nodefony/http").Http2Response | import("@nodefony/http").HttpResponse | import("@nodefony/http").WebsocketResponse>;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Monte les routes du flux de session BFF — appelé par le module framework à
|
|
67
|
+
* `onKernelReady`, seulement si le service `authFlow` est présent.
|
|
68
|
+
*
|
|
69
|
+
* Routes nommées `security.auth.*` (espace data plane
|
|
70
|
+
* `/nodefony/security/api/*`, convention « toujours ≥ 3 segments »). HTTP-only :
|
|
71
|
+
* la sémantique session-par-socket du pont WS-RPC n'est pas conçue (P6+).
|
|
72
|
+
*/
|
|
73
|
+
export declare function mountSessionAuthRoutes(frameworkModule: Module): void;
|
|
74
|
+
export default SessionAuthController;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { Module } from "nodefony";
|
|
2
|
+
import type { ContextType } from "@nodefony/http";
|
|
3
|
+
import Controller from "../src/Controller.js";
|
|
4
|
+
/**
|
|
5
|
+
* Vue MINIMALE du service d'émission de jetons (`tokenService`, posé au container
|
|
6
|
+
* par `@nodefony/security` P6 J4). Contrat structurel local : framework ne dépend
|
|
7
|
+
* JAMAIS de security — couplage par nom de service (comme `authFlow`/`adminBroker`).
|
|
8
|
+
*/
|
|
9
|
+
export interface ITokenIssuer {
|
|
10
|
+
/** `true` si l'émission JWT est opérationnelle (JWT activé + store prêt). */
|
|
11
|
+
isEnabled(): boolean;
|
|
12
|
+
/** Émet access+refresh après vérification d'un credential (grant M2M/CLI). */
|
|
13
|
+
issueForCredentials(identifier: unknown, password: unknown, scopes?: string[], resource?: unknown): Promise<unknown>;
|
|
14
|
+
/** Rotation d'un refresh token (nouveau couple, ancien révoqué). */
|
|
15
|
+
refresh(rawRefresh: unknown, resource?: unknown): Promise<unknown>;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Endpoints HTTP d'émission/rotation de **JWT** — adaptateurs MINCES au-dessus du
|
|
19
|
+
* service `tokenService` (`@nodefony/security`) :
|
|
20
|
+
*
|
|
21
|
+
* - `POST /nodefony/security/api/token` — body `{username, password, scope?}`
|
|
22
|
+
* → `{access_token, refresh_token, token_type, expires_in, scope}` (RFC 6749 §5.1)
|
|
23
|
+
* - `POST /nodefony/security/api/token/refresh` — body `{refresh_token}` → rotation
|
|
24
|
+
*
|
|
25
|
+
* Montés par le module framework UNIQUEMENT si le service `tokenService` existe
|
|
26
|
+
* (module security chargé + JWT activé) — sans lui, les routes n'existent pas
|
|
27
|
+
* (404, zéro surface).
|
|
28
|
+
*
|
|
29
|
+
* Le JWT part en **réponse JSON** (Bearer), JAMAIS en cookie ni en URL (anti
|
|
30
|
+
* fuite / non-révocable). Erreurs mappées par DUCK-TYPING sur `code` (401/429) —
|
|
31
|
+
* framework ne peut pas importer les classes d'erreur de security ; un 429
|
|
32
|
+
* reporte le `Retry-After` (RFC 6585) du throttler NIST.
|
|
33
|
+
*
|
|
34
|
+
* @remarks Dérogation RFC 7235 §3.1 (comme `SessionAuthController`) : ces 401 ne
|
|
35
|
+
* portent pas de `WWW-Authenticate` — l'endpoint d'émission n'est pas une
|
|
36
|
+
* ressource protégée par Bearer, il DÉLIVRE le Bearer.
|
|
37
|
+
*/
|
|
38
|
+
declare class TokenAuthController extends Controller {
|
|
39
|
+
#private;
|
|
40
|
+
constructor(context: ContextType);
|
|
41
|
+
/** Émission : credential présenté → couple access/refresh. */
|
|
42
|
+
token(): Promise<import("@nodefony/http").Http2Response | import("@nodefony/http").HttpResponse | import("@nodefony/http").WebsocketResponse>;
|
|
43
|
+
/** Rotation : refresh présenté → nouveau couple (ancien révoqué). */
|
|
44
|
+
refresh(): Promise<import("@nodefony/http").Http2Response | import("@nodefony/http").HttpResponse | import("@nodefony/http").WebsocketResponse>;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Monte les routes d'émission/rotation JWT — appelé par le module framework à
|
|
48
|
+
* `onKernelReady`, seulement si le service `tokenService` est présent.
|
|
49
|
+
*
|
|
50
|
+
* Routes nommées `security.token.*` (espace data plane `/nodefony/security/api/*`).
|
|
51
|
+
* `bypassFirewall: true` : ces routes SONT le mécanisme d'émission — l'aire data
|
|
52
|
+
* plane les matcherait sinon (obtenir un token exigerait d'être déjà authentifié,
|
|
53
|
+
* deadlock).
|
|
54
|
+
*/
|
|
55
|
+
export declare function mountTokenAuthRoutes(frameworkModule: Module): void;
|
|
56
|
+
export default TokenAuthController;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { Module } from "nodefony";
|
|
2
|
+
import type { ContextType } from "@nodefony/http";
|
|
3
|
+
import Controller from "../src/Controller.js";
|
|
4
|
+
/**
|
|
5
|
+
* Endpoints HTTP **self-service 2FA TOTP (P6.17)** — console « ma sécurité »,
|
|
6
|
+
* adaptateurs MINCES au-dessus du service `totp` (`@nodefony/security`) :
|
|
7
|
+
*
|
|
8
|
+
* - `POST /nodefony/security/api/totp/enroll` → `{secretBase32, otpauthUri}`
|
|
9
|
+
* (secret affiché 1× pour le QR ; secret PENDING tant que non confirmé)
|
|
10
|
+
* - `POST /nodefony/security/api/totp/confirm` — body `{code}` → `{recoveryCodes}`
|
|
11
|
+
* (active le 2FA ; codes de récupération affichés 1×)
|
|
12
|
+
* - `POST /nodefony/security/api/totp/disable` → `{ok}` (retire le 2FA)
|
|
13
|
+
* - `GET /nodefony/security/api/totp/status` → `{enabled, pending, recoveryCodesRemaining}`
|
|
14
|
+
*
|
|
15
|
+
* **PAS de `bypassFirewall`** (≠ login/totp) : ces routes vivent DANS la zone data
|
|
16
|
+
* plane `^/nodefony/[^/]+/api(/|$)` → **session BFF requise**. Le sujet est
|
|
17
|
+
* TOUJOURS l'utilisateur courant (`authFlow.me`), jamais un paramètre — on n'active
|
|
18
|
+
* /ne désactive jamais le 2FA d'autrui (anti-IDOR). Montés seulement si le service
|
|
19
|
+
* `totp` existe (security chargé + 2FA activé) → 404, zéro surface, sinon.
|
|
20
|
+
*/
|
|
21
|
+
declare class TotpController extends Controller {
|
|
22
|
+
#private;
|
|
23
|
+
constructor(context: ContextType);
|
|
24
|
+
/** Démarre l'enrôlement : secret + URI otpauth affichés une seule fois. */
|
|
25
|
+
enroll(): Promise<import("@nodefony/http").Http2Response | import("@nodefony/http").HttpResponse | import("@nodefony/http").WebsocketResponse>;
|
|
26
|
+
/** Confirme l'enrôlement par un 1ᵉʳ code → active + codes de récupération (1×). */
|
|
27
|
+
confirm(): Promise<import("@nodefony/http").Http2Response | import("@nodefony/http").HttpResponse | import("@nodefony/http").WebsocketResponse>;
|
|
28
|
+
/** Désactive le 2FA du porteur courant (retire secret + codes). */
|
|
29
|
+
disable(): Promise<import("@nodefony/http").Http2Response | import("@nodefony/http").HttpResponse | import("@nodefony/http").WebsocketResponse>;
|
|
30
|
+
/** État 2FA du porteur courant (alimente la console « ma sécurité »). */
|
|
31
|
+
status(): Promise<import("@nodefony/http").Http2Response | import("@nodefony/http").HttpResponse | import("@nodefony/http").WebsocketResponse>;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Monte les routes self-service 2FA — appelé par le module framework à
|
|
35
|
+
* `onKernelReady`, seulement si le service `totp` est présent.
|
|
36
|
+
*
|
|
37
|
+
* Routes nommées `security.totp.*` (espace data plane `/nodefony/security/api/*`).
|
|
38
|
+
* **Aucun `bypassFirewall`** : l'aire data plane (session BFF) les garde — gérer
|
|
39
|
+
* son 2FA exige d'être authentifié.
|
|
40
|
+
*/
|
|
41
|
+
export declare function mountTotpRoutes(frameworkModule: Module): void;
|
|
42
|
+
export default TotpController;
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import type { Module } from "nodefony";
|
|
2
|
+
import type { ContextType } from "@nodefony/http";
|
|
3
|
+
import Controller from "../src/Controller.js";
|
|
4
|
+
/**
|
|
5
|
+
* Options de cérémonie renvoyées au navigateur (forme JSON WebAuthn) — seul le
|
|
6
|
+
* `challenge` nous intéresse côté serveur (stocké en session, anti-replay) ; le
|
|
7
|
+
* reste est relayé tel quel au client.
|
|
8
|
+
*/
|
|
9
|
+
type CeremonyOptions = {
|
|
10
|
+
challenge: string;
|
|
11
|
+
} & Record<string, unknown>;
|
|
12
|
+
/**
|
|
13
|
+
* Vue MINIMALE du service `webauthn` (`@nodefony/security`) — couplage par NOM
|
|
14
|
+
* (framework ne dépend jamais de security ni de `@simplewebauthn`). Contrat
|
|
15
|
+
* structurel imposé par cast (`this.get<…>`), aucune liaison de build.
|
|
16
|
+
*/
|
|
17
|
+
export interface IWebAuthnService {
|
|
18
|
+
isEnabled(): boolean;
|
|
19
|
+
generateRegistrationOptions(user: {
|
|
20
|
+
id: string;
|
|
21
|
+
name: string;
|
|
22
|
+
displayName?: string;
|
|
23
|
+
}): Promise<CeremonyOptions>;
|
|
24
|
+
verifyRegistration(response: unknown, expectedChallenge: string, userId: string, requestOrigin?: string): Promise<{
|
|
25
|
+
id: string;
|
|
26
|
+
}>;
|
|
27
|
+
generateAuthenticationOptions(userId?: string): Promise<CeremonyOptions>;
|
|
28
|
+
verifyAuthentication(response: unknown, expectedChallenge: string, requestOrigin?: string): Promise<{
|
|
29
|
+
userId: string;
|
|
30
|
+
}>;
|
|
31
|
+
listUserCredentials(userId: string): Promise<Array<{
|
|
32
|
+
id: string;
|
|
33
|
+
transports: string[];
|
|
34
|
+
backupState: boolean;
|
|
35
|
+
createdAt: number;
|
|
36
|
+
lastUsedAt: number | null;
|
|
37
|
+
}>>;
|
|
38
|
+
removeUserCredential(userId: string, credentialId: string): Promise<boolean>;
|
|
39
|
+
}
|
|
40
|
+
/** Vue minimale d'une session — porte le challenge de cérémonie (anti-replay). */
|
|
41
|
+
export interface IWebAuthnSession {
|
|
42
|
+
get(key: string): unknown;
|
|
43
|
+
set(key: string, value: unknown): unknown;
|
|
44
|
+
save(): Promise<unknown>;
|
|
45
|
+
}
|
|
46
|
+
/** Vue minimale du flux de session BFF (`authFlow`) consommée ici. */
|
|
47
|
+
export interface IWebAuthnBffFlow {
|
|
48
|
+
me(context: ContextType): Promise<{
|
|
49
|
+
username: string;
|
|
50
|
+
} | null>;
|
|
51
|
+
/**
|
|
52
|
+
* @param reason - facteur d'authentification journalisé par l'audit
|
|
53
|
+
* (`"webauthn"` ici). Omis, il retombe sur `"federated"`, qui ne distingue
|
|
54
|
+
* plus une passkey d'un login social dans le journal.
|
|
55
|
+
*/
|
|
56
|
+
establishSessionFor(context: ContextType, identifier: string, reason?: string): Promise<unknown>;
|
|
57
|
+
/** Garantit une session (la démarre si déconnecté) pour porter le challenge. */
|
|
58
|
+
ensureSession(context: ContextType): Promise<IWebAuthnSession | null>;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Endpoints HTTP des cérémonies **WebAuthn / passkeys** (P6 J9) — adaptateurs
|
|
62
|
+
* MINCES au-dessus du service `webauthn` (`@nodefony/security`) :
|
|
63
|
+
*
|
|
64
|
+
* - `POST /nodefony/security/api/webauthn/register/options` — défi de création
|
|
65
|
+
* (utilisateur DÉJÀ connecté : lie un passkey à son compte)
|
|
66
|
+
* - `POST /nodefony/security/api/webauthn/register/verify` — vérifie + stocke
|
|
67
|
+
* - `POST /nodefony/security/api/webauthn/login/options` — défi d'assertion
|
|
68
|
+
* - `POST /nodefony/security/api/webauthn/login/verify` — vérifie + ouvre
|
|
69
|
+
* la session BFF (l'empreinte remplace le mot de passe)
|
|
70
|
+
*
|
|
71
|
+
* Le **challenge** est stocké côté serveur en session (jamais rejouable) entre
|
|
72
|
+
* `options` et `verify`.
|
|
73
|
+
*
|
|
74
|
+
* **Deux conditions distinctes, deux réponses distinctes** : les routes ne sont
|
|
75
|
+
* montées que si le service `webauthn` est dans le container (`framework/index.ts:400`),
|
|
76
|
+
* c'est-à-dire dès que `@nodefony/security` est chargé — sans security, **404**,
|
|
77
|
+
* zéro surface. Le service reste enregistré même passkeys DÉSACTIVÉS
|
|
78
|
+
* (`@services` l'instancie inconditionnellement) : dans ce cas les routes existent
|
|
79
|
+
* et répondent **503** (`isEnabled()` faux). Ne pas lire un 503 comme « route
|
|
80
|
+
* absente », ni un 404 comme « passkeys coupés ».
|
|
81
|
+
*
|
|
82
|
+
* @remarks `bypassFirewall` : `login/*` précède toute authentification ;
|
|
83
|
+
* `register/*` exige une session active, vérifiée ICI (`me()` → 401), pas par le
|
|
84
|
+
* firewall (qui, sur l'aire data plane, déclencherait un deadlock identique au
|
|
85
|
+
* login BFF).
|
|
86
|
+
*/
|
|
87
|
+
declare class WebAuthnController extends Controller {
|
|
88
|
+
#private;
|
|
89
|
+
constructor(context: ContextType);
|
|
90
|
+
/** Défi d'enregistrement — l'utilisateur connecté ajoute un passkey. */
|
|
91
|
+
registerOptions(): Promise<import("@nodefony/http").Http2Response | import("@nodefony/http").HttpResponse | import("@nodefony/http").WebsocketResponse>;
|
|
92
|
+
/** Vérifie la réponse d'enregistrement et persiste le credential. */
|
|
93
|
+
registerVerify(): Promise<import("@nodefony/http").Http2Response | import("@nodefony/http").HttpResponse | import("@nodefony/http").WebsocketResponse>;
|
|
94
|
+
/**
|
|
95
|
+
* Défi d'authentification. **Le ciblage ne vient jamais de la requête** : un
|
|
96
|
+
* appelant anonyme obtient un défi découvrable (`allowCredentials` omis), un
|
|
97
|
+
* appelant déjà authentifié obtient le sien (ré-authentification / step-up).
|
|
98
|
+
*
|
|
99
|
+
* @remarks Route en `bypassFirewall` : n'importe qui peut la poster. Peupler
|
|
100
|
+
* `allowCredentials` depuis un identifiant fourni dirait deux choses à cet
|
|
101
|
+
* inconnu — que le compte porte une passkey, et **lesquelles** (W3C WebAuthn
|
|
102
|
+
* L3, « Privacy leak via credential IDs » : un `credentialId` est un
|
|
103
|
+
* identifiant corrélable, exposé il dés-anonymise entre sites et confirme
|
|
104
|
+
* une hypothèse d'identité avec un accès momentané à l'authenticator). La
|
|
105
|
+
* spec propose deux remèdes, ce sont les deux régimes ci-dessous : les
|
|
106
|
+
* credentials découvrables pour l'anonyme, une **authentification préalable**
|
|
107
|
+
* quand on cible vraiment. Verrouillé par
|
|
108
|
+
* `tests/unit/webauthnLoginOptionsPrivacy.test.ts`.
|
|
109
|
+
*/
|
|
110
|
+
loginOptions(): Promise<import("@nodefony/http").Http2Response | import("@nodefony/http").HttpResponse | import("@nodefony/http").WebsocketResponse>;
|
|
111
|
+
/** Vérifie l'assertion (empreinte) et OUVRE la session BFF. */
|
|
112
|
+
loginVerify(): Promise<import("@nodefony/http").Http2Response | import("@nodefony/http").HttpResponse | import("@nodefony/http").WebsocketResponse>;
|
|
113
|
+
/** Liste les passkeys de l'utilisateur courant (console « mes appareils »). */
|
|
114
|
+
listCredentials(): Promise<import("@nodefony/http").Http2Response | import("@nodefony/http").HttpResponse | import("@nodefony/http").WebsocketResponse>;
|
|
115
|
+
/** Supprime une passkey DU porteur courant (sinon 404 — anti-IDOR/anti-énumération). */
|
|
116
|
+
removeCredential(id: unknown): Promise<import("@nodefony/http").Http2Response | import("@nodefony/http").HttpResponse | import("@nodefony/http").WebsocketResponse>;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Monte les routes des cérémonies WebAuthn — appelé par le module framework à
|
|
120
|
+
* `onKernelReady`, seulement si le service `webauthn` est présent.
|
|
121
|
+
*/
|
|
122
|
+
export declare function mountWebAuthnRoutes(frameworkModule: Module): void;
|
|
123
|
+
export default WebAuthnController;
|