tina4-nodejs 3.13.142 → 3.13.143
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/CLAUDE.md +28 -2
- package/package.json +1 -1
- package/packages/cli/dist/bin.js +379 -85
- package/packages/cli/src/commands/generate.ts +7 -2
- package/packages/cli/src/commands/init.ts +18 -8
- package/packages/core/dist/index.js +377 -78
- package/packages/core/gallery/auth/src/routes/gallery/auth/get.ts +27 -11
- package/packages/core/gallery/queue/src/routes/gallery/queue/get.ts +20 -7
- package/packages/core/gallery/templates/src/templates/gallery_page.twig +34 -15
- package/packages/core/src/csp.ts +138 -0
- package/packages/core/src/errorOverlay.ts +80 -37
- package/packages/core/src/index.ts +10 -0
- package/packages/core/src/middleware.ts +20 -6
- package/packages/core/src/response.ts +14 -3
- package/packages/core/src/server.ts +52 -15
- package/packages/core/src/session.ts +154 -7
- package/packages/core/src/sso.ts +1 -1
- package/packages/core/src/types.ts +8 -0
- package/packages/core/templates/errors/302.twig +1 -1
- package/packages/core/templates/errors/401.twig +2 -2
- package/packages/core/templates/errors/403.twig +1 -1
- package/packages/core/templates/errors/404.twig +1 -1
- package/packages/core/templates/errors/500.twig +1 -1
- package/packages/core/templates/errors/502.twig +2 -2
- package/packages/core/templates/errors/503.twig +2 -2
- package/packages/core/templates/errors/base.twig +4 -1
- package/packages/frond/dist/index.js +2 -0
- package/packages/frond/src/engine.ts +23 -0
- package/packages/orm/dist/index.js +378 -87
- package/packages/swagger/dist/index.js +4 -4
- package/packages/swagger/src/ui.ts +7 -4
- package/types/core/src/csp.d.ts +42 -0
- package/types/core/src/index.d.ts +1 -0
- package/types/core/src/middleware.d.ts +6 -0
- package/types/core/src/session.d.ts +62 -2
- package/types/core/src/types.d.ts +8 -0
- package/types/frond/src/engine.d.ts +7 -0
|
@@ -17430,6 +17430,73 @@ var init_trustedProxy = __esm({
|
|
|
17430
17430
|
}
|
|
17431
17431
|
});
|
|
17432
17432
|
|
|
17433
|
+
// src/csp.ts
|
|
17434
|
+
import { AsyncLocalStorage as AsyncLocalStorage3 } from "node:async_hooks";
|
|
17435
|
+
import { randomBytes as randomBytes4 } from "node:crypto";
|
|
17436
|
+
function generateNonce() {
|
|
17437
|
+
return randomBytes4(16).toString("base64");
|
|
17438
|
+
}
|
|
17439
|
+
function runWithCspNonce(nonce, fn) {
|
|
17440
|
+
return nonceStore.run({ nonce }, fn);
|
|
17441
|
+
}
|
|
17442
|
+
function setCurrentNonce(nonce) {
|
|
17443
|
+
const store = nonceStore.getStore();
|
|
17444
|
+
if (store) store.nonce = nonce;
|
|
17445
|
+
else nonceFallback = nonce;
|
|
17446
|
+
}
|
|
17447
|
+
function clearCurrentNonce() {
|
|
17448
|
+
setCurrentNonce(void 0);
|
|
17449
|
+
}
|
|
17450
|
+
function currentCspNonce() {
|
|
17451
|
+
const store = nonceStore.getStore();
|
|
17452
|
+
let value = store ? store.nonce : nonceFallback;
|
|
17453
|
+
if (!value) {
|
|
17454
|
+
value = generateNonce();
|
|
17455
|
+
if (store) store.nonce = value;
|
|
17456
|
+
else nonceFallback = value;
|
|
17457
|
+
}
|
|
17458
|
+
return value;
|
|
17459
|
+
}
|
|
17460
|
+
function cspNonce() {
|
|
17461
|
+
return currentCspNonce();
|
|
17462
|
+
}
|
|
17463
|
+
function injectNonceIntoCsp(csp, nonce) {
|
|
17464
|
+
const token = `'nonce-${nonce}'`;
|
|
17465
|
+
const directives = [];
|
|
17466
|
+
for (const rawPart of csp.split(";")) {
|
|
17467
|
+
const part = rawPart.trim();
|
|
17468
|
+
if (!part) continue;
|
|
17469
|
+
const spaceAt = part.search(/\s/);
|
|
17470
|
+
const name = (spaceAt === -1 ? part : part.slice(0, spaceAt)).toLowerCase();
|
|
17471
|
+
const value = spaceAt === -1 ? "" : part.slice(spaceAt + 1).trim();
|
|
17472
|
+
directives.push([name, value]);
|
|
17473
|
+
}
|
|
17474
|
+
const defaultEntry = directives.find(([name]) => name === "default-src");
|
|
17475
|
+
const defaultValue = defaultEntry && defaultEntry[1] ? defaultEntry[1] : "'self'";
|
|
17476
|
+
for (const directive of ["style-src", "script-src"]) {
|
|
17477
|
+
const existing = directives.find(([name]) => name === directive);
|
|
17478
|
+
if (existing) {
|
|
17479
|
+
if (!existing[1].split(/\s+/).includes(token)) {
|
|
17480
|
+
existing[1] = `${existing[1]} ${token}`.trim();
|
|
17481
|
+
}
|
|
17482
|
+
} else {
|
|
17483
|
+
directives.push([directive, `${defaultValue} ${token}`.trim()]);
|
|
17484
|
+
}
|
|
17485
|
+
}
|
|
17486
|
+
return directives.map(([name, value]) => value ? `${name} ${value}` : name).join("; ");
|
|
17487
|
+
}
|
|
17488
|
+
function resolveCspHeader(nonce) {
|
|
17489
|
+
const csp = process.env.TINA4_CSP || "default-src 'self'";
|
|
17490
|
+
return injectNonceIntoCsp(csp, nonce);
|
|
17491
|
+
}
|
|
17492
|
+
var nonceStore, nonceFallback;
|
|
17493
|
+
var init_csp = __esm({
|
|
17494
|
+
"src/csp.ts"() {
|
|
17495
|
+
"use strict";
|
|
17496
|
+
nonceStore = new AsyncLocalStorage3();
|
|
17497
|
+
}
|
|
17498
|
+
});
|
|
17499
|
+
|
|
17433
17500
|
// ../frond/src/engine.ts
|
|
17434
17501
|
var engine_exports = {};
|
|
17435
17502
|
__export(engine_exports, {
|
|
@@ -17439,9 +17506,10 @@ __export(engine_exports, {
|
|
|
17439
17506
|
expressionFormCache: () => expressionFormCache,
|
|
17440
17507
|
filterChainCache: () => filterChainCache,
|
|
17441
17508
|
pathParseCache: () => pathParseCache,
|
|
17509
|
+
setCspNonceProvider: () => setCspNonceProvider,
|
|
17442
17510
|
setFormTokenSessionId: () => setFormTokenSessionId
|
|
17443
17511
|
});
|
|
17444
|
-
import { createHash as createHash3, createHmac as createHmac3, randomBytes as
|
|
17512
|
+
import { createHash as createHash3, createHmac as createHmac3, randomBytes as randomBytes5 } from "node:crypto";
|
|
17445
17513
|
import { readFileSync as readFileSync6, existsSync as existsSync7, statSync as statSync6, realpathSync } from "node:fs";
|
|
17446
17514
|
import { join as join10, resolve as resolve6, isAbsolute as isAbsolute4, sep as sep3 } from "node:path";
|
|
17447
17515
|
function jsonText(value) {
|
|
@@ -18508,6 +18576,9 @@ function _b64url(data) {
|
|
|
18508
18576
|
function setFormTokenSessionId(sessionId) {
|
|
18509
18577
|
_formTokenSessionId = sessionId || "";
|
|
18510
18578
|
}
|
|
18579
|
+
function setCspNonceProvider(fn) {
|
|
18580
|
+
_cspNonceProvider = fn;
|
|
18581
|
+
}
|
|
18511
18582
|
function _buildFormTokenJwt(descriptor = "") {
|
|
18512
18583
|
const secret = process.env.TINA4_SECRET ?? "";
|
|
18513
18584
|
const secretBytes = Buffer.byteLength(secret, "utf8");
|
|
@@ -18519,7 +18590,7 @@ function _buildFormTokenJwt(descriptor = "") {
|
|
|
18519
18590
|
const ttlMinutes = parseInt(process.env.TINA4_TOKEN_LIMIT || "60", 10);
|
|
18520
18591
|
const header = { alg: "HS256", typ: "JWT" };
|
|
18521
18592
|
const now = Math.floor(Date.now() / 1e3);
|
|
18522
|
-
const payload = { type: "form", nonce:
|
|
18593
|
+
const payload = { type: "form", nonce: randomBytes5(8).toString("hex"), iat: now, exp: now + ttlMinutes * 60 };
|
|
18523
18594
|
if (descriptor) {
|
|
18524
18595
|
if (descriptor.includes("|")) {
|
|
18525
18596
|
const [ctx, ref] = descriptor.split("|", 2);
|
|
@@ -18546,7 +18617,7 @@ function _generateFormToken(descriptor = "") {
|
|
|
18546
18617
|
function _generateFormTokenValue(descriptor = "") {
|
|
18547
18618
|
return new SafeString(_buildFormTokenJwt(descriptor));
|
|
18548
18619
|
}
|
|
18549
|
-
var SafeString, KNOWN_TAGS, TERMINATOR_TAGS, GATEABLE_TAGS, BLOCK_TAG_ENDS, JSON_UNSAFE_RE, JSON_UNSAFE_MAP, NUMERIC_RE, METHOD_CALL_RE, FN_CALL_RE, IS_NOT_RE, IS_RE, NOT_IN_RE, IN_RE, DIVISIBLE_BY_RE, FILTER_WITH_ARGS_RE, FILTER_COMPARISON_RE, TITLE_WORD_RE, STRIP_TAGS_RE, FAST_FILTERS, FORMAT_RE, LEADING_WS_RE, TRAILING_WS_RE, THOUSANDS_RE, LIVE_RE, LIVE_WS_RE, LIVE_SRC_RE, EXTENDS_RE, EXTENDS_RE_GLOBAL, filterChainCache, pathParseCache, TEMPLATE_CACHE_MAX, MEMO_CACHE_MAX, TOKEN_RE, RAW_BLOCK_RE, EXPR_NOT_MATCHED, ARITHMETIC_OPERATIONS, EXPR_EVALUATORS, expressionFormCache, VarRef, JS_SAFE_RE, MEDIA_TYPE_RE, SANDBOX_EXPR_KEYWORDS, SANDBOX_IDENT_RE, SANDBOX_STRING_RE, UNSAFE_KEYS, BUILTIN_FILTERS, _formTokenSessionId, Frond;
|
|
18620
|
+
var SafeString, KNOWN_TAGS, TERMINATOR_TAGS, GATEABLE_TAGS, BLOCK_TAG_ENDS, JSON_UNSAFE_RE, JSON_UNSAFE_MAP, NUMERIC_RE, METHOD_CALL_RE, FN_CALL_RE, IS_NOT_RE, IS_RE, NOT_IN_RE, IN_RE, DIVISIBLE_BY_RE, FILTER_WITH_ARGS_RE, FILTER_COMPARISON_RE, TITLE_WORD_RE, STRIP_TAGS_RE, FAST_FILTERS, FORMAT_RE, LEADING_WS_RE, TRAILING_WS_RE, THOUSANDS_RE, LIVE_RE, LIVE_WS_RE, LIVE_SRC_RE, EXTENDS_RE, EXTENDS_RE_GLOBAL, filterChainCache, pathParseCache, TEMPLATE_CACHE_MAX, MEMO_CACHE_MAX, TOKEN_RE, RAW_BLOCK_RE, EXPR_NOT_MATCHED, ARITHMETIC_OPERATIONS, EXPR_EVALUATORS, expressionFormCache, VarRef, JS_SAFE_RE, MEDIA_TYPE_RE, SANDBOX_EXPR_KEYWORDS, SANDBOX_IDENT_RE, SANDBOX_STRING_RE, UNSAFE_KEYS, BUILTIN_FILTERS, _formTokenSessionId, _cspNonceProvider, Frond;
|
|
18550
18621
|
var init_engine = __esm({
|
|
18551
18622
|
"../frond/src/engine.ts"() {
|
|
18552
18623
|
"use strict";
|
|
@@ -18900,6 +18971,7 @@ var init_engine = __esm({
|
|
|
18900
18971
|
js_escape: (v) => new SafeString(jsEscape(v))
|
|
18901
18972
|
};
|
|
18902
18973
|
_formTokenSessionId = "";
|
|
18974
|
+
_cspNonceProvider = () => "";
|
|
18903
18975
|
Frond = class _Frond {
|
|
18904
18976
|
// ── Class-level registries ──────────────────────────────────
|
|
18905
18977
|
// Persist globals, filters, and tests across hot-reloads and at
|
|
@@ -19053,6 +19125,7 @@ var init_engine = __esm({
|
|
|
19053
19125
|
this.globals.formTokenValue = (descriptor) => _generateFormTokenValue(descriptor || "");
|
|
19054
19126
|
this.globals.form_token_value = (descriptor) => _generateFormTokenValue(descriptor || "");
|
|
19055
19127
|
this.globals.dump = (value) => renderDump(value);
|
|
19128
|
+
this.globals.csp_nonce = () => _cspNonceProvider();
|
|
19056
19129
|
for (const [k, v] of _Frond.classFilters) this.filters[k] = v;
|
|
19057
19130
|
for (const [k, v] of _Frond.classGlobals) this.globals[k] = v;
|
|
19058
19131
|
for (const [k, v] of _Frond.classTests) this.tests[k] = v;
|
|
@@ -20316,7 +20389,8 @@ async function getFrond() {
|
|
|
20316
20389
|
const dir = _defaultTemplatesDir ?? nodePath.resolve(process.cwd(), "src/templates");
|
|
20317
20390
|
let engine = _frondCache.get(dir);
|
|
20318
20391
|
if (!engine) {
|
|
20319
|
-
const { Frond: Frond2 } = await Promise.resolve().then(() => (init_engine(), engine_exports));
|
|
20392
|
+
const { Frond: Frond2, setCspNonceProvider: setCspNonceProvider2 } = await Promise.resolve().then(() => (init_engine(), engine_exports));
|
|
20393
|
+
setCspNonceProvider2(currentCspNonce);
|
|
20320
20394
|
engine = new Frond2(dir);
|
|
20321
20395
|
_frondCache.set(dir, engine);
|
|
20322
20396
|
}
|
|
@@ -20327,7 +20401,8 @@ async function getFrameworkFrond() {
|
|
|
20327
20401
|
const frameworkDir = nodePath.resolve(nodePath.dirname(import.meta.url.replace("file://", "")), "..", "templates");
|
|
20328
20402
|
if (!_frameworkFrond && fs2.existsSync(frameworkDir)) {
|
|
20329
20403
|
try {
|
|
20330
|
-
const { Frond: Frond2 } = await Promise.resolve().then(() => (init_engine(), engine_exports));
|
|
20404
|
+
const { Frond: Frond2, setCspNonceProvider: setCspNonceProvider2 } = await Promise.resolve().then(() => (init_engine(), engine_exports));
|
|
20405
|
+
setCspNonceProvider2(currentCspNonce);
|
|
20331
20406
|
_frameworkFrond = new Frond2(frameworkDir);
|
|
20332
20407
|
} catch {
|
|
20333
20408
|
return null;
|
|
@@ -20427,6 +20502,7 @@ function createResponse(res) {
|
|
|
20427
20502
|
return response;
|
|
20428
20503
|
};
|
|
20429
20504
|
response.raw = res;
|
|
20505
|
+
response.cspNonce = currentCspNonce();
|
|
20430
20506
|
response.json = function(data, status2) {
|
|
20431
20507
|
if (res.headersSent) return response;
|
|
20432
20508
|
if (status2 !== void 0) res.statusCode = status2;
|
|
@@ -20572,7 +20648,8 @@ function createResponse(res) {
|
|
|
20572
20648
|
};
|
|
20573
20649
|
response.render = async function(templateName, data, status2, templateDir) {
|
|
20574
20650
|
try {
|
|
20575
|
-
const { Frond: Frond2 } = await Promise.resolve().then(() => (init_engine(), engine_exports));
|
|
20651
|
+
const { Frond: Frond2, setCspNonceProvider: setCspNonceProvider2 } = await Promise.resolve().then(() => (init_engine(), engine_exports));
|
|
20652
|
+
setCspNonceProvider2(currentCspNonce);
|
|
20576
20653
|
const dir = templateDir ?? _defaultTemplatesDir ?? nodePath.resolve(process.cwd(), "src/templates");
|
|
20577
20654
|
let engine = _frondCache.get(dir);
|
|
20578
20655
|
if (!engine) {
|
|
@@ -20690,6 +20767,7 @@ var HEADER_TOKEN, _frondCache, _defaultTemplatesDir, _globalFrond, _frameworkFro
|
|
|
20690
20767
|
var init_response = __esm({
|
|
20691
20768
|
"src/response.ts"() {
|
|
20692
20769
|
"use strict";
|
|
20770
|
+
init_csp();
|
|
20693
20771
|
HEADER_TOKEN = /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/;
|
|
20694
20772
|
_frondCache = /* @__PURE__ */ new Map();
|
|
20695
20773
|
_defaultTemplatesDir = null;
|
|
@@ -20901,6 +20979,7 @@ var init_middleware = __esm({
|
|
|
20901
20979
|
init_dotenv();
|
|
20902
20980
|
init_router();
|
|
20903
20981
|
init_trustedProxy();
|
|
20982
|
+
init_csp();
|
|
20904
20983
|
init_response();
|
|
20905
20984
|
MiddlewareChain = class {
|
|
20906
20985
|
middlewares = [];
|
|
@@ -21333,12 +21412,18 @@ var init_middleware = __esm({
|
|
|
21333
21412
|
* overrides, WITHOUT Strict-Transport-Security, which depends on the
|
|
21334
21413
|
* request's scheme. Also what a transport rejection carries (ADR-0068
|
|
21335
21414
|
* section 4), written before the scheme is known.
|
|
21415
|
+
*
|
|
21416
|
+
* Content-Security-Policy always carries a per-response nonce in style-src
|
|
21417
|
+
* AND script-src (ADR-0088), read from the request async-context (minted on
|
|
21418
|
+
* first access), so the framework's own inline content runs under the strict
|
|
21419
|
+
* default without 'unsafe-inline'. The nonce is random per response, so a
|
|
21420
|
+
* caller comparing this header must assert its STRUCTURE, never a byte string.
|
|
21336
21421
|
*/
|
|
21337
21422
|
static canonicalHeaders() {
|
|
21338
21423
|
return {
|
|
21339
21424
|
"X-Frame-Options": process.env.TINA4_FRAME_OPTIONS ?? "SAMEORIGIN",
|
|
21340
21425
|
"X-Content-Type-Options": "nosniff",
|
|
21341
|
-
"Content-Security-Policy":
|
|
21426
|
+
"Content-Security-Policy": resolveCspHeader(currentCspNonce()),
|
|
21342
21427
|
"Referrer-Policy": process.env.TINA4_REFERRER_POLICY ?? "strict-origin-when-cross-origin",
|
|
21343
21428
|
"X-XSS-Protection": "0",
|
|
21344
21429
|
"Permissions-Policy": process.env.TINA4_PERMISSIONS_POLICY ?? "camera=(), microphone=(), geolocation=()"
|
|
@@ -21380,7 +21465,7 @@ var init_middleware = __esm({
|
|
|
21380
21465
|
static warnCspDefaultOnce() {
|
|
21381
21466
|
if (_SecurityHeadersMiddleware.cspDefaultWarned) return;
|
|
21382
21467
|
_SecurityHeadersMiddleware.cspDefaultWarned = true;
|
|
21383
|
-
const message = `TINA4_CSP is not set, so Tina4 is serving the default Content-Security-Policy "default-src 'self'" on every response.
|
|
21468
|
+
const message = `TINA4_CSP is not set, so Tina4 is serving the default Content-Security-Policy "default-src 'self'" on every response. The framework injects a per-response nonce into style-src and script-src, so its own inline <style>/<script> (and your templates using the csp_nonce() Frond global) work under this policy. The default still blocks cross-origin fonts/scripts/CDNs, data: URIs, and cross-origin WebSocket/XHR (e.g. a separate API or LiveKit host). If your app uses any of these, set TINA4_CSP to a policy that allows them (see https://tina4.com); to silence this notice without changing behaviour, set TINA4_CSP="default-src 'self'".`;
|
|
21384
21469
|
try {
|
|
21385
21470
|
Log.warning(message);
|
|
21386
21471
|
} catch {
|
|
@@ -23773,7 +23858,7 @@ __export(session_exports, {
|
|
|
23773
23858
|
sessionCookieName: () => sessionCookieName,
|
|
23774
23859
|
sessionStrictMode: () => sessionStrictMode
|
|
23775
23860
|
});
|
|
23776
|
-
import { createHash as createHash5, randomBytes as
|
|
23861
|
+
import { createHash as createHash5, randomBytes as randomBytes6 } from "node:crypto";
|
|
23777
23862
|
import { existsSync as existsSync10, mkdirSync as mkdirSync10, readFileSync as readFileSync8, writeFileSync as writeFileSync7, unlinkSync as unlinkSync5, readdirSync as readdirSync6 } from "node:fs";
|
|
23778
23863
|
import { join as join14 } from "node:path";
|
|
23779
23864
|
function isValidSessionId(sessionId) {
|
|
@@ -23791,6 +23876,20 @@ function resolveBackend(name) {
|
|
|
23791
23876
|
}
|
|
23792
23877
|
return normalised;
|
|
23793
23878
|
}
|
|
23879
|
+
function fingerprint(value) {
|
|
23880
|
+
try {
|
|
23881
|
+
return JSON.stringify(value);
|
|
23882
|
+
} catch {
|
|
23883
|
+
return void 0;
|
|
23884
|
+
}
|
|
23885
|
+
}
|
|
23886
|
+
function fingerprints(data) {
|
|
23887
|
+
const prints = /* @__PURE__ */ new Map();
|
|
23888
|
+
for (const [key, value] of Object.entries(data)) {
|
|
23889
|
+
prints.set(key, fingerprint(value));
|
|
23890
|
+
}
|
|
23891
|
+
return prints;
|
|
23892
|
+
}
|
|
23794
23893
|
function isSecureScheme(forwardedProto, socketEncrypted) {
|
|
23795
23894
|
const forwarded = (forwardedProto ?? "").trim();
|
|
23796
23895
|
if (forwarded !== "") {
|
|
@@ -23988,6 +24087,29 @@ var init_session = __esm({
|
|
|
23988
24087
|
* Lets `start()` tell "no such session" from "the store is unreachable".
|
|
23989
24088
|
*/
|
|
23990
24089
|
lastReadFailed = false;
|
|
24090
|
+
/**
|
|
24091
|
+
* What this request last saw of the stored record, one fingerprint per key:
|
|
24092
|
+
* taken by start() and again by every save. save() compares the live data
|
|
24093
|
+
* against it to find the keys THIS request changed.
|
|
24094
|
+
*/
|
|
24095
|
+
loaded = /* @__PURE__ */ new Map();
|
|
24096
|
+
/**
|
|
24097
|
+
* True when this request is working on a record the store holds: start()
|
|
24098
|
+
* adopted one, or a save has written one since. A save never re-creates such
|
|
24099
|
+
* a record once another request has removed it.
|
|
24100
|
+
*/
|
|
24101
|
+
stored = false;
|
|
24102
|
+
/**
|
|
24103
|
+
* True when clear() ran since the last save: the save then replaces the
|
|
24104
|
+
* stored record instead of merging into it.
|
|
24105
|
+
*/
|
|
24106
|
+
cleared = false;
|
|
24107
|
+
/**
|
|
24108
|
+
* True once this request found its session ended by another request (the
|
|
24109
|
+
* record it loaded is gone). It stays ended for the rest of the request:
|
|
24110
|
+
* regenerate() mints nothing either.
|
|
24111
|
+
*/
|
|
24112
|
+
ended = false;
|
|
23991
24113
|
constructor(backend, config) {
|
|
23992
24114
|
const backendType = resolveBackend(
|
|
23993
24115
|
backend ?? config?.backend ?? process.env.TINA4_SESSION_BACKEND ?? "file"
|
|
@@ -24132,6 +24254,10 @@ var init_session = __esm({
|
|
|
24132
24254
|
const now2 = Math.floor(Date.now() / 1e3);
|
|
24133
24255
|
this.data = { _created: now2, _accessed: now2 };
|
|
24134
24256
|
this.dirty = false;
|
|
24257
|
+
this.stored = false;
|
|
24258
|
+
this.loaded = /* @__PURE__ */ new Map();
|
|
24259
|
+
this.cleared = false;
|
|
24260
|
+
this.ended = false;
|
|
24135
24261
|
return sessionId;
|
|
24136
24262
|
}
|
|
24137
24263
|
if (loaded) {
|
|
@@ -24141,14 +24267,22 @@ var init_session = __esm({
|
|
|
24141
24267
|
this.data._accessed = now2;
|
|
24142
24268
|
this.dirty = false;
|
|
24143
24269
|
this.safeWrite(this.sessionId, this.data, this.ttl);
|
|
24270
|
+
this.stored = true;
|
|
24271
|
+
this.loaded = fingerprints(this.data);
|
|
24272
|
+
this.cleared = false;
|
|
24273
|
+
this.ended = false;
|
|
24144
24274
|
return sessionId;
|
|
24145
24275
|
}
|
|
24146
24276
|
}
|
|
24147
|
-
this.sessionId =
|
|
24277
|
+
this.sessionId = randomBytes6(16).toString("hex");
|
|
24148
24278
|
const now = Math.floor(Date.now() / 1e3);
|
|
24149
24279
|
this.data = { _created: now, _accessed: now };
|
|
24150
24280
|
this.dirty = false;
|
|
24151
24281
|
this.safeWrite(this.sessionId, this.data, this.ttl);
|
|
24282
|
+
this.stored = false;
|
|
24283
|
+
this.loaded = /* @__PURE__ */ new Map();
|
|
24284
|
+
this.cleared = false;
|
|
24285
|
+
this.ended = false;
|
|
24152
24286
|
return this.sessionId;
|
|
24153
24287
|
}
|
|
24154
24288
|
/**
|
|
@@ -24189,9 +24323,7 @@ var init_session = __esm({
|
|
|
24189
24323
|
if (this.sessionId) {
|
|
24190
24324
|
this.safeDestroy(this.sessionId);
|
|
24191
24325
|
}
|
|
24192
|
-
this.
|
|
24193
|
-
this.data = null;
|
|
24194
|
-
this.dirty = false;
|
|
24326
|
+
this.forget();
|
|
24195
24327
|
}
|
|
24196
24328
|
/**
|
|
24197
24329
|
* Get all session data (excluding internal keys).
|
|
@@ -24214,6 +24346,7 @@ var init_session = __esm({
|
|
|
24214
24346
|
const now = Math.floor(Date.now() / 1e3);
|
|
24215
24347
|
this.data = { _created: this.data._created, _accessed: now };
|
|
24216
24348
|
this.dirty = true;
|
|
24349
|
+
this.cleared = true;
|
|
24217
24350
|
this.save();
|
|
24218
24351
|
}
|
|
24219
24352
|
/**
|
|
@@ -24230,17 +24363,36 @@ var init_session = __esm({
|
|
|
24230
24363
|
* session fixation — the pre-auth ID is destroyed and the data is carried
|
|
24231
24364
|
* onto a fresh, unguessable ID. A backend destroy/write failure is logged
|
|
24232
24365
|
* (never silent) but does not throw under the default policy.
|
|
24366
|
+
*
|
|
24367
|
+
* What is carried is the session as it is stored now with this request's
|
|
24368
|
+
* own changes applied, the same merge save() does. If another request ended
|
|
24369
|
+
* the session after this one loaded it (a logout, or a regenerate of its
|
|
24370
|
+
* own), it stays ended: nothing is carried, no id is minted and null is
|
|
24371
|
+
* returned, so no cookie goes out to replace the one that request sent.
|
|
24233
24372
|
*/
|
|
24234
24373
|
regenerate() {
|
|
24374
|
+
if (this.ended) return null;
|
|
24375
|
+
if (this.sessionId && this.stored && this.data) {
|
|
24376
|
+
const current = this.safeRead(this.sessionId);
|
|
24377
|
+
if (!this.lastReadFailed) {
|
|
24378
|
+
if (!current) {
|
|
24379
|
+
this.forget();
|
|
24380
|
+
this.ended = true;
|
|
24381
|
+
return null;
|
|
24382
|
+
}
|
|
24383
|
+
if (!this.cleared) this.data = this.merged(current);
|
|
24384
|
+
}
|
|
24385
|
+
}
|
|
24235
24386
|
const oldId = this.sessionId;
|
|
24236
24387
|
const oldData = this.data;
|
|
24237
24388
|
if (oldId) {
|
|
24238
24389
|
this.safeDestroy(oldId);
|
|
24239
24390
|
}
|
|
24240
|
-
this.sessionId =
|
|
24391
|
+
this.sessionId = randomBytes6(16).toString("hex");
|
|
24241
24392
|
this.data = oldData ?? { _created: Math.floor(Date.now() / 1e3), _accessed: Math.floor(Date.now() / 1e3) };
|
|
24242
24393
|
this.data._accessed = Math.floor(Date.now() / 1e3);
|
|
24243
24394
|
this.dirty = true;
|
|
24395
|
+
this.stored = false;
|
|
24244
24396
|
this.save();
|
|
24245
24397
|
return this.sessionId;
|
|
24246
24398
|
}
|
|
@@ -24316,17 +24468,84 @@ var init_session = __esm({
|
|
|
24316
24468
|
*
|
|
24317
24469
|
* Returns true on a successful persist, false if the backend was unreachable
|
|
24318
24470
|
* (logged). The dirty flag is cleared only on success so a later save()
|
|
24319
|
-
* retries once the backend recovers. A
|
|
24471
|
+
* retries once the backend recovers. A call with no session returns true.
|
|
24472
|
+
*
|
|
24473
|
+
* Other requests on the same session run while this one does, so the save
|
|
24474
|
+
* writes only THIS request's changes, onto the record as it is stored now.
|
|
24475
|
+
* Writing back the whole snapshot start() loaded undid whatever another
|
|
24476
|
+
* request did in between: a logout (destroy, clear, regenerate) or a
|
|
24477
|
+
* privilege change made with set(). A record removed since this request
|
|
24478
|
+
* loaded it is never re-created: the session has ended, for this request
|
|
24479
|
+
* too. A request that has not loaded a stored record (a new session, the new
|
|
24480
|
+
* id regenerate() mints) writes its data whole, as before.
|
|
24481
|
+
*
|
|
24482
|
+
* A session the store already holds is re-written even when this request
|
|
24483
|
+
* changed nothing: that write re-stamps the backend deadline to
|
|
24484
|
+
* now + TINA4_SESSION_TTL, so a session expires after that span of INACTIVITY
|
|
24485
|
+
* rather than that long after its last CHANGE (ADR-0087 — a session's expiry
|
|
24486
|
+
* slides on activity). The re-write is the re-read, merged record, so sliding
|
|
24487
|
+
* the deadline is concurrent-safe: it never clobbers another request's change,
|
|
24488
|
+
* never re-creates an ended record, and TINA4_SESSION_TTL=0 still re-stamps a
|
|
24489
|
+
* never-expires deadline (ADR-0027). A request with no stored session and no
|
|
24490
|
+
* change writes nothing — there is no deadline to move.
|
|
24320
24491
|
*/
|
|
24321
24492
|
save() {
|
|
24322
24493
|
if (!this.sessionId || !this.data) return true;
|
|
24323
|
-
if (!this.dirty) return true;
|
|
24324
|
-
|
|
24494
|
+
if (!this.dirty && !this.stored) return true;
|
|
24495
|
+
let record = this.data;
|
|
24496
|
+
if (this.stored) {
|
|
24497
|
+
const current = this.safeRead(this.sessionId);
|
|
24498
|
+
if (this.lastReadFailed) return false;
|
|
24499
|
+
if (!current) {
|
|
24500
|
+
this.forget();
|
|
24501
|
+
this.ended = true;
|
|
24502
|
+
return true;
|
|
24503
|
+
}
|
|
24504
|
+
if (!this.cleared) record = this.merged(current);
|
|
24505
|
+
}
|
|
24506
|
+
if (this.safeWrite(this.sessionId, record, this.ttl)) {
|
|
24325
24507
|
this.dirty = false;
|
|
24508
|
+
this.stored = true;
|
|
24509
|
+
this.cleared = false;
|
|
24510
|
+
this.loaded = fingerprints(this.data);
|
|
24326
24511
|
return true;
|
|
24327
24512
|
}
|
|
24328
24513
|
return false;
|
|
24329
24514
|
}
|
|
24515
|
+
/**
|
|
24516
|
+
* The stored record with this request's changes applied: every key whose
|
|
24517
|
+
* value differs from what this request loaded or last saved, and the
|
|
24518
|
+
* removal of every key it deleted. Keys it did not touch keep whatever is
|
|
24519
|
+
* stored now.
|
|
24520
|
+
*/
|
|
24521
|
+
merged(current) {
|
|
24522
|
+
const data = this.data;
|
|
24523
|
+
const record = { ...current };
|
|
24524
|
+
for (const [key, value] of Object.entries(data)) {
|
|
24525
|
+
const print = fingerprint(value);
|
|
24526
|
+
if (print === void 0 || !this.loaded.has(key) || this.loaded.get(key) !== print) {
|
|
24527
|
+
record[key] = value;
|
|
24528
|
+
}
|
|
24529
|
+
}
|
|
24530
|
+
for (const key of this.loaded.keys()) {
|
|
24531
|
+
if (!Object.prototype.hasOwnProperty.call(data, key)) {
|
|
24532
|
+
delete record[key];
|
|
24533
|
+
}
|
|
24534
|
+
}
|
|
24535
|
+
return record;
|
|
24536
|
+
}
|
|
24537
|
+
/**
|
|
24538
|
+
* End the session for this request without touching the store: no id and
|
|
24539
|
+
* no data, so nothing more is written and no cookie goes out for it.
|
|
24540
|
+
*/
|
|
24541
|
+
forget() {
|
|
24542
|
+
this.sessionId = null;
|
|
24543
|
+
this.data = null;
|
|
24544
|
+
this.dirty = false;
|
|
24545
|
+
this.stored = false;
|
|
24546
|
+
this.cleared = false;
|
|
24547
|
+
this.loaded = /* @__PURE__ */ new Map();
|
|
24548
|
+
}
|
|
24330
24549
|
};
|
|
24331
24550
|
}
|
|
24332
24551
|
});
|
|
@@ -24695,11 +24914,11 @@ function formatSourceBlock(filename, lineno) {
|
|
|
24695
24914
|
const lines = readSourceLines(filename, lineno);
|
|
24696
24915
|
if (lines.length === 0) return "";
|
|
24697
24916
|
const rows = lines.map(([num, text, isError]) => {
|
|
24698
|
-
const
|
|
24917
|
+
const rowClass = isError ? "eo-line eo-line-err" : "eo-line";
|
|
24699
24918
|
const marker = isError ? "▶" : " ";
|
|
24700
|
-
return `<div
|
|
24919
|
+
return `<div class="${rowClass}"><span class="eo-ln">${num}</span><span class="eo-marker">${marker}</span><span class="eo-code">${esc(text)}</span></div>`;
|
|
24701
24920
|
}).join("\n");
|
|
24702
|
-
return `<div
|
|
24921
|
+
return `<div class="eo-source">` + rows + `</div>`;
|
|
24703
24922
|
}
|
|
24704
24923
|
function formatFrame(frame, capturedAt = 0) {
|
|
24705
24924
|
const source = frame.file && frame.line > 0 ? formatSourceBlock(frame.file, frame.line) : "";
|
|
@@ -24711,23 +24930,23 @@ function formatFrame(frame, capturedAt = 0) {
|
|
|
24711
24930
|
if (mtime > capturedAt + 0.5) {
|
|
24712
24931
|
const d = new Date(mtime * 1e3);
|
|
24713
24932
|
const mtimeIso = `${String(d.getUTCHours()).padStart(2, "0")}:${String(d.getUTCMinutes()).padStart(2, "0")}:${String(d.getUTCSeconds()).padStart(2, "0")}`;
|
|
24714
|
-
staleBadge = ` <span
|
|
24933
|
+
staleBadge = ` <span class="eo-stale">FILE MODIFIED @ ${mtimeIso} UTC — source may not match what failed</span>`;
|
|
24715
24934
|
}
|
|
24716
24935
|
} catch {
|
|
24717
24936
|
}
|
|
24718
24937
|
}
|
|
24719
|
-
return `<div
|
|
24938
|
+
return `<div class="eo-frame"><div class="eo-frame-head"><span class="eo-file">${esc(frame.file)}</span><span class="eo-sep"> : </span><span class="eo-lineno">${frame.line}</span><span class="eo-sep"> in </span><span class="eo-fn">${esc(frame.func)}</span>` + staleBadge + `</div>` + source + `</div>`;
|
|
24720
24939
|
}
|
|
24721
24940
|
function collapsible(title, content, openByDefault = false) {
|
|
24722
24941
|
const open = openByDefault ? " open" : "";
|
|
24723
|
-
return `<details
|
|
24942
|
+
return `<details class="eo-details"${open}><summary class="eo-summary">${esc(title)}</summary><div class="eo-details-body">${content}</div></details>`;
|
|
24724
24943
|
}
|
|
24725
24944
|
function table(pairs) {
|
|
24726
|
-
if (pairs.length === 0) return `<span
|
|
24945
|
+
if (pairs.length === 0) return `<span class="eo-none">None</span>`;
|
|
24727
24946
|
const rows = pairs.map(
|
|
24728
|
-
([key, val]) => `<tr><td
|
|
24947
|
+
([key, val]) => `<tr><td class="eo-key">${esc(key)}</td><td class="eo-val">${esc(val)}</td></tr>`
|
|
24729
24948
|
).join("");
|
|
24730
|
-
return `<table
|
|
24949
|
+
return `<table class="eo-table">${rows}</table>`;
|
|
24731
24950
|
}
|
|
24732
24951
|
function renderErrorOverlay(error2, request) {
|
|
24733
24952
|
const capturedAt = Date.now() / 1e3;
|
|
@@ -24740,7 +24959,7 @@ function renderErrorOverlay(error2, request) {
|
|
|
24740
24959
|
}
|
|
24741
24960
|
const hidden = frames.length - Math.min(frames.length, MAX_FRAMES);
|
|
24742
24961
|
if (hidden > 0) {
|
|
24743
|
-
framesHtml += `<div
|
|
24962
|
+
framesHtml += `<div class="eo-hidden-frames">… ${hidden} more stack frames hidden (truncated at ${MAX_FRAMES})</div>`;
|
|
24744
24963
|
}
|
|
24745
24964
|
const requestPairs = [];
|
|
24746
24965
|
if (request != null) {
|
|
@@ -24777,37 +24996,72 @@ function renderErrorOverlay(error2, request) {
|
|
|
24777
24996
|
];
|
|
24778
24997
|
const envSection = collapsible("Environment", table(envPairs));
|
|
24779
24998
|
const stackSection = collapsible("Stack Trace", framesHtml, true);
|
|
24999
|
+
const nonce = currentCspNonce();
|
|
24780
25000
|
return `<!DOCTYPE html>
|
|
24781
25001
|
<html lang="en">
|
|
24782
25002
|
<head>
|
|
24783
25003
|
<meta charset="utf-8">
|
|
24784
25004
|
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
24785
25005
|
<title>Tina4 Error \u2014 ${esc(excType)}</title>
|
|
24786
|
-
<style>
|
|
24787
|
-
|
|
24788
|
-
body{background:${BG};color:${TEXT};font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;padding:24px;line-height:1.5;}
|
|
25006
|
+
<style nonce="${nonce}">
|
|
25007
|
+
${overlayStylesheet()}
|
|
24789
25008
|
</style>
|
|
24790
25009
|
</head>
|
|
24791
25010
|
<body>
|
|
24792
|
-
<div
|
|
24793
|
-
<div
|
|
24794
|
-
<div
|
|
24795
|
-
<span
|
|
24796
|
-
<span
|
|
25011
|
+
<div class="eo-wrap">
|
|
25012
|
+
<div class="eo-header">
|
|
25013
|
+
<div class="eo-badge-row">
|
|
25014
|
+
<span class="eo-badge">Error</span>
|
|
25015
|
+
<span class="eo-sub">Tina4 Debug Overlay</span>
|
|
24797
25016
|
</div>
|
|
24798
|
-
<h1
|
|
24799
|
-
<p
|
|
25017
|
+
<h1 class="eo-type">${esc(excType)}</h1>
|
|
25018
|
+
<p class="eo-msg">${esc(excMsg)}</p>
|
|
24800
25019
|
</div>
|
|
24801
25020
|
${stackSection}
|
|
24802
25021
|
${requestSection}
|
|
24803
25022
|
${envSection}
|
|
24804
|
-
<div
|
|
25023
|
+
<div class="eo-footer">
|
|
24805
25024
|
Tina4 Debug Overlay — This page is only shown in debug mode. Set TINA4_DEBUG=false in production.
|
|
24806
25025
|
</div>
|
|
24807
25026
|
</div>
|
|
24808
25027
|
</body>
|
|
24809
25028
|
</html>`;
|
|
24810
25029
|
}
|
|
25030
|
+
function overlayStylesheet() {
|
|
25031
|
+
return `
|
|
25032
|
+
*{margin:0;padding:0;box-sizing:border-box;}
|
|
25033
|
+
body{background:${BG};color:${TEXT};font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;padding:24px;line-height:1.5;}
|
|
25034
|
+
.eo-wrap{max-width:960px;margin:0 auto;}
|
|
25035
|
+
.eo-header{margin-bottom:24px;}
|
|
25036
|
+
.eo-badge-row{display:flex;align-items:center;gap:12px;margin-bottom:12px;}
|
|
25037
|
+
.eo-badge{background:${RED};color:${BG};padding:4px 12px;border-radius:4px;font-weight:700;font-size:13px;text-transform:uppercase;}
|
|
25038
|
+
.eo-sub{color:${SUBTEXT};font-size:14px;}
|
|
25039
|
+
.eo-type{color:${RED};font-size:28px;font-weight:700;margin-bottom:8px;}
|
|
25040
|
+
.eo-msg{color:${TEXT};font-size:18px;font-family:'SF Mono','Fira Code','Consolas',monospace;background:${SURFACE};padding:12px 16px;border-radius:6px;border-left:4px solid ${RED};}
|
|
25041
|
+
.eo-footer{margin-top:32px;padding-top:16px;border-top:1px solid ${OVERLAY};color:${SUBTEXT};font-size:12px;}
|
|
25042
|
+
.eo-source{background:${SURFACE};border-radius:6px;padding:12px;overflow-x:auto;font-family:'SF Mono','Fira Code','Consolas',monospace;font-size:13px;line-height:1.6;}
|
|
25043
|
+
.eo-line{display:flex;padding:1px 0;}
|
|
25044
|
+
.eo-line-err{background:${ERROR_LINE_BG};}
|
|
25045
|
+
.eo-ln{color:${YELLOW};min-width:3.5em;text-align:right;padding-right:1em;user-select:none;}
|
|
25046
|
+
.eo-marker{color:${RED};width:1.2em;user-select:none;}
|
|
25047
|
+
.eo-code{color:${TEXT};white-space:pre-wrap;tab-size:4;}
|
|
25048
|
+
.eo-frame{margin-bottom:16px;}
|
|
25049
|
+
.eo-frame-head{margin-bottom:4px;}
|
|
25050
|
+
.eo-file{color:${BLUE};}
|
|
25051
|
+
.eo-sep{color:${SUBTEXT};}
|
|
25052
|
+
.eo-lineno{color:${YELLOW};}
|
|
25053
|
+
.eo-fn{color:${GREEN};}
|
|
25054
|
+
.eo-stale{background:${PEACH};color:${BG};padding:1px 8px;border-radius:3px;font-size:11px;font-weight:700;margin-left:6px;}
|
|
25055
|
+
.eo-details{margin-top:16px;}
|
|
25056
|
+
.eo-summary{cursor:pointer;color:${LAVENDER};font-weight:600;font-size:15px;padding:8px 0;user-select:none;}
|
|
25057
|
+
.eo-details-body{padding:8px 0;}
|
|
25058
|
+
.eo-table{border-collapse:collapse;width:100%;}
|
|
25059
|
+
.eo-key{color:${PEACH};padding:4px 16px 4px 0;vertical-align:top;white-space:nowrap;}
|
|
25060
|
+
.eo-val{color:${TEXT};padding:4px 0;word-break:break-all;}
|
|
25061
|
+
.eo-none{color:${SUBTEXT};}
|
|
25062
|
+
.eo-hidden-frames{color:${SUBTEXT};padding:8px 0;font-size:13px;}
|
|
25063
|
+
`;
|
|
25064
|
+
}
|
|
24811
25065
|
function isDebugMode() {
|
|
24812
25066
|
return isTruthy(process.env.TINA4_DEBUG);
|
|
24813
25067
|
}
|
|
@@ -24816,6 +25070,7 @@ var init_errorOverlay = __esm({
|
|
|
24816
25070
|
"src/errorOverlay.ts"() {
|
|
24817
25071
|
"use strict";
|
|
24818
25072
|
init_dotenv();
|
|
25073
|
+
init_csp();
|
|
24819
25074
|
MAX_FRAMES = 50;
|
|
24820
25075
|
SENSITIVE_KEY_RE = /password|passwd|secret|token|authorization|cookie|key/i;
|
|
24821
25076
|
REDACTED2 = "[redacted]";
|
|
@@ -26666,7 +26921,11 @@ export default async function (req: Tina4Request, res: Tina4Response) {
|
|
|
26666
26921
|
`{% extends "base.twig" %}
|
|
26667
26922
|
{% block title %}Login{% endblock %}
|
|
26668
26923
|
{% block content %}
|
|
26669
|
-
|
|
26924
|
+
{# No inline style attribute: a nonce lets this <style> block run under the
|
|
26925
|
+
strict default Content-Security-Policy. csp_nonce() is a built-in Frond global
|
|
26926
|
+
that returns this response's nonce (ADR-0088). #}
|
|
26927
|
+
<style nonce="{{ csp_nonce() }}">.auth-form{max-width:400px;margin:0 auto}</style>
|
|
26928
|
+
<div class="container mt-4 auth-form">
|
|
26670
26929
|
<h1>Login</h1>
|
|
26671
26930
|
<form method="post" action="/api/auth/login">
|
|
26672
26931
|
{{ form_token() }}
|
|
@@ -26690,7 +26949,8 @@ export default async function (req: Tina4Request, res: Tina4Response) {
|
|
|
26690
26949
|
`{% extends "base.twig" %}
|
|
26691
26950
|
{% block title %}Register{% endblock %}
|
|
26692
26951
|
{% block content %}
|
|
26693
|
-
<
|
|
26952
|
+
<style nonce="{{ csp_nonce() }}">.auth-form{max-width:400px;margin:0 auto}</style>
|
|
26953
|
+
<div class="container mt-4 auth-form">
|
|
26694
26954
|
<h1>Register</h1>
|
|
26695
26955
|
<form method="post" action="/api/auth/register">
|
|
26696
26956
|
{{ form_token() }}
|
|
@@ -27627,7 +27887,7 @@ import * as path3 from "node:path";
|
|
|
27627
27887
|
import * as os from "node:os";
|
|
27628
27888
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
27629
27889
|
import { createRequire as createRequire8 } from "node:module";
|
|
27630
|
-
import { randomBytes as
|
|
27890
|
+
import { randomBytes as randomBytes7 } from "node:crypto";
|
|
27631
27891
|
function reqSibling(pkg) {
|
|
27632
27892
|
return req(`../../${pkg}/src/index.ts`);
|
|
27633
27893
|
}
|
|
@@ -28949,7 +29209,7 @@ var init_mcp = __esm({
|
|
|
28949
29209
|
// ── Session lifecycle + protocol negotiation ──────────────────
|
|
28950
29210
|
/** Mint a new session id and remember it. Called on `initialize`. */
|
|
28951
29211
|
openSession() {
|
|
28952
|
-
const sid =
|
|
29212
|
+
const sid = randomBytes7(16).toString("hex");
|
|
28953
29213
|
this._sessions.set(sid, Date.now());
|
|
28954
29214
|
return sid;
|
|
28955
29215
|
}
|
|
@@ -36725,8 +36985,8 @@ var init_devAdmin = __esm({
|
|
|
36725
36985
|
* Duplicate errors (same message) increment count and update last_seen.
|
|
36726
36986
|
*/
|
|
36727
36987
|
static capture(errorType, message, traceback = "", file = "", line = 0) {
|
|
36728
|
-
const
|
|
36729
|
-
const existing = this.errors.find((e) => e.fingerprint ===
|
|
36988
|
+
const fingerprint2 = `${errorType}|${message}|${file}|${line}`;
|
|
36989
|
+
const existing = this.errors.find((e) => e.fingerprint === fingerprint2);
|
|
36730
36990
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
36731
36991
|
if (existing) {
|
|
36732
36992
|
existing.count = (existing.count || 1) + 1;
|
|
@@ -36739,7 +36999,7 @@ var init_devAdmin = __esm({
|
|
|
36739
36999
|
message,
|
|
36740
37000
|
stack: traceback || void 0,
|
|
36741
37001
|
resolved: false,
|
|
36742
|
-
...{ fingerprint, error_type: errorType, file, line, count: 1, first_seen: now, last_seen: now }
|
|
37002
|
+
...{ fingerprint: fingerprint2, error_type: errorType, file, line, count: 1, first_seen: now, last_seen: now }
|
|
36743
37003
|
});
|
|
36744
37004
|
if (this.errors.length > this.maxErrors) {
|
|
36745
37005
|
this.errors = this.errors.slice(-this.maxErrors);
|
|
@@ -39418,7 +39678,7 @@ function swaggerEnabled() {
|
|
|
39418
39678
|
}
|
|
39419
39679
|
function createSwaggerRoutes(getSpec) {
|
|
39420
39680
|
const serveUi = async (_req, res) => {
|
|
39421
|
-
res.html(SWAGGER_UI_HTML("/swagger/openapi.json"));
|
|
39681
|
+
res.html(SWAGGER_UI_HTML("/swagger/openapi.json", res.cspNonce));
|
|
39422
39682
|
};
|
|
39423
39683
|
return [
|
|
39424
39684
|
{
|
|
@@ -39462,13 +39722,13 @@ var SWAGGER_UI_HTML;
|
|
|
39462
39722
|
var init_ui = __esm({
|
|
39463
39723
|
"../swagger/src/ui.ts"() {
|
|
39464
39724
|
"use strict";
|
|
39465
|
-
SWAGGER_UI_HTML = (specUrl) => `<!DOCTYPE html>
|
|
39725
|
+
SWAGGER_UI_HTML = (specUrl, nonce) => `<!DOCTYPE html>
|
|
39466
39726
|
<html lang="en">
|
|
39467
39727
|
<head>
|
|
39468
39728
|
<meta charset="UTF-8">
|
|
39469
39729
|
<title>Tina4 API Documentation</title>
|
|
39470
39730
|
<link rel="stylesheet" href="${swaggerUiCdn()}/swagger-ui.css">
|
|
39471
|
-
<style>
|
|
39731
|
+
<style nonce="${nonce}">
|
|
39472
39732
|
body { margin: 0; background: #fafafa; }
|
|
39473
39733
|
.topbar { display: none !important; }
|
|
39474
39734
|
</style>
|
|
@@ -39476,7 +39736,7 @@ var init_ui = __esm({
|
|
|
39476
39736
|
<body>
|
|
39477
39737
|
<div id="swagger-ui"></div>
|
|
39478
39738
|
<script src="${swaggerUiCdn()}/swagger-ui-bundle.js"></script>
|
|
39479
|
-
<script>
|
|
39739
|
+
<script nonce="${nonce}">
|
|
39480
39740
|
SwaggerUIBundle({
|
|
39481
39741
|
url: "${specUrl}",
|
|
39482
39742
|
dom_id: '#swagger-ui',
|
|
@@ -39516,7 +39776,7 @@ __export(sso_exports, {
|
|
|
39516
39776
|
SsoError: () => SsoError,
|
|
39517
39777
|
liveSessionIdentity: () => liveSessionIdentity
|
|
39518
39778
|
});
|
|
39519
|
-
import { createHash as createHash9, randomBytes as
|
|
39779
|
+
import { createHash as createHash9, randomBytes as randomBytes8, timingSafeEqual as timingSafeEqual3 } from "node:crypto";
|
|
39520
39780
|
function expiresAt(tokens) {
|
|
39521
39781
|
const lifetime = Number(tokens.expires_in ?? 0);
|
|
39522
39782
|
return lifetime > 0 ? Math.floor(Date.now() / 1e3) + lifetime : 0;
|
|
@@ -39644,9 +39904,9 @@ var init_sso = __esm({
|
|
|
39644
39904
|
async login(requestOrSession, returnTo = "/") {
|
|
39645
39905
|
const session = this.session(requestOrSession);
|
|
39646
39906
|
if (!session) throw new SsoError("SSO login requires a Tina4 Session");
|
|
39647
|
-
const state =
|
|
39648
|
-
const nonce =
|
|
39649
|
-
const verifier =
|
|
39907
|
+
const state = randomBytes8(32).toString("base64url");
|
|
39908
|
+
const nonce = randomBytes8(32).toString("base64url");
|
|
39909
|
+
const verifier = randomBytes8(64).toString("base64url");
|
|
39650
39910
|
const challenge = createHash9("sha256").update(verifier).digest("base64url");
|
|
39651
39911
|
session.set(_Sso.PENDING_KEY, { state, nonce, verifier, return_to: _Sso.safeReturn(returnTo), created_at: Math.floor(Date.now() / 1e3) });
|
|
39652
39912
|
const metadata = await this.discover();
|
|
@@ -39874,7 +40134,7 @@ var init_docsAutoDiscovery = __esm({
|
|
|
39874
40134
|
});
|
|
39875
40135
|
|
|
39876
40136
|
// src/server.ts
|
|
39877
|
-
import { randomBytes as
|
|
40137
|
+
import { randomBytes as randomBytes9 } from "node:crypto";
|
|
39878
40138
|
import { resolve as resolve19, dirname as dirname13, join as join30, relative as relative10 } from "node:path";
|
|
39879
40139
|
import { existsSync as existsSync25, readdirSync as readdirSync17, readFileSync as readFileSync23, statSync as statSync18 } from "node:fs";
|
|
39880
40140
|
import { isatty } from "node:tty";
|
|
@@ -40054,7 +40314,8 @@ function isDevMode() {
|
|
|
40054
40314
|
}
|
|
40055
40315
|
async function renderErrorPage(code, data, templatesDir) {
|
|
40056
40316
|
try {
|
|
40057
|
-
const { Frond: Frond2 } = await Promise.resolve().then(() => (init_engine(), engine_exports));
|
|
40317
|
+
const { Frond: Frond2, setCspNonceProvider: setCspNonceProvider2 } = await Promise.resolve().then(() => (init_engine(), engine_exports));
|
|
40318
|
+
setCspNonceProvider2(currentCspNonce);
|
|
40058
40319
|
const templateFile = `errors/${code}.twig`;
|
|
40059
40320
|
const getCachedFrond = (dir) => {
|
|
40060
40321
|
let instance = frondCache.get(dir);
|
|
@@ -40170,6 +40431,7 @@ function resolveTemplate(pathname, templatesDir) {
|
|
|
40170
40431
|
}
|
|
40171
40432
|
function renderLandingPage(port = 7148) {
|
|
40172
40433
|
const version = TINA4_VERSION;
|
|
40434
|
+
const nonce = currentCspNonce();
|
|
40173
40435
|
const galleryItems = [
|
|
40174
40436
|
{ id: "rest-api", icon: "🚀", name: "REST API", desc: "A simple JSON API with GET and POST endpoints", accent: "accent-blue", tryUrl: "/api/gallery/hello" },
|
|
40175
40437
|
{ id: "orm", icon: "🗃", name: "ORM", desc: "Product model with CRUD endpoints", accent: "accent-green", tryUrl: "/api/gallery/products" },
|
|
@@ -40184,7 +40446,7 @@ function renderLandingPage(port = 7148) {
|
|
|
40184
40446
|
const isDeployed = deployed[item.id] === true;
|
|
40185
40447
|
const tryBtn = isDeployed ? `<a href="${item.tryUrl}" class="gbtn gbtn-try" target="_blank">Try It</a>` : "";
|
|
40186
40448
|
const viewBtn = isDeployed ? `<a href="${item.tryUrl}" class="gbtn gbtn-view" target="_blank">View</a>` : "";
|
|
40187
|
-
const deployBtn = isDeployed ? `<span class="gbtn gbtn-deployed">Deployed</span>` : `<button class="gbtn gbtn-deploy"
|
|
40449
|
+
const deployBtn = isDeployed ? `<span class="gbtn gbtn-deployed">Deployed</span>` : `<button class="gbtn gbtn-deploy" data-deploy="${item.id}">Deploy</button>`;
|
|
40188
40450
|
return `<div class="gallery-card">
|
|
40189
40451
|
<div class="accent ${item.accent}"></div>
|
|
40190
40452
|
<div class="icon">${item.icon}</div>
|
|
@@ -40199,7 +40461,7 @@ function renderLandingPage(port = 7148) {
|
|
|
40199
40461
|
<meta charset="utf-8">
|
|
40200
40462
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
40201
40463
|
<title>Tina4NodeJs</title>
|
|
40202
|
-
<style>
|
|
40464
|
+
<style nonce="${nonce}">
|
|
40203
40465
|
*{margin:0;padding:0;box-sizing:border-box}
|
|
40204
40466
|
body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;background:#0f172a;color:#e2e8f0;min-height:100vh;display:flex;flex-direction:column;align-items:center;position:relative}
|
|
40205
40467
|
.bg-watermark{position:fixed;bottom:-5%;right:-5%;width:45%;opacity:0.04;pointer-events:none;z-index:0}
|
|
@@ -40238,6 +40500,11 @@ h1{font-size:3rem;font-weight:700;margin-bottom:0.25rem;letter-spacing:-1px}
|
|
|
40238
40500
|
.gbtn-deployed{background:transparent;border:1px solid #22c55e;color:#22c55e;cursor:default;font-size:0.7rem}
|
|
40239
40501
|
@keyframes wiggle{0%{transform:rotate(0deg)}15%{transform:rotate(14deg)}30%{transform:rotate(-10deg)}45%{transform:rotate(8deg)}60%{transform:rotate(-4deg)}75%{transform:rotate(2deg)}100%{transform:rotate(0deg)}}
|
|
40240
40502
|
.star-wiggle{display:inline-block;transform-origin:center}
|
|
40503
|
+
.gallery-cards{display:grid;grid-template-columns:repeat(auto-fit,minmax(280px,1fr));gap:1rem}
|
|
40504
|
+
.tok-comment{color:#64748b}
|
|
40505
|
+
.tok-kw{color:#c084fc}
|
|
40506
|
+
.tok-str{color:#4ade80}
|
|
40507
|
+
.tok-fn{color:#38bdf8}
|
|
40241
40508
|
</style>
|
|
40242
40509
|
</head>
|
|
40243
40510
|
<body>
|
|
@@ -40263,23 +40530,23 @@ h1{font-size:3rem;font-weight:700;margin-bottom:0.25rem;letter-spacing:-1px}
|
|
|
40263
40530
|
<div class="section">
|
|
40264
40531
|
<div class="card">
|
|
40265
40532
|
<h2>Getting Started</h2>
|
|
40266
|
-
<pre class="code-block"><code><span
|
|
40267
|
-
<span
|
|
40533
|
+
<pre class="code-block"><code><span class="tok-comment">// app.ts</span>
|
|
40534
|
+
<span class="tok-kw">import</span> { startServer, Router } <span class="tok-kw">from</span> <span class="tok-str">"tina4-nodejs"</span>;
|
|
40268
40535
|
|
|
40269
|
-
Router.get(<span
|
|
40270
|
-
<span
|
|
40536
|
+
Router.get(<span class="tok-str">"/hello"</span>, <span class="tok-kw">async</span> (<span class="tok-fn">req</span>, <span class="tok-fn">res</span>) => {
|
|
40537
|
+
<span class="tok-kw">return</span> res.json({ message: <span class="tok-str">"Hello World!"</span> });
|
|
40271
40538
|
});
|
|
40272
40539
|
|
|
40273
|
-
startServer({ port: 7148 }); <span
|
|
40540
|
+
startServer({ port: 7148 }); <span class="tok-comment">// starts on port 7148</span></code></pre>
|
|
40274
40541
|
</div>
|
|
40275
40542
|
</div>
|
|
40276
40543
|
<div class="gallery">
|
|
40277
40544
|
<h2 id="gallery">What You Can Build</h2>
|
|
40278
|
-
<div
|
|
40545
|
+
<div class="gallery-cards">
|
|
40279
40546
|
${galleryCards}
|
|
40280
40547
|
</div>
|
|
40281
40548
|
</div>
|
|
40282
|
-
<script>
|
|
40549
|
+
<script nonce="${nonce}">
|
|
40283
40550
|
function deployGallery(name) {
|
|
40284
40551
|
if (!confirm('Deploy the "' + name + '" gallery example into your project? This will copy files into src/.')) return;
|
|
40285
40552
|
fetch('/__dev/api/gallery/deploy', {
|
|
@@ -40314,6 +40581,12 @@ function deployGallery(name) {
|
|
|
40314
40581
|
}
|
|
40315
40582
|
setTimeout(doWiggle,3000);
|
|
40316
40583
|
})();
|
|
40584
|
+
// CSP-clean wiring: no inline onclick handlers \u2014 bind every Deploy button from
|
|
40585
|
+
// its data-deploy attribute (a nonce covers script elements, not event-handler
|
|
40586
|
+
// attributes).
|
|
40587
|
+
document.querySelectorAll('.gbtn-deploy[data-deploy]').forEach(function(btn){
|
|
40588
|
+
btn.addEventListener('click', function(){ deployGallery(btn.dataset.deploy); });
|
|
40589
|
+
});
|
|
40317
40590
|
</script>
|
|
40318
40591
|
</body>
|
|
40319
40592
|
</html>`;
|
|
@@ -40530,7 +40803,7 @@ async function renderDispatchError(err, req2, res, templatesDir) {
|
|
|
40530
40803
|
}
|
|
40531
40804
|
}
|
|
40532
40805
|
}
|
|
40533
|
-
const requestId = Log.getRequestId() ??
|
|
40806
|
+
const requestId = Log.getRequestId() ?? randomBytes9(4).toString("hex");
|
|
40534
40807
|
if (wantsJson(req2)) {
|
|
40535
40808
|
const body = negotiatedErrorBody(500, "Internal Server Error", requestId);
|
|
40536
40809
|
res.raw.writeHead(500, { "Content-Type": "application/json" });
|
|
@@ -40601,7 +40874,7 @@ function serveStaticAsset(ctx) {
|
|
|
40601
40874
|
return false;
|
|
40602
40875
|
}
|
|
40603
40876
|
async function serveNotFound(ctx) {
|
|
40604
|
-
const requestId = Log.getRequestId() ??
|
|
40877
|
+
const requestId = Log.getRequestId() ?? randomBytes9(4).toString("hex");
|
|
40605
40878
|
if (wantsJson(ctx.req)) {
|
|
40606
40879
|
const body = negotiatedErrorBody(404, "Not Found", requestId);
|
|
40607
40880
|
ctx.res.raw.writeHead(404, httpReason(404), { "Content-Type": "application/json" });
|
|
@@ -40624,7 +40897,8 @@ async function buildDispatchContext(router, base) {
|
|
|
40624
40897
|
const templatesDir = resolve19(root, "src/templates");
|
|
40625
40898
|
let frondEngine = null;
|
|
40626
40899
|
try {
|
|
40627
|
-
const { Frond: Frond2 } = await Promise.resolve().then(() => (init_engine(), engine_exports));
|
|
40900
|
+
const { Frond: Frond2, setCspNonceProvider: setCspNonceProvider2 } = await Promise.resolve().then(() => (init_engine(), engine_exports));
|
|
40901
|
+
setCspNonceProvider2(currentCspNonce);
|
|
40628
40902
|
frondEngine = new Frond2(templatesDir);
|
|
40629
40903
|
} catch {
|
|
40630
40904
|
}
|
|
@@ -40734,9 +41008,13 @@ async function dispatchInner(ctx, rawReq, rawRes, requestId) {
|
|
|
40734
41008
|
}
|
|
40735
41009
|
}
|
|
40736
41010
|
async function runDispatch(ctx, rawReq, rawRes) {
|
|
40737
|
-
const requestId = Log.sanitizeRequestId(rawReq.headers["x-request-id"]) ??
|
|
41011
|
+
const requestId = Log.sanitizeRequestId(rawReq.headers["x-request-id"]) ?? randomBytes9(4).toString("hex");
|
|
40738
41012
|
if (!rawRes.headersSent) rawRes.setHeader("x-request-id", requestId);
|
|
40739
|
-
|
|
41013
|
+
const requestNonce = generateNonce();
|
|
41014
|
+
return Log.runWithRequestId(
|
|
41015
|
+
requestId,
|
|
41016
|
+
() => runWithCspNonce(requestNonce, () => dispatchInner(ctx, rawReq, rawRes, requestId))
|
|
41017
|
+
);
|
|
40740
41018
|
}
|
|
40741
41019
|
function loopbackBindHosts(host) {
|
|
40742
41020
|
const normalized = host.trim().replace(/^\[|\]$/g, "").toLowerCase();
|
|
@@ -40863,7 +41141,8 @@ async function startServer(config) {
|
|
|
40863
41141
|
let frondEngine = null;
|
|
40864
41142
|
setDefaultTemplatesDir(templatesDir);
|
|
40865
41143
|
try {
|
|
40866
|
-
const { Frond: Frond2 } = await Promise.resolve().then(() => (init_engine(), engine_exports));
|
|
41144
|
+
const { Frond: Frond2, setCspNonceProvider: setCspNonceProvider2 } = await Promise.resolve().then(() => (init_engine(), engine_exports));
|
|
41145
|
+
setCspNonceProvider2(currentCspNonce);
|
|
40867
41146
|
frondEngine = new Frond2(templatesDir);
|
|
40868
41147
|
router.addRoute({
|
|
40869
41148
|
method: "GET",
|
|
@@ -41004,10 +41283,12 @@ async function startServer(config) {
|
|
|
41004
41283
|
}
|
|
41005
41284
|
});
|
|
41006
41285
|
aiServer.on("error", (err) => {
|
|
41007
|
-
if (err
|
|
41286
|
+
if (err?.code === "EADDRINUSE") {
|
|
41008
41287
|
Log.warning(`Test port ${testPort} in use \u2014 skipping`);
|
|
41009
|
-
|
|
41288
|
+
} else {
|
|
41289
|
+
Log.error(`Test port ${testPort} bind failed (${err?.code ?? err}) \u2014 skipping`);
|
|
41010
41290
|
}
|
|
41291
|
+
aiServer = null;
|
|
41011
41292
|
});
|
|
41012
41293
|
aiServer.listen(testPort, host);
|
|
41013
41294
|
}
|
|
@@ -41222,6 +41503,7 @@ var init_server = __esm({
|
|
|
41222
41503
|
init_static();
|
|
41223
41504
|
init_dotenv();
|
|
41224
41505
|
init_errorOverlay();
|
|
41506
|
+
init_csp();
|
|
41225
41507
|
init_health();
|
|
41226
41508
|
init_rateLimiter();
|
|
41227
41509
|
init_logger();
|
|
@@ -41471,7 +41753,7 @@ var init_mqttMessage = __esm({
|
|
|
41471
41753
|
// src/mqtt.ts
|
|
41472
41754
|
import net2 from "node:net";
|
|
41473
41755
|
import tls from "node:tls";
|
|
41474
|
-
import { randomBytes as
|
|
41756
|
+
import { randomBytes as randomBytes10 } from "node:crypto";
|
|
41475
41757
|
import { existsSync as existsSync26, readFileSync as readFileSync24 } from "node:fs";
|
|
41476
41758
|
var MqttError, MqttTimeoutError, CONNECT, CONNACK, PUBLISH, PUBACK, SUBSCRIBE, SUBACK, PINGREQ, PINGRESP, DISCONNECT, PROTOCOL_LEVEL, DEFAULT_PORT, DEFAULT_TLS_PORT, DEFAULT_URL, DEFAULT_KEEPALIVE, SUBSCRIPTION_REFUSED, MAX_REMAINING_LENGTH, QOS2_REFUSED_MESSAGE, CONNACK_RETURN_CODES, Mqtt;
|
|
41477
41759
|
var init_mqtt = __esm({
|
|
@@ -41559,7 +41841,7 @@ var init_mqtt = __esm({
|
|
|
41559
41841
|
this.caFile = options.caFile ?? (Env.str("TINA4_MQTT_CA_FILE") || null);
|
|
41560
41842
|
this.tlsVerify = options.tlsVerify ?? Env.bool("TINA4_MQTT_TLS_VERIFY", true);
|
|
41561
41843
|
let cid = options.clientId ?? (Env.str("TINA4_MQTT_CLIENT_ID") || null);
|
|
41562
|
-
if (cid === null || cid === "") cid = "tina4-" +
|
|
41844
|
+
if (cid === null || cid === "") cid = "tina4-" + randomBytes10(8).toString("hex");
|
|
41563
41845
|
this.clientId = cid;
|
|
41564
41846
|
this.keepalive = options.keepalive ?? Env.int("TINA4_MQTT_KEEPALIVE", DEFAULT_KEEPALIVE);
|
|
41565
41847
|
this.cleanSession = options.cleanSession ?? true;
|
|
@@ -42673,7 +42955,7 @@ var init_ssrf = __esm({
|
|
|
42673
42955
|
import http from "node:http";
|
|
42674
42956
|
import https from "node:https";
|
|
42675
42957
|
import { URL as URL2 } from "node:url";
|
|
42676
|
-
import { randomBytes as
|
|
42958
|
+
import { randomBytes as randomBytes11 } from "node:crypto";
|
|
42677
42959
|
import { promises as fsp, createWriteStream } from "node:fs";
|
|
42678
42960
|
import { basename as basename6 } from "node:path";
|
|
42679
42961
|
import { pipeline } from "node:stream/promises";
|
|
@@ -43065,7 +43347,7 @@ var init_api = __esm({
|
|
|
43065
43347
|
return { http_code: null, body: null, headers: {}, error: "upload requires filePath or fileBytes" };
|
|
43066
43348
|
}
|
|
43067
43349
|
const partContentType = guessContentType(uploadName);
|
|
43068
|
-
const boundary = "----Tina4Boundary" +
|
|
43350
|
+
const boundary = "----Tina4Boundary" + randomBytes11(16).toString("hex");
|
|
43069
43351
|
const bodyBuffer = buildMultipartBody(boundary, fieldName, uploadName, content, partContentType, extraFields);
|
|
43070
43352
|
const contentType = `multipart/form-data; boundary=${boundary}`;
|
|
43071
43353
|
return this.execute("POST", this.buildUrl(path10), bodyBuffer, contentType, headers);
|
|
@@ -46029,7 +46311,7 @@ import {
|
|
|
46029
46311
|
createHmac as createHmac4,
|
|
46030
46312
|
createPrivateKey,
|
|
46031
46313
|
createSign as createSign2,
|
|
46032
|
-
randomBytes as
|
|
46314
|
+
randomBytes as randomBytes12
|
|
46033
46315
|
} from "node:crypto";
|
|
46034
46316
|
function encodeBase64Url(value) {
|
|
46035
46317
|
return Buffer.from(value).toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
|
|
@@ -46109,7 +46391,7 @@ function encryptPayload(payload, subscription) {
|
|
|
46109
46391
|
]);
|
|
46110
46392
|
const prkKey = hmac(authSecret, sharedSecret);
|
|
46111
46393
|
const ikm = hkdfExpand(prkKey, keyInfo, 32);
|
|
46112
|
-
const salt =
|
|
46394
|
+
const salt = randomBytes12(16);
|
|
46113
46395
|
const contentPrk = hmac(salt, ikm);
|
|
46114
46396
|
const cek = hkdfExpand(contentPrk, Buffer.from("Content-Encoding: aes128gcm\0", "ascii"), 16);
|
|
46115
46397
|
const nonce = hkdfExpand(contentPrk, Buffer.from("Content-Encoding: nonce\0", "ascii"), 12);
|
|
@@ -48866,6 +49148,7 @@ __export(index_exports, {
|
|
|
48866
49148
|
cacheStats: () => cacheStats,
|
|
48867
49149
|
checkPassword: () => checkPassword,
|
|
48868
49150
|
clearCache: () => clearCache,
|
|
49151
|
+
clearCurrentNonce: () => clearCurrentNonce,
|
|
48869
49152
|
computeAcceptKey: () => computeAcceptKey,
|
|
48870
49153
|
container: () => container,
|
|
48871
49154
|
cors: () => cors,
|
|
@@ -48877,6 +49160,8 @@ __export(index_exports, {
|
|
|
48877
49160
|
createMessenger: () => createMessenger,
|
|
48878
49161
|
createRequest: () => createRequest,
|
|
48879
49162
|
createResponse: () => createResponse,
|
|
49163
|
+
cspNonce: () => cspNonce,
|
|
49164
|
+
currentCspNonce: () => currentCspNonce,
|
|
48880
49165
|
decodeRequest: () => decodeRequest,
|
|
48881
49166
|
defaultContext: () => defaultContext,
|
|
48882
49167
|
defaultRouter: () => defaultRouter,
|
|
@@ -48902,6 +49187,7 @@ __export(index_exports, {
|
|
|
48902
49187
|
feedbackWhitelist: () => feedbackWhitelist,
|
|
48903
49188
|
fts5Supported: () => fts5Supported,
|
|
48904
49189
|
generateContext: () => generateContext,
|
|
49190
|
+
generateNonce: () => generateNonce,
|
|
48905
49191
|
generateVapidKeys: () => generateVapidKeys,
|
|
48906
49192
|
get: () => get,
|
|
48907
49193
|
getDefaultDevServer: () => getDefaultDevServer,
|
|
@@ -48924,6 +49210,7 @@ __export(index_exports, {
|
|
|
48924
49210
|
httpReason: () => httpReason,
|
|
48925
49211
|
inContainer: () => inContainer,
|
|
48926
49212
|
injectFeedbackWidget: () => injectFeedbackWidget,
|
|
49213
|
+
injectNonceIntoCsp: () => injectNonceIntoCsp,
|
|
48927
49214
|
installAll: () => installAll,
|
|
48928
49215
|
installSelected: () => installSelected,
|
|
48929
49216
|
isBannerSuppressed: () => isBannerSuppressed,
|
|
@@ -48978,6 +49265,7 @@ __export(index_exports, {
|
|
|
48978
49265
|
resetTrustedProxyCache: () => resetTrustedProxyCache,
|
|
48979
49266
|
resolveAlgorithm: () => resolveAlgorithm,
|
|
48980
49267
|
resolveClientIp: () => resolveClientIp,
|
|
49268
|
+
resolveCspHeader: () => resolveCspHeader,
|
|
48981
49269
|
resolveFrameworkVersion: () => resolveFrameworkVersion,
|
|
48982
49270
|
resolvePortAndHost: () => resolvePortAndHost,
|
|
48983
49271
|
resolveStringMiddleware: () => resolveStringMiddleware,
|
|
@@ -48985,12 +49273,14 @@ __export(index_exports, {
|
|
|
48985
49273
|
responseCache: () => responseCache,
|
|
48986
49274
|
runAll: () => runAll,
|
|
48987
49275
|
runRouteMiddlewares: () => runRouteMiddlewares,
|
|
49276
|
+
runWithCspNonce: () => runWithCspNonce,
|
|
48988
49277
|
runtimeDir: () => runtimeDir,
|
|
48989
49278
|
saveUpload: () => saveUpload,
|
|
48990
49279
|
schemaFromParams: () => schemaFromParams,
|
|
48991
49280
|
selectablePids: () => selectablePids,
|
|
48992
49281
|
serveWebSocketRoute: () => serveWebSocketRoute,
|
|
48993
49282
|
sessionCookieName: () => sessionCookieName,
|
|
49283
|
+
setCurrentNonce: () => setCurrentNonce,
|
|
48994
49284
|
setDefaultTemplatesDir: () => setDefaultTemplatesDir,
|
|
48995
49285
|
setFrond: () => setFrond,
|
|
48996
49286
|
showMenu: () => showMenu,
|
|
@@ -49056,6 +49346,7 @@ var init_index = __esm({
|
|
|
49056
49346
|
init_wsdl();
|
|
49057
49347
|
init_htmlElement();
|
|
49058
49348
|
init_errorOverlay();
|
|
49349
|
+
init_csp();
|
|
49059
49350
|
init_ai();
|
|
49060
49351
|
init_sso();
|
|
49061
49352
|
init_push();
|
|
@@ -49246,6 +49537,7 @@ export {
|
|
|
49246
49537
|
cacheStats,
|
|
49247
49538
|
checkPassword,
|
|
49248
49539
|
clearCache,
|
|
49540
|
+
clearCurrentNonce,
|
|
49249
49541
|
computeAcceptKey,
|
|
49250
49542
|
container,
|
|
49251
49543
|
cors,
|
|
@@ -49257,6 +49549,8 @@ export {
|
|
|
49257
49549
|
createMessenger,
|
|
49258
49550
|
createRequest,
|
|
49259
49551
|
createResponse,
|
|
49552
|
+
cspNonce,
|
|
49553
|
+
currentCspNonce,
|
|
49260
49554
|
decodeRequest,
|
|
49261
49555
|
defaultContext,
|
|
49262
49556
|
defaultRouter,
|
|
@@ -49282,6 +49576,7 @@ export {
|
|
|
49282
49576
|
feedbackWhitelist,
|
|
49283
49577
|
fts5Supported,
|
|
49284
49578
|
generateContext,
|
|
49579
|
+
generateNonce,
|
|
49285
49580
|
generateVapidKeys,
|
|
49286
49581
|
get,
|
|
49287
49582
|
getDefaultDevServer,
|
|
@@ -49304,6 +49599,7 @@ export {
|
|
|
49304
49599
|
httpReason,
|
|
49305
49600
|
inContainer,
|
|
49306
49601
|
injectFeedbackWidget,
|
|
49602
|
+
injectNonceIntoCsp,
|
|
49307
49603
|
installAll,
|
|
49308
49604
|
installSelected,
|
|
49309
49605
|
isBannerSuppressed,
|
|
@@ -49358,6 +49654,7 @@ export {
|
|
|
49358
49654
|
resetTrustedProxyCache,
|
|
49359
49655
|
resolveAlgorithm,
|
|
49360
49656
|
resolveClientIp,
|
|
49657
|
+
resolveCspHeader,
|
|
49361
49658
|
resolveFrameworkVersion,
|
|
49362
49659
|
resolvePortAndHost,
|
|
49363
49660
|
resolveStringMiddleware,
|
|
@@ -49365,12 +49662,14 @@ export {
|
|
|
49365
49662
|
responseCache,
|
|
49366
49663
|
runAll,
|
|
49367
49664
|
runRouteMiddlewares,
|
|
49665
|
+
runWithCspNonce,
|
|
49368
49666
|
runtimeDir,
|
|
49369
49667
|
saveUpload,
|
|
49370
49668
|
schemaFromParams,
|
|
49371
49669
|
selectablePids,
|
|
49372
49670
|
serveWebSocketRoute,
|
|
49373
49671
|
sessionCookieName,
|
|
49672
|
+
setCurrentNonce,
|
|
49374
49673
|
setDefaultTemplatesDir,
|
|
49375
49674
|
setFrond,
|
|
49376
49675
|
showMenu,
|