tina4-nodejs 3.13.141 → 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 +817 -538
- package/packages/cli/src/bin.ts +4 -14
- package/packages/cli/src/commands/build.ts +2 -18
- package/packages/cli/src/commands/generate.ts +9 -24
- package/packages/cli/src/commands/init.ts +18 -8
- package/packages/cli/src/commands/lint.ts +3 -13
- package/packages/cli/src/commands/migrate.ts +2 -6
- package/packages/cli/src/commands/migrateRollback.ts +2 -6
- package/packages/cli/src/commands/migrateStatus.ts +2 -6
- package/packages/cli/src/commands/queue.ts +2 -30
- package/packages/cli/src/util.ts +90 -0
- package/packages/core/dist/index.js +592 -262
- package/packages/core/gallery/auth/src/routes/gallery/auth/get.ts +27 -11
- package/packages/core/gallery/queue/src/lib/queueDb.ts +11 -0
- package/packages/core/gallery/queue/src/routes/api/gallery/queue/consume/post.ts +2 -5
- package/packages/core/gallery/queue/src/routes/api/gallery/queue/fail/post.ts +2 -5
- 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/devAdmin.ts +3 -12
- package/packages/core/src/dispatchPipeline.ts +2 -18
- package/packages/core/src/errorOverlay.ts +80 -37
- package/packages/core/src/etag.ts +24 -0
- package/packages/core/src/feedback.ts +2 -11
- 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 +174 -71
- package/packages/core/src/sessionHandlers/respSessionHandler.ts +94 -0
- package/packages/core/src/sessionHandlers/valkeyHandler.ts +22 -73
- package/packages/core/src/sso.ts +1 -1
- package/packages/core/src/static.ts +1 -13
- package/packages/core/src/types.ts +8 -0
- package/packages/core/src/upstreamResponse.ts +31 -0
- package/packages/core/src/websocket.ts +48 -7
- 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 +593 -271
- package/packages/orm/src/graph/adapters/bolt.ts +8 -29
- package/packages/orm/src/graph/adapters/rowMapping.ts +57 -0
- package/packages/orm/src/graph/adapters/ultipa.ts +8 -30
- package/packages/swagger/dist/index.js +4 -4
- package/packages/swagger/src/ui.ts +7 -4
- package/types/cli/src/util.d.ts +28 -0
- package/types/core/src/csp.d.ts +42 -0
- package/types/core/src/etag.d.ts +9 -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 +64 -21
- package/types/core/src/sessionHandlers/respSessionHandler.d.ts +52 -0
- package/types/core/src/sessionHandlers/valkeyHandler.d.ts +2 -26
- package/types/core/src/types.d.ts +8 -0
- package/types/core/src/upstreamResponse.d.ts +10 -0
- package/types/core/src/websocket.d.ts +21 -0
- package/types/frond/src/engine.d.ts +7 -0
- package/types/orm/src/graph/adapters/rowMapping.d.ts +26 -0
|
@@ -13695,6 +13695,36 @@ var init_shapes = __esm({
|
|
|
13695
13695
|
}
|
|
13696
13696
|
});
|
|
13697
13697
|
|
|
13698
|
+
// ../orm/src/graph/adapters/rowMapping.ts
|
|
13699
|
+
function errorMessage(exc) {
|
|
13700
|
+
if (exc instanceof Error) return exc.message;
|
|
13701
|
+
return String(exc);
|
|
13702
|
+
}
|
|
13703
|
+
function nodeFromRow(row) {
|
|
13704
|
+
if (row === null || row === void 0) return null;
|
|
13705
|
+
return new GraphNode(
|
|
13706
|
+
String(row.id),
|
|
13707
|
+
row.labels ?? [],
|
|
13708
|
+
row.props ?? {}
|
|
13709
|
+
);
|
|
13710
|
+
}
|
|
13711
|
+
function edgeFromRow(row) {
|
|
13712
|
+
if (row === null || row === void 0) return null;
|
|
13713
|
+
return new GraphEdge(
|
|
13714
|
+
String(row.id),
|
|
13715
|
+
String(row.type),
|
|
13716
|
+
String(row.f),
|
|
13717
|
+
String(row.t),
|
|
13718
|
+
row.props ?? {}
|
|
13719
|
+
);
|
|
13720
|
+
}
|
|
13721
|
+
var init_rowMapping = __esm({
|
|
13722
|
+
"../orm/src/graph/adapters/rowMapping.ts"() {
|
|
13723
|
+
"use strict";
|
|
13724
|
+
init_shapes();
|
|
13725
|
+
}
|
|
13726
|
+
});
|
|
13727
|
+
|
|
13698
13728
|
// ../orm/src/graph/connectTimeout.ts
|
|
13699
13729
|
function resolveGraphConnectTimeout() {
|
|
13700
13730
|
const raw = (process.env[GRAPH_CONNECT_TIMEOUT_VARIABLE] ?? "").trim();
|
|
@@ -13734,15 +13764,12 @@ function propClause(properties) {
|
|
|
13734
13764
|
for (const key of keys) params[`p_${key}`] = props[key];
|
|
13735
13765
|
return { clause: `{${pairs}}`, params };
|
|
13736
13766
|
}
|
|
13737
|
-
function errorMessage(exc) {
|
|
13738
|
-
if (exc instanceof Error) return exc.message;
|
|
13739
|
-
return String(exc);
|
|
13740
|
-
}
|
|
13741
13767
|
var DRIVER_PACKAGE, driver, UltipaClient, UltipaConnectError, UNBOUNDED_CONNECT_SECONDS, UltipaGraphAdapter;
|
|
13742
13768
|
var init_ultipa = __esm({
|
|
13743
13769
|
async "../orm/src/graph/adapters/ultipa.ts"() {
|
|
13744
13770
|
"use strict";
|
|
13745
13771
|
init_shapes();
|
|
13772
|
+
init_rowMapping();
|
|
13746
13773
|
init_errors();
|
|
13747
13774
|
init_connectTimeout2();
|
|
13748
13775
|
DRIVER_PACKAGE = "tina4-ultipa";
|
|
@@ -13800,12 +13827,7 @@ var init_ultipa = __esm({
|
|
|
13800
13827
|
}
|
|
13801
13828
|
// -- portable node/edge/traverse core (GQL) ----------------------------
|
|
13802
13829
|
nodeFromRow(row) {
|
|
13803
|
-
|
|
13804
|
-
return new GraphNode(
|
|
13805
|
-
String(row.id),
|
|
13806
|
-
row.labels ?? [],
|
|
13807
|
-
row.props ?? {}
|
|
13808
|
-
);
|
|
13830
|
+
return nodeFromRow(row);
|
|
13809
13831
|
}
|
|
13810
13832
|
async addNode(label, properties = null) {
|
|
13811
13833
|
const { clause, params } = propClause(properties);
|
|
@@ -13819,15 +13841,7 @@ var init_ultipa = __esm({
|
|
|
13819
13841
|
params.to_id = toId;
|
|
13820
13842
|
const gql = `MATCH (a), (b) WHERE id(a) = $from_id AND id(b) = $to_id INSERT (a)-[e:\`${type2}\` ${clause}]->(b) RETURN id(e) AS id, type(e) AS type, id(a) AS f, id(b) AS t, properties(e) AS props`;
|
|
13821
13843
|
const rows = (await this.run(gql, params, false)).dicts();
|
|
13822
|
-
|
|
13823
|
-
const row = rows[0];
|
|
13824
|
-
return new GraphEdge(
|
|
13825
|
-
String(row.id),
|
|
13826
|
-
String(row.type),
|
|
13827
|
-
String(row.f),
|
|
13828
|
-
String(row.t),
|
|
13829
|
-
row.props ?? {}
|
|
13830
|
-
);
|
|
13844
|
+
return edgeFromRow(rows[0]);
|
|
13831
13845
|
}
|
|
13832
13846
|
async getNode(nodeId) {
|
|
13833
13847
|
const gql = `MATCH (n) WHERE id(n) = $id RETURN id(n) AS id, labels(n) AS labels, properties(n) AS props`;
|
|
@@ -13892,10 +13906,6 @@ var bolt_exports = {};
|
|
|
13892
13906
|
__export(bolt_exports, {
|
|
13893
13907
|
BoltGraphAdapter: () => BoltGraphAdapter
|
|
13894
13908
|
});
|
|
13895
|
-
function errorMessage2(exc) {
|
|
13896
|
-
if (exc instanceof Error) return exc.message;
|
|
13897
|
-
return String(exc);
|
|
13898
|
-
}
|
|
13899
13909
|
function boltId(id) {
|
|
13900
13910
|
const value = Number(id);
|
|
13901
13911
|
return Number.isNaN(value) ? -1 : value;
|
|
@@ -13905,6 +13915,7 @@ var init_bolt = __esm({
|
|
|
13905
13915
|
async "../orm/src/graph/adapters/bolt.ts"() {
|
|
13906
13916
|
"use strict";
|
|
13907
13917
|
init_shapes();
|
|
13918
|
+
init_rowMapping();
|
|
13908
13919
|
init_errors();
|
|
13909
13920
|
init_connectTimeout2();
|
|
13910
13921
|
DRIVER_PACKAGE2 = "neo4j-driver";
|
|
@@ -13940,7 +13951,7 @@ var init_bolt = __esm({
|
|
|
13940
13951
|
const result = await session.run(cypher, params ?? {});
|
|
13941
13952
|
return result.records.map((record) => record.toObject());
|
|
13942
13953
|
} catch (exc) {
|
|
13943
|
-
this.lastError =
|
|
13954
|
+
this.lastError = errorMessage(exc);
|
|
13944
13955
|
const code = exc?.code ?? "";
|
|
13945
13956
|
const message = this.lastError.toLowerCase();
|
|
13946
13957
|
if (code === "ServiceUnavailable" || code === neo4j.error?.SERVICE_UNAVAILABLE || message.includes("timed out") || message.includes("timeout")) {
|
|
@@ -13964,12 +13975,7 @@ var init_bolt = __esm({
|
|
|
13964
13975
|
}
|
|
13965
13976
|
// -- portable node/edge/traverse core (Cypher) -------------------------
|
|
13966
13977
|
nodeFromRow(row) {
|
|
13967
|
-
|
|
13968
|
-
return new GraphNode(
|
|
13969
|
-
String(row.id),
|
|
13970
|
-
row.labels ?? [],
|
|
13971
|
-
row.props ?? {}
|
|
13972
|
-
);
|
|
13978
|
+
return nodeFromRow(row);
|
|
13973
13979
|
}
|
|
13974
13980
|
async addNode(label, properties = null) {
|
|
13975
13981
|
const cypher = `CREATE (n:\`${label}\` $props) RETURN id(n) AS id, labels(n) AS labels, properties(n) AS props`;
|
|
@@ -13983,15 +13989,7 @@ var init_bolt = __esm({
|
|
|
13983
13989
|
to_id: boltId(toId),
|
|
13984
13990
|
props: properties ?? {}
|
|
13985
13991
|
});
|
|
13986
|
-
|
|
13987
|
-
const row = rows[0];
|
|
13988
|
-
return new GraphEdge(
|
|
13989
|
-
String(row.id),
|
|
13990
|
-
String(row.type),
|
|
13991
|
-
String(row.f),
|
|
13992
|
-
String(row.t),
|
|
13993
|
-
row.props ?? {}
|
|
13994
|
-
);
|
|
13992
|
+
return edgeFromRow(rows[0]);
|
|
13995
13993
|
}
|
|
13996
13994
|
async getNode(nodeId) {
|
|
13997
13995
|
const cypher = `MATCH (n) WHERE id(n) = $id RETURN id(n) AS id, labels(n) AS labels, properties(n) AS props`;
|
|
@@ -14057,7 +14055,7 @@ function cleanProps(doc) {
|
|
|
14057
14055
|
}
|
|
14058
14056
|
return props;
|
|
14059
14057
|
}
|
|
14060
|
-
function
|
|
14058
|
+
function errorMessage2(exc) {
|
|
14061
14059
|
if (exc instanceof Error) return exc.message;
|
|
14062
14060
|
return String(exc);
|
|
14063
14061
|
}
|
|
@@ -14096,14 +14094,14 @@ var init_arango = __esm({
|
|
|
14096
14094
|
this.db = new ArangoDatabase(config);
|
|
14097
14095
|
}
|
|
14098
14096
|
connectOrError(exc) {
|
|
14099
|
-
const text =
|
|
14097
|
+
const text = errorMessage2(exc).toLowerCase();
|
|
14100
14098
|
if (text.includes("timed out") || text.includes("timeout") || text.includes("connection") || text.includes("econnrefused") || text.includes("etimedout") || text.includes("max retries")) {
|
|
14101
14099
|
return new GraphConnectTimeout(
|
|
14102
14100
|
`Graph connect to ${this.url.host}:${this.url.port} timed out (${GRAPH_CONNECT_TIMEOUT_VARIABLE}). Raise ${GRAPH_CONNECT_TIMEOUT_VARIABLE} if the server is simply slow, or set it to 0 to wait indefinitely.`,
|
|
14103
14101
|
exc
|
|
14104
14102
|
);
|
|
14105
14103
|
}
|
|
14106
|
-
return new GraphError(
|
|
14104
|
+
return new GraphError(errorMessage2(exc), exc);
|
|
14107
14105
|
}
|
|
14108
14106
|
async ensureCollections() {
|
|
14109
14107
|
if (this.ensured) return;
|
|
@@ -14114,7 +14112,7 @@ var init_arango = __esm({
|
|
|
14114
14112
|
if (!await edges.exists()) await this.db.createEdgeCollection(EDGE_COLLECTION);
|
|
14115
14113
|
this.ensured = true;
|
|
14116
14114
|
} catch (exc) {
|
|
14117
|
-
this.lastError =
|
|
14115
|
+
this.lastError = errorMessage2(exc);
|
|
14118
14116
|
throw this.connectOrError(exc);
|
|
14119
14117
|
}
|
|
14120
14118
|
}
|
|
@@ -14124,7 +14122,7 @@ var init_arango = __esm({
|
|
|
14124
14122
|
const cursor = await this.db.query({ query, bindVars: bind2 ?? {} });
|
|
14125
14123
|
return await cursor.all();
|
|
14126
14124
|
} catch (exc) {
|
|
14127
|
-
this.lastError =
|
|
14125
|
+
this.lastError = errorMessage2(exc);
|
|
14128
14126
|
throw this.connectOrError(exc);
|
|
14129
14127
|
}
|
|
14130
14128
|
}
|
|
@@ -17432,6 +17430,73 @@ var init_trustedProxy = __esm({
|
|
|
17432
17430
|
}
|
|
17433
17431
|
});
|
|
17434
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
|
+
|
|
17435
17500
|
// ../frond/src/engine.ts
|
|
17436
17501
|
var engine_exports = {};
|
|
17437
17502
|
__export(engine_exports, {
|
|
@@ -17441,9 +17506,10 @@ __export(engine_exports, {
|
|
|
17441
17506
|
expressionFormCache: () => expressionFormCache,
|
|
17442
17507
|
filterChainCache: () => filterChainCache,
|
|
17443
17508
|
pathParseCache: () => pathParseCache,
|
|
17509
|
+
setCspNonceProvider: () => setCspNonceProvider,
|
|
17444
17510
|
setFormTokenSessionId: () => setFormTokenSessionId
|
|
17445
17511
|
});
|
|
17446
|
-
import { createHash as createHash3, createHmac as createHmac3, randomBytes as
|
|
17512
|
+
import { createHash as createHash3, createHmac as createHmac3, randomBytes as randomBytes5 } from "node:crypto";
|
|
17447
17513
|
import { readFileSync as readFileSync6, existsSync as existsSync7, statSync as statSync6, realpathSync } from "node:fs";
|
|
17448
17514
|
import { join as join10, resolve as resolve6, isAbsolute as isAbsolute4, sep as sep3 } from "node:path";
|
|
17449
17515
|
function jsonText(value) {
|
|
@@ -18510,6 +18576,9 @@ function _b64url(data) {
|
|
|
18510
18576
|
function setFormTokenSessionId(sessionId) {
|
|
18511
18577
|
_formTokenSessionId = sessionId || "";
|
|
18512
18578
|
}
|
|
18579
|
+
function setCspNonceProvider(fn) {
|
|
18580
|
+
_cspNonceProvider = fn;
|
|
18581
|
+
}
|
|
18513
18582
|
function _buildFormTokenJwt(descriptor = "") {
|
|
18514
18583
|
const secret = process.env.TINA4_SECRET ?? "";
|
|
18515
18584
|
const secretBytes = Buffer.byteLength(secret, "utf8");
|
|
@@ -18521,7 +18590,7 @@ function _buildFormTokenJwt(descriptor = "") {
|
|
|
18521
18590
|
const ttlMinutes = parseInt(process.env.TINA4_TOKEN_LIMIT || "60", 10);
|
|
18522
18591
|
const header = { alg: "HS256", typ: "JWT" };
|
|
18523
18592
|
const now = Math.floor(Date.now() / 1e3);
|
|
18524
|
-
const payload = { type: "form", nonce:
|
|
18593
|
+
const payload = { type: "form", nonce: randomBytes5(8).toString("hex"), iat: now, exp: now + ttlMinutes * 60 };
|
|
18525
18594
|
if (descriptor) {
|
|
18526
18595
|
if (descriptor.includes("|")) {
|
|
18527
18596
|
const [ctx, ref] = descriptor.split("|", 2);
|
|
@@ -18548,7 +18617,7 @@ function _generateFormToken(descriptor = "") {
|
|
|
18548
18617
|
function _generateFormTokenValue(descriptor = "") {
|
|
18549
18618
|
return new SafeString(_buildFormTokenJwt(descriptor));
|
|
18550
18619
|
}
|
|
18551
|
-
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;
|
|
18552
18621
|
var init_engine = __esm({
|
|
18553
18622
|
"../frond/src/engine.ts"() {
|
|
18554
18623
|
"use strict";
|
|
@@ -18902,6 +18971,7 @@ var init_engine = __esm({
|
|
|
18902
18971
|
js_escape: (v) => new SafeString(jsEscape(v))
|
|
18903
18972
|
};
|
|
18904
18973
|
_formTokenSessionId = "";
|
|
18974
|
+
_cspNonceProvider = () => "";
|
|
18905
18975
|
Frond = class _Frond {
|
|
18906
18976
|
// ── Class-level registries ──────────────────────────────────
|
|
18907
18977
|
// Persist globals, filters, and tests across hot-reloads and at
|
|
@@ -19055,6 +19125,7 @@ var init_engine = __esm({
|
|
|
19055
19125
|
this.globals.formTokenValue = (descriptor) => _generateFormTokenValue(descriptor || "");
|
|
19056
19126
|
this.globals.form_token_value = (descriptor) => _generateFormTokenValue(descriptor || "");
|
|
19057
19127
|
this.globals.dump = (value) => renderDump(value);
|
|
19128
|
+
this.globals.csp_nonce = () => _cspNonceProvider();
|
|
19058
19129
|
for (const [k, v] of _Frond.classFilters) this.filters[k] = v;
|
|
19059
19130
|
for (const [k, v] of _Frond.classGlobals) this.globals[k] = v;
|
|
19060
19131
|
for (const [k, v] of _Frond.classTests) this.tests[k] = v;
|
|
@@ -20318,7 +20389,8 @@ async function getFrond() {
|
|
|
20318
20389
|
const dir = _defaultTemplatesDir ?? nodePath.resolve(process.cwd(), "src/templates");
|
|
20319
20390
|
let engine = _frondCache.get(dir);
|
|
20320
20391
|
if (!engine) {
|
|
20321
|
-
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);
|
|
20322
20394
|
engine = new Frond2(dir);
|
|
20323
20395
|
_frondCache.set(dir, engine);
|
|
20324
20396
|
}
|
|
@@ -20329,7 +20401,8 @@ async function getFrameworkFrond() {
|
|
|
20329
20401
|
const frameworkDir = nodePath.resolve(nodePath.dirname(import.meta.url.replace("file://", "")), "..", "templates");
|
|
20330
20402
|
if (!_frameworkFrond && fs2.existsSync(frameworkDir)) {
|
|
20331
20403
|
try {
|
|
20332
|
-
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);
|
|
20333
20406
|
_frameworkFrond = new Frond2(frameworkDir);
|
|
20334
20407
|
} catch {
|
|
20335
20408
|
return null;
|
|
@@ -20429,6 +20502,7 @@ function createResponse(res) {
|
|
|
20429
20502
|
return response;
|
|
20430
20503
|
};
|
|
20431
20504
|
response.raw = res;
|
|
20505
|
+
response.cspNonce = currentCspNonce();
|
|
20432
20506
|
response.json = function(data, status2) {
|
|
20433
20507
|
if (res.headersSent) return response;
|
|
20434
20508
|
if (status2 !== void 0) res.statusCode = status2;
|
|
@@ -20574,7 +20648,8 @@ function createResponse(res) {
|
|
|
20574
20648
|
};
|
|
20575
20649
|
response.render = async function(templateName, data, status2, templateDir) {
|
|
20576
20650
|
try {
|
|
20577
|
-
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);
|
|
20578
20653
|
const dir = templateDir ?? _defaultTemplatesDir ?? nodePath.resolve(process.cwd(), "src/templates");
|
|
20579
20654
|
let engine = _frondCache.get(dir);
|
|
20580
20655
|
if (!engine) {
|
|
@@ -20692,6 +20767,7 @@ var HEADER_TOKEN, _frondCache, _defaultTemplatesDir, _globalFrond, _frameworkFro
|
|
|
20692
20767
|
var init_response = __esm({
|
|
20693
20768
|
"src/response.ts"() {
|
|
20694
20769
|
"use strict";
|
|
20770
|
+
init_csp();
|
|
20695
20771
|
HEADER_TOKEN = /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/;
|
|
20696
20772
|
_frondCache = /* @__PURE__ */ new Map();
|
|
20697
20773
|
_defaultTemplatesDir = null;
|
|
@@ -20903,6 +20979,7 @@ var init_middleware = __esm({
|
|
|
20903
20979
|
init_dotenv();
|
|
20904
20980
|
init_router();
|
|
20905
20981
|
init_trustedProxy();
|
|
20982
|
+
init_csp();
|
|
20906
20983
|
init_response();
|
|
20907
20984
|
MiddlewareChain = class {
|
|
20908
20985
|
middlewares = [];
|
|
@@ -21335,12 +21412,18 @@ var init_middleware = __esm({
|
|
|
21335
21412
|
* overrides, WITHOUT Strict-Transport-Security, which depends on the
|
|
21336
21413
|
* request's scheme. Also what a transport rejection carries (ADR-0068
|
|
21337
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.
|
|
21338
21421
|
*/
|
|
21339
21422
|
static canonicalHeaders() {
|
|
21340
21423
|
return {
|
|
21341
21424
|
"X-Frame-Options": process.env.TINA4_FRAME_OPTIONS ?? "SAMEORIGIN",
|
|
21342
21425
|
"X-Content-Type-Options": "nosniff",
|
|
21343
|
-
"Content-Security-Policy":
|
|
21426
|
+
"Content-Security-Policy": resolveCspHeader(currentCspNonce()),
|
|
21344
21427
|
"Referrer-Policy": process.env.TINA4_REFERRER_POLICY ?? "strict-origin-when-cross-origin",
|
|
21345
21428
|
"X-XSS-Protection": "0",
|
|
21346
21429
|
"Permissions-Policy": process.env.TINA4_PERMISSIONS_POLICY ?? "camera=(), microphone=(), geolocation=()"
|
|
@@ -21382,7 +21465,7 @@ var init_middleware = __esm({
|
|
|
21382
21465
|
static warnCspDefaultOnce() {
|
|
21383
21466
|
if (_SecurityHeadersMiddleware.cspDefaultWarned) return;
|
|
21384
21467
|
_SecurityHeadersMiddleware.cspDefaultWarned = true;
|
|
21385
|
-
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'".`;
|
|
21386
21469
|
try {
|
|
21387
21470
|
Log.warning(message);
|
|
21388
21471
|
} catch {
|
|
@@ -22387,6 +22470,21 @@ var init_portTakeover = __esm({
|
|
|
22387
22470
|
}
|
|
22388
22471
|
});
|
|
22389
22472
|
|
|
22473
|
+
// src/etag.ts
|
|
22474
|
+
function etagMatches(ifNoneMatch, etag) {
|
|
22475
|
+
const strip = (tag) => tag.trim().replace(/^W\//, "");
|
|
22476
|
+
const target = strip(etag);
|
|
22477
|
+
return ifNoneMatch.split(",").some((candidate) => {
|
|
22478
|
+
const trimmed = candidate.trim();
|
|
22479
|
+
return trimmed === "*" || strip(trimmed) === target;
|
|
22480
|
+
});
|
|
22481
|
+
}
|
|
22482
|
+
var init_etag = __esm({
|
|
22483
|
+
"src/etag.ts"() {
|
|
22484
|
+
"use strict";
|
|
22485
|
+
}
|
|
22486
|
+
});
|
|
22487
|
+
|
|
22390
22488
|
// src/sessionHandlers/syncBridge.ts
|
|
22391
22489
|
import { Worker } from "node:worker_threads";
|
|
22392
22490
|
function getBridge(key, workerSource, workerData) {
|
|
@@ -22668,42 +22766,35 @@ var init_respClient = __esm({
|
|
|
22668
22766
|
}
|
|
22669
22767
|
});
|
|
22670
22768
|
|
|
22671
|
-
// src/sessionHandlers/
|
|
22672
|
-
var
|
|
22673
|
-
var
|
|
22674
|
-
"src/sessionHandlers/
|
|
22769
|
+
// src/sessionHandlers/respSessionHandler.ts
|
|
22770
|
+
var RespSessionHandler;
|
|
22771
|
+
var init_respSessionHandler = __esm({
|
|
22772
|
+
"src/sessionHandlers/respSessionHandler.ts"() {
|
|
22675
22773
|
"use strict";
|
|
22676
22774
|
init_respClient();
|
|
22677
|
-
|
|
22678
|
-
|
|
22679
|
-
|
|
22680
|
-
|
|
22681
|
-
prefix;
|
|
22682
|
-
db;
|
|
22683
|
-
constructor(config) {
|
|
22684
|
-
this.host = config?.host ?? process.env.TINA4_SESSION_VALKEY_HOST ?? "127.0.0.1";
|
|
22685
|
-
this.port = config?.port ?? (process.env.TINA4_SESSION_VALKEY_PORT ? parseInt(process.env.TINA4_SESSION_VALKEY_PORT, 10) : 6379);
|
|
22686
|
-
this.password = config?.password ?? process.env.TINA4_SESSION_VALKEY_PASSWORD ?? "";
|
|
22687
|
-
this.prefix = config?.prefix ?? process.env.TINA4_SESSION_VALKEY_PREFIX ?? "tina4:session:";
|
|
22688
|
-
this.db = config?.db ?? (process.env.TINA4_SESSION_VALKEY_DB ? parseInt(process.env.TINA4_SESSION_VALKEY_DB, 10) : 0);
|
|
22775
|
+
RespSessionHandler = class {
|
|
22776
|
+
constructor(conn, label) {
|
|
22777
|
+
this.conn = conn;
|
|
22778
|
+
this.label = label;
|
|
22689
22779
|
}
|
|
22780
|
+
conn;
|
|
22781
|
+
label;
|
|
22690
22782
|
/**
|
|
22691
|
-
* Execute a RESP command synchronously against the live
|
|
22783
|
+
* Execute a RESP command synchronously against the live server.
|
|
22692
22784
|
*
|
|
22693
|
-
* Delegates to the shared {@link respCommandSync} transport: a genuine key
|
|
22694
|
-
* yields `""`, and a transport/connection FAILURE
|
|
22695
|
-
*
|
|
22696
|
-
* (silent) from "backend failed" (log-loud + degrade). Backend-failure parity.
|
|
22785
|
+
* Delegates to the shared {@link respCommandSync} transport: a genuine key
|
|
22786
|
+
* miss yields `""`, and a transport/connection FAILURE THROWS so the caller
|
|
22787
|
+
* can distinguish "not found" from "backend failed". Backend-failure parity.
|
|
22697
22788
|
*/
|
|
22698
22789
|
execSync(args) {
|
|
22699
22790
|
return respCommandSync(
|
|
22700
|
-
{ host: this.host, port: this.port, password: this.password, db: this.db },
|
|
22791
|
+
{ host: this.conn.host, port: this.conn.port, password: this.conn.password, db: this.conn.db },
|
|
22701
22792
|
args,
|
|
22702
|
-
|
|
22793
|
+
this.label
|
|
22703
22794
|
);
|
|
22704
22795
|
}
|
|
22705
22796
|
key(sessionId) {
|
|
22706
|
-
return `${this.prefix}${sessionId}`;
|
|
22797
|
+
return `${this.conn.prefix}${sessionId}`;
|
|
22707
22798
|
}
|
|
22708
22799
|
read(sessionId) {
|
|
22709
22800
|
const raw = this.execSync(["GET", this.key(sessionId)]);
|
|
@@ -22714,7 +22805,7 @@ var init_valkeyHandler = __esm({
|
|
|
22714
22805
|
return null;
|
|
22715
22806
|
}
|
|
22716
22807
|
}
|
|
22717
|
-
write(sessionId, data, ttl) {
|
|
22808
|
+
write(sessionId, data, ttl = 0) {
|
|
22718
22809
|
const json = JSON.stringify(data);
|
|
22719
22810
|
if (ttl > 0) {
|
|
22720
22811
|
this.execSync(["SETEX", this.key(sessionId), String(ttl), json]);
|
|
@@ -22729,6 +22820,29 @@ var init_valkeyHandler = __esm({
|
|
|
22729
22820
|
}
|
|
22730
22821
|
});
|
|
22731
22822
|
|
|
22823
|
+
// src/sessionHandlers/valkeyHandler.ts
|
|
22824
|
+
var ValkeySessionHandler;
|
|
22825
|
+
var init_valkeyHandler = __esm({
|
|
22826
|
+
"src/sessionHandlers/valkeyHandler.ts"() {
|
|
22827
|
+
"use strict";
|
|
22828
|
+
init_respSessionHandler();
|
|
22829
|
+
ValkeySessionHandler = class extends RespSessionHandler {
|
|
22830
|
+
constructor(config) {
|
|
22831
|
+
super(
|
|
22832
|
+
{
|
|
22833
|
+
host: config?.host ?? process.env.TINA4_SESSION_VALKEY_HOST ?? "127.0.0.1",
|
|
22834
|
+
port: config?.port ?? (process.env.TINA4_SESSION_VALKEY_PORT ? parseInt(process.env.TINA4_SESSION_VALKEY_PORT, 10) : 6379),
|
|
22835
|
+
password: config?.password ?? process.env.TINA4_SESSION_VALKEY_PASSWORD ?? "",
|
|
22836
|
+
prefix: config?.prefix ?? process.env.TINA4_SESSION_VALKEY_PREFIX ?? "tina4:session:",
|
|
22837
|
+
db: config?.db ?? (process.env.TINA4_SESSION_VALKEY_DB ? parseInt(process.env.TINA4_SESSION_VALKEY_DB, 10) : 0)
|
|
22838
|
+
},
|
|
22839
|
+
"Valkey"
|
|
22840
|
+
);
|
|
22841
|
+
}
|
|
22842
|
+
};
|
|
22843
|
+
}
|
|
22844
|
+
});
|
|
22845
|
+
|
|
22732
22846
|
// src/sessionHandlers/memcachedHandler.ts
|
|
22733
22847
|
import { createHash as createHash4 } from "node:crypto";
|
|
22734
22848
|
var MAX_KEY_BYTES, MAX_RELATIVE_EXPTIME, MemcachedSessionHandler;
|
|
@@ -23744,7 +23858,7 @@ __export(session_exports, {
|
|
|
23744
23858
|
sessionCookieName: () => sessionCookieName,
|
|
23745
23859
|
sessionStrictMode: () => sessionStrictMode
|
|
23746
23860
|
});
|
|
23747
|
-
import { createHash as createHash5, randomBytes as
|
|
23861
|
+
import { createHash as createHash5, randomBytes as randomBytes6 } from "node:crypto";
|
|
23748
23862
|
import { existsSync as existsSync10, mkdirSync as mkdirSync10, readFileSync as readFileSync8, writeFileSync as writeFileSync7, unlinkSync as unlinkSync5, readdirSync as readdirSync6 } from "node:fs";
|
|
23749
23863
|
import { join as join14 } from "node:path";
|
|
23750
23864
|
function isValidSessionId(sessionId) {
|
|
@@ -23762,6 +23876,20 @@ function resolveBackend(name) {
|
|
|
23762
23876
|
}
|
|
23763
23877
|
return normalised;
|
|
23764
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
|
+
}
|
|
23765
23893
|
function isSecureScheme(forwardedProto, socketEncrypted) {
|
|
23766
23894
|
const forwarded = (forwardedProto ?? "").trim();
|
|
23767
23895
|
if (forwarded !== "") {
|
|
@@ -23794,7 +23922,7 @@ var init_session = __esm({
|
|
|
23794
23922
|
"use strict";
|
|
23795
23923
|
init_logger();
|
|
23796
23924
|
init_dotenv();
|
|
23797
|
-
|
|
23925
|
+
init_respSessionHandler();
|
|
23798
23926
|
init_valkeyHandler();
|
|
23799
23927
|
init_memcachedHandler();
|
|
23800
23928
|
init_mongoHandler();
|
|
@@ -23920,57 +24048,19 @@ var init_session = __esm({
|
|
|
23920
24048
|
}
|
|
23921
24049
|
}
|
|
23922
24050
|
};
|
|
23923
|
-
RedisSessionHandler = class {
|
|
23924
|
-
host;
|
|
23925
|
-
port;
|
|
23926
|
-
password;
|
|
23927
|
-
prefix;
|
|
23928
|
-
db;
|
|
24051
|
+
RedisSessionHandler = class extends RespSessionHandler {
|
|
23929
24052
|
constructor(config) {
|
|
23930
|
-
|
|
23931
|
-
|
|
23932
|
-
|
|
23933
|
-
|
|
23934
|
-
|
|
23935
|
-
|
|
23936
|
-
|
|
23937
|
-
|
|
23938
|
-
*
|
|
23939
|
-
* Delegates to the shared {@link respCommandSync} transport: a genuine key miss
|
|
23940
|
-
* yields `""`, and a transport/connection FAILURE (server unreachable, rejected
|
|
23941
|
-
* AUTH, timeout) THROWS so the Session boundary can distinguish "not found"
|
|
23942
|
-
* (silent) from "backend failed" (log-loud + degrade). Backend-failure parity.
|
|
23943
|
-
*/
|
|
23944
|
-
execSync(args) {
|
|
23945
|
-
return respCommandSync(
|
|
23946
|
-
{ host: this.host, port: this.port, password: this.password, db: this.db },
|
|
23947
|
-
args,
|
|
24053
|
+
super(
|
|
24054
|
+
{
|
|
24055
|
+
host: config?.redisHost ?? process.env.TINA4_SESSION_REDIS_HOST ?? "127.0.0.1",
|
|
24056
|
+
port: config?.redisPort ?? (process.env.TINA4_SESSION_REDIS_PORT ? parseInt(process.env.TINA4_SESSION_REDIS_PORT, 10) : 6379),
|
|
24057
|
+
password: config?.redisPassword ?? process.env.TINA4_SESSION_REDIS_PASSWORD ?? "",
|
|
24058
|
+
prefix: config?.redisPrefix ?? process.env.TINA4_SESSION_REDIS_PREFIX ?? "tina4:session:",
|
|
24059
|
+
db: config?.redisDb ?? (process.env.TINA4_SESSION_REDIS_DB ? parseInt(process.env.TINA4_SESSION_REDIS_DB, 10) : 0)
|
|
24060
|
+
},
|
|
23948
24061
|
"Redis"
|
|
23949
24062
|
);
|
|
23950
24063
|
}
|
|
23951
|
-
key(sessionId) {
|
|
23952
|
-
return `${this.prefix}${sessionId}`;
|
|
23953
|
-
}
|
|
23954
|
-
read(sessionId) {
|
|
23955
|
-
const raw = this.execSync(["GET", this.key(sessionId)]);
|
|
23956
|
-
if (!raw) return null;
|
|
23957
|
-
try {
|
|
23958
|
-
return JSON.parse(raw);
|
|
23959
|
-
} catch {
|
|
23960
|
-
return null;
|
|
23961
|
-
}
|
|
23962
|
-
}
|
|
23963
|
-
write(sessionId, data, ttl = 0) {
|
|
23964
|
-
const json = JSON.stringify(data);
|
|
23965
|
-
if (ttl > 0) {
|
|
23966
|
-
this.execSync(["SETEX", this.key(sessionId), String(ttl), json]);
|
|
23967
|
-
} else {
|
|
23968
|
-
this.execSync(["SET", this.key(sessionId), json]);
|
|
23969
|
-
}
|
|
23970
|
-
}
|
|
23971
|
-
destroy(sessionId) {
|
|
23972
|
-
this.execSync(["DEL", this.key(sessionId)]);
|
|
23973
|
-
}
|
|
23974
24064
|
};
|
|
23975
24065
|
FLASH_PREFIX = "_flash_";
|
|
23976
24066
|
Session = class {
|
|
@@ -23997,6 +24087,29 @@ var init_session = __esm({
|
|
|
23997
24087
|
* Lets `start()` tell "no such session" from "the store is unreachable".
|
|
23998
24088
|
*/
|
|
23999
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;
|
|
24000
24113
|
constructor(backend, config) {
|
|
24001
24114
|
const backendType = resolveBackend(
|
|
24002
24115
|
backend ?? config?.backend ?? process.env.TINA4_SESSION_BACKEND ?? "file"
|
|
@@ -24141,6 +24254,10 @@ var init_session = __esm({
|
|
|
24141
24254
|
const now2 = Math.floor(Date.now() / 1e3);
|
|
24142
24255
|
this.data = { _created: now2, _accessed: now2 };
|
|
24143
24256
|
this.dirty = false;
|
|
24257
|
+
this.stored = false;
|
|
24258
|
+
this.loaded = /* @__PURE__ */ new Map();
|
|
24259
|
+
this.cleared = false;
|
|
24260
|
+
this.ended = false;
|
|
24144
24261
|
return sessionId;
|
|
24145
24262
|
}
|
|
24146
24263
|
if (loaded) {
|
|
@@ -24150,14 +24267,22 @@ var init_session = __esm({
|
|
|
24150
24267
|
this.data._accessed = now2;
|
|
24151
24268
|
this.dirty = false;
|
|
24152
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;
|
|
24153
24274
|
return sessionId;
|
|
24154
24275
|
}
|
|
24155
24276
|
}
|
|
24156
|
-
this.sessionId =
|
|
24277
|
+
this.sessionId = randomBytes6(16).toString("hex");
|
|
24157
24278
|
const now = Math.floor(Date.now() / 1e3);
|
|
24158
24279
|
this.data = { _created: now, _accessed: now };
|
|
24159
24280
|
this.dirty = false;
|
|
24160
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;
|
|
24161
24286
|
return this.sessionId;
|
|
24162
24287
|
}
|
|
24163
24288
|
/**
|
|
@@ -24198,9 +24323,7 @@ var init_session = __esm({
|
|
|
24198
24323
|
if (this.sessionId) {
|
|
24199
24324
|
this.safeDestroy(this.sessionId);
|
|
24200
24325
|
}
|
|
24201
|
-
this.
|
|
24202
|
-
this.data = null;
|
|
24203
|
-
this.dirty = false;
|
|
24326
|
+
this.forget();
|
|
24204
24327
|
}
|
|
24205
24328
|
/**
|
|
24206
24329
|
* Get all session data (excluding internal keys).
|
|
@@ -24223,6 +24346,7 @@ var init_session = __esm({
|
|
|
24223
24346
|
const now = Math.floor(Date.now() / 1e3);
|
|
24224
24347
|
this.data = { _created: this.data._created, _accessed: now };
|
|
24225
24348
|
this.dirty = true;
|
|
24349
|
+
this.cleared = true;
|
|
24226
24350
|
this.save();
|
|
24227
24351
|
}
|
|
24228
24352
|
/**
|
|
@@ -24239,17 +24363,36 @@ var init_session = __esm({
|
|
|
24239
24363
|
* session fixation — the pre-auth ID is destroyed and the data is carried
|
|
24240
24364
|
* onto a fresh, unguessable ID. A backend destroy/write failure is logged
|
|
24241
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.
|
|
24242
24372
|
*/
|
|
24243
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
|
+
}
|
|
24244
24386
|
const oldId = this.sessionId;
|
|
24245
24387
|
const oldData = this.data;
|
|
24246
24388
|
if (oldId) {
|
|
24247
24389
|
this.safeDestroy(oldId);
|
|
24248
24390
|
}
|
|
24249
|
-
this.sessionId =
|
|
24391
|
+
this.sessionId = randomBytes6(16).toString("hex");
|
|
24250
24392
|
this.data = oldData ?? { _created: Math.floor(Date.now() / 1e3), _accessed: Math.floor(Date.now() / 1e3) };
|
|
24251
24393
|
this.data._accessed = Math.floor(Date.now() / 1e3);
|
|
24252
24394
|
this.dirty = true;
|
|
24395
|
+
this.stored = false;
|
|
24253
24396
|
this.save();
|
|
24254
24397
|
return this.sessionId;
|
|
24255
24398
|
}
|
|
@@ -24325,17 +24468,84 @@ var init_session = __esm({
|
|
|
24325
24468
|
*
|
|
24326
24469
|
* Returns true on a successful persist, false if the backend was unreachable
|
|
24327
24470
|
* (logged). The dirty flag is cleared only on success so a later save()
|
|
24328
|
-
* 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.
|
|
24329
24491
|
*/
|
|
24330
24492
|
save() {
|
|
24331
24493
|
if (!this.sessionId || !this.data) return true;
|
|
24332
|
-
if (!this.dirty) return true;
|
|
24333
|
-
|
|
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)) {
|
|
24334
24507
|
this.dirty = false;
|
|
24508
|
+
this.stored = true;
|
|
24509
|
+
this.cleared = false;
|
|
24510
|
+
this.loaded = fingerprints(this.data);
|
|
24335
24511
|
return true;
|
|
24336
24512
|
}
|
|
24337
24513
|
return false;
|
|
24338
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
|
+
}
|
|
24339
24549
|
};
|
|
24340
24550
|
}
|
|
24341
24551
|
});
|
|
@@ -24380,14 +24590,6 @@ function headStripIntercept(rawReq, rawRes) {
|
|
|
24380
24590
|
function isCompressibleContentType(contentType) {
|
|
24381
24591
|
return COMPRESSIBLE_PREFIXES.some((prefix) => contentType.includes(prefix));
|
|
24382
24592
|
}
|
|
24383
|
-
function etagMatchesInm(ifNoneMatch, etag) {
|
|
24384
|
-
const strip = (tag) => tag.trim().replace(/^W\//, "");
|
|
24385
|
-
const target = strip(etag);
|
|
24386
|
-
return ifNoneMatch.split(",").some((candidate) => {
|
|
24387
|
-
const trimmed = candidate.trim();
|
|
24388
|
-
return trimmed === "*" || strip(trimmed) === target;
|
|
24389
|
-
});
|
|
24390
|
-
}
|
|
24391
24593
|
function maybeCompressResponse(body, rawReq, rawRes) {
|
|
24392
24594
|
const acceptEncoding = String(rawReq.headers["accept-encoding"] ?? "");
|
|
24393
24595
|
const contentTypeHeader = rawRes.getHeader("content-type");
|
|
@@ -24403,7 +24605,7 @@ function maybeCompressResponse(body, rawReq, rawRes) {
|
|
|
24403
24605
|
}
|
|
24404
24606
|
function responseIsNotModified(rawReq, rawRes, etag) {
|
|
24405
24607
|
const ifNoneMatch = String(rawReq.headers["if-none-match"] ?? "");
|
|
24406
|
-
if (ifNoneMatch) return
|
|
24608
|
+
if (ifNoneMatch) return etagMatches(ifNoneMatch, etag);
|
|
24407
24609
|
const lastModifiedHeader = rawRes.getHeader("last-modified");
|
|
24408
24610
|
if (typeof lastModifiedHeader !== "string") return false;
|
|
24409
24611
|
const ifModifiedSince = String(rawReq.headers["if-modified-since"] ?? "");
|
|
@@ -24531,6 +24733,7 @@ var init_dispatchPipeline = __esm({
|
|
|
24531
24733
|
"src/dispatchPipeline.ts"() {
|
|
24532
24734
|
"use strict";
|
|
24533
24735
|
init_logger();
|
|
24736
|
+
init_etag();
|
|
24534
24737
|
COMPRESSIBLE_PREFIXES = [
|
|
24535
24738
|
"text/",
|
|
24536
24739
|
"application/json",
|
|
@@ -24626,18 +24829,11 @@ function isNotModified(req2, etag, stat) {
|
|
|
24626
24829
|
}
|
|
24627
24830
|
return false;
|
|
24628
24831
|
}
|
|
24629
|
-
function etagMatches(ifNoneMatch, etag) {
|
|
24630
|
-
const strip = (tag) => tag.trim().replace(/^W\//, "");
|
|
24631
|
-
const target = strip(etag);
|
|
24632
|
-
return ifNoneMatch.split(",").some((candidate) => {
|
|
24633
|
-
const trimmed = candidate.trim();
|
|
24634
|
-
return trimmed === "*" || strip(trimmed) === target;
|
|
24635
|
-
});
|
|
24636
|
-
}
|
|
24637
24832
|
var COMPRESSIBLE_PREFIXES2, MIME_TYPES;
|
|
24638
24833
|
var init_static = __esm({
|
|
24639
24834
|
"src/static.ts"() {
|
|
24640
24835
|
"use strict";
|
|
24836
|
+
init_etag();
|
|
24641
24837
|
COMPRESSIBLE_PREFIXES2 = [
|
|
24642
24838
|
"text/",
|
|
24643
24839
|
"application/json",
|
|
@@ -24718,11 +24914,11 @@ function formatSourceBlock(filename, lineno) {
|
|
|
24718
24914
|
const lines = readSourceLines(filename, lineno);
|
|
24719
24915
|
if (lines.length === 0) return "";
|
|
24720
24916
|
const rows = lines.map(([num, text, isError]) => {
|
|
24721
|
-
const
|
|
24917
|
+
const rowClass = isError ? "eo-line eo-line-err" : "eo-line";
|
|
24722
24918
|
const marker = isError ? "▶" : " ";
|
|
24723
|
-
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>`;
|
|
24724
24920
|
}).join("\n");
|
|
24725
|
-
return `<div
|
|
24921
|
+
return `<div class="eo-source">` + rows + `</div>`;
|
|
24726
24922
|
}
|
|
24727
24923
|
function formatFrame(frame, capturedAt = 0) {
|
|
24728
24924
|
const source = frame.file && frame.line > 0 ? formatSourceBlock(frame.file, frame.line) : "";
|
|
@@ -24734,23 +24930,23 @@ function formatFrame(frame, capturedAt = 0) {
|
|
|
24734
24930
|
if (mtime > capturedAt + 0.5) {
|
|
24735
24931
|
const d = new Date(mtime * 1e3);
|
|
24736
24932
|
const mtimeIso = `${String(d.getUTCHours()).padStart(2, "0")}:${String(d.getUTCMinutes()).padStart(2, "0")}:${String(d.getUTCSeconds()).padStart(2, "0")}`;
|
|
24737
|
-
staleBadge = ` <span
|
|
24933
|
+
staleBadge = ` <span class="eo-stale">FILE MODIFIED @ ${mtimeIso} UTC — source may not match what failed</span>`;
|
|
24738
24934
|
}
|
|
24739
24935
|
} catch {
|
|
24740
24936
|
}
|
|
24741
24937
|
}
|
|
24742
|
-
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>`;
|
|
24743
24939
|
}
|
|
24744
24940
|
function collapsible(title, content, openByDefault = false) {
|
|
24745
24941
|
const open = openByDefault ? " open" : "";
|
|
24746
|
-
return `<details
|
|
24942
|
+
return `<details class="eo-details"${open}><summary class="eo-summary">${esc(title)}</summary><div class="eo-details-body">${content}</div></details>`;
|
|
24747
24943
|
}
|
|
24748
24944
|
function table(pairs) {
|
|
24749
|
-
if (pairs.length === 0) return `<span
|
|
24945
|
+
if (pairs.length === 0) return `<span class="eo-none">None</span>`;
|
|
24750
24946
|
const rows = pairs.map(
|
|
24751
|
-
([key, val]) => `<tr><td
|
|
24947
|
+
([key, val]) => `<tr><td class="eo-key">${esc(key)}</td><td class="eo-val">${esc(val)}</td></tr>`
|
|
24752
24948
|
).join("");
|
|
24753
|
-
return `<table
|
|
24949
|
+
return `<table class="eo-table">${rows}</table>`;
|
|
24754
24950
|
}
|
|
24755
24951
|
function renderErrorOverlay(error2, request) {
|
|
24756
24952
|
const capturedAt = Date.now() / 1e3;
|
|
@@ -24763,7 +24959,7 @@ function renderErrorOverlay(error2, request) {
|
|
|
24763
24959
|
}
|
|
24764
24960
|
const hidden = frames.length - Math.min(frames.length, MAX_FRAMES);
|
|
24765
24961
|
if (hidden > 0) {
|
|
24766
|
-
framesHtml += `<div
|
|
24962
|
+
framesHtml += `<div class="eo-hidden-frames">… ${hidden} more stack frames hidden (truncated at ${MAX_FRAMES})</div>`;
|
|
24767
24963
|
}
|
|
24768
24964
|
const requestPairs = [];
|
|
24769
24965
|
if (request != null) {
|
|
@@ -24800,37 +24996,72 @@ function renderErrorOverlay(error2, request) {
|
|
|
24800
24996
|
];
|
|
24801
24997
|
const envSection = collapsible("Environment", table(envPairs));
|
|
24802
24998
|
const stackSection = collapsible("Stack Trace", framesHtml, true);
|
|
24999
|
+
const nonce = currentCspNonce();
|
|
24803
25000
|
return `<!DOCTYPE html>
|
|
24804
25001
|
<html lang="en">
|
|
24805
25002
|
<head>
|
|
24806
25003
|
<meta charset="utf-8">
|
|
24807
25004
|
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
24808
25005
|
<title>Tina4 Error \u2014 ${esc(excType)}</title>
|
|
24809
|
-
<style>
|
|
24810
|
-
|
|
24811
|
-
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()}
|
|
24812
25008
|
</style>
|
|
24813
25009
|
</head>
|
|
24814
25010
|
<body>
|
|
24815
|
-
<div
|
|
24816
|
-
<div
|
|
24817
|
-
<div
|
|
24818
|
-
<span
|
|
24819
|
-
<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>
|
|
24820
25016
|
</div>
|
|
24821
|
-
<h1
|
|
24822
|
-
<p
|
|
25017
|
+
<h1 class="eo-type">${esc(excType)}</h1>
|
|
25018
|
+
<p class="eo-msg">${esc(excMsg)}</p>
|
|
24823
25019
|
</div>
|
|
24824
25020
|
${stackSection}
|
|
24825
25021
|
${requestSection}
|
|
24826
25022
|
${envSection}
|
|
24827
|
-
<div
|
|
25023
|
+
<div class="eo-footer">
|
|
24828
25024
|
Tina4 Debug Overlay — This page is only shown in debug mode. Set TINA4_DEBUG=false in production.
|
|
24829
25025
|
</div>
|
|
24830
25026
|
</div>
|
|
24831
25027
|
</body>
|
|
24832
25028
|
</html>`;
|
|
24833
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
|
+
}
|
|
24834
25065
|
function isDebugMode() {
|
|
24835
25066
|
return isTruthy(process.env.TINA4_DEBUG);
|
|
24836
25067
|
}
|
|
@@ -24839,6 +25070,7 @@ var init_errorOverlay = __esm({
|
|
|
24839
25070
|
"src/errorOverlay.ts"() {
|
|
24840
25071
|
"use strict";
|
|
24841
25072
|
init_dotenv();
|
|
25073
|
+
init_csp();
|
|
24842
25074
|
MAX_FRAMES = 50;
|
|
24843
25075
|
SENSITIVE_KEY_RE = /password|passwd|secret|token|authorization|cookie|key/i;
|
|
24844
25076
|
REDACTED2 = "[redacted]";
|
|
@@ -25214,6 +25446,25 @@ Generated by DevMailbox.seed() for development purposes.`,
|
|
|
25214
25446
|
}
|
|
25215
25447
|
});
|
|
25216
25448
|
|
|
25449
|
+
// src/upstreamResponse.ts
|
|
25450
|
+
async function relayUpstreamResponse(res, upstream) {
|
|
25451
|
+
const raw = await upstream.text();
|
|
25452
|
+
const status2 = upstream.status || 200;
|
|
25453
|
+
try {
|
|
25454
|
+
res.json(JSON.parse(raw), status2);
|
|
25455
|
+
} catch {
|
|
25456
|
+
res.raw.writeHead(status2, {
|
|
25457
|
+
"Content-Type": upstream.headers.get("content-type") ?? "text/plain; charset=utf-8"
|
|
25458
|
+
});
|
|
25459
|
+
res.raw.end(raw);
|
|
25460
|
+
}
|
|
25461
|
+
}
|
|
25462
|
+
var init_upstreamResponse = __esm({
|
|
25463
|
+
"src/upstreamResponse.ts"() {
|
|
25464
|
+
"use strict";
|
|
25465
|
+
}
|
|
25466
|
+
});
|
|
25467
|
+
|
|
25217
25468
|
// src/metrics.ts
|
|
25218
25469
|
import * as fs3 from "node:fs";
|
|
25219
25470
|
import * as path2 from "node:path";
|
|
@@ -25429,6 +25680,7 @@ var init_feedback = __esm({
|
|
|
25429
25680
|
"use strict";
|
|
25430
25681
|
init_auth();
|
|
25431
25682
|
init_devAdmin();
|
|
25683
|
+
init_upstreamResponse();
|
|
25432
25684
|
RATE_LIMIT_WINDOW_SEC = 3600;
|
|
25433
25685
|
RATE_LIMIT_MAX = 5;
|
|
25434
25686
|
_rateLimitHits = /* @__PURE__ */ new Map();
|
|
@@ -25475,16 +25727,7 @@ var init_feedback = __esm({
|
|
|
25475
25727
|
return;
|
|
25476
25728
|
}
|
|
25477
25729
|
clearTimeout(timer);
|
|
25478
|
-
|
|
25479
|
-
const status2 = upstream.status || 200;
|
|
25480
|
-
try {
|
|
25481
|
-
res.json(JSON.parse(raw), status2);
|
|
25482
|
-
} catch {
|
|
25483
|
-
res.raw.writeHead(status2, {
|
|
25484
|
-
"Content-Type": upstream.headers.get("content-type") ?? "text/plain; charset=utf-8"
|
|
25485
|
-
});
|
|
25486
|
-
res.raw.end(raw);
|
|
25487
|
-
}
|
|
25730
|
+
await relayUpstreamResponse(res, upstream);
|
|
25488
25731
|
};
|
|
25489
25732
|
__feedbackDirname = dirname7(fileURLToPath2(import.meta.url));
|
|
25490
25733
|
WIDGET_BUNDLE_PATH = resolve9(__feedbackDirname, "__feedback", "widget.js");
|
|
@@ -25534,6 +25777,38 @@ var init_version = __esm({
|
|
|
25534
25777
|
}
|
|
25535
25778
|
});
|
|
25536
25779
|
|
|
25780
|
+
// ../cli/src/util.ts
|
|
25781
|
+
function parseFlags(args, booleanFlags) {
|
|
25782
|
+
const flags = {};
|
|
25783
|
+
const positional = [];
|
|
25784
|
+
let i = 0;
|
|
25785
|
+
while (i < args.length) {
|
|
25786
|
+
const arg = args[i];
|
|
25787
|
+
if (arg.startsWith("--")) {
|
|
25788
|
+
const key = arg.slice(2);
|
|
25789
|
+
if (booleanFlags?.has(key)) {
|
|
25790
|
+
flags[key] = true;
|
|
25791
|
+
i += 1;
|
|
25792
|
+
} else if (i + 1 < args.length && !args[i + 1].startsWith("--")) {
|
|
25793
|
+
flags[key] = args[i + 1];
|
|
25794
|
+
i += 2;
|
|
25795
|
+
} else {
|
|
25796
|
+
flags[key] = true;
|
|
25797
|
+
i += 1;
|
|
25798
|
+
}
|
|
25799
|
+
} else {
|
|
25800
|
+
positional.push(arg);
|
|
25801
|
+
i += 1;
|
|
25802
|
+
}
|
|
25803
|
+
}
|
|
25804
|
+
return { flags, positional };
|
|
25805
|
+
}
|
|
25806
|
+
var init_util = __esm({
|
|
25807
|
+
"../cli/src/util.ts"() {
|
|
25808
|
+
"use strict";
|
|
25809
|
+
}
|
|
25810
|
+
});
|
|
25811
|
+
|
|
25537
25812
|
// ../cli/src/commands/generate.ts
|
|
25538
25813
|
var generate_exports = {};
|
|
25539
25814
|
__export(generate_exports, {
|
|
@@ -25798,28 +26073,7 @@ function parseCliArgs(args) {
|
|
|
25798
26073
|
"json",
|
|
25799
26074
|
"dry-run"
|
|
25800
26075
|
]);
|
|
25801
|
-
|
|
25802
|
-
const positional = [];
|
|
25803
|
-
let i = 0;
|
|
25804
|
-
while (i < args.length) {
|
|
25805
|
-
if (args[i].startsWith("--")) {
|
|
25806
|
-
const key = args[i].slice(2);
|
|
25807
|
-
if (booleanFlags.has(key)) {
|
|
25808
|
-
flags[key] = true;
|
|
25809
|
-
i += 1;
|
|
25810
|
-
} else if (i + 1 < args.length && !args[i + 1].startsWith("--")) {
|
|
25811
|
-
flags[key] = args[i + 1];
|
|
25812
|
-
i += 2;
|
|
25813
|
-
} else {
|
|
25814
|
-
flags[key] = true;
|
|
25815
|
-
i += 1;
|
|
25816
|
-
}
|
|
25817
|
-
} else {
|
|
25818
|
-
positional.push(args[i]);
|
|
25819
|
-
i += 1;
|
|
25820
|
-
}
|
|
25821
|
-
}
|
|
25822
|
-
return { flags, positional };
|
|
26076
|
+
return parseFlags(args, booleanFlags);
|
|
25823
26077
|
}
|
|
25824
26078
|
function parseEvery(every) {
|
|
25825
26079
|
if (!every || every === true) return 60;
|
|
@@ -26667,7 +26921,11 @@ export default async function (req: Tina4Request, res: Tina4Response) {
|
|
|
26667
26921
|
`{% extends "base.twig" %}
|
|
26668
26922
|
{% block title %}Login{% endblock %}
|
|
26669
26923
|
{% block content %}
|
|
26670
|
-
|
|
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">
|
|
26671
26929
|
<h1>Login</h1>
|
|
26672
26930
|
<form method="post" action="/api/auth/login">
|
|
26673
26931
|
{{ form_token() }}
|
|
@@ -26691,7 +26949,8 @@ export default async function (req: Tina4Request, res: Tina4Response) {
|
|
|
26691
26949
|
`{% extends "base.twig" %}
|
|
26692
26950
|
{% block title %}Register{% endblock %}
|
|
26693
26951
|
{% block content %}
|
|
26694
|
-
<
|
|
26952
|
+
<style nonce="{{ csp_nonce() }}">.auth-form{max-width:400px;margin:0 auto}</style>
|
|
26953
|
+
<div class="container mt-4 auth-form">
|
|
26695
26954
|
<h1>Register</h1>
|
|
26696
26955
|
<form method="post" action="/api/auth/register">
|
|
26697
26956
|
{{ form_token() }}
|
|
@@ -27380,6 +27639,7 @@ var FIELD_TYPE_MAP, SQL_RESERVED_TABLE_NAMES, RESOLUTION_ENVELOPE_VERSION, __res
|
|
|
27380
27639
|
var init_generate = __esm({
|
|
27381
27640
|
"../cli/src/commands/generate.ts"() {
|
|
27382
27641
|
"use strict";
|
|
27642
|
+
init_util();
|
|
27383
27643
|
FIELD_TYPE_MAP = {
|
|
27384
27644
|
string: { orm: '"string"', sql: "VARCHAR(255)", defaultVal: "''" },
|
|
27385
27645
|
str: { orm: '"string"', sql: "VARCHAR(255)", defaultVal: "''" },
|
|
@@ -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
|
}
|
|
@@ -30998,6 +31258,7 @@ __export(websocket_exports, {
|
|
|
30998
31258
|
CLOSE_NORMAL: () => CLOSE_NORMAL,
|
|
30999
31259
|
CLOSE_POLICY_VIOLATION: () => CLOSE_POLICY_VIOLATION,
|
|
31000
31260
|
CLOSE_PROTOCOL_ERROR: () => CLOSE_PROTOCOL_ERROR,
|
|
31261
|
+
MessageAssembler: () => MessageAssembler,
|
|
31001
31262
|
OP_BINARY: () => OP_BINARY,
|
|
31002
31263
|
OP_CLOSE: () => OP_CLOSE,
|
|
31003
31264
|
OP_CONTINUATION: () => OP_CONTINUATION,
|
|
@@ -31255,21 +31516,24 @@ function serveWebSocketRoute(req2, socket, head) {
|
|
|
31255
31516
|
state.closed = true;
|
|
31256
31517
|
void Promise.resolve().then(() => conn._onClose ? conn._onClose() : handler(conn, "close", "")).catch((e) => Log.error(`WebSocket close handler error: ${e.message}`)).finally(() => wsRouteManager.remove(conn.id));
|
|
31257
31518
|
};
|
|
31519
|
+
const assembler = new MessageAssembler();
|
|
31258
31520
|
socket.on("data", (chunk) => {
|
|
31259
31521
|
buffer = Buffer.concat([buffer, chunk]);
|
|
31260
31522
|
while (buffer.length > 0) {
|
|
31261
31523
|
const frame = parseFrame(buffer);
|
|
31262
31524
|
if (!frame) break;
|
|
31263
31525
|
buffer = buffer.subarray(frame.bytesConsumed);
|
|
31264
|
-
|
|
31526
|
+
const whole = assembler.push(frame);
|
|
31527
|
+
if (!whole) continue;
|
|
31528
|
+
switch (whole.opcode) {
|
|
31265
31529
|
case OP_TEXT: {
|
|
31266
|
-
const text =
|
|
31530
|
+
const text = whole.payload.toString("utf-8");
|
|
31267
31531
|
void Promise.resolve().then(() => conn._onMessage ? conn._onMessage(text) : handler(conn, "message", text)).catch((e) => Log.error(`WebSocket message handler error: ${e.message}`));
|
|
31268
31532
|
break;
|
|
31269
31533
|
}
|
|
31270
31534
|
case OP_PING:
|
|
31271
31535
|
try {
|
|
31272
|
-
socket.write(buildFrame(OP_PONG,
|
|
31536
|
+
socket.write(buildFrame(OP_PONG, whole.payload));
|
|
31273
31537
|
} catch {
|
|
31274
31538
|
}
|
|
31275
31539
|
break;
|
|
@@ -31288,7 +31552,7 @@ function serveWebSocketRoute(req2, socket, head) {
|
|
|
31288
31552
|
socket.on("error", fireClose);
|
|
31289
31553
|
return true;
|
|
31290
31554
|
}
|
|
31291
|
-
var MAGIC_STRING, OP_CONTINUATION, OP_TEXT, OP_BINARY, OP_CLOSE, OP_PING, OP_PONG, CLOSE_NORMAL, CLOSE_GOING_AWAY, CLOSE_PROTOCOL_ERROR, CLOSE_POLICY_VIOLATION, WebSocketServer, WsRouteManager, wsRouteManager, DevReloadWsManager, devReloadWs;
|
|
31555
|
+
var MAGIC_STRING, OP_CONTINUATION, OP_TEXT, OP_BINARY, OP_CLOSE, OP_PING, OP_PONG, CLOSE_NORMAL, CLOSE_GOING_AWAY, CLOSE_PROTOCOL_ERROR, CLOSE_POLICY_VIOLATION, MessageAssembler, WebSocketServer, WsRouteManager, wsRouteManager, DevReloadWsManager, devReloadWs;
|
|
31292
31556
|
var init_websocket = __esm({
|
|
31293
31557
|
"src/websocket.ts"() {
|
|
31294
31558
|
"use strict";
|
|
@@ -31307,6 +31571,30 @@ var init_websocket = __esm({
|
|
|
31307
31571
|
CLOSE_GOING_AWAY = 1001;
|
|
31308
31572
|
CLOSE_PROTOCOL_ERROR = 1002;
|
|
31309
31573
|
CLOSE_POLICY_VIOLATION = 1008;
|
|
31574
|
+
MessageAssembler = class {
|
|
31575
|
+
parts = [];
|
|
31576
|
+
opcode = 0;
|
|
31577
|
+
push(frame) {
|
|
31578
|
+
if (frame.opcode >= 8) return { opcode: frame.opcode, payload: frame.payload };
|
|
31579
|
+
if (frame.opcode === OP_CONTINUATION) {
|
|
31580
|
+
if (!this.opcode) return null;
|
|
31581
|
+
this.parts.push(frame.payload);
|
|
31582
|
+
if (!frame.fin) return null;
|
|
31583
|
+
const whole = { opcode: this.opcode, payload: Buffer.concat(this.parts) };
|
|
31584
|
+
this.parts = [];
|
|
31585
|
+
this.opcode = 0;
|
|
31586
|
+
return whole;
|
|
31587
|
+
}
|
|
31588
|
+
if (frame.fin) {
|
|
31589
|
+
this.parts = [];
|
|
31590
|
+
this.opcode = 0;
|
|
31591
|
+
return { opcode: frame.opcode, payload: frame.payload };
|
|
31592
|
+
}
|
|
31593
|
+
this.opcode = frame.opcode;
|
|
31594
|
+
this.parts = [frame.payload];
|
|
31595
|
+
return null;
|
|
31596
|
+
}
|
|
31597
|
+
};
|
|
31310
31598
|
WebSocketServer = class {
|
|
31311
31599
|
port;
|
|
31312
31600
|
server = null;
|
|
@@ -31749,6 +32037,15 @@ var init_websocket = __esm({
|
|
|
31749
32037
|
this.emit("error", err, client);
|
|
31750
32038
|
});
|
|
31751
32039
|
}
|
|
32040
|
+
assemblers = /* @__PURE__ */ new WeakMap();
|
|
32041
|
+
assemblerFor(client) {
|
|
32042
|
+
let a = this.assemblers.get(client);
|
|
32043
|
+
if (!a) {
|
|
32044
|
+
a = new MessageAssembler();
|
|
32045
|
+
this.assemblers.set(client, a);
|
|
32046
|
+
}
|
|
32047
|
+
return a;
|
|
32048
|
+
}
|
|
31752
32049
|
processBuffer(client, buffer, setBuffer) {
|
|
31753
32050
|
let remaining = buffer;
|
|
31754
32051
|
while (remaining.length > 0) {
|
|
@@ -31756,15 +32053,17 @@ var init_websocket = __esm({
|
|
|
31756
32053
|
if (!frame) break;
|
|
31757
32054
|
remaining = remaining.subarray(frame.bytesConsumed);
|
|
31758
32055
|
client.lastActivity = Date.now();
|
|
31759
|
-
|
|
32056
|
+
const whole = this.assemblerFor(client).push(frame);
|
|
32057
|
+
if (!whole) continue;
|
|
32058
|
+
switch (whole.opcode) {
|
|
31760
32059
|
case OP_TEXT:
|
|
31761
|
-
this.emit("message", client,
|
|
32060
|
+
this.emit("message", client, whole.payload.toString("utf-8"));
|
|
31762
32061
|
break;
|
|
31763
32062
|
case OP_BINARY:
|
|
31764
|
-
this.emit("message", client,
|
|
32063
|
+
this.emit("message", client, whole.payload);
|
|
31765
32064
|
break;
|
|
31766
32065
|
case OP_PING: {
|
|
31767
|
-
const pongFrame = buildFrame(OP_PONG,
|
|
32066
|
+
const pongFrame = buildFrame(OP_PONG, whole.payload);
|
|
31768
32067
|
try {
|
|
31769
32068
|
client.socket.write(pongFrame);
|
|
31770
32069
|
} catch {
|
|
@@ -36142,16 +36441,7 @@ async function proxyToSupervisor(req2, res, downstreamPath) {
|
|
|
36142
36441
|
return;
|
|
36143
36442
|
}
|
|
36144
36443
|
clearTimeout(timer);
|
|
36145
|
-
|
|
36146
|
-
const status2 = upstream.status || 200;
|
|
36147
|
-
try {
|
|
36148
|
-
res.json(JSON.parse(raw), status2);
|
|
36149
|
-
} catch {
|
|
36150
|
-
res.raw.writeHead(status2, {
|
|
36151
|
-
"Content-Type": upstream.headers.get("content-type") ?? "text/plain; charset=utf-8"
|
|
36152
|
-
});
|
|
36153
|
-
res.raw.end(raw);
|
|
36154
|
-
}
|
|
36444
|
+
await relayUpstreamResponse(res, upstream);
|
|
36155
36445
|
}
|
|
36156
36446
|
function resolveDevEnvVar(key) {
|
|
36157
36447
|
const live = process.env[key];
|
|
@@ -36593,6 +36883,7 @@ var init_devAdmin = __esm({
|
|
|
36593
36883
|
"src/devAdmin.ts"() {
|
|
36594
36884
|
"use strict";
|
|
36595
36885
|
init_devMailbox();
|
|
36886
|
+
init_upstreamResponse();
|
|
36596
36887
|
init_dotenv();
|
|
36597
36888
|
init_metrics();
|
|
36598
36889
|
init_feedback();
|
|
@@ -36694,8 +36985,8 @@ var init_devAdmin = __esm({
|
|
|
36694
36985
|
* Duplicate errors (same message) increment count and update last_seen.
|
|
36695
36986
|
*/
|
|
36696
36987
|
static capture(errorType, message, traceback = "", file = "", line = 0) {
|
|
36697
|
-
const
|
|
36698
|
-
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);
|
|
36699
36990
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
36700
36991
|
if (existing) {
|
|
36701
36992
|
existing.count = (existing.count || 1) + 1;
|
|
@@ -36708,7 +36999,7 @@ var init_devAdmin = __esm({
|
|
|
36708
36999
|
message,
|
|
36709
37000
|
stack: traceback || void 0,
|
|
36710
37001
|
resolved: false,
|
|
36711
|
-
...{ 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 }
|
|
36712
37003
|
});
|
|
36713
37004
|
if (this.errors.length > this.maxErrors) {
|
|
36714
37005
|
this.errors = this.errors.slice(-this.maxErrors);
|
|
@@ -39387,7 +39678,7 @@ function swaggerEnabled() {
|
|
|
39387
39678
|
}
|
|
39388
39679
|
function createSwaggerRoutes(getSpec) {
|
|
39389
39680
|
const serveUi = async (_req, res) => {
|
|
39390
|
-
res.html(SWAGGER_UI_HTML("/swagger/openapi.json"));
|
|
39681
|
+
res.html(SWAGGER_UI_HTML("/swagger/openapi.json", res.cspNonce));
|
|
39391
39682
|
};
|
|
39392
39683
|
return [
|
|
39393
39684
|
{
|
|
@@ -39431,13 +39722,13 @@ var SWAGGER_UI_HTML;
|
|
|
39431
39722
|
var init_ui = __esm({
|
|
39432
39723
|
"../swagger/src/ui.ts"() {
|
|
39433
39724
|
"use strict";
|
|
39434
|
-
SWAGGER_UI_HTML = (specUrl) => `<!DOCTYPE html>
|
|
39725
|
+
SWAGGER_UI_HTML = (specUrl, nonce) => `<!DOCTYPE html>
|
|
39435
39726
|
<html lang="en">
|
|
39436
39727
|
<head>
|
|
39437
39728
|
<meta charset="UTF-8">
|
|
39438
39729
|
<title>Tina4 API Documentation</title>
|
|
39439
39730
|
<link rel="stylesheet" href="${swaggerUiCdn()}/swagger-ui.css">
|
|
39440
|
-
<style>
|
|
39731
|
+
<style nonce="${nonce}">
|
|
39441
39732
|
body { margin: 0; background: #fafafa; }
|
|
39442
39733
|
.topbar { display: none !important; }
|
|
39443
39734
|
</style>
|
|
@@ -39445,7 +39736,7 @@ var init_ui = __esm({
|
|
|
39445
39736
|
<body>
|
|
39446
39737
|
<div id="swagger-ui"></div>
|
|
39447
39738
|
<script src="${swaggerUiCdn()}/swagger-ui-bundle.js"></script>
|
|
39448
|
-
<script>
|
|
39739
|
+
<script nonce="${nonce}">
|
|
39449
39740
|
SwaggerUIBundle({
|
|
39450
39741
|
url: "${specUrl}",
|
|
39451
39742
|
dom_id: '#swagger-ui',
|
|
@@ -39485,7 +39776,7 @@ __export(sso_exports, {
|
|
|
39485
39776
|
SsoError: () => SsoError,
|
|
39486
39777
|
liveSessionIdentity: () => liveSessionIdentity
|
|
39487
39778
|
});
|
|
39488
|
-
import { createHash as createHash9, randomBytes as
|
|
39779
|
+
import { createHash as createHash9, randomBytes as randomBytes8, timingSafeEqual as timingSafeEqual3 } from "node:crypto";
|
|
39489
39780
|
function expiresAt(tokens) {
|
|
39490
39781
|
const lifetime = Number(tokens.expires_in ?? 0);
|
|
39491
39782
|
return lifetime > 0 ? Math.floor(Date.now() / 1e3) + lifetime : 0;
|
|
@@ -39613,9 +39904,9 @@ var init_sso = __esm({
|
|
|
39613
39904
|
async login(requestOrSession, returnTo = "/") {
|
|
39614
39905
|
const session = this.session(requestOrSession);
|
|
39615
39906
|
if (!session) throw new SsoError("SSO login requires a Tina4 Session");
|
|
39616
|
-
const state =
|
|
39617
|
-
const nonce =
|
|
39618
|
-
const verifier =
|
|
39907
|
+
const state = randomBytes8(32).toString("base64url");
|
|
39908
|
+
const nonce = randomBytes8(32).toString("base64url");
|
|
39909
|
+
const verifier = randomBytes8(64).toString("base64url");
|
|
39619
39910
|
const challenge = createHash9("sha256").update(verifier).digest("base64url");
|
|
39620
39911
|
session.set(_Sso.PENDING_KEY, { state, nonce, verifier, return_to: _Sso.safeReturn(returnTo), created_at: Math.floor(Date.now() / 1e3) });
|
|
39621
39912
|
const metadata = await this.discover();
|
|
@@ -39843,7 +40134,7 @@ var init_docsAutoDiscovery = __esm({
|
|
|
39843
40134
|
});
|
|
39844
40135
|
|
|
39845
40136
|
// src/server.ts
|
|
39846
|
-
import { randomBytes as
|
|
40137
|
+
import { randomBytes as randomBytes9 } from "node:crypto";
|
|
39847
40138
|
import { resolve as resolve19, dirname as dirname13, join as join30, relative as relative10 } from "node:path";
|
|
39848
40139
|
import { existsSync as existsSync25, readdirSync as readdirSync17, readFileSync as readFileSync23, statSync as statSync18 } from "node:fs";
|
|
39849
40140
|
import { isatty } from "node:tty";
|
|
@@ -40023,7 +40314,8 @@ function isDevMode() {
|
|
|
40023
40314
|
}
|
|
40024
40315
|
async function renderErrorPage(code, data, templatesDir) {
|
|
40025
40316
|
try {
|
|
40026
|
-
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);
|
|
40027
40319
|
const templateFile = `errors/${code}.twig`;
|
|
40028
40320
|
const getCachedFrond = (dir) => {
|
|
40029
40321
|
let instance = frondCache.get(dir);
|
|
@@ -40139,6 +40431,7 @@ function resolveTemplate(pathname, templatesDir) {
|
|
|
40139
40431
|
}
|
|
40140
40432
|
function renderLandingPage(port = 7148) {
|
|
40141
40433
|
const version = TINA4_VERSION;
|
|
40434
|
+
const nonce = currentCspNonce();
|
|
40142
40435
|
const galleryItems = [
|
|
40143
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" },
|
|
40144
40437
|
{ id: "orm", icon: "🗃", name: "ORM", desc: "Product model with CRUD endpoints", accent: "accent-green", tryUrl: "/api/gallery/products" },
|
|
@@ -40153,7 +40446,7 @@ function renderLandingPage(port = 7148) {
|
|
|
40153
40446
|
const isDeployed = deployed[item.id] === true;
|
|
40154
40447
|
const tryBtn = isDeployed ? `<a href="${item.tryUrl}" class="gbtn gbtn-try" target="_blank">Try It</a>` : "";
|
|
40155
40448
|
const viewBtn = isDeployed ? `<a href="${item.tryUrl}" class="gbtn gbtn-view" target="_blank">View</a>` : "";
|
|
40156
|
-
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>`;
|
|
40157
40450
|
return `<div class="gallery-card">
|
|
40158
40451
|
<div class="accent ${item.accent}"></div>
|
|
40159
40452
|
<div class="icon">${item.icon}</div>
|
|
@@ -40168,7 +40461,7 @@ function renderLandingPage(port = 7148) {
|
|
|
40168
40461
|
<meta charset="utf-8">
|
|
40169
40462
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
40170
40463
|
<title>Tina4NodeJs</title>
|
|
40171
|
-
<style>
|
|
40464
|
+
<style nonce="${nonce}">
|
|
40172
40465
|
*{margin:0;padding:0;box-sizing:border-box}
|
|
40173
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}
|
|
40174
40467
|
.bg-watermark{position:fixed;bottom:-5%;right:-5%;width:45%;opacity:0.04;pointer-events:none;z-index:0}
|
|
@@ -40207,6 +40500,11 @@ h1{font-size:3rem;font-weight:700;margin-bottom:0.25rem;letter-spacing:-1px}
|
|
|
40207
40500
|
.gbtn-deployed{background:transparent;border:1px solid #22c55e;color:#22c55e;cursor:default;font-size:0.7rem}
|
|
40208
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)}}
|
|
40209
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}
|
|
40210
40508
|
</style>
|
|
40211
40509
|
</head>
|
|
40212
40510
|
<body>
|
|
@@ -40232,23 +40530,23 @@ h1{font-size:3rem;font-weight:700;margin-bottom:0.25rem;letter-spacing:-1px}
|
|
|
40232
40530
|
<div class="section">
|
|
40233
40531
|
<div class="card">
|
|
40234
40532
|
<h2>Getting Started</h2>
|
|
40235
|
-
<pre class="code-block"><code><span
|
|
40236
|
-
<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>;
|
|
40237
40535
|
|
|
40238
|
-
Router.get(<span
|
|
40239
|
-
<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> });
|
|
40240
40538
|
});
|
|
40241
40539
|
|
|
40242
|
-
startServer({ port: 7148 }); <span
|
|
40540
|
+
startServer({ port: 7148 }); <span class="tok-comment">// starts on port 7148</span></code></pre>
|
|
40243
40541
|
</div>
|
|
40244
40542
|
</div>
|
|
40245
40543
|
<div class="gallery">
|
|
40246
40544
|
<h2 id="gallery">What You Can Build</h2>
|
|
40247
|
-
<div
|
|
40545
|
+
<div class="gallery-cards">
|
|
40248
40546
|
${galleryCards}
|
|
40249
40547
|
</div>
|
|
40250
40548
|
</div>
|
|
40251
|
-
<script>
|
|
40549
|
+
<script nonce="${nonce}">
|
|
40252
40550
|
function deployGallery(name) {
|
|
40253
40551
|
if (!confirm('Deploy the "' + name + '" gallery example into your project? This will copy files into src/.')) return;
|
|
40254
40552
|
fetch('/__dev/api/gallery/deploy', {
|
|
@@ -40283,6 +40581,12 @@ function deployGallery(name) {
|
|
|
40283
40581
|
}
|
|
40284
40582
|
setTimeout(doWiggle,3000);
|
|
40285
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
|
+
});
|
|
40286
40590
|
</script>
|
|
40287
40591
|
</body>
|
|
40288
40592
|
</html>`;
|
|
@@ -40499,7 +40803,7 @@ async function renderDispatchError(err, req2, res, templatesDir) {
|
|
|
40499
40803
|
}
|
|
40500
40804
|
}
|
|
40501
40805
|
}
|
|
40502
|
-
const requestId = Log.getRequestId() ??
|
|
40806
|
+
const requestId = Log.getRequestId() ?? randomBytes9(4).toString("hex");
|
|
40503
40807
|
if (wantsJson(req2)) {
|
|
40504
40808
|
const body = negotiatedErrorBody(500, "Internal Server Error", requestId);
|
|
40505
40809
|
res.raw.writeHead(500, { "Content-Type": "application/json" });
|
|
@@ -40570,7 +40874,7 @@ function serveStaticAsset(ctx) {
|
|
|
40570
40874
|
return false;
|
|
40571
40875
|
}
|
|
40572
40876
|
async function serveNotFound(ctx) {
|
|
40573
|
-
const requestId = Log.getRequestId() ??
|
|
40877
|
+
const requestId = Log.getRequestId() ?? randomBytes9(4).toString("hex");
|
|
40574
40878
|
if (wantsJson(ctx.req)) {
|
|
40575
40879
|
const body = negotiatedErrorBody(404, "Not Found", requestId);
|
|
40576
40880
|
ctx.res.raw.writeHead(404, httpReason(404), { "Content-Type": "application/json" });
|
|
@@ -40593,7 +40897,8 @@ async function buildDispatchContext(router, base) {
|
|
|
40593
40897
|
const templatesDir = resolve19(root, "src/templates");
|
|
40594
40898
|
let frondEngine = null;
|
|
40595
40899
|
try {
|
|
40596
|
-
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);
|
|
40597
40902
|
frondEngine = new Frond2(templatesDir);
|
|
40598
40903
|
} catch {
|
|
40599
40904
|
}
|
|
@@ -40703,9 +41008,13 @@ async function dispatchInner(ctx, rawReq, rawRes, requestId) {
|
|
|
40703
41008
|
}
|
|
40704
41009
|
}
|
|
40705
41010
|
async function runDispatch(ctx, rawReq, rawRes) {
|
|
40706
|
-
const requestId = Log.sanitizeRequestId(rawReq.headers["x-request-id"]) ??
|
|
41011
|
+
const requestId = Log.sanitizeRequestId(rawReq.headers["x-request-id"]) ?? randomBytes9(4).toString("hex");
|
|
40707
41012
|
if (!rawRes.headersSent) rawRes.setHeader("x-request-id", requestId);
|
|
40708
|
-
|
|
41013
|
+
const requestNonce = generateNonce();
|
|
41014
|
+
return Log.runWithRequestId(
|
|
41015
|
+
requestId,
|
|
41016
|
+
() => runWithCspNonce(requestNonce, () => dispatchInner(ctx, rawReq, rawRes, requestId))
|
|
41017
|
+
);
|
|
40709
41018
|
}
|
|
40710
41019
|
function loopbackBindHosts(host) {
|
|
40711
41020
|
const normalized = host.trim().replace(/^\[|\]$/g, "").toLowerCase();
|
|
@@ -40832,7 +41141,8 @@ async function startServer(config) {
|
|
|
40832
41141
|
let frondEngine = null;
|
|
40833
41142
|
setDefaultTemplatesDir(templatesDir);
|
|
40834
41143
|
try {
|
|
40835
|
-
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);
|
|
40836
41146
|
frondEngine = new Frond2(templatesDir);
|
|
40837
41147
|
router.addRoute({
|
|
40838
41148
|
method: "GET",
|
|
@@ -40973,10 +41283,12 @@ async function startServer(config) {
|
|
|
40973
41283
|
}
|
|
40974
41284
|
});
|
|
40975
41285
|
aiServer.on("error", (err) => {
|
|
40976
|
-
if (err
|
|
41286
|
+
if (err?.code === "EADDRINUSE") {
|
|
40977
41287
|
Log.warning(`Test port ${testPort} in use \u2014 skipping`);
|
|
40978
|
-
|
|
41288
|
+
} else {
|
|
41289
|
+
Log.error(`Test port ${testPort} bind failed (${err?.code ?? err}) \u2014 skipping`);
|
|
40979
41290
|
}
|
|
41291
|
+
aiServer = null;
|
|
40980
41292
|
});
|
|
40981
41293
|
aiServer.listen(testPort, host);
|
|
40982
41294
|
}
|
|
@@ -41191,6 +41503,7 @@ var init_server = __esm({
|
|
|
41191
41503
|
init_static();
|
|
41192
41504
|
init_dotenv();
|
|
41193
41505
|
init_errorOverlay();
|
|
41506
|
+
init_csp();
|
|
41194
41507
|
init_health();
|
|
41195
41508
|
init_rateLimiter();
|
|
41196
41509
|
init_logger();
|
|
@@ -41440,7 +41753,7 @@ var init_mqttMessage = __esm({
|
|
|
41440
41753
|
// src/mqtt.ts
|
|
41441
41754
|
import net2 from "node:net";
|
|
41442
41755
|
import tls from "node:tls";
|
|
41443
|
-
import { randomBytes as
|
|
41756
|
+
import { randomBytes as randomBytes10 } from "node:crypto";
|
|
41444
41757
|
import { existsSync as existsSync26, readFileSync as readFileSync24 } from "node:fs";
|
|
41445
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;
|
|
41446
41759
|
var init_mqtt = __esm({
|
|
@@ -41528,7 +41841,7 @@ var init_mqtt = __esm({
|
|
|
41528
41841
|
this.caFile = options.caFile ?? (Env.str("TINA4_MQTT_CA_FILE") || null);
|
|
41529
41842
|
this.tlsVerify = options.tlsVerify ?? Env.bool("TINA4_MQTT_TLS_VERIFY", true);
|
|
41530
41843
|
let cid = options.clientId ?? (Env.str("TINA4_MQTT_CLIENT_ID") || null);
|
|
41531
|
-
if (cid === null || cid === "") cid = "tina4-" +
|
|
41844
|
+
if (cid === null || cid === "") cid = "tina4-" + randomBytes10(8).toString("hex");
|
|
41532
41845
|
this.clientId = cid;
|
|
41533
41846
|
this.keepalive = options.keepalive ?? Env.int("TINA4_MQTT_KEEPALIVE", DEFAULT_KEEPALIVE);
|
|
41534
41847
|
this.cleanSession = options.cleanSession ?? true;
|
|
@@ -42642,7 +42955,7 @@ var init_ssrf = __esm({
|
|
|
42642
42955
|
import http from "node:http";
|
|
42643
42956
|
import https from "node:https";
|
|
42644
42957
|
import { URL as URL2 } from "node:url";
|
|
42645
|
-
import { randomBytes as
|
|
42958
|
+
import { randomBytes as randomBytes11 } from "node:crypto";
|
|
42646
42959
|
import { promises as fsp, createWriteStream } from "node:fs";
|
|
42647
42960
|
import { basename as basename6 } from "node:path";
|
|
42648
42961
|
import { pipeline } from "node:stream/promises";
|
|
@@ -43034,7 +43347,7 @@ var init_api = __esm({
|
|
|
43034
43347
|
return { http_code: null, body: null, headers: {}, error: "upload requires filePath or fileBytes" };
|
|
43035
43348
|
}
|
|
43036
43349
|
const partContentType = guessContentType(uploadName);
|
|
43037
|
-
const boundary = "----Tina4Boundary" +
|
|
43350
|
+
const boundary = "----Tina4Boundary" + randomBytes11(16).toString("hex");
|
|
43038
43351
|
const bodyBuffer = buildMultipartBody(boundary, fieldName, uploadName, content, partContentType, extraFields);
|
|
43039
43352
|
const contentType = `multipart/form-data; boundary=${boundary}`;
|
|
43040
43353
|
return this.execute("POST", this.buildUrl(path10), bodyBuffer, contentType, headers);
|
|
@@ -45998,7 +46311,7 @@ import {
|
|
|
45998
46311
|
createHmac as createHmac4,
|
|
45999
46312
|
createPrivateKey,
|
|
46000
46313
|
createSign as createSign2,
|
|
46001
|
-
randomBytes as
|
|
46314
|
+
randomBytes as randomBytes12
|
|
46002
46315
|
} from "node:crypto";
|
|
46003
46316
|
function encodeBase64Url(value) {
|
|
46004
46317
|
return Buffer.from(value).toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
|
|
@@ -46078,7 +46391,7 @@ function encryptPayload(payload, subscription) {
|
|
|
46078
46391
|
]);
|
|
46079
46392
|
const prkKey = hmac(authSecret, sharedSecret);
|
|
46080
46393
|
const ikm = hkdfExpand(prkKey, keyInfo, 32);
|
|
46081
|
-
const salt =
|
|
46394
|
+
const salt = randomBytes12(16);
|
|
46082
46395
|
const contentPrk = hmac(salt, ikm);
|
|
46083
46396
|
const cek = hkdfExpand(contentPrk, Buffer.from("Content-Encoding: aes128gcm\0", "ascii"), 16);
|
|
46084
46397
|
const nonce = hkdfExpand(contentPrk, Buffer.from("Content-Encoding: nonce\0", "ascii"), 12);
|
|
@@ -48835,6 +49148,7 @@ __export(index_exports, {
|
|
|
48835
49148
|
cacheStats: () => cacheStats,
|
|
48836
49149
|
checkPassword: () => checkPassword,
|
|
48837
49150
|
clearCache: () => clearCache,
|
|
49151
|
+
clearCurrentNonce: () => clearCurrentNonce,
|
|
48838
49152
|
computeAcceptKey: () => computeAcceptKey,
|
|
48839
49153
|
container: () => container,
|
|
48840
49154
|
cors: () => cors,
|
|
@@ -48846,6 +49160,8 @@ __export(index_exports, {
|
|
|
48846
49160
|
createMessenger: () => createMessenger,
|
|
48847
49161
|
createRequest: () => createRequest,
|
|
48848
49162
|
createResponse: () => createResponse,
|
|
49163
|
+
cspNonce: () => cspNonce,
|
|
49164
|
+
currentCspNonce: () => currentCspNonce,
|
|
48849
49165
|
decodeRequest: () => decodeRequest,
|
|
48850
49166
|
defaultContext: () => defaultContext,
|
|
48851
49167
|
defaultRouter: () => defaultRouter,
|
|
@@ -48871,6 +49187,7 @@ __export(index_exports, {
|
|
|
48871
49187
|
feedbackWhitelist: () => feedbackWhitelist,
|
|
48872
49188
|
fts5Supported: () => fts5Supported,
|
|
48873
49189
|
generateContext: () => generateContext,
|
|
49190
|
+
generateNonce: () => generateNonce,
|
|
48874
49191
|
generateVapidKeys: () => generateVapidKeys,
|
|
48875
49192
|
get: () => get,
|
|
48876
49193
|
getDefaultDevServer: () => getDefaultDevServer,
|
|
@@ -48893,6 +49210,7 @@ __export(index_exports, {
|
|
|
48893
49210
|
httpReason: () => httpReason,
|
|
48894
49211
|
inContainer: () => inContainer,
|
|
48895
49212
|
injectFeedbackWidget: () => injectFeedbackWidget,
|
|
49213
|
+
injectNonceIntoCsp: () => injectNonceIntoCsp,
|
|
48896
49214
|
installAll: () => installAll,
|
|
48897
49215
|
installSelected: () => installSelected,
|
|
48898
49216
|
isBannerSuppressed: () => isBannerSuppressed,
|
|
@@ -48947,6 +49265,7 @@ __export(index_exports, {
|
|
|
48947
49265
|
resetTrustedProxyCache: () => resetTrustedProxyCache,
|
|
48948
49266
|
resolveAlgorithm: () => resolveAlgorithm,
|
|
48949
49267
|
resolveClientIp: () => resolveClientIp,
|
|
49268
|
+
resolveCspHeader: () => resolveCspHeader,
|
|
48950
49269
|
resolveFrameworkVersion: () => resolveFrameworkVersion,
|
|
48951
49270
|
resolvePortAndHost: () => resolvePortAndHost,
|
|
48952
49271
|
resolveStringMiddleware: () => resolveStringMiddleware,
|
|
@@ -48954,12 +49273,14 @@ __export(index_exports, {
|
|
|
48954
49273
|
responseCache: () => responseCache,
|
|
48955
49274
|
runAll: () => runAll,
|
|
48956
49275
|
runRouteMiddlewares: () => runRouteMiddlewares,
|
|
49276
|
+
runWithCspNonce: () => runWithCspNonce,
|
|
48957
49277
|
runtimeDir: () => runtimeDir,
|
|
48958
49278
|
saveUpload: () => saveUpload,
|
|
48959
49279
|
schemaFromParams: () => schemaFromParams,
|
|
48960
49280
|
selectablePids: () => selectablePids,
|
|
48961
49281
|
serveWebSocketRoute: () => serveWebSocketRoute,
|
|
48962
49282
|
sessionCookieName: () => sessionCookieName,
|
|
49283
|
+
setCurrentNonce: () => setCurrentNonce,
|
|
48963
49284
|
setDefaultTemplatesDir: () => setDefaultTemplatesDir,
|
|
48964
49285
|
setFrond: () => setFrond,
|
|
48965
49286
|
showMenu: () => showMenu,
|
|
@@ -49025,6 +49346,7 @@ var init_index = __esm({
|
|
|
49025
49346
|
init_wsdl();
|
|
49026
49347
|
init_htmlElement();
|
|
49027
49348
|
init_errorOverlay();
|
|
49349
|
+
init_csp();
|
|
49028
49350
|
init_ai();
|
|
49029
49351
|
init_sso();
|
|
49030
49352
|
init_push();
|
|
@@ -49215,6 +49537,7 @@ export {
|
|
|
49215
49537
|
cacheStats,
|
|
49216
49538
|
checkPassword,
|
|
49217
49539
|
clearCache,
|
|
49540
|
+
clearCurrentNonce,
|
|
49218
49541
|
computeAcceptKey,
|
|
49219
49542
|
container,
|
|
49220
49543
|
cors,
|
|
@@ -49226,6 +49549,8 @@ export {
|
|
|
49226
49549
|
createMessenger,
|
|
49227
49550
|
createRequest,
|
|
49228
49551
|
createResponse,
|
|
49552
|
+
cspNonce,
|
|
49553
|
+
currentCspNonce,
|
|
49229
49554
|
decodeRequest,
|
|
49230
49555
|
defaultContext,
|
|
49231
49556
|
defaultRouter,
|
|
@@ -49251,6 +49576,7 @@ export {
|
|
|
49251
49576
|
feedbackWhitelist,
|
|
49252
49577
|
fts5Supported,
|
|
49253
49578
|
generateContext,
|
|
49579
|
+
generateNonce,
|
|
49254
49580
|
generateVapidKeys,
|
|
49255
49581
|
get,
|
|
49256
49582
|
getDefaultDevServer,
|
|
@@ -49273,6 +49599,7 @@ export {
|
|
|
49273
49599
|
httpReason,
|
|
49274
49600
|
inContainer,
|
|
49275
49601
|
injectFeedbackWidget,
|
|
49602
|
+
injectNonceIntoCsp,
|
|
49276
49603
|
installAll,
|
|
49277
49604
|
installSelected,
|
|
49278
49605
|
isBannerSuppressed,
|
|
@@ -49327,6 +49654,7 @@ export {
|
|
|
49327
49654
|
resetTrustedProxyCache,
|
|
49328
49655
|
resolveAlgorithm,
|
|
49329
49656
|
resolveClientIp,
|
|
49657
|
+
resolveCspHeader,
|
|
49330
49658
|
resolveFrameworkVersion,
|
|
49331
49659
|
resolvePortAndHost,
|
|
49332
49660
|
resolveStringMiddleware,
|
|
@@ -49334,12 +49662,14 @@ export {
|
|
|
49334
49662
|
responseCache,
|
|
49335
49663
|
runAll,
|
|
49336
49664
|
runRouteMiddlewares,
|
|
49665
|
+
runWithCspNonce,
|
|
49337
49666
|
runtimeDir,
|
|
49338
49667
|
saveUpload,
|
|
49339
49668
|
schemaFromParams,
|
|
49340
49669
|
selectablePids,
|
|
49341
49670
|
serveWebSocketRoute,
|
|
49342
49671
|
sessionCookieName,
|
|
49672
|
+
setCurrentNonce,
|
|
49343
49673
|
setDefaultTemplatesDir,
|
|
49344
49674
|
setFrond,
|
|
49345
49675
|
showMenu,
|