@daloyjs/core 0.39.1 → 0.42.0
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 +6 -2
- package/dist/adapters/node.js +11 -0
- package/dist/app.d.ts +131 -1
- package/dist/app.js +259 -18
- package/dist/cli.js +22 -0
- package/dist/docs.d.ts +49 -0
- package/dist/docs.js +39 -0
- package/dist/idempotency.d.ts +26 -4
- package/dist/idempotency.js +25 -5
- package/dist/index.d.ts +5 -2
- package/dist/index.js +2 -0
- package/dist/response-cache.d.ts +26 -2
- package/dist/response-cache.js +14 -2
- package/dist/sbom.cdx.json +9 -9
- package/dist/sbom.spdx.json +5 -5
- package/dist/tenancy.d.ts +243 -0
- package/dist/tenancy.js +293 -0
- package/package.json +8 -2
package/dist/docs.d.ts
CHANGED
|
@@ -226,6 +226,24 @@ export interface DocsAssetOptions {
|
|
|
226
226
|
* @since 0.39.0
|
|
227
227
|
*/
|
|
228
228
|
redocScriptIntegrity?: string;
|
|
229
|
+
/** Override the AsyncAPI React standalone bundle URL (useful for self-hosting). */
|
|
230
|
+
asyncapiScriptUrl?: string;
|
|
231
|
+
/**
|
|
232
|
+
* SRI hash for {@link asyncapiScriptUrl}. One or more space-separated
|
|
233
|
+
* `sha256-`/`sha384-`/`sha512-` base64 digests. Invalid values throw.
|
|
234
|
+
*
|
|
235
|
+
* @since 0.42.0
|
|
236
|
+
*/
|
|
237
|
+
asyncapiScriptIntegrity?: string;
|
|
238
|
+
/** Override the AsyncAPI React component stylesheet URL (useful for self-hosting). */
|
|
239
|
+
asyncapiStyleUrl?: string;
|
|
240
|
+
/**
|
|
241
|
+
* SRI hash for {@link asyncapiStyleUrl}. One or more space-separated
|
|
242
|
+
* `sha256-`/`sha384-`/`sha512-` base64 digests. Invalid values throw.
|
|
243
|
+
*
|
|
244
|
+
* @since 0.42.0
|
|
245
|
+
*/
|
|
246
|
+
asyncapiStyleIntegrity?: string;
|
|
229
247
|
/**
|
|
230
248
|
* `crossorigin` attribute value emitted alongside any pinned integrity
|
|
231
249
|
* hash. SRI on a cross-origin asset requires CORS, so this defaults to
|
|
@@ -264,6 +282,20 @@ export interface RedocHtmlOptions extends DocsOptions {
|
|
|
264
282
|
/** Forwarded as the options object to `Redoc.init(specUrl, configuration, element)`. */
|
|
265
283
|
configuration?: RedocConfiguration;
|
|
266
284
|
}
|
|
285
|
+
/**
|
|
286
|
+
* Options for {@link asyncapiHtml}; adds AsyncAPI-specific UI configuration.
|
|
287
|
+
*
|
|
288
|
+
* @since 0.42.0
|
|
289
|
+
*/
|
|
290
|
+
export interface AsyncApiHtmlOptions extends DocsOptions {
|
|
291
|
+
/**
|
|
292
|
+
* Forwarded as the `config` object to `AsyncApiStandalone.render({ schema, config }, el)`.
|
|
293
|
+
* Defaults to showing the sidebar and inline errors.
|
|
294
|
+
*/
|
|
295
|
+
configuration?: {
|
|
296
|
+
[key: string]: ScalarJsonValue | undefined;
|
|
297
|
+
};
|
|
298
|
+
}
|
|
267
299
|
/** Options for {@link docsContentSecurityPolicy}. */
|
|
268
300
|
export interface DocsContentSecurityPolicyOptions {
|
|
269
301
|
/** Extra origins to allow for `script-src` / `style-src` (defaults to jsDelivr). */
|
|
@@ -316,6 +348,23 @@ export declare function swaggerUiHtml(opts: DocsOptions): string;
|
|
|
316
348
|
* @since 0.39.0
|
|
317
349
|
*/
|
|
318
350
|
export declare function redocHtml(opts: RedocHtmlOptions): string;
|
|
351
|
+
/**
|
|
352
|
+
* Render an AsyncAPI HTML page that loads `opts.specUrl` (an AsyncAPI 3.0
|
|
353
|
+
* document) into the official AsyncAPI React component. Same shape as
|
|
354
|
+
* {@link redocHtml}: a prebuilt standalone bundle is loaded from a CDN via a
|
|
355
|
+
* `<script>` tag (no build step, no extra deps) and the spec URL is handed to
|
|
356
|
+
* `AsyncApiStandalone.render(...)`. This is the AsyncAPI equivalent of the
|
|
357
|
+
* Scalar / Swagger UI / Redoc OpenAPI viewers.
|
|
358
|
+
*
|
|
359
|
+
* Serve it with the same CSP as the OpenAPI docs UIs ({@link docsContentSecurityPolicy}):
|
|
360
|
+
* it needs the asset origin (jsDelivr by default) in `script-src` / `style-src`
|
|
361
|
+
* and `connect-src 'self'` so the component can `fetch` the spec. The spec URL
|
|
362
|
+
* and configuration are embedded with `<`-escaped JSON so an attacker-controlled
|
|
363
|
+
* value cannot break out of the inline `<script>`.
|
|
364
|
+
*
|
|
365
|
+
* @since 0.42.0
|
|
366
|
+
*/
|
|
367
|
+
export declare function asyncapiHtml(opts: AsyncApiHtmlOptions): string;
|
|
319
368
|
/**
|
|
320
369
|
* Build a Content-Security-Policy string compatible with the docs HTML
|
|
321
370
|
* produced by {@link scalarHtml} / {@link swaggerUiHtml}.
|
package/dist/docs.js
CHANGED
|
@@ -123,6 +123,45 @@ export function redocHtml(opts) {
|
|
|
123
123
|
<script${nonce}>Redoc.init(${specArg},${optionsArg},document.getElementById("redoc"));</script>
|
|
124
124
|
</body></html>`;
|
|
125
125
|
}
|
|
126
|
+
/**
|
|
127
|
+
* Render an AsyncAPI HTML page that loads `opts.specUrl` (an AsyncAPI 3.0
|
|
128
|
+
* document) into the official AsyncAPI React component. Same shape as
|
|
129
|
+
* {@link redocHtml}: a prebuilt standalone bundle is loaded from a CDN via a
|
|
130
|
+
* `<script>` tag (no build step, no extra deps) and the spec URL is handed to
|
|
131
|
+
* `AsyncApiStandalone.render(...)`. This is the AsyncAPI equivalent of the
|
|
132
|
+
* Scalar / Swagger UI / Redoc OpenAPI viewers.
|
|
133
|
+
*
|
|
134
|
+
* Serve it with the same CSP as the OpenAPI docs UIs ({@link docsContentSecurityPolicy}):
|
|
135
|
+
* it needs the asset origin (jsDelivr by default) in `script-src` / `style-src`
|
|
136
|
+
* and `connect-src 'self'` so the component can `fetch` the spec. The spec URL
|
|
137
|
+
* and configuration are embedded with `<`-escaped JSON so an attacker-controlled
|
|
138
|
+
* value cannot break out of the inline `<script>`.
|
|
139
|
+
*
|
|
140
|
+
* @since 0.42.0
|
|
141
|
+
*/
|
|
142
|
+
export function asyncapiHtml(opts) {
|
|
143
|
+
const title = escapeHtml(opts.title ?? "AsyncAPI");
|
|
144
|
+
const scriptUrl = escapeHtml(opts.assets?.asyncapiScriptUrl ??
|
|
145
|
+
`${JSDELIVR_ORIGIN}/npm/@asyncapi/react-component/browser/standalone/index.js`);
|
|
146
|
+
const styleUrl = escapeHtml(opts.assets?.asyncapiStyleUrl ??
|
|
147
|
+
`${JSDELIVR_ORIGIN}/npm/@asyncapi/react-component/styles/default.min.css`);
|
|
148
|
+
const scriptSri = integrityAttr(opts.assets?.asyncapiScriptIntegrity, opts.assets?.crossOrigin);
|
|
149
|
+
const styleSri = integrityAttr(opts.assets?.asyncapiStyleIntegrity, opts.assets?.crossOrigin);
|
|
150
|
+
const nonce = nonceAttr(opts.scriptNonce);
|
|
151
|
+
const specArg = jsonForScript(opts.specUrl);
|
|
152
|
+
const configArg = jsonForScript(opts.configuration ?? { show: { sidebar: true, errors: true } });
|
|
153
|
+
return `<!doctype html>
|
|
154
|
+
<html><head>
|
|
155
|
+
<meta charset="utf-8" />
|
|
156
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
157
|
+
<title>${title}</title>
|
|
158
|
+
<link rel="stylesheet" href="${styleUrl}"${styleSri} />
|
|
159
|
+
</head><body>
|
|
160
|
+
<div id="asyncapi"></div>
|
|
161
|
+
<script src="${scriptUrl}"${scriptSri}${nonce}></script>
|
|
162
|
+
<script${nonce}>AsyncApiStandalone.render({schema:{url:${specArg},options:{method:"GET"}},config:${configArg}},document.getElementById("asyncapi"));</script>
|
|
163
|
+
</body></html>`;
|
|
164
|
+
}
|
|
126
165
|
/**
|
|
127
166
|
* Build a Content-Security-Policy string compatible with the docs HTML
|
|
128
167
|
* produced by {@link scalarHtml} / {@link swaggerUiHtml}.
|
package/dist/idempotency.d.ts
CHANGED
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
* @module
|
|
32
32
|
* @since 0.37.0
|
|
33
33
|
*/
|
|
34
|
-
import type { Hooks } from "./types.js";
|
|
34
|
+
import type { BaseContext, Hooks } from "./types.js";
|
|
35
35
|
/**
|
|
36
36
|
* Test-only helper that clears the process-wide shared stores used by
|
|
37
37
|
* `idempotency({ groupId })`. Not part of the documented public API.
|
|
@@ -149,6 +149,24 @@ export interface IdempotencyOptions {
|
|
|
149
149
|
* store; supply an explicit `store` to coordinate across processes.
|
|
150
150
|
*/
|
|
151
151
|
groupId?: string;
|
|
152
|
+
/**
|
|
153
|
+
* Namespace idempotency keys by the calling principal so one client can
|
|
154
|
+
* never replay another client's stored response by reusing the same key
|
|
155
|
+
* (CWE-524 — cross-tenant cached-response disclosure). The returned string
|
|
156
|
+
* is mixed into the store key, so two principals using the *same*
|
|
157
|
+
* `Idempotency-Key` get independent reservations.
|
|
158
|
+
*
|
|
159
|
+
* Defaults to the request's `Authorization` header value, which scopes the
|
|
160
|
+
* common bearer- / API-key-authenticated case (Stripe-style idempotency)
|
|
161
|
+
* out of the box. Override it when identity lives elsewhere, e.g.
|
|
162
|
+
* `scope: (ctx) => ctx.state.session?.id` for cookie-based sessions, or
|
|
163
|
+
* return `undefined` to opt a request out of scoping (e.g. truly public,
|
|
164
|
+
* unauthenticated idempotent writes). Returning a stable per-user id is
|
|
165
|
+
* preferable to the raw credential when tokens rotate between retries.
|
|
166
|
+
*
|
|
167
|
+
* @since 0.40.0
|
|
168
|
+
*/
|
|
169
|
+
scope?: (ctx: BaseContext<any, any>) => string | undefined | Promise<string | undefined>;
|
|
152
170
|
}
|
|
153
171
|
/**
|
|
154
172
|
* In-memory {@link IdempotencyStore}. Suitable for tests and single-process
|
|
@@ -157,10 +175,14 @@ export interface IdempotencyOptions {
|
|
|
157
175
|
*/
|
|
158
176
|
export declare class MemoryIdempotencyStore implements IdempotencyStore {
|
|
159
177
|
private readonly map;
|
|
178
|
+
/**
|
|
179
|
+
* @inheritDoc
|
|
180
|
+
* `_ttlMs` is part of the {@link IdempotencyStore} contract but unused here:
|
|
181
|
+
* the in-memory store derives expiry from `record.expiresAt`.
|
|
182
|
+
*/
|
|
183
|
+
reserve(key: string, record: IdempotencyRecord, _ttlMs?: number): IdempotencyRecord | null;
|
|
160
184
|
/** @inheritDoc */
|
|
161
|
-
|
|
162
|
-
/** @inheritDoc */
|
|
163
|
-
complete(key: string, record: IdempotencyRecord): void;
|
|
185
|
+
complete(key: string, record: IdempotencyRecord, _ttlMs?: number): void;
|
|
164
186
|
/** @inheritDoc */
|
|
165
187
|
release(key: string): void;
|
|
166
188
|
private read;
|
package/dist/idempotency.js
CHANGED
|
@@ -61,8 +61,12 @@ export function _resetSharedIdempotencyStoresForTests() {
|
|
|
61
61
|
*/
|
|
62
62
|
export class MemoryIdempotencyStore {
|
|
63
63
|
map = new Map();
|
|
64
|
-
/**
|
|
65
|
-
|
|
64
|
+
/**
|
|
65
|
+
* @inheritDoc
|
|
66
|
+
* `_ttlMs` is part of the {@link IdempotencyStore} contract but unused here:
|
|
67
|
+
* the in-memory store derives expiry from `record.expiresAt`.
|
|
68
|
+
*/
|
|
69
|
+
reserve(key, record, _ttlMs) {
|
|
66
70
|
const existing = this.read(key);
|
|
67
71
|
if (existing)
|
|
68
72
|
return existing;
|
|
@@ -72,7 +76,7 @@ export class MemoryIdempotencyStore {
|
|
|
72
76
|
return null;
|
|
73
77
|
}
|
|
74
78
|
/** @inheritDoc */
|
|
75
|
-
complete(key, record) {
|
|
79
|
+
complete(key, record, _ttlMs) {
|
|
76
80
|
this.map.set(key, record);
|
|
77
81
|
}
|
|
78
82
|
/** @inheritDoc */
|
|
@@ -154,7 +158,16 @@ function stableStringify(value) {
|
|
|
154
158
|
async function computeFingerprint(method, ctx) {
|
|
155
159
|
const url = new URL(ctx.request.url);
|
|
156
160
|
const material = `${method}\n${url.pathname}${url.search}\n${stableStringify(ctx.body)}`;
|
|
157
|
-
|
|
161
|
+
return sha256Hex(material);
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* SHA-256 hex of an arbitrary string. Used to fingerprint requests and to
|
|
165
|
+
* derive a fixed-length, delimiter-safe tag for the caller-scope namespace so
|
|
166
|
+
* a long or attacker-controlled `Authorization` value cannot inject into or
|
|
167
|
+
* bloat the store key.
|
|
168
|
+
*/
|
|
169
|
+
async function sha256Hex(input) {
|
|
170
|
+
const digest = new Uint8Array(await getSubtle().digest("SHA-256", enc.encode(input)));
|
|
158
171
|
return bytesToHex(digest);
|
|
159
172
|
}
|
|
160
173
|
// Printable ASCII only (no control chars / whitespace). Anchored + bounded to
|
|
@@ -271,7 +284,14 @@ export function idempotency(opts = {}) {
|
|
|
271
284
|
const key = rawKey.trim();
|
|
272
285
|
validateKey(key, headerName, maxKeyLength);
|
|
273
286
|
const fingerprint = await computeFingerprint(method, ctx);
|
|
274
|
-
|
|
287
|
+
// Namespace the key by the calling principal so client B can never
|
|
288
|
+
// replay client A's stored response by reusing the same Idempotency-Key
|
|
289
|
+
// (CWE-524). Defaults to the Authorization header; `scope` overrides.
|
|
290
|
+
const scopeRaw = opts.scope
|
|
291
|
+
? await opts.scope(ctx)
|
|
292
|
+
: (ctx.request.headers.get("authorization") ?? undefined);
|
|
293
|
+
const scopeTag = scopeRaw ? `${await sha256Hex(scopeRaw)}:` : "";
|
|
294
|
+
const storeKey = `${keyPrefix}${scopeTag}${key}`;
|
|
275
295
|
const now = Date.now();
|
|
276
296
|
const record = {
|
|
277
297
|
fingerprint,
|
package/dist/index.d.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
export { App } from "./app.js";
|
|
2
2
|
export { createApp } from "./app.js";
|
|
3
|
+
export { findRoutesMissingResponseBodySchema } from "./app.js";
|
|
3
4
|
export { _resetPackageJsonCacheForTests } from "./app.js";
|
|
4
5
|
export { _resetCrashHandlersForTests } from "./app.js";
|
|
5
6
|
export { _resetInsecureDefaultsLogForTests } from "./app.js";
|
|
6
|
-
export type { AppOptions, AppOpenAPIOptions, DocsRouteOptions, HealthRouteOptions, CspReportRouteOptions, MetricsRouteOptions, IntrospectedRoute, PluginInstalledEvent, PluginExtension, ShutdownEvent, SecurityPreset, } from "./app.js";
|
|
7
|
+
export type { AppOptions, AppOpenAPIOptions, DocsRouteOptions, AsyncAPIRouteOptions, HealthRouteOptions, CspReportRouteOptions, MetricsRouteOptions, IntrospectedRoute, PluginInstalledEvent, PluginExtension, ShutdownEvent, SecurityPreset, } from "./app.js";
|
|
7
8
|
export { getConnInfo, setConnInfo, assertBehindProxy, resolveClientIp, readRemoteAddress, readRemotePort, pickForwardedForByHops, } from "./conn-info.js";
|
|
8
9
|
export type { BehindProxyConfig, ConnInfo } from "./conn-info.js";
|
|
9
10
|
export { subdomains, PSL_SNAPSHOT_DATE, PSL_PUBLIC_SUFFIXES, MAX_SNAPSHOT_AGE_DAYS, } from "./subdomains.js";
|
|
@@ -72,7 +73,7 @@ export type { RequestIdOptions, SecureHeadersOptions, CspDirectivesOptions, Cors
|
|
|
72
73
|
export type { BearerAuthOptions, BearerAuthVerifyHook } from "./middleware.js";
|
|
73
74
|
export { createLogger, noopLogger, DEFAULT_REDACT_KEYS } from "./logger.js";
|
|
74
75
|
export type { Logger, LogLevel, ConsoleLoggerOptions, LoggerRedactionOptions, } from "./logger.js";
|
|
75
|
-
export type { ScalarJsonPrimitive, ScalarJsonValue, ScalarReferenceConfiguration, ScalarTheme, RedocConfiguration, RedocHtmlOptions, DocsAssetOptions, } from "./docs.js";
|
|
76
|
+
export type { ScalarJsonPrimitive, ScalarJsonValue, ScalarReferenceConfiguration, ScalarTheme, RedocConfiguration, RedocHtmlOptions, AsyncApiHtmlOptions, DocsAssetOptions, } from "./docs.js";
|
|
76
77
|
export { formatStartupBanner, printStartupBanner } from "./banner.js";
|
|
77
78
|
export type { StartupBannerLink, StartupBannerOptions } from "./banner.js";
|
|
78
79
|
export { sseStream, sseResponse, ndjsonStream, ndjsonResponse, } from "./streaming.js";
|
|
@@ -95,5 +96,7 @@ export { fileField, multipartObject, isFileFieldSchema, isMultipartObjectSchema,
|
|
|
95
96
|
export type { FileFieldSchema, FileFieldOptions, FileMagicBytesOption, FileMagicBytesSignature, MultipartObjectOptions, MultipartShape, UploadedFile, } from "./multipart.js";
|
|
96
97
|
export { otelTracing, TRACING_SPAN_KIND_SERVER, TRACING_SPAN_STATUS_UNSET, TRACING_SPAN_STATUS_OK, TRACING_SPAN_STATUS_ERROR, } from "./tracing.js";
|
|
97
98
|
export type { OtelTracingOptions, TracingAttributes, TracingAttributeValue, TracingSpan, TracingStartSpanOptions, TracingTracer, } from "./tracing.js";
|
|
99
|
+
export { tenancy, tenantScope, tenantFromSubdomain, tenantFromHeader, tenantFromPathPrefix, tenantFromClaim, defaultTenantNormalize, } from "./tenancy.js";
|
|
100
|
+
export type { TenancyOptions, TenantResolver, TenantScopeOptions, SubdomainTenantOptions, PathPrefixTenantOptions, ClaimTenantOptions, UnresolvedStatus, InvalidStatus, } from "./tenancy.js";
|
|
98
101
|
export { defineWebSocket, WebSocketRegistry, WebSocketProtocolError, WebSocketPayloadTooLargeError, WS_GUID, WS_READY_STATE, WS_OPCODE, WS_CLOSE_CODE, WS_MAX_CONTROL_PAYLOAD, DEFAULT_WS_BACKPRESSURE_LIMIT, DEFAULT_WS_MAX_PAYLOAD_LENGTH, DEFAULT_WS_IDLE_TIMEOUT_SECONDS, computeAcceptKey, parseSubprotocols, validateSelectedSubprotocol, validateUpgrade, checkWebSocketOrigin, parseFrame, encodeFrame, encodeClosePayload, decodeClosePayload, encodeSendPayload, normalizeWebSocketOptions, wsRateLimit, FrameSink, FRAME_INCOMPLETE, } from "./websocket.js";
|
|
99
102
|
export type { WebSocketConnection, WebSocketContext, WebSocketHandler, WebSocketMeta, WebSocketRouteEntry, NormalizedWebSocketOptions, WebSocketBeforeUpgrade, HandshakeResult, ParsedFrame, MessageEvent as WebSocketMessageEvent, FrameSinkEvents, } from "./websocket.js";
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export { App } from "./app.js";
|
|
2
2
|
export { createApp } from "./app.js";
|
|
3
|
+
export { findRoutesMissingResponseBodySchema } from "./app.js";
|
|
3
4
|
export { _resetPackageJsonCacheForTests } from "./app.js";
|
|
4
5
|
export { _resetCrashHandlersForTests } from "./app.js";
|
|
5
6
|
export { _resetInsecureDefaultsLogForTests } from "./app.js";
|
|
@@ -47,4 +48,5 @@ export { encodeCursor, decodeCursor, buildLinkHeader, buildPageLinks, pagination
|
|
|
47
48
|
export { MetricsRegistry, Counter, Gauge, Histogram, httpMetrics, DEFAULT_DURATION_BUCKETS, PROMETHEUS_CONTENT_TYPE, } from "./metrics.js";
|
|
48
49
|
export { fileField, multipartObject, isFileFieldSchema, isMultipartObjectSchema, } from "./multipart.js";
|
|
49
50
|
export { otelTracing, TRACING_SPAN_KIND_SERVER, TRACING_SPAN_STATUS_UNSET, TRACING_SPAN_STATUS_OK, TRACING_SPAN_STATUS_ERROR, } from "./tracing.js";
|
|
51
|
+
export { tenancy, tenantScope, tenantFromSubdomain, tenantFromHeader, tenantFromPathPrefix, tenantFromClaim, defaultTenantNormalize, } from "./tenancy.js";
|
|
50
52
|
export { defineWebSocket, WebSocketRegistry, WebSocketProtocolError, WebSocketPayloadTooLargeError, WS_GUID, WS_READY_STATE, WS_OPCODE, WS_CLOSE_CODE, WS_MAX_CONTROL_PAYLOAD, DEFAULT_WS_BACKPRESSURE_LIMIT, DEFAULT_WS_MAX_PAYLOAD_LENGTH, DEFAULT_WS_IDLE_TIMEOUT_SECONDS, computeAcceptKey, parseSubprotocols, validateSelectedSubprotocol, validateUpgrade, checkWebSocketOrigin, parseFrame, encodeFrame, encodeClosePayload, decodeClosePayload, encodeSendPayload, normalizeWebSocketOptions, wsRateLimit, FrameSink, FRAME_INCOMPLETE, } from "./websocket.js";
|
package/dist/response-cache.d.ts
CHANGED
|
@@ -141,6 +141,26 @@ export interface ResponseCacheOptions {
|
|
|
141
141
|
* store.
|
|
142
142
|
*/
|
|
143
143
|
groupId?: string;
|
|
144
|
+
/**
|
|
145
|
+
* Whether to cache responses to requests that carry an `Authorization`
|
|
146
|
+
* header. Default: `false`.
|
|
147
|
+
*
|
|
148
|
+
* A shared response cache keyed on method + URL (the default) does not
|
|
149
|
+
* include the credential, so caching an authenticated response would serve
|
|
150
|
+
* one user's private data to the next user requesting the same URL
|
|
151
|
+
* (CWE-524 — cross-tenant cached-response disclosure). For that reason, and
|
|
152
|
+
* per RFC 9111 §3.5 (a shared cache MUST NOT reuse a response to an
|
|
153
|
+
* `Authorization`-bearing request unless explicitly permitted), such
|
|
154
|
+
* requests bypass the cache entirely by default.
|
|
155
|
+
*
|
|
156
|
+
* Set this to `true` only when the response is genuinely shareable across
|
|
157
|
+
* principals (e.g. public reference data served behind a bearer gate) — and
|
|
158
|
+
* then also add the credential to {@link varyHeaders} or a custom
|
|
159
|
+
* {@link keyGenerator} so distinct callers cannot collide.
|
|
160
|
+
*
|
|
161
|
+
* @since 0.40.0
|
|
162
|
+
*/
|
|
163
|
+
cacheAuthenticatedRequests?: boolean;
|
|
144
164
|
}
|
|
145
165
|
/**
|
|
146
166
|
* In-memory {@link ResponseCacheStore}. Suitable for tests and single-process
|
|
@@ -151,8 +171,12 @@ export declare class MemoryResponseCacheStore implements ResponseCacheStore {
|
|
|
151
171
|
private readonly map;
|
|
152
172
|
/** @inheritDoc */
|
|
153
173
|
get(key: string): CachedResponse | null;
|
|
154
|
-
/**
|
|
155
|
-
|
|
174
|
+
/**
|
|
175
|
+
* @inheritDoc
|
|
176
|
+
* `_ttlMs` is part of the {@link ResponseCacheStore} contract but unused
|
|
177
|
+
* here: the in-memory store derives freshness from `entry.freshUntil`.
|
|
178
|
+
*/
|
|
179
|
+
set(key: string, entry: CachedResponse, _ttlMs?: number): void;
|
|
156
180
|
/** @inheritDoc */
|
|
157
181
|
delete(key: string): void;
|
|
158
182
|
private prune;
|
package/dist/response-cache.js
CHANGED
|
@@ -76,8 +76,12 @@ export class MemoryResponseCacheStore {
|
|
|
76
76
|
}
|
|
77
77
|
return entry;
|
|
78
78
|
}
|
|
79
|
-
/**
|
|
80
|
-
|
|
79
|
+
/**
|
|
80
|
+
* @inheritDoc
|
|
81
|
+
* `_ttlMs` is part of the {@link ResponseCacheStore} contract but unused
|
|
82
|
+
* here: the in-memory store derives freshness from `entry.freshUntil`.
|
|
83
|
+
*/
|
|
84
|
+
set(key, entry, _ttlMs) {
|
|
81
85
|
this.map.set(key, entry);
|
|
82
86
|
if (this.map.size > 10_000)
|
|
83
87
|
this.prune();
|
|
@@ -245,6 +249,7 @@ export function responseCache(opts = {}) {
|
|
|
245
249
|
throw new Error("responseCache(): maxBodyBytes must be a positive integer.");
|
|
246
250
|
}
|
|
247
251
|
const methods = new Set((opts.methods ?? ["GET", "HEAD"]).map((m) => m.toUpperCase()));
|
|
252
|
+
const cacheAuthenticatedRequests = opts.cacheAuthenticatedRequests === true;
|
|
248
253
|
const cacheableStatus = opts.cacheableStatus ?? ((status) => status === 200);
|
|
249
254
|
const varyHeaders = (opts.varyHeaders ?? []).map((h) => h.toLowerCase());
|
|
250
255
|
const statusHeaderName = opts.statusHeaderName === null ? null : (opts.statusHeaderName ?? "x-cache").toLowerCase();
|
|
@@ -289,6 +294,13 @@ export function responseCache(opts = {}) {
|
|
|
289
294
|
const method = ctx.request.method.toUpperCase();
|
|
290
295
|
if (!methods.has(method))
|
|
291
296
|
return undefined;
|
|
297
|
+
// RFC 9111 §3.5 / CWE-524: a shared cache keyed on method+URL must not
|
|
298
|
+
// store or reuse a response to an Authorization-bearing request, or it
|
|
299
|
+
// would serve one principal's private data to the next caller. Opt in
|
|
300
|
+
// via `cacheAuthenticatedRequests` for genuinely shareable content.
|
|
301
|
+
if (!cacheAuthenticatedRequests && ctx.request.headers.has("authorization")) {
|
|
302
|
+
return undefined;
|
|
303
|
+
}
|
|
292
304
|
const reqCc = parseCacheControl(ctx.request.headers.get("cache-control"));
|
|
293
305
|
if (reqCc.has("no-store"))
|
|
294
306
|
return undefined;
|
package/dist/sbom.cdx.json
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"bomFormat": "CycloneDX",
|
|
3
3
|
"specVersion": "1.5",
|
|
4
|
-
"serialNumber": "urn:uuid:
|
|
4
|
+
"serialNumber": "urn:uuid:c3e0bc4b-403c-5789-b2e4-dbafa73b03ee",
|
|
5
5
|
"version": 1,
|
|
6
6
|
"metadata": {
|
|
7
|
-
"timestamp": "2026-06-
|
|
7
|
+
"timestamp": "2026-06-19T08:56:49.249Z",
|
|
8
8
|
"tools": [
|
|
9
9
|
{
|
|
10
10
|
"vendor": "DaloyJS",
|
|
11
11
|
"name": "daloy-generate-sbom",
|
|
12
|
-
"version": "0.
|
|
12
|
+
"version": "0.42.0"
|
|
13
13
|
}
|
|
14
14
|
],
|
|
15
15
|
"authors": [
|
|
@@ -19,11 +19,11 @@
|
|
|
19
19
|
],
|
|
20
20
|
"component": {
|
|
21
21
|
"type": "library",
|
|
22
|
-
"bom-ref": "pkg:npm/@daloyjs/core@0.
|
|
22
|
+
"bom-ref": "pkg:npm/@daloyjs/core@0.42.0",
|
|
23
23
|
"name": "@daloyjs/core",
|
|
24
|
-
"version": "0.
|
|
24
|
+
"version": "0.42.0",
|
|
25
25
|
"description": "DaloyJS is a runtime-portable, contract-first TypeScript web framework with built-in OpenAPI (Hey API), typed client generation, large-scale maintainability, and security-first defaults. Hono-grade portability, Elysia-grade DX, FastAPI-grade docs, Fastify-grade ops — distributed via pnpm.",
|
|
26
|
-
"purl": "pkg:npm/@daloyjs/core@0.
|
|
26
|
+
"purl": "pkg:npm/@daloyjs/core@0.42.0",
|
|
27
27
|
"licenses": [
|
|
28
28
|
{
|
|
29
29
|
"license": {
|
|
@@ -46,9 +46,9 @@
|
|
|
46
46
|
}
|
|
47
47
|
],
|
|
48
48
|
"swid": {
|
|
49
|
-
"tagId": "swidtag--daloyjs-core-0.
|
|
49
|
+
"tagId": "swidtag--daloyjs-core-0.42.0",
|
|
50
50
|
"name": "@daloyjs/core",
|
|
51
|
-
"version": "0.
|
|
51
|
+
"version": "0.42.0",
|
|
52
52
|
"tagVersion": 0,
|
|
53
53
|
"patch": false
|
|
54
54
|
}
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
"components": [],
|
|
58
58
|
"dependencies": [
|
|
59
59
|
{
|
|
60
|
-
"ref": "pkg:npm/@daloyjs/core@0.
|
|
60
|
+
"ref": "pkg:npm/@daloyjs/core@0.42.0",
|
|
61
61
|
"dependsOn": []
|
|
62
62
|
}
|
|
63
63
|
]
|
package/dist/sbom.spdx.json
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
"spdxVersion": "SPDX-2.3",
|
|
3
3
|
"dataLicense": "CC0-1.0",
|
|
4
4
|
"SPDXID": "SPDXRef-DOCUMENT",
|
|
5
|
-
"name": "@daloyjs/core-0.
|
|
6
|
-
"documentNamespace": "https://github.com/daloyjs/daloy/sbom/@daloyjs/core-0.
|
|
5
|
+
"name": "@daloyjs/core-0.42.0",
|
|
6
|
+
"documentNamespace": "https://github.com/daloyjs/daloy/sbom/@daloyjs/core-0.42.0-c3e0bc4b-403c-5789-b2e4-dbafa73b03ee",
|
|
7
7
|
"creationInfo": {
|
|
8
|
-
"created": "2026-06-
|
|
8
|
+
"created": "2026-06-19T08:56:49.249Z",
|
|
9
9
|
"creators": [
|
|
10
10
|
"Tool: daloy-generate-sbom",
|
|
11
11
|
"Organization: DaloyJS"
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
{
|
|
17
17
|
"SPDXID": "SPDXRef-Package--daloyjs-core",
|
|
18
18
|
"name": "@daloyjs/core",
|
|
19
|
-
"versionInfo": "0.
|
|
19
|
+
"versionInfo": "0.42.0",
|
|
20
20
|
"downloadLocation": "https://github.com/daloyjs/daloy",
|
|
21
21
|
"filesAnalyzed": false,
|
|
22
22
|
"licenseConcluded": "MIT",
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
{
|
|
28
28
|
"referenceCategory": "PACKAGE-MANAGER",
|
|
29
29
|
"referenceType": "purl",
|
|
30
|
-
"referenceLocator": "pkg:npm/@daloyjs/core@0.
|
|
30
|
+
"referenceLocator": "pkg:npm/@daloyjs/core@0.42.0"
|
|
31
31
|
}
|
|
32
32
|
]
|
|
33
33
|
}
|