@nodefony/security 10.0.0-beta.4 → 10.0.0-beta.6
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/dist/_virtual/{_@oxc-project_runtime@0.155.0 → _@oxc-project_runtime@0.157.0}/helpers/esm/decorate.js +1 -1
- package/dist/index.js +60 -60
- package/dist/nodefony/config/config.js +13 -5
- package/dist/nodefony/contracts/IWebhookPayload.js +1 -0
- package/dist/nodefony/errors/index.js +4 -4
- package/dist/nodefony/keycloak/keycloakAdmin.js +3 -3
- package/dist/nodefony/keycloak/security-keycloak-realm.js +1 -1
- package/dist/nodefony/service/accessTokenVerifier.js +5 -5
- package/dist/nodefony/service/apiKeys.js +8 -4
- package/dist/nodefony/service/authFlow.js +3 -3
- package/dist/nodefony/service/authorization.js +1 -1
- package/dist/nodefony/service/firewall.js +233 -41
- package/dist/nodefony/service/keycloak.js +1 -1
- package/dist/nodefony/service/oauth2.js +8 -4
- package/dist/nodefony/service/securityHeaders.js +340 -47
- package/dist/nodefony/service/tokenService.js +8 -8
- package/dist/nodefony/service/totp.js +3 -3
- package/dist/nodefony/service/webAuthn.js +1 -1
- package/dist/nodefony/service/webhooks.js +11 -4
- package/dist/nodefony/src/admin/SecurityAdminApi.js +19 -0
- package/dist/nodefony/src/authenticator/ApiKeyAuthenticator.js +1 -1
- package/dist/nodefony/src/authenticator/ExternalSubjectResolver.js +31 -7
- package/dist/nodefony/src/authenticator/FirewallRealtimeAuthenticator.js +62 -20
- package/dist/nodefony/src/authenticator/JwtAuthenticator.js +5 -2
- package/dist/nodefony/src/authenticator/SessionAuthenticator.js +1 -1
- package/dist/nodefony/src/authenticator/authenticatorRegistry.js +1 -1
- package/dist/nodefony/src/csp.js +265 -10
- package/dist/nodefony/src/cspReports.js +307 -0
- package/dist/nodefony/src/oauth/clientCredentials.js +1 -1
- package/dist/nodefony/src/oauth/metadata.js +1 -1
- package/dist/nodefony/src/oauth/oauthProviderRegistry.js +17 -4
- package/dist/nodefony/src/oauth/providers/github.js +1 -1
- package/dist/nodefony/src/realtime/UserRealtimeToken.js +59 -2
- package/dist/nodefony/src/realtime/frameAuthorizer.js +7 -6
- package/dist/nodefony/src/token/RemoteJwtVerifier.js +8 -2
- package/dist/nodefony/src/token/tokenTimeClaims.js +34 -0
- package/dist/nodefony/src/totp/totpOperations.js +1 -1
- package/dist/nodefony/src/voter/RoleVoter.js +1 -1
- package/dist/nodefony/src/webhook/WebhookDispatcher.js +71 -17
- package/dist/nodefony/src/webhook/webhookDelivery.js +3 -3
- package/dist/nodefony/testing/index.js +98 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/nodefony/config/config.d.ts +2 -0
- package/dist/types/nodefony/contracts/IApiKey.d.ts +8 -2
- package/dist/types/nodefony/contracts/IFirewall.d.ts +15 -2
- package/dist/types/nodefony/contracts/ITokenStore.d.ts +7 -3
- package/dist/types/nodefony/contracts/IWebhookEndpoint.d.ts +13 -3
- package/dist/types/nodefony/contracts/IWebhookPayload.d.ts +24 -0
- package/dist/types/nodefony/service/apiKeys.d.ts +3 -0
- package/dist/types/nodefony/service/firewall.d.ts +28 -7
- package/dist/types/nodefony/service/securityHeaders.d.ts +82 -11
- package/dist/types/nodefony/service/webhooks.d.ts +7 -1
- package/dist/types/nodefony/src/admin/SecurityAdminApi.d.ts +3 -0
- package/dist/types/nodefony/src/authenticator/ExternalSubjectResolver.d.ts +11 -0
- package/dist/types/nodefony/src/csp.d.ts +142 -0
- package/dist/types/nodefony/src/cspReports.d.ts +73 -0
- package/dist/types/nodefony/src/oauth/oauthProviderRegistry.d.ts +15 -0
- package/dist/types/nodefony/src/realtime/UserRealtimeToken.d.ts +35 -1
- package/dist/types/nodefony/src/realtime/frameAuthorizer.d.ts +2 -2
- package/dist/types/nodefony/src/realtime/realtimeContracts.d.ts +25 -17
- package/dist/types/nodefony/src/token/tokenTimeClaims.d.ts +32 -0
- package/dist/types/nodefony/src/webhook/WebhookDispatcher.d.ts +36 -1
- package/dist/types/nodefony/testing/index.d.ts +57 -0
- package/docs/api-keys.md +3 -1
- package/docs/authenticators.md +6 -4
- package/docs/csrf.md +3 -2
- package/docs/external-jwt.md +5 -1
- package/docs/firewall.md +3 -2
- package/docs/headers.md +427 -71
- package/docs/login-page.md +4 -3
- package/docs/totp.md +2 -2
- package/docs/webauthn.md +2 -2
- package/docs/webhooks.md +57 -11
- package/package.json +20 -14
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { webhookSignatureHeaders } from "./webhookSignature.js";
|
|
2
|
+
import { MAX_CHANNEL_PATTERN_WILDCARDS, compileChannelPattern, countWildcards } from "nodefony";
|
|
2
3
|
// nodefony/src/webhook/WebhookDispatcher.ts
|
|
3
4
|
/**
|
|
4
5
|
* Dispatcher de webhooks sortants — abonné au journal d'audit, il filtre les
|
|
@@ -20,15 +21,60 @@ import { webhookSignatureHeaders } from "./webhookSignature.js";
|
|
|
20
21
|
*/
|
|
21
22
|
const MAX_BACKOFF_MS = 3e5;
|
|
22
23
|
const BASE_BACKOFF_MS = 5e3;
|
|
23
|
-
/**
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
24
|
+
/**
|
|
25
|
+
* Motifs compilés par liste d'abonnement — une fois par tableau `events`, jamais par
|
|
26
|
+
* événement. La clé est le tableau lui-même : un abonnement modifié porte un tableau
|
|
27
|
+
* neuf, donc se recompile, et l'ancien part avec lui au ramasse-miettes.
|
|
28
|
+
*/
|
|
29
|
+
const compiledSubscriptions = /* @__PURE__ */ new WeakMap();
|
|
30
|
+
/**
|
|
31
|
+
* Rend les motifs compilés d'une liste d'abonnement, compilés à sa première lecture puis relus du cache.
|
|
32
|
+
*
|
|
33
|
+
* Deux appels sur le MÊME tableau rendent la même liste : c'est ce qui garantit
|
|
34
|
+
* qu'un abonnement n'est jamais recompilé par événement. Un motif trop large
|
|
35
|
+
* (refusé par `compileChannelPattern`) n'y figure pas.
|
|
36
|
+
*
|
|
37
|
+
* @param patterns - les motifs de l'abonnement (`IWebhookEndpoint.events`).
|
|
38
|
+
* @returns les expressions compilées, une par motif valide.
|
|
39
|
+
*/
|
|
40
|
+
function compiledPatterns(patterns) {
|
|
41
|
+
let compiled = compiledSubscriptions.get(patterns);
|
|
42
|
+
if (compiled === void 0) {
|
|
43
|
+
const list = [];
|
|
44
|
+
for (const p of patterns) {
|
|
45
|
+
const re = compileChannelPattern(p);
|
|
46
|
+
if (re !== null) list.push(re);
|
|
47
|
+
}
|
|
48
|
+
compiled = list;
|
|
49
|
+
compiledSubscriptions.set(patterns, compiled);
|
|
28
50
|
}
|
|
51
|
+
return compiled;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Dit si un abonnement webhook désigne une action d'audit, selon la grammaire commune aux canaux et aux webhooks (ADR-0023 W1).
|
|
55
|
+
*
|
|
56
|
+
* `*` vaut n'importe quelle suite de caractères : `*` désigne tout, `login.*` les
|
|
57
|
+
* actions qui commencent par `login.`, `commande:*` aussi bien que `*:créée`
|
|
58
|
+
* (`compileChannelPattern`, cœur). Chaque liste n'est compilée qu'une fois.
|
|
59
|
+
*
|
|
60
|
+
* @param patterns - les motifs de l'abonnement (`IWebhookEndpoint.events`).
|
|
61
|
+
* @param action - le nom de l'action d'audit.
|
|
62
|
+
* @returns `true` si un motif au moins désigne l'action.
|
|
63
|
+
*/
|
|
64
|
+
function matchesSubscription(patterns, action) {
|
|
65
|
+
for (const re of compiledPatterns(patterns)) if (re.test(action)) return true;
|
|
29
66
|
return false;
|
|
30
67
|
}
|
|
31
68
|
/**
|
|
69
|
+
* Refuse une liste d'abonnement dont un motif porte trop de `*`, en le nommant.
|
|
70
|
+
*
|
|
71
|
+
* @param patterns - les motifs proposés à l'enregistrement ou à la mise à jour.
|
|
72
|
+
* @throws Error si un motif dépasse `MAX_CHANNEL_PATTERN_WILDCARDS` étoiles.
|
|
73
|
+
*/
|
|
74
|
+
function assertSubscriptionPatterns(patterns) {
|
|
75
|
+
for (const p of patterns) if (countWildcards(p) > MAX_CHANNEL_PATTERN_WILDCARDS) throw new Error(`webhooks: le motif « ${p} » porte ${countWildcards(p)} caractères « * » (maximum ${MAX_CHANNEL_PATTERN_WILDCARDS}) — un motif en demande un, deux au pire.`);
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
32
78
|
* Classe le résultat d'une livraison : 2xx = succès ; réseau/timeout/429/408/5xx =
|
|
33
79
|
* réessayable ; 3xx/4xx (config cliente erronée) = échec définitif (pas de retry).
|
|
34
80
|
*/
|
|
@@ -71,7 +117,8 @@ var WebhookDispatcher = class {
|
|
|
71
117
|
this.#enqueue({
|
|
72
118
|
ep,
|
|
73
119
|
event,
|
|
74
|
-
attempt: 0
|
|
120
|
+
attempt: 0,
|
|
121
|
+
messageId: null
|
|
75
122
|
});
|
|
76
123
|
}
|
|
77
124
|
}
|
|
@@ -112,17 +159,20 @@ var WebhookDispatcher = class {
|
|
|
112
159
|
}
|
|
113
160
|
/** Signe + livre une tentative ; gère succès / retry / échec définitif. */
|
|
114
161
|
async #process(job) {
|
|
115
|
-
const {
|
|
162
|
+
const { event, attempt } = job;
|
|
163
|
+
const ep = this.#deps.currentEndpoint(job.ep.id);
|
|
164
|
+
if (ep === null || !ep.enabled || !matchesSubscription(ep.events, event.action)) return;
|
|
116
165
|
try {
|
|
117
|
-
const id = this.#deps.newMessageId();
|
|
166
|
+
const id = job.messageId ?? this.#deps.newMessageId();
|
|
118
167
|
const nowMs = this.#deps.now();
|
|
119
168
|
const tsS = Math.floor(nowMs / 1e3);
|
|
120
|
-
const
|
|
169
|
+
const payload = {
|
|
121
170
|
id,
|
|
122
171
|
timestamp: new Date(nowMs).toISOString(),
|
|
123
172
|
type: event.action,
|
|
124
173
|
data: event
|
|
125
|
-
}
|
|
174
|
+
};
|
|
175
|
+
const body = JSON.stringify(payload);
|
|
126
176
|
const headers = {
|
|
127
177
|
"content-type": "application/json",
|
|
128
178
|
"user-agent": "Nodefony-Webhooks/1.0",
|
|
@@ -132,7 +182,7 @@ var WebhookDispatcher = class {
|
|
|
132
182
|
try {
|
|
133
183
|
addresses = await this.#deps.resolveTarget(ep.url);
|
|
134
184
|
} catch (e) {
|
|
135
|
-
const error = `ssrf: ${e.message}`;
|
|
185
|
+
const error = `ssrf: ${e instanceof Error ? e.message : String(e)}`;
|
|
136
186
|
await this.#deps.markDelivery(ep.id, {
|
|
137
187
|
ok: false,
|
|
138
188
|
status: null,
|
|
@@ -157,7 +207,7 @@ var WebhookDispatcher = class {
|
|
|
157
207
|
allowHttp: this.#deps.policy.allowHttp
|
|
158
208
|
});
|
|
159
209
|
if (classifyDelivery(result) === "retry" && attempt < this.#deps.policy.maxRetries) {
|
|
160
|
-
this.#scheduleRetry(job);
|
|
210
|
+
this.#scheduleRetry(job, id);
|
|
161
211
|
return;
|
|
162
212
|
}
|
|
163
213
|
await this.#deps.markDelivery(ep.id, result);
|
|
@@ -173,11 +223,14 @@ var WebhookDispatcher = class {
|
|
|
173
223
|
responseBody: result.responseBody ?? null
|
|
174
224
|
});
|
|
175
225
|
} catch (e) {
|
|
176
|
-
this.#deps.log(`webhook dispatch ${ep.id}: ${e.message}`);
|
|
226
|
+
this.#deps.log(`webhook dispatch ${ep.id}: ${e instanceof Error ? e.message : String(e)}`);
|
|
177
227
|
}
|
|
178
228
|
}
|
|
179
|
-
/**
|
|
180
|
-
|
|
229
|
+
/**
|
|
230
|
+
* Replanifie la livraison après backoff (repasse par la file bornée), sous le
|
|
231
|
+
* MÊME `webhook-id` que la tentative échouée.
|
|
232
|
+
*/
|
|
233
|
+
#scheduleRetry(job, messageId) {
|
|
181
234
|
if (this.#stopped) return;
|
|
182
235
|
this.#timers ??= /* @__PURE__ */ new Set();
|
|
183
236
|
const cancel = this.#deps.schedule(() => {
|
|
@@ -185,7 +238,8 @@ var WebhookDispatcher = class {
|
|
|
185
238
|
this.#enqueue({
|
|
186
239
|
ep: job.ep,
|
|
187
240
|
event: job.event,
|
|
188
|
-
attempt: job.attempt + 1
|
|
241
|
+
attempt: job.attempt + 1,
|
|
242
|
+
messageId
|
|
189
243
|
});
|
|
190
244
|
}, backoffMs(job.attempt));
|
|
191
245
|
this.#timers.add(cancel);
|
|
@@ -205,4 +259,4 @@ var WebhookDispatcher = class {
|
|
|
205
259
|
this.#queue = null;
|
|
206
260
|
}
|
|
207
261
|
};
|
|
208
|
-
export { WebhookDispatcher, backoffMs, classifyDelivery, matchesSubscription };
|
|
262
|
+
export { WebhookDispatcher, assertSubscriptionPatterns, backoffMs, classifyDelivery, compiledPatterns, matchesSubscription };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
+
import { request } from "node:http";
|
|
1
2
|
import { Buffer } from "node:buffer";
|
|
2
3
|
import { isIP } from "node:net";
|
|
3
|
-
import { request } from "node:https";
|
|
4
|
-
import { request as request$1 } from "node:http";
|
|
4
|
+
import { request as request$1 } from "node:https";
|
|
5
5
|
// nodefony/src/webhook/webhookDelivery.ts
|
|
6
6
|
/**
|
|
7
7
|
* Émetteur HTTP d'une livraison webhook — `node:http(s)` natif (0 dép). Trois
|
|
@@ -49,7 +49,7 @@ function deliverWebhook(url, body, headers, opts) {
|
|
|
49
49
|
else callback(null, pinned, family);
|
|
50
50
|
} : void 0;
|
|
51
51
|
const payload = Buffer.from(body, "utf8");
|
|
52
|
-
const requestFn = isHttps ? request : request
|
|
52
|
+
const requestFn = isHttps ? request$1 : request;
|
|
53
53
|
return new Promise((resolve) => {
|
|
54
54
|
let settled = false;
|
|
55
55
|
const done = (r) => {
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { RoleHierarchyWalker } from "../src/RoleHierarchyWalker.js";
|
|
2
|
+
import { DEFAULT_SYSTEM_RULES, buildFrameAuthorizer } from "../src/realtime/frameAuthorizer.js";
|
|
3
|
+
// nodefony/testing/index.ts
|
|
4
|
+
/**
|
|
5
|
+
* Une identité de connexion AUTHENTIFIÉE, telle que le pare-feu l'aurait posée
|
|
6
|
+
* au handshake.
|
|
7
|
+
*
|
|
8
|
+
* Pour éprouver un visiteur anonyme, ne rien passer au harnais : il pose alors
|
|
9
|
+
* le jeton anonyme, comme le repli Zero Trust du runtime.
|
|
10
|
+
*
|
|
11
|
+
* @param options - identifiant, rôles, scopes, utilisateur.
|
|
12
|
+
* @returns un jeton à passer en `identity` au harnais temps réel.
|
|
13
|
+
*/
|
|
14
|
+
function createTestIdentity(options = {}) {
|
|
15
|
+
const identifier = options.identifier ?? "alice";
|
|
16
|
+
const roles = options.roles ?? ["ROLE_USER"];
|
|
17
|
+
return new TestToken(identifier, options.id ?? identifier, roles, options.scopes ?? [], options.user ?? {
|
|
18
|
+
identifier,
|
|
19
|
+
roles: [...roles]
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
/** Le jeton que rend {@link createTestIdentity}. */
|
|
23
|
+
var TestToken = class {
|
|
24
|
+
identifier;
|
|
25
|
+
id;
|
|
26
|
+
roles;
|
|
27
|
+
scopes;
|
|
28
|
+
user;
|
|
29
|
+
type = "session";
|
|
30
|
+
#identity = null;
|
|
31
|
+
constructor(identifier, id, roles, scopes, user) {
|
|
32
|
+
this.identifier = identifier;
|
|
33
|
+
this.id = id;
|
|
34
|
+
this.roles = roles;
|
|
35
|
+
this.scopes = scopes;
|
|
36
|
+
this.user = user;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Identité d'admission gelée — ce que reçoit la règle `allow` d'un canal.
|
|
40
|
+
*
|
|
41
|
+
* Même forme que celle du jeton du pare-feu (`UserRealtimeToken.getIdentity`) :
|
|
42
|
+
* sans elle, tout canal à `allow` refusait la connexion d'essai, et une
|
|
43
|
+
* application ne pouvait éprouver un salon privé qu'en écrivant son propre jeton.
|
|
44
|
+
*/
|
|
45
|
+
getIdentity() {
|
|
46
|
+
if (this.#identity !== null) return this.#identity;
|
|
47
|
+
this.#identity = Object.freeze({
|
|
48
|
+
type: this.type,
|
|
49
|
+
authenticated: true,
|
|
50
|
+
id: this.id,
|
|
51
|
+
userIdentifier: this.identifier,
|
|
52
|
+
roles: Object.freeze([...this.roles]),
|
|
53
|
+
scopes: Object.freeze([...this.scopes]),
|
|
54
|
+
issuer: null,
|
|
55
|
+
account: true
|
|
56
|
+
});
|
|
57
|
+
return this.#identity;
|
|
58
|
+
}
|
|
59
|
+
getUserIdentifier() {
|
|
60
|
+
return this.identifier;
|
|
61
|
+
}
|
|
62
|
+
isAuthenticated() {
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
getRoles() {
|
|
66
|
+
return [...this.roles];
|
|
67
|
+
}
|
|
68
|
+
getScopes() {
|
|
69
|
+
return [...this.scopes];
|
|
70
|
+
}
|
|
71
|
+
getAttribute(key) {
|
|
72
|
+
return key === "user" ? this.user : void 0;
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
/**
|
|
76
|
+
* Le VRAI verrou de frame du pare-feu, pour le harnais temps réel.
|
|
77
|
+
*
|
|
78
|
+
* C'est lui qui applique `@RealtimeChannel(nom, { roles })`,
|
|
79
|
+
* `@RealtimeAction(nom, { roles })` et les canaux système : sans lui, une
|
|
80
|
+
* politique déclarée n'est appliquée par personne — en test comme au runtime.
|
|
81
|
+
*
|
|
82
|
+
* @param channels - ce qui connaît les politiques déclarées : le hub temps réel
|
|
83
|
+
* (`getRealtimeHub()`), lu à CHAQUE frame — les politiques que le controller
|
|
84
|
+
* enregistre à sa construction y sont donc vues.
|
|
85
|
+
* @param options - hiérarchie de rôles de l'application.
|
|
86
|
+
* @returns le verrou à passer en `frameAuthorizer` au harnais.
|
|
87
|
+
*/
|
|
88
|
+
function createTestFrameAuthorizer(channels, options = {}) {
|
|
89
|
+
const walker = new RoleHierarchyWalker(options.roleHierarchy ?? {});
|
|
90
|
+
return buildFrameAuthorizer({
|
|
91
|
+
matchPath: () => null,
|
|
92
|
+
hasRole: (userRoles, required) => walker.hasRole(userRoles, required)
|
|
93
|
+
}, {
|
|
94
|
+
channelResolver: channels,
|
|
95
|
+
systemRules: DEFAULT_SYSTEM_RULES
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
export { createTestFrameAuthorizer, createTestIdentity };
|
package/dist/types/index.d.ts
CHANGED
|
@@ -168,6 +168,7 @@ export { WEBHOOK_SORTABLE_FIELDS, WEBHOOK_DEFAULT_ORDER, } from "./nodefony/src/
|
|
|
168
168
|
export { registerWebhookStore, getWebhookStoreFactory, listWebhookStores, } from "./nodefony/src/webhook/webhookStoreRegistry.js";
|
|
169
169
|
export type { WebhookStoreFactory, IWebhookStoreFactoryContext, } from "./nodefony/src/webhook/webhookStoreRegistry.js";
|
|
170
170
|
export type { IWebhookEndpoint, WebhookEndpointUpdate, WebhookEndpointSummary, } from "./nodefony/contracts/IWebhookEndpoint.js";
|
|
171
|
+
export type { IWebhookPayload } from "./nodefony/contracts/IWebhookPayload.js";
|
|
171
172
|
export type { IWebhookListQuery, IWebhookStore, } from "./nodefony/contracts/IWebhookStore.js";
|
|
172
173
|
export { assertPublicUrl, isBlockedAddress, } from "./nodefony/src/net/ssrfGuard.js";
|
|
173
174
|
export type { IAssertPublicUrlOptions, IPublicUrlResult, DnsResolver, } from "./nodefony/src/net/ssrfGuard.js";
|
|
@@ -85,7 +85,9 @@ export declare const securityConfigSchema: z.ZodObject<{
|
|
|
85
85
|
hsts: z.ZodDefault<z.ZodBoolean>;
|
|
86
86
|
hstsMaxAgeS: z.ZodDefault<z.ZodNumber>;
|
|
87
87
|
csp: z.ZodDefault<z.ZodString>;
|
|
88
|
+
cspSources: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodRecord<z.ZodString, z.ZodArray<z.ZodString>>>>;
|
|
88
89
|
cspNonces: z.ZodDefault<z.ZodBoolean>;
|
|
90
|
+
cspReport: z.ZodDefault<z.ZodUnion<readonly [z.ZodBoolean, z.ZodString]>>;
|
|
89
91
|
frameguard: z.ZodDefault<z.ZodEnum<{
|
|
90
92
|
deny: "deny";
|
|
91
93
|
sameorigin: "sameorigin";
|
|
@@ -16,7 +16,10 @@ export interface IApiKeyView {
|
|
|
16
16
|
subjectId: string;
|
|
17
17
|
/** Discriminant du porteur. */
|
|
18
18
|
subjectType: "user" | "service";
|
|
19
|
-
/**
|
|
19
|
+
/**
|
|
20
|
+
* Périmètre d'un locataire — réservé, toujours `null` tant que le multi-tenant
|
|
21
|
+
* n'existe pas (`createForSubject` refuse une valeur).
|
|
22
|
+
*/
|
|
20
23
|
tenantId: string | null;
|
|
21
24
|
/** Création (epoch ms). */
|
|
22
25
|
createdAt: number;
|
|
@@ -70,6 +73,9 @@ export interface ICreateApiKeyOptions {
|
|
|
70
73
|
scopes?: string[];
|
|
71
74
|
/** Durée de vie en jours ; `null` = sans expiration ; omis = défaut config. */
|
|
72
75
|
expiresInDays?: number | null;
|
|
73
|
-
/**
|
|
76
|
+
/**
|
|
77
|
+
* Périmètre d'un locataire — réservé, **refusé s'il est rempli** (`ApiKeyError` 400)
|
|
78
|
+
* tant que le multi-tenant n'existe pas. Absent ou `null` : clé sans périmètre.
|
|
79
|
+
*/
|
|
74
80
|
tenantId?: string | null;
|
|
75
81
|
}
|
|
@@ -28,13 +28,26 @@ export interface IFirewall extends IFirewallGate {
|
|
|
28
28
|
handleCors(context: ContextType): number | undefined;
|
|
29
29
|
/**
|
|
30
30
|
* Pose les en-têtes de sécurité applicatifs (CSP, Referrer-Policy, COOP/COEP/
|
|
31
|
-
* CORP, Permissions-Policy) sur la réponse
|
|
31
|
+
* CORP, Permissions-Policy) sur la réponse, selon son type (ADR-0024 §C11).
|
|
32
|
+
* No-op si désactivés / hors HTTP.
|
|
33
|
+
*
|
|
34
|
+
* @param contentType - `Content-Type` de la réponse ; `null` = sans contenu ;
|
|
35
|
+
* omis = type inconnu (politique complète).
|
|
36
|
+
* @param servedAsFile - corps servi tel quel (fichier statique, relais), qui
|
|
37
|
+
* ne peut pas porter de nonce : un document y reçoit la politique sans
|
|
38
|
+
* nonce ni `'strict-dynamic'` (ADR-0024 §C1).
|
|
32
39
|
*/
|
|
33
|
-
applySecurityHeaders(context: ContextType): void;
|
|
40
|
+
applySecurityHeaders(context: ContextType, contentType?: string | null, servedAsFile?: boolean): void;
|
|
34
41
|
/**
|
|
35
42
|
* Déclare des directives CSP additionnelles pour un module (ex. `@nodefony/frontend`
|
|
36
43
|
* en dev : origines Vite + `'unsafe-eval'`). Fusionnées dans le CSP de base et
|
|
37
44
|
* recomposées (re-split nonce) au (dé)enregistrement — jamais par requête.
|
|
45
|
+
*
|
|
46
|
+
* Ne lève jamais : une contribution MALFORMÉE est ignorée en entier (journal
|
|
47
|
+
* ERROR), une source TROP LARGE (`*`, `https:`, `https://*`) est retirée
|
|
48
|
+
* (journal WARNING). La politique ne devient jamais plus permissive que demandé,
|
|
49
|
+
* et l'application démarre. Ce que l'application écrit elle-même (sa config,
|
|
50
|
+
* ses `@Csp`) ne passe pas par ce filtre.
|
|
38
51
|
*/
|
|
39
52
|
registerCspOrigins(moduleName: string, fragment: CspFragment): void;
|
|
40
53
|
/** Retire les directives CSP d'un module (ex. `stopDev` du frontend). */
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* 3. **seuil par porteur** (`invalidBefore`) — révocation EN MASSE (tout access
|
|
15
15
|
* auto-porté émis avant un instant T est rejeté → « déconnexion globale » / ban).
|
|
16
16
|
*/
|
|
17
|
-
import type { IPage, IPageQuery, ISortableSource } from "nodefony";
|
|
17
|
+
import type { IPage, IPageQuery, ISortableSource, TokenStatus } from "nodefony";
|
|
18
18
|
/** Raison de révocation d'un jeton — tracée pour l'audit de sécurité. */
|
|
19
19
|
export type TokenRevokeReason = "logout" | "rotated" | "reuse_detected" | "manual" | "compromised" | "subject_disabled" | "expired_cleanup";
|
|
20
20
|
/**
|
|
@@ -153,8 +153,12 @@ export interface ITokenListQuery extends IPageQuery {
|
|
|
153
153
|
*/
|
|
154
154
|
status?: TokenStatus;
|
|
155
155
|
}
|
|
156
|
-
/**
|
|
157
|
-
|
|
156
|
+
/**
|
|
157
|
+
* État de vie d'un jeton — partition exhaustive, évaluée révoqué → expiré → actif.
|
|
158
|
+
* Défini au cœur (le temps réel le lit aussi, sans dépendre de la sécurité) et
|
|
159
|
+
* réexporté ici sous le même nom.
|
|
160
|
+
*/
|
|
161
|
+
export type { TokenStatus } from "nodefony";
|
|
158
162
|
/**
|
|
159
163
|
* Contrat du store de jetons — implémentations enregistrées via
|
|
160
164
|
* `registerTokenStore`, sélectionnées par config. Toutes les opérations sont
|
|
@@ -15,15 +15,25 @@ export interface IWebhookEndpoint {
|
|
|
15
15
|
/** Secret de signature **chiffré** au repos (blob `gcm1.…`). Jamais en clair. */
|
|
16
16
|
readonly secretEnc: string;
|
|
17
17
|
/**
|
|
18
|
-
*
|
|
19
|
-
*
|
|
18
|
+
* Motifs des actions d'audit souscrites, dans la grammaire commune aux canaux et
|
|
19
|
+
* aux webhooks (ADR-0023 W1) : `*` vaut n'importe quelle suite de caractères —
|
|
20
|
+
* `"*"` désigne tout, `"login.*"` les actions qui commencent par `login.`,
|
|
21
|
+
* `"commande:*"` comme `"*:créée"` ; le reste est littéral, trois `*` au plus
|
|
22
|
+
* (refusé à l'enregistrement au-delà). La livraison ne part que si un motif désigne
|
|
23
|
+
* l'action. Sans `tenantId`, l'abonnement a le périmètre de la PLATEFORME : il reçoit
|
|
24
|
+
* aussi les événements qu'une version ultérieure rattachera à un locataire
|
|
25
|
+
* (ADR-0023 W2).
|
|
20
26
|
*/
|
|
21
27
|
readonly events: readonly string[];
|
|
22
28
|
/** Endpoint actif ? (désactivé = aucune livraison). */
|
|
23
29
|
readonly enabled: boolean;
|
|
24
30
|
/** Libellé humain optionnel (console admin). */
|
|
25
31
|
readonly description: string | null;
|
|
26
|
-
/**
|
|
32
|
+
/**
|
|
33
|
+
* Périmètre d'un locataire — réservé, toujours `null` tant que le multi-tenant
|
|
34
|
+
* n'existe pas (`register` refuse une valeur). Un abonnement sans périmètre ne
|
|
35
|
+
* reçoit que les événements sans périmètre (ADR-0023 W2).
|
|
36
|
+
*/
|
|
27
37
|
readonly tenantId: string | null;
|
|
28
38
|
/** Identité de l'admin créateur (soft ref, traçabilité). */
|
|
29
39
|
readonly createdBy: string | null;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Corps JSON d'une livraison webhook, tel que le destinataire le reçoit — le type à poser sur le `JSON.parse` du récepteur.
|
|
3
|
+
*
|
|
4
|
+
* Signé avec ses en-têtes (`webhook-id`, `webhook-timestamp`, `webhook-signature`,
|
|
5
|
+
* convention Standard Webhooks) : vérifier la signature AVANT de lire le corps.
|
|
6
|
+
*
|
|
7
|
+
* @typeParam T - la forme de `data` ; `unknown` tant que le récepteur ne l'a pas vérifiée.
|
|
8
|
+
*/
|
|
9
|
+
export interface IWebhookPayload<T = unknown> {
|
|
10
|
+
/**
|
|
11
|
+
* Identifiant du MESSAGE, égal à l'en-tête `webhook-id` et stable d'un réessai à l'autre : la clé de déduplication du récepteur.
|
|
12
|
+
*/
|
|
13
|
+
readonly id: string;
|
|
14
|
+
/**
|
|
15
|
+
* Heure de la TENTATIVE de livraison (ISO 8601), pas celle de l'événement : un réessai porte une heure plus tardive.
|
|
16
|
+
*
|
|
17
|
+
* L'heure de l'événement vit dans `data` (`ts` pour un événement d'audit).
|
|
18
|
+
*/
|
|
19
|
+
readonly timestamp: string;
|
|
20
|
+
/** Nom de l'événement — l'action d'audit, ex. `"login.success"` ; c'est lui que filtrent les motifs d'abonnement. */
|
|
21
|
+
readonly type: string;
|
|
22
|
+
/** L'événement lui-même ; pour l'audit, un `IAuditEvent`. */
|
|
23
|
+
readonly data: T;
|
|
24
|
+
}
|
|
@@ -49,6 +49,9 @@ declare class ApiKeyService extends Service {
|
|
|
49
49
|
* @param roles - rôles du porteur tels que son compte les porte ; omis =
|
|
50
50
|
* aucun rôle, donc tout scope réservé est refusé (fail-closed).
|
|
51
51
|
* @throws ApiKeyError 400 — nom vide/trop long, scope hors catalogue, expiry invalide.
|
|
52
|
+
* @throws ApiKeyError 400 — `tenantId` rempli : le périmètre d'un locataire n'est
|
|
53
|
+
* pas encore appliqué (aucun lecteur ne cloisonne sur lui) — l'accepter rendrait
|
|
54
|
+
* la clé trompeuse, et l'appliquer un jour lui retirerait des accès (rupture).
|
|
52
55
|
* @throws ApiKeyError 403 — scope réservé à des rôles que le porteur n'a pas.
|
|
53
56
|
* @throws ApiKeyError 409 — plafond `maxPerSubject` atteint.
|
|
54
57
|
* @throws ApiKeyError 403 — aucun compte sous cet identifiant (la clé est liée
|
|
@@ -8,6 +8,12 @@ import type { IFirewall } from "../contracts/IFirewall.js";
|
|
|
8
8
|
import type { IAuthenticator } from "../contracts/IAuthenticator.js";
|
|
9
9
|
import type { ISecuredArea } from "../contracts/ISecuredArea.js";
|
|
10
10
|
import type { IFirewallDescription, IRoleHierarchyDescription } from "../contracts/IFirewallDescription.js";
|
|
11
|
+
/**
|
|
12
|
+
* `Cache-Control` par défaut d'une réponse dont la CSP porte un nonce : jamais
|
|
13
|
+
* gardée par un cache partagé (le nonce y serait servi à plusieurs visiteurs),
|
|
14
|
+
* toujours redemandée avant réutilisation (ADR-0024 §C2).
|
|
15
|
+
*/
|
|
16
|
+
export declare const NONCED_DOCUMENT_CACHE_CONTROL = "private, no-cache";
|
|
11
17
|
/**
|
|
12
18
|
* Orchestrateur de sécurité Nodefony — refonte 2026 (P6).
|
|
13
19
|
*
|
|
@@ -27,7 +33,7 @@ import type { IFirewallDescription, IRoleHierarchyDescription } from "../contrac
|
|
|
27
33
|
*
|
|
28
34
|
* Conformité : tout 401 porte un challenge `WWW-Authenticate` (RFC 9110
|
|
29
35
|
* §15.5.2) — celui du premier authenticator de la zone qui en déclare un, sinon
|
|
30
|
-
* le défi de repli {@link
|
|
36
|
+
* le défi de repli {@link SESSION_AUTH_CHALLENGE}.
|
|
31
37
|
*
|
|
32
38
|
* CORS, CSRF et autorisation par décorateurs viennent se brancher en S4/S5.
|
|
33
39
|
* Toutes les structures sont **lazy** (perf : une app sans zone = zéro alloc).
|
|
@@ -141,20 +147,35 @@ declare class Firewall extends Service implements IFirewall {
|
|
|
141
147
|
/**
|
|
142
148
|
* En-têtes de sécurité APPLICATIFS (P6 J5) — CSP, Referrer-Policy, isolation
|
|
143
149
|
* cross-origin (COOP/COEP/CORP), Origin-Agent-Cluster, Permissions-Policy.
|
|
144
|
-
*
|
|
145
|
-
*
|
|
146
|
-
* SANS le ré-émettre. No-op si désactivé
|
|
150
|
+
* Appelé par la réponse juste avant son `writeHead`, une fois son type fixé :
|
|
151
|
+
* la politique suit ce type (ADR-0024 §C11). Complète le socle transport de
|
|
152
|
+
* `@nodefony/http` (nosniff/frame/HSTS) SANS le ré-émettre. No-op si désactivé
|
|
153
|
+
* ou réponse non-HTTP (WS).
|
|
147
154
|
*
|
|
148
155
|
* En-têtes constants = table figée pré-calculée au boot (0 alloc/concat). Le CSP
|
|
149
|
-
* nonce/req
|
|
156
|
+
* nonce/req ajoute 1 `join` + 1 `setHeader`, sur un document seulement.
|
|
157
|
+
*
|
|
158
|
+
* @param context - contexte de la réponse.
|
|
159
|
+
* @param contentType - `Content-Type` de la réponse ; `null` = sans contenu ;
|
|
160
|
+
* omis = type inconnu, servi comme un document (politique complète).
|
|
161
|
+
* @param servedAsFile - le corps est servi tel quel (fichier statique,
|
|
162
|
+
* relais) : aucun gabarit ne l'a écrit, il ne peut pas porter de nonce. Un
|
|
163
|
+
* document reçoit alors la politique sans nonce ni `'strict-dynamic'`
|
|
164
|
+
* (ADR-0024 §C1) ; les autres types n'en changent pas.
|
|
150
165
|
*/
|
|
151
|
-
applySecurityHeaders(context: ContextType): void;
|
|
166
|
+
applySecurityHeaders(context: ContextType, contentType?: string | null, servedAsFile?: boolean): void;
|
|
152
167
|
/**
|
|
153
168
|
* Déclare des directives CSP additionnelles pour `moduleName` (cf `IFirewall`).
|
|
154
169
|
* No-op si les en-têtes applicatifs sont désactivés (pas de CSP à étendre).
|
|
155
170
|
* Recompose `#securityHeaders` (merge + re-split nonce) — hors hot-path.
|
|
156
171
|
*/
|
|
157
|
-
registerCspOrigins(moduleName: string,
|
|
172
|
+
registerCspOrigins(moduleName: string, declared: CspFragment): void;
|
|
173
|
+
/**
|
|
174
|
+
* Le noyau HTTP a recalculé ses en-têtes de transport (édition à chaud de sa
|
|
175
|
+
* config) : la politique suit `frameOptions`, qui pilote aussi
|
|
176
|
+
* `frame-ancestors` (ADR-0024 §C5). Hors chemin chaud.
|
|
177
|
+
*/
|
|
178
|
+
onTransportHeadersChanged(): void;
|
|
158
179
|
/** Retire les directives CSP de `moduleName` et recompose si nécessaire. */
|
|
159
180
|
unregisterCspOrigins(moduleName: string): void;
|
|
160
181
|
log(pci: unknown, severity?: Severity, msgid?: Msgid, msg?: Message): Pdu;
|
|
@@ -1,10 +1,26 @@
|
|
|
1
1
|
import { type CspFragment } from "../src/csp.js";
|
|
2
|
+
/**
|
|
3
|
+
* Famille d'une réponse au regard de la politique de contenu (ADR-0024 §C11) :
|
|
4
|
+
* `document` (HTML, XHTML, SVG — politique complète avec nonce et
|
|
5
|
+
* `Permissions-Policy`), `script` (JavaScript — politique complète SANS nonce,
|
|
6
|
+
* celle que prend un worker), `other` (tout le reste — politique courte figée).
|
|
7
|
+
*/
|
|
8
|
+
export type ResponseKind = "document" | "script" | "other";
|
|
9
|
+
/**
|
|
10
|
+
* Range une réponse dans sa famille d'après son `Content-Type`.
|
|
11
|
+
*
|
|
12
|
+
* @param contentType - valeur de `Content-Type` (paramètres admis) ; `null` =
|
|
13
|
+
* réponse sans contenu ; `undefined` = type INCONNU de l'appelant, traité en
|
|
14
|
+
* document — la politique la plus complète, jamais une plus faible par défaut.
|
|
15
|
+
* @returns la famille dont la réponse reçoit la politique.
|
|
16
|
+
*/
|
|
17
|
+
export declare function responseKindOf(contentType: string | null | undefined): ResponseKind;
|
|
2
18
|
/**
|
|
3
19
|
* Sous-ensemble APPLICATIF de la config `headers` (cf defineSecurityConfig).
|
|
4
20
|
*
|
|
5
21
|
* `hsts` / `frameguard` / `noSniff` n'y figurent PAS volontairement : ces trois
|
|
6
|
-
* en-têtes « transport » sont posés par `@nodefony/http`
|
|
7
|
-
* (`
|
|
22
|
+
* en-têtes « transport » sont posés par `@nodefony/http` sur toute réponse
|
|
23
|
+
* (`HttpKernel.transportHeaders`, figés au démarrage) pour couvrir AUSSI les fichiers statiques,
|
|
8
24
|
* les erreurs précoces et un serveur sans module security (secure-by-default).
|
|
9
25
|
* `@nodefony/security` ne les ré-émet pas → une seule source par en-tête.
|
|
10
26
|
*/
|
|
@@ -18,6 +34,12 @@ export interface ISecurityHeadersOptions {
|
|
|
18
34
|
corp?: string | undefined;
|
|
19
35
|
originAgentCluster?: boolean | undefined;
|
|
20
36
|
permissionsPolicy?: string | undefined;
|
|
37
|
+
/**
|
|
38
|
+
* Valeur de `Reporting-Endpoints` (`groupe="url"`), quand la politique
|
|
39
|
+
* désigne un groupe `report-to` composé par le framework (ADR-0024 §C7).
|
|
40
|
+
* Posé là où la politique complète part : document, script, fichier servi.
|
|
41
|
+
*/
|
|
42
|
+
reportingEndpoints?: string | null | undefined;
|
|
21
43
|
}
|
|
22
44
|
/**
|
|
23
45
|
* En-têtes de sécurité **applicatifs** de Nodefony (couche `@nodefony/security`,
|
|
@@ -26,19 +48,65 @@ export interface ISecurityHeadersOptions {
|
|
|
26
48
|
* cross-origin COOP/COEP/CORP, Origin-Agent-Cluster, Permissions-Policy) → zéro
|
|
27
49
|
* alloc, zéro concat par requête (le firewall la pose telle quelle).
|
|
28
50
|
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
51
|
+
* Pour le CSP, deux régimes mutuellement exclusifs : **statique** (posé tel quel via
|
|
52
|
+
* `headers`, 0 alloc/req) OU **nonce par requête** (`cspNonces` + placeholder
|
|
53
|
+
* `{{nonce}}`) → segments pré-split au boot, recomposés par requête via
|
|
54
|
+
* `cspFor(nonce)` (1 concaténation, aucun parse/regex dans le hot-path).
|
|
55
|
+
*
|
|
56
|
+
* **Deux variantes, composées au boot** (ADR-0024 §C6) : la politique telle
|
|
57
|
+
* qu'écrite, servie en TLS, et la même SANS `upgrade-insecure-requests`, servie
|
|
58
|
+
* en clair — sur une page `http`, la directive réécrirait les sous-ressources en
|
|
59
|
+
* `https`, que le serveur ne sert pas. Sans la directive, les deux variantes sont
|
|
60
|
+
* le MÊME objet : par requête, un booléen choisit, rien de plus.
|
|
34
61
|
*
|
|
35
62
|
* @see Fetch Metadata · W3C CSP Level 3 · WHATWG (COOP/COEP/CORP).
|
|
36
63
|
*/
|
|
37
64
|
export declare class SecurityHeaders {
|
|
38
65
|
#private;
|
|
39
|
-
|
|
40
|
-
|
|
66
|
+
/**
|
|
67
|
+
* @param o - options des en-têtes (config `security.headers`, CSP déjà fusionnée).
|
|
68
|
+
* @param onRejectedExtra - appelé quand les directives `@Csp` d'une route sortent
|
|
69
|
+
* de la grammaire CSP3 : la route sert alors la politique de base.
|
|
70
|
+
*/
|
|
71
|
+
constructor(o: ISecurityHeadersOptions, onRejectedExtra?: (error: unknown) => void);
|
|
72
|
+
/**
|
|
73
|
+
* Table d'en-têtes figée d'un DOCUMENT servi tel quel — fichier statique,
|
|
74
|
+
* réponse relayée — dont le corps ne passe par aucun gabarit et ne peut donc
|
|
75
|
+
* pas porter de nonce (ADR-0024 §C1) : politique complète sans nonce ni
|
|
76
|
+
* `'strict-dynamic'` orphelin, et tous les en-têtes d'un document.
|
|
77
|
+
*
|
|
78
|
+
* @param tls - la page est servie en `https` (garde `upgrade-insecure-requests`).
|
|
79
|
+
* @returns la table à poser telle quelle.
|
|
80
|
+
*/
|
|
81
|
+
headersForServedFile(tls: boolean): Readonly<Record<string, string>>;
|
|
82
|
+
/**
|
|
83
|
+
* Table d'en-têtes figée d'une réponse qui n'est pas un document (ADR-0024
|
|
84
|
+
* §C11) : JavaScript (politique complète sans nonce, Referrer-Policy, COEP,
|
|
85
|
+
* CORP) ou reste (politique courte `default-src 'none'`, Referrer-Policy,
|
|
86
|
+
* CORP). Ni l'une ni l'autre ne porte de nonce ; COOP, Origin-Agent-Cluster
|
|
87
|
+
* et Permissions-Policy, lus sur un document seulement, n'y figurent pas.
|
|
88
|
+
*
|
|
89
|
+
* @param kind - famille de la réponse (cf {@link responseKindOf}).
|
|
90
|
+
* @param tls - la page est servie en `https` (garde `upgrade-insecure-requests`).
|
|
91
|
+
* @returns la table à poser telle quelle.
|
|
92
|
+
*/
|
|
93
|
+
headersForResource(kind: Exclude<ResponseKind, "document">, tls: boolean): Readonly<Record<string, string>>;
|
|
94
|
+
/** Table d'en-têtes applicatifs CONSTANTS (figée) d'une réponse en clair. */
|
|
41
95
|
get headers(): Readonly<Record<string, string>>;
|
|
96
|
+
/**
|
|
97
|
+
* Table constante SANS la CSP statique d'une réponse en clair — pour une
|
|
98
|
+
* réponse qui pose sa propre politique (route `@Csp`), afin que la CSP ne
|
|
99
|
+
* soit écrite qu'une fois.
|
|
100
|
+
*/
|
|
101
|
+
get headersWithoutCsp(): Readonly<Record<string, string>>;
|
|
102
|
+
/**
|
|
103
|
+
* Table d'en-têtes constants selon le transport de la réponse.
|
|
104
|
+
*
|
|
105
|
+
* @param tls - la page est servie en `https` (proxy de confiance compris).
|
|
106
|
+
* @param withoutCsp - omettre la CSP statique (route `@Csp` qui pose la sienne).
|
|
107
|
+
* @returns la table figée à poser telle quelle.
|
|
108
|
+
*/
|
|
109
|
+
headersFor(tls: boolean, withoutCsp?: boolean): Readonly<Record<string, string>>;
|
|
42
110
|
/** `true` si le CSP est recomposé par requête avec un nonce (→ `cspFor`). */
|
|
43
111
|
get hasNonce(): boolean;
|
|
44
112
|
/**
|
|
@@ -48,9 +116,11 @@ export declare class SecurityHeaders {
|
|
|
48
116
|
* `'`/`;`/espace) → aucune évasion possible du token CSP.
|
|
49
117
|
*
|
|
50
118
|
* @param nonce - nonce base64 de la requête (`Context.cspNonce`).
|
|
119
|
+
* @param tls - la page est servie en `https` : la politique garde
|
|
120
|
+
* `upgrade-insecure-requests`. Défaut : en clair.
|
|
51
121
|
* @returns la valeur `Content-Security-Policy` à poser sur la réponse.
|
|
52
122
|
*/
|
|
53
|
-
cspFor(nonce: string): string;
|
|
123
|
+
cspFor(nonce: string, tls?: boolean): string;
|
|
54
124
|
/**
|
|
55
125
|
* Recompose le CSP en fusionnant ADDITIVEMENT les directives `extra` d'une
|
|
56
126
|
* route (`@Csp`) dans le CSP de base, PUIS en substituant le `nonce`. Le merge
|
|
@@ -60,8 +130,9 @@ export declare class SecurityHeaders {
|
|
|
60
130
|
*
|
|
61
131
|
* @param nonce - nonce base64 de la requête (ignoré si le CSP n'a pas de nonce).
|
|
62
132
|
* @param extra - directives additionnelles de la route (`directive → sources`).
|
|
133
|
+
* @param tls - la page est servie en `https`. Défaut : en clair.
|
|
63
134
|
* @returns la valeur `Content-Security-Policy` à poser sur la réponse.
|
|
64
135
|
*/
|
|
65
|
-
cspForExtra(nonce: string, extra: CspFragment): string;
|
|
136
|
+
cspForExtra(nonce: string, extra: CspFragment, tls?: boolean): string;
|
|
66
137
|
}
|
|
67
138
|
export default SecurityHeaders;
|