@lenne.tech/nest-server 11.31.2 → 11.32.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/rules/architecture.md +2 -0
- package/.claude/rules/configurable-features.md +2 -0
- package/.claude/rules/versioning.md +5 -8
- package/CLAUDE.md +31 -4
- package/FRAMEWORK-API.md +7 -2
- package/README.md +1 -0
- package/dist/config.env.d.ts +4 -0
- package/dist/config.env.js +32 -2
- package/dist/config.env.js.map +1 -1
- package/dist/core/common/helpers/logging.helper.d.ts +1 -0
- package/dist/core/common/helpers/logging.helper.js +12 -0
- package/dist/core/common/helpers/logging.helper.js.map +1 -1
- package/dist/core/common/helpers/meta.helper.d.ts +1 -0
- package/dist/core/common/helpers/meta.helper.js +32 -1
- package/dist/core/common/helpers/meta.helper.js.map +1 -1
- package/dist/core/common/helpers/process-diagnostics.helper.d.ts +18 -0
- package/dist/core/common/helpers/process-diagnostics.helper.js +88 -0
- package/dist/core/common/helpers/process-diagnostics.helper.js.map +1 -0
- package/dist/core/common/interfaces/server-options.interface.d.ts +12 -0
- package/dist/core/common/services/brevo.service.d.ts +7 -1
- package/dist/core/common/services/brevo.service.js +37 -16
- package/dist/core/common/services/brevo.service.js.map +1 -1
- package/dist/core/common/services/email.service.d.ts +3 -1
- package/dist/core/common/services/email.service.js +33 -2
- package/dist/core/common/services/email.service.js.map +1 -1
- package/dist/core/common/services/template.service.js +9 -4
- package/dist/core/common/services/template.service.js.map +1 -1
- package/dist/core/modules/better-auth/core-better-auth-email-verification.service.js +9 -4
- package/dist/core/modules/better-auth/core-better-auth-email-verification.service.js.map +1 -1
- package/dist/core/modules/hub/core-hub-actions.controller.d.ts +22 -0
- package/dist/core/modules/hub/core-hub-actions.controller.js +141 -0
- package/dist/core/modules/hub/core-hub-actions.controller.js.map +1 -0
- package/dist/core/modules/hub/core-hub-html.service.d.ts +8 -0
- package/dist/core/modules/hub/core-hub-html.service.js +123 -0
- package/dist/core/modules/hub/core-hub-html.service.js.map +1 -0
- package/dist/core/modules/hub/core-hub.controller.d.ts +56 -0
- package/dist/core/modules/hub/core-hub.controller.js +398 -0
- package/dist/core/modules/hub/core-hub.controller.js.map +1 -0
- package/dist/core/modules/hub/core-hub.module.d.ts +17 -0
- package/dist/core/modules/hub/core-hub.module.js +109 -0
- package/dist/core/modules/hub/core-hub.module.js.map +1 -0
- package/dist/core/modules/hub/core-hub.service.d.ts +18 -0
- package/dist/core/modules/hub/core-hub.service.js +153 -0
- package/dist/core/modules/hub/core-hub.service.js.map +1 -0
- package/dist/core/modules/hub/helpers/hub-client-js.helper.d.ts +1 -0
- package/dist/core/modules/hub/helpers/hub-client-js.helper.js +755 -0
- package/dist/core/modules/hub/helpers/hub-client-js.helper.js.map +1 -0
- package/dist/core/modules/hub/helpers/hub-command-shape.helper.d.ts +1 -0
- package/dist/core/modules/hub/helpers/hub-command-shape.helper.js +33 -0
- package/dist/core/modules/hub/helpers/hub-command-shape.helper.js.map +1 -0
- package/dist/core/modules/hub/helpers/hub-mask.helper.d.ts +1 -0
- package/dist/core/modules/hub/helpers/hub-mask.helper.js +46 -0
- package/dist/core/modules/hub/helpers/hub-mask.helper.js.map +1 -0
- package/dist/core/modules/hub/helpers/hub-mermaid.helper.d.ts +10 -0
- package/dist/core/modules/hub/helpers/hub-mermaid.helper.js +35 -0
- package/dist/core/modules/hub/helpers/hub-mermaid.helper.js.map +1 -0
- package/dist/core/modules/hub/helpers/hub-shell.helper.d.ts +9 -0
- package/dist/core/modules/hub/helpers/hub-shell.helper.js +62 -0
- package/dist/core/modules/hub/helpers/hub-shell.helper.js.map +1 -0
- package/dist/core/modules/hub/hub-action-messages.d.ts +17 -0
- package/dist/core/modules/hub/hub-action-messages.js +21 -0
- package/dist/core/modules/hub/hub-action-messages.js.map +1 -0
- package/dist/core/modules/hub/hub-config.helper.d.ts +11 -0
- package/dist/core/modules/hub/hub-config.helper.js +164 -0
- package/dist/core/modules/hub/hub-config.helper.js.map +1 -0
- package/dist/core/modules/hub/hub-nav.d.ts +12 -0
- package/dist/core/modules/hub/hub-nav.js +43 -0
- package/dist/core/modules/hub/hub-nav.js.map +1 -0
- package/dist/core/modules/hub/hub-ring-buffer.d.ts +22 -0
- package/dist/core/modules/hub/hub-ring-buffer.js +59 -0
- package/dist/core/modules/hub/hub-ring-buffer.js.map +1 -0
- package/dist/core/modules/hub/hub.constants.d.ts +18 -0
- package/dist/core/modules/hub/hub.constants.js +22 -0
- package/dist/core/modules/hub/hub.constants.js.map +1 -0
- package/dist/core/modules/hub/index.d.ts +25 -0
- package/dist/core/modules/hub/index.js +42 -0
- package/dist/core/modules/hub/index.js.map +1 -0
- package/dist/core/modules/hub/interfaces/hub-config.interface.d.ts +115 -0
- package/dist/core/modules/hub/interfaces/hub-config.interface.js +3 -0
- package/dist/core/modules/hub/interfaces/hub-config.interface.js.map +1 -0
- package/dist/core/modules/hub/interfaces/hub-panels.interface.d.ts +231 -0
- package/dist/core/modules/hub/interfaces/hub-panels.interface.js +3 -0
- package/dist/core/modules/hub/interfaces/hub-panels.interface.js.map +1 -0
- package/dist/core/modules/hub/middleware/hub-trace.middleware.d.ts +8 -0
- package/dist/core/modules/hub/middleware/hub-trace.middleware.js +50 -0
- package/dist/core/modules/hub/middleware/hub-trace.middleware.js.map +1 -0
- package/dist/core/modules/hub/services/core-hub-actions.service.d.ts +48 -0
- package/dist/core/modules/hub/services/core-hub-actions.service.js +136 -0
- package/dist/core/modules/hub/services/core-hub-actions.service.js.map +1 -0
- package/dist/core/modules/hub/services/core-hub-db.service.d.ts +19 -0
- package/dist/core/modules/hub/services/core-hub-db.service.js +180 -0
- package/dist/core/modules/hub/services/core-hub-db.service.js.map +1 -0
- package/dist/core/modules/hub/services/core-hub-email.service.d.ts +24 -0
- package/dist/core/modules/hub/services/core-hub-email.service.js +148 -0
- package/dist/core/modules/hub/services/core-hub-email.service.js.map +1 -0
- package/dist/core/modules/hub/services/core-hub-mailbox.service.d.ts +20 -0
- package/dist/core/modules/hub/services/core-hub-mailbox.service.js +103 -0
- package/dist/core/modules/hub/services/core-hub-mailbox.service.js.map +1 -0
- package/dist/core/modules/hub/services/core-hub-migrations.service.d.ts +25 -0
- package/dist/core/modules/hub/services/core-hub-migrations.service.js +115 -0
- package/dist/core/modules/hub/services/core-hub-migrations.service.js.map +1 -0
- package/dist/core/modules/hub/services/core-hub-sources.service.d.ts +28 -0
- package/dist/core/modules/hub/services/core-hub-sources.service.js +187 -0
- package/dist/core/modules/hub/services/core-hub-sources.service.js.map +1 -0
- package/dist/core/modules/hub/services/hub-log-buffer.service.d.ts +24 -0
- package/dist/core/modules/hub/services/hub-log-buffer.service.js +210 -0
- package/dist/core/modules/hub/services/hub-log-buffer.service.js.map +1 -0
- package/dist/core/modules/hub/services/hub-query-profiler.service.d.ts +38 -0
- package/dist/core/modules/hub/services/hub-query-profiler.service.js +235 -0
- package/dist/core/modules/hub/services/hub-query-profiler.service.js.map +1 -0
- package/dist/core/modules/hub/services/hub-trace-buffer.service.d.ts +18 -0
- package/dist/core/modules/hub/services/hub-trace-buffer.service.js +123 -0
- package/dist/core/modules/hub/services/hub-trace-buffer.service.js.map +1 -0
- package/dist/core.module.js +42 -1
- package/dist/core.module.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/main.js +5 -2
- package/dist/main.js.map +1 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/docs/REQUEST-LIFECYCLE.md +2 -0
- package/docs/brevo-manual-test.md +166 -0
- package/docs/security-overrides.md +90 -0
- package/migration-guides/11.31.2-to-11.31.3.md +135 -0
- package/migration-guides/11.31.3-to-11.32.0.md +254 -0
- package/package.json +16 -14
- package/src/config.env.ts +75 -2
- package/src/core/common/helpers/logging.helper.spec.ts +61 -0
- package/src/core/common/helpers/logging.helper.ts +48 -0
- package/src/core/common/helpers/meta.helper.ts +46 -1
- package/src/core/common/helpers/process-diagnostics.helper.spec.ts +310 -0
- package/src/core/common/helpers/process-diagnostics.helper.ts +321 -0
- package/src/core/common/interfaces/server-options.interface.ts +78 -0
- package/src/core/common/services/brevo.service.spec.ts +266 -0
- package/src/core/common/services/brevo.service.ts +100 -17
- package/src/core/common/services/email.service.ts +33 -1
- package/src/core/common/services/template.service.ts +21 -16
- package/src/core/modules/better-auth/core-better-auth-email-verification.service.ts +14 -4
- package/src/core/modules/hub/INTEGRATION-CHECKLIST.md +64 -0
- package/src/core/modules/hub/README.md +159 -0
- package/src/core/modules/hub/core-hub-actions.controller.ts +137 -0
- package/src/core/modules/hub/core-hub-html.service.ts +135 -0
- package/src/core/modules/hub/core-hub.controller.ts +286 -0
- package/src/core/modules/hub/core-hub.module.spec.ts +108 -0
- package/src/core/modules/hub/core-hub.module.ts +159 -0
- package/src/core/modules/hub/core-hub.service.ts +169 -0
- package/src/core/modules/hub/helpers/hub-client-js.helper.ts +768 -0
- package/src/core/modules/hub/helpers/hub-command-shape.helper.spec.ts +48 -0
- package/src/core/modules/hub/helpers/hub-command-shape.helper.ts +47 -0
- package/src/core/modules/hub/helpers/hub-mask.helper.spec.ts +67 -0
- package/src/core/modules/hub/helpers/hub-mask.helper.ts +78 -0
- package/src/core/modules/hub/helpers/hub-mermaid.helper.spec.ts +54 -0
- package/src/core/modules/hub/helpers/hub-mermaid.helper.ts +62 -0
- package/src/core/modules/hub/helpers/hub-shell.helper.spec.ts +106 -0
- package/src/core/modules/hub/helpers/hub-shell.helper.ts +90 -0
- package/src/core/modules/hub/hub-action-messages.ts +47 -0
- package/src/core/modules/hub/hub-config.helper.spec.ts +108 -0
- package/src/core/modules/hub/hub-config.helper.ts +233 -0
- package/src/core/modules/hub/hub-nav.ts +66 -0
- package/src/core/modules/hub/hub-ring-buffer.spec.ts +95 -0
- package/src/core/modules/hub/hub-ring-buffer.ts +101 -0
- package/src/core/modules/hub/hub.constants.ts +84 -0
- package/src/core/modules/hub/index.ts +25 -0
- package/src/core/modules/hub/interfaces/hub-config.interface.ts +265 -0
- package/src/core/modules/hub/interfaces/hub-panels.interface.ts +186 -0
- package/src/core/modules/hub/middleware/hub-trace.middleware.ts +45 -0
- package/src/core/modules/hub/services/core-hub-actions.service.ts +133 -0
- package/src/core/modules/hub/services/core-hub-db.service.ts +185 -0
- package/src/core/modules/hub/services/core-hub-email.service.ts +158 -0
- package/src/core/modules/hub/services/core-hub-mailbox.service.spec.ts +116 -0
- package/src/core/modules/hub/services/core-hub-mailbox.service.ts +121 -0
- package/src/core/modules/hub/services/core-hub-migrations.service.ts +112 -0
- package/src/core/modules/hub/services/core-hub-sources.service.ts +194 -0
- package/src/core/modules/hub/services/hub-log-buffer.service.ts +252 -0
- package/src/core/modules/hub/services/hub-query-profiler.service.ts +274 -0
- package/src/core/modules/hub/services/hub-trace-buffer.service.spec.ts +112 -0
- package/src/core/modules/hub/services/hub-trace-buffer.service.ts +134 -0
- package/src/core.module.ts +61 -1
- package/src/index.ts +7 -0
- package/src/main.ts +22 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lenne.tech/nest-server",
|
|
3
|
-
"version": "11.
|
|
3
|
+
"version": "11.32.0",
|
|
4
4
|
"description": "Modern, fast, powerful Node.js web framework in TypeScript based on Nest with a GraphQL API and a connection to MongoDB (or other databases).",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"node",
|
|
@@ -23,20 +23,21 @@
|
|
|
23
23
|
"build:dev": "pnpm run build",
|
|
24
24
|
"c": "pnpm run check",
|
|
25
25
|
"check": "node scripts/check.mjs",
|
|
26
|
-
"check:raw": "pnpm install --frozen-lockfile && pnpm audit && pnpm run format:check && pnpm run lint && pnpm run check:swc-tdz && pnpm test && pnpm run build && pnpm run check:manifest && bash scripts/check-server-start.sh",
|
|
27
|
-
"check:fix": "pnpm install && pnpm audit --fix && pnpm run format && pnpm run lint:fix && pnpm run check:swc-tdz && pnpm test && pnpm run build && pnpm run check:manifest && bash scripts/check-server-start.sh",
|
|
28
|
-
"check:naf": "pnpm install && pnpm run format && pnpm run lint:fix && pnpm run check:swc-tdz && pnpm test && pnpm run build && pnpm run check:manifest && bash scripts/check-server-start.sh",
|
|
26
|
+
"check:raw": "pnpm install --frozen-lockfile && pnpm run spectaql:sync && pnpm audit && pnpm run format:check && pnpm run lint && pnpm run typecheck:tests && pnpm run check:swc-tdz && pnpm test && pnpm run build && pnpm run check:manifest && bash scripts/check-server-start.sh",
|
|
27
|
+
"check:fix": "pnpm install && pnpm run spectaql:sync && pnpm audit --fix && pnpm run format && pnpm run lint:fix && pnpm run typecheck:tests && pnpm run check:swc-tdz && pnpm test && pnpm run build && pnpm run check:manifest && bash scripts/check-server-start.sh",
|
|
28
|
+
"check:naf": "pnpm install && pnpm run spectaql:sync && pnpm run format && pnpm run lint:fix && pnpm run typecheck:tests && pnpm run check:swc-tdz && pnpm test && pnpm run build && pnpm run check:manifest && bash scripts/check-server-start.sh",
|
|
29
29
|
"check:manifest": "node scripts/check-package-manifest.mjs",
|
|
30
30
|
"check:swc-tdz": "nest build -b swc -p tsconfig.swc-tdz.json && node scripts/check-swc-tdz.mjs",
|
|
31
|
+
"spectaql:sync": "node extras/update-spectaql-version.mjs",
|
|
31
32
|
"cf": "pnpm run check:fix",
|
|
32
33
|
"cnaf": "pnpm run check:naf",
|
|
33
34
|
"docs": "pnpm run docs:ci && open http://127.0.0.1:8080/ && open ./public/index.html && compodoc -p tsconfig.json -s ",
|
|
34
35
|
"docs:bootstrap": "node extras/update-spectaql-version.mjs && node scripts/run-spectaql.mjs",
|
|
35
36
|
"docs:ci": "ts-node ./scripts/init-server.ts && pnpm run docs:bootstrap && compodoc -p tsconfig.json",
|
|
36
|
-
"format": "oxfmt --write src/",
|
|
37
|
-
"format:check": "oxfmt --check src/",
|
|
38
|
-
"lint": "oxlint --ignore-path .oxlintignore src/ tests/",
|
|
39
|
-
"lint:fix": "oxlint --fix --fix-suggestions --ignore-path .oxlintignore src/ tests/",
|
|
37
|
+
"format": "oxfmt --write src/ scripts/",
|
|
38
|
+
"format:check": "oxfmt --check src/ scripts/",
|
|
39
|
+
"lint": "oxlint --ignore-path .oxlintignore src/ tests/ scripts/",
|
|
40
|
+
"lint:fix": "oxlint --fix --fix-suggestions --ignore-path .oxlintignore src/ tests/ scripts/",
|
|
40
41
|
"prestart:prod": "pnpm run build",
|
|
41
42
|
"reinit": "rimraf pnpm-lock.yaml && rimraf node_modules && pnpm install && pnpm run lint && pnpm run test:e2e && pnpm run test:ci && pnpm run build",
|
|
42
43
|
"reinit:clean": "rimraf pnpm-lock.yaml && rimraf node_modules && pnpm store prune && pnpm install && pnpm run test:e2e && pnpm run build",
|
|
@@ -65,6 +66,7 @@
|
|
|
65
66
|
"vitest:unit:cov": "vitest run --coverage --config vitest.config.ts",
|
|
66
67
|
"test:unit:watch": "vitest --config vitest.config.ts",
|
|
67
68
|
"test:types": "tsc --noEmit --skipLibCheck -p tests/types/tsconfig.json",
|
|
69
|
+
"typecheck:tests": "tsc --noEmit -p tsconfig.tests.json",
|
|
68
70
|
"test:cleanup": "find tests -type f \\( -name '*.txt' -o -name '*.bin' \\) -not -name '.gitkeep' -delete && echo 'Test artifacts cleaned up'",
|
|
69
71
|
"watch": "npm-watch"
|
|
70
72
|
},
|
|
@@ -84,7 +86,7 @@
|
|
|
84
86
|
"@apollo/server": "5.5.1",
|
|
85
87
|
"@as-integrations/express5": "1.1.2",
|
|
86
88
|
"@better-auth/passkey": "1.6.23",
|
|
87
|
-
"@getbrevo/brevo": "
|
|
89
|
+
"@getbrevo/brevo": "6.0.2",
|
|
88
90
|
"@modelcontextprotocol/sdk": "1.29.0",
|
|
89
91
|
"@nestjs/apollo": "13.4.2",
|
|
90
92
|
"@nestjs/common": "11.1.28",
|
|
@@ -98,7 +100,7 @@
|
|
|
98
100
|
"@nestjs/swagger": "11.4.6",
|
|
99
101
|
"@nestjs/terminus": "11.1.1",
|
|
100
102
|
"@tus/file-store": "2.1.0",
|
|
101
|
-
"@tus/server": "2.4.
|
|
103
|
+
"@tus/server": "2.4.2",
|
|
102
104
|
"@types/supertest": "7.2.1",
|
|
103
105
|
"bcrypt": "6.0.0",
|
|
104
106
|
"better-auth": "1.6.23",
|
|
@@ -116,11 +118,11 @@
|
|
|
116
118
|
"graphql-upload": "15.0.2",
|
|
117
119
|
"graphql-ws": "6.1.0",
|
|
118
120
|
"jose": "6.2.3",
|
|
119
|
-
"js-sha256": "0.
|
|
121
|
+
"js-sha256": "0.12.0",
|
|
120
122
|
"json-to-graphql-query": "2.3.0",
|
|
121
123
|
"lodash": "4.18.1",
|
|
122
|
-
"mongodb": "7.
|
|
123
|
-
"mongoose": "9.
|
|
124
|
+
"mongodb": "7.5.0",
|
|
125
|
+
"mongoose": "9.8.0",
|
|
124
126
|
"multer": "2.2.0",
|
|
125
127
|
"node-mailjet": "6.0.11",
|
|
126
128
|
"nodemailer": "9.0.3",
|
|
@@ -140,7 +142,7 @@
|
|
|
140
142
|
"@nestjs/schematics": "11.1.0",
|
|
141
143
|
"@nestjs/testing": "11.1.28",
|
|
142
144
|
"@swc/cli": "0.8.1",
|
|
143
|
-
"@swc/core": "1.15.
|
|
145
|
+
"@swc/core": "1.15.46",
|
|
144
146
|
"@types/compression": "1.8.1",
|
|
145
147
|
"@types/cookie-parser": "1.4.10",
|
|
146
148
|
"@types/ejs": "3.1.5",
|
package/src/config.env.ts
CHANGED
|
@@ -20,6 +20,7 @@ import { IServerOptions } from './core/common/interfaces/server-options.interfac
|
|
|
20
20
|
// which is purely cosmetic and carries promotional content. Warnings (`⚠`) for genuine
|
|
21
21
|
// misconfiguration still surface.
|
|
22
22
|
dotenv.config({ quiet: true });
|
|
23
|
+
|
|
23
24
|
const config: { [env: string]: IServerOptions } = {
|
|
24
25
|
// ===========================================================================
|
|
25
26
|
// CI environment
|
|
@@ -141,6 +142,10 @@ const config: { [env: string]: IServerOptions } = {
|
|
|
141
142
|
modelDocumentation: false,
|
|
142
143
|
uri: process.env.MONGODB_URI || 'mongodb://127.0.0.1/nest-server-ci',
|
|
143
144
|
},
|
|
145
|
+
hub: {
|
|
146
|
+
collectors: { queries: { warnMs: 1 } },
|
|
147
|
+
mailbox: { mode: 'capture' },
|
|
148
|
+
},
|
|
144
149
|
permissions: true,
|
|
145
150
|
port: Number(process.env.PORT) || 3000,
|
|
146
151
|
security: {
|
|
@@ -260,6 +265,14 @@ const config: { [env: string]: IServerOptions } = {
|
|
|
260
265
|
modelDocumentation: false,
|
|
261
266
|
uri: process.env.MONGODB_URI || 'mongodb://127.0.0.1/nest-server-dev',
|
|
262
267
|
},
|
|
268
|
+
// Hub (operator cockpit) — OPTIONAL and enabled PER ENVIRONMENT (it is never on implicitly).
|
|
269
|
+
// `queries` (query profiler) and `mailbox` are opt-in; the whole thing stays ADMIN-gated.
|
|
270
|
+
// See src/core/modules/hub/README.md. In production it is off by default (see that block for how
|
|
271
|
+
// to enable it safely). Also toggleable via `NSC__HUB__*` env vars (see .env.example).
|
|
272
|
+
hub: {
|
|
273
|
+
collectors: { queries: true },
|
|
274
|
+
mailbox: { mode: 'capture' },
|
|
275
|
+
},
|
|
263
276
|
permissions: true,
|
|
264
277
|
port: Number(process.env.PORT) || 3000,
|
|
265
278
|
security: {
|
|
@@ -405,6 +418,10 @@ const config: { [env: string]: IServerOptions } = {
|
|
|
405
418
|
modelDocumentation: false,
|
|
406
419
|
uri: process.env.MONGODB_URI || 'mongodb://127.0.0.1/nest-server-e2e',
|
|
407
420
|
},
|
|
421
|
+
hub: {
|
|
422
|
+
collectors: { queries: { warnMs: 1 } },
|
|
423
|
+
mailbox: { mode: 'capture' },
|
|
424
|
+
},
|
|
408
425
|
permissions: true,
|
|
409
426
|
port: Number(process.env.PORT) || 3000,
|
|
410
427
|
security: {
|
|
@@ -559,9 +576,14 @@ const config: { [env: string]: IServerOptions } = {
|
|
|
559
576
|
modelDocumentation: true,
|
|
560
577
|
uri: process.env.MONGODB_URI || 'mongodb://127.0.0.1/nest-server-local',
|
|
561
578
|
},
|
|
562
|
-
|
|
563
|
-
|
|
579
|
+
hub: {
|
|
580
|
+
collectors: { queries: true },
|
|
581
|
+
mailbox: { mode: 'capture' },
|
|
564
582
|
},
|
|
583
|
+
// Authored intent: ADMIN-gated (reach it via the Hub's "Routes / Permissions" panel). The
|
|
584
|
+
// permissions-follows-hub invariant is enforced centrally by enforcePermissionsSecurity() below —
|
|
585
|
+
// e.g. if the Hub here is disabled, that pass flips this to a public localhost-only report.
|
|
586
|
+
permissions: true,
|
|
565
587
|
port: Number(process.env.PORT) || 3000,
|
|
566
588
|
security: {
|
|
567
589
|
checkResponseInterceptor: {
|
|
@@ -665,6 +687,18 @@ const config: { [env: string]: IServerOptions } = {
|
|
|
665
687
|
},
|
|
666
688
|
enabled: true,
|
|
667
689
|
},
|
|
690
|
+
// Hub (operator cockpit) is OFF in production by default (never enabled implicitly). To turn it
|
|
691
|
+
// on here — it stays ADMIN-gated — uncomment the block below. Note the production guard-rails:
|
|
692
|
+
// - mailbox: NEVER 'capture' in production/staging (it suppresses real mail; it throws at
|
|
693
|
+
// startup). Use `false`, or `{ mode: 'copy' }` if you want a redacted audit copy.
|
|
694
|
+
// - collectors.queries: opt-in (it opts the MongoDB driver into command monitoring).
|
|
695
|
+
// The standalone permissions report follows the Hub (see enforcePermissionsSecurity below), so
|
|
696
|
+
// enabling the Hub also makes the ADMIN-gated "Routes / Permissions" panel available.
|
|
697
|
+
// See src/core/modules/hub/README.md; also configurable via NSC__HUB__* (see .env.example).
|
|
698
|
+
// hub: {
|
|
699
|
+
// collectors: { queries: false },
|
|
700
|
+
// mailbox: false,
|
|
701
|
+
// },
|
|
668
702
|
ignoreSelectionsForPopulate: true,
|
|
669
703
|
jwt: {
|
|
670
704
|
refresh: {
|
|
@@ -716,6 +750,45 @@ const config: { [env: string]: IServerOptions } = {
|
|
|
716
750
|
},
|
|
717
751
|
};
|
|
718
752
|
|
|
753
|
+
/**
|
|
754
|
+
* Enforce the permissions-report security invariant across every environment.
|
|
755
|
+
*
|
|
756
|
+
* The report is the full authorization map (every route + its required roles + `@Restricted` field
|
|
757
|
+
* rules) — a reconnaissance goldmine. The Hub does NOT need it (its "Routes / Permissions" panel
|
|
758
|
+
* reuses the scanner but degrades gracefully when the module is absent), so the rule is about the
|
|
759
|
+
* DANGEROUS direction: the report must never become a standalone, reachable surface.
|
|
760
|
+
*
|
|
761
|
+
* Derived per environment from THAT env's Hub state:
|
|
762
|
+
* - Hub OFF, non-`local` → report NOT registered (no in-app consumer → pure attack surface). This
|
|
763
|
+
* makes the combination "non-local + Hub off + visible permissions" impossible to configure.
|
|
764
|
+
* - Hub OFF, `local` → public (`role: false`) report allowed — frictionless, safe ONLY because
|
|
765
|
+
* `local` is a non-reachable localhost env with no admin surface.
|
|
766
|
+
* - Hub ON → left as authored (ADMIN when enabled, or off — the panel just degrades),
|
|
767
|
+
* EXCEPT a public (`role: false`) report is forced back to ADMIN in every non-`local` stage.
|
|
768
|
+
*
|
|
769
|
+
* NOTE: runs on the statically-authored config; `NSC__HUB__*` / `NSC__PERMISSIONS__*` env overrides
|
|
770
|
+
* are applied afterwards by getEnvironmentConfig and take precedence.
|
|
771
|
+
*/
|
|
772
|
+
export function enforcePermissionsSecurity(cfg: { [env: string]: IServerOptions }): void {
|
|
773
|
+
for (const [envName, opts] of Object.entries(cfg)) {
|
|
774
|
+
const hub = opts.hub;
|
|
775
|
+
const hubOn =
|
|
776
|
+
hub === true || (typeof hub === 'object' && hub !== null && (hub as { enabled?: boolean }).enabled !== false);
|
|
777
|
+
const perms = opts.permissions;
|
|
778
|
+
const isPublic = typeof perms === 'object' && perms !== null && (perms as { role?: unknown }).role === false;
|
|
779
|
+
|
|
780
|
+
if (!hubOn) {
|
|
781
|
+
// Hub off: only a public localhost report (in `local`) is allowed; never register it elsewhere.
|
|
782
|
+
opts.permissions = envName === 'local' ? { role: false } : false;
|
|
783
|
+
} else if (envName !== 'local' && isPublic) {
|
|
784
|
+
// Reachable stage with the Hub on: the report must never be public — force the ADMIN gate.
|
|
785
|
+
opts.permissions = true;
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
enforcePermissionsSecurity(config);
|
|
791
|
+
|
|
719
792
|
/**
|
|
720
793
|
* Export config merged with other configs and environment variables as default
|
|
721
794
|
*/
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { redactSensitiveText } from './logging.helper';
|
|
4
|
+
|
|
5
|
+
describe('redactSensitiveText', () => {
|
|
6
|
+
it('redacts JWTs appearing anywhere in a line', () => {
|
|
7
|
+
const jwt = 'eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.dozjgNryP4J3jVmNHl0w5N_XgL0n3I9PlFUP0THsR8U';
|
|
8
|
+
const out = redactSensitiveText(`token issued: ${jwt} done`);
|
|
9
|
+
|
|
10
|
+
expect(out).not.toContain(jwt);
|
|
11
|
+
expect(out).toContain('token issued:');
|
|
12
|
+
expect(out).toContain('done');
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it('redacts Authorization Bearer values', () => {
|
|
16
|
+
const out = redactSensitiveText('Authorization: Bearer abcdef1234567890secret');
|
|
17
|
+
|
|
18
|
+
expect(out).not.toContain('abcdef1234567890secret');
|
|
19
|
+
expect(out.toLowerCase()).toContain('authorization');
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('redacts key/value secrets regardless of separator or quoting', () => {
|
|
23
|
+
expect(redactSensitiveText('password=SuperSecret123')).not.toContain('SuperSecret123');
|
|
24
|
+
expect(redactSensitiveText('token: "abcd1234efgh"')).not.toContain('abcd1234efgh');
|
|
25
|
+
expect(redactSensitiveText("apiKey='pk_live_9999'")).not.toContain('pk_live_9999');
|
|
26
|
+
expect(redactSensitiveText('client_secret=verySecretValue')).not.toContain('verySecretValue');
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('redacts cookie headers', () => {
|
|
30
|
+
const out = redactSensitiveText('cookie: session=abc123; other=xyz789');
|
|
31
|
+
|
|
32
|
+
expect(out).not.toContain('abc123');
|
|
33
|
+
expect(out).not.toContain('xyz789');
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('redacts reset/verification tokens carried as a URL PATH segment (not key=value)', () => {
|
|
37
|
+
const link = 'https://app.example.com/verify/AbCdEf0123456789XyZtoken please click';
|
|
38
|
+
const out = redactSensitiveText(link);
|
|
39
|
+
expect(out).not.toContain('AbCdEf0123456789XyZtoken');
|
|
40
|
+
expect(out).toContain('/verify/');
|
|
41
|
+
// also for reset / set-password style links
|
|
42
|
+
expect(redactSensitiveText('/reset/SUPERSECRETVALUE1234567890')).not.toContain('SUPERSECRETVALUE1234567890');
|
|
43
|
+
expect(redactSensitiveText('/set-password/ZZZ1112223334445556667')).not.toContain('ZZZ1112223334445556667');
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('leaves harmless short path segments untouched', () => {
|
|
47
|
+
// Only long token-like segments (>= 16 chars) are masked — normal routes stay readable.
|
|
48
|
+
const text = 'GET /verify/email done';
|
|
49
|
+
expect(redactSensitiveText(text)).toBe(text);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('leaves harmless text untouched', () => {
|
|
53
|
+
const text = 'User signed in successfully with id 507f1f77bcf86cd799439011';
|
|
54
|
+
expect(redactSensitiveText(text)).toBe(text);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('handles empty / non-secret input without throwing', () => {
|
|
58
|
+
expect(redactSensitiveText('')).toBe('');
|
|
59
|
+
expect(redactSensitiveText('plain message')).toBe('plain message');
|
|
60
|
+
});
|
|
61
|
+
});
|
|
@@ -132,3 +132,51 @@ export function maskToken(token: null | string | undefined): string {
|
|
|
132
132
|
}
|
|
133
133
|
return `${token.substring(0, 4)}...${token.substring(token.length - 4)}`;
|
|
134
134
|
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Redacts secrets from a free-form log line.
|
|
138
|
+
*
|
|
139
|
+
* Composes the individual maskers above into a single regex-based pass for text where the sensitive
|
|
140
|
+
* value is not at a known position (log messages, error strings). Applied by the Hub log collector
|
|
141
|
+
* before truncation, so a truncation cut can never leak the prefix of a secret.
|
|
142
|
+
*
|
|
143
|
+
* Covers: JWTs anywhere in the line, `Authorization: Bearer …`, common `key=value` secrets
|
|
144
|
+
* (password/token/secret/apiKey/client_secret/refresh_token/…), and cookie headers.
|
|
145
|
+
*
|
|
146
|
+
* @param text - The log line to redact
|
|
147
|
+
* @returns The line with secret values replaced by masked placeholders
|
|
148
|
+
*
|
|
149
|
+
* @example
|
|
150
|
+
* redactSensitiveText('password=hunter2') // 'password=***'
|
|
151
|
+
*/
|
|
152
|
+
export function redactSensitiveText(text: string): string {
|
|
153
|
+
if (!text) {
|
|
154
|
+
return text;
|
|
155
|
+
}
|
|
156
|
+
return (
|
|
157
|
+
text
|
|
158
|
+
// JWTs anywhere in the line
|
|
159
|
+
.replace(/eyJ[\w-]{6,}\.[\w-]{6,}\.[\w-]{4,}/g, (match) => maskToken(match))
|
|
160
|
+
// reset/verification/invite tokens carried as a URL PATH segment (e.g. /verify/<token>,
|
|
161
|
+
// /reset/<token>) — these have no `key=value`, so the query-style rule below misses them.
|
|
162
|
+
.replace(
|
|
163
|
+
/(\/(?:verify|reset|confirm|activate|invite|magic-?link|set-password|change-email)\/)([A-Za-z0-9._~-]{16,})/gi,
|
|
164
|
+
(_m, prefix, token) => `${prefix}${maskToken(token)}`,
|
|
165
|
+
)
|
|
166
|
+
// authorization: Bearer xyz / Authorization=xyz
|
|
167
|
+
.replace(
|
|
168
|
+
/(authorization["']?\s*[:=]\s*["']?)(?:Bearer\s+)?([^\s"',;]+)/gi,
|
|
169
|
+
(_m, prefix, value) => `${prefix}${maskToken(value)}`,
|
|
170
|
+
)
|
|
171
|
+
// cookie headers (mask every value, keep names)
|
|
172
|
+
.replace(
|
|
173
|
+
/((?:^|[\s,;])cookie["']?\s*[:=]\s*)([^\n]+)/gi,
|
|
174
|
+
(_m, prefix, value) => `${prefix}${maskCookieHeader(value)}`,
|
|
175
|
+
)
|
|
176
|
+
// key/value secrets: password=..., token: "...", apiKey=..., secret=..., client_secret=..., refresh_token=...
|
|
177
|
+
.replace(
|
|
178
|
+
/((?:password|passwd|pwd|token|secret|api[-_]?key|client[-_]?secret|refresh[-_]?token|access[-_]?token|private[-_]?key)["']?\s*[:=]\s*["']?)([^\s"',;&}]+)/gi,
|
|
179
|
+
(_m, prefix, value) => `${prefix}${maskSensitive(value)}`,
|
|
180
|
+
)
|
|
181
|
+
);
|
|
182
|
+
}
|
|
@@ -15,12 +15,18 @@
|
|
|
15
15
|
* @see getBuildInfo
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
|
+
import { existsSync, readFileSync } from 'fs';
|
|
19
|
+
import { join } from 'path';
|
|
20
|
+
|
|
18
21
|
/** Default environment variable the build commit SHA is baked into. */
|
|
19
22
|
export const DEFAULT_COMMIT_ENV = 'APP_VERSION_COMMIT';
|
|
20
23
|
|
|
21
24
|
/** Defined value returned when no commit could be resolved. */
|
|
22
25
|
export const UNKNOWN_COMMIT = 'unknown';
|
|
23
26
|
|
|
27
|
+
/** Cached package.json version lookup (`null` = looked up and not found). */
|
|
28
|
+
let cachedVersion: null | string | undefined;
|
|
29
|
+
|
|
24
30
|
/**
|
|
25
31
|
* Build identity of the running process.
|
|
26
32
|
*/
|
|
@@ -64,6 +70,45 @@ export function getBuildInfo(options: { commitEnvName?: string; env?: string; ve
|
|
|
64
70
|
return {
|
|
65
71
|
commit: getCommit(options.commitEnvName),
|
|
66
72
|
env: options.env,
|
|
67
|
-
version: options.version ||
|
|
73
|
+
version: options.version || getVersion(),
|
|
68
74
|
};
|
|
69
75
|
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Resolve the semantic version of the running app from its `package.json`.
|
|
79
|
+
*
|
|
80
|
+
* Reads the `version` field of the nearest `package.json` (searching up from the current working
|
|
81
|
+
* directory), cached after the first lookup. This is the deployed app's own version — in a consumer
|
|
82
|
+
* project that is the API's version, in this repo it is the framework version. Falls back to
|
|
83
|
+
* `'unknown'` when no `package.json` / version can be resolved.
|
|
84
|
+
*
|
|
85
|
+
* Prefer an explicit `IServerOptions.version` when set; this is the zero-config default.
|
|
86
|
+
*/
|
|
87
|
+
export function getVersion(): string {
|
|
88
|
+
if (cachedVersion !== undefined) {
|
|
89
|
+
return cachedVersion ?? UNKNOWN_COMMIT;
|
|
90
|
+
}
|
|
91
|
+
cachedVersion = null;
|
|
92
|
+
try {
|
|
93
|
+
// Walk up from cwd so a monorepo `projects/api` started from the repo root still resolves.
|
|
94
|
+
let dir = process.cwd();
|
|
95
|
+
for (let i = 0; i < 6; i++) {
|
|
96
|
+
const pkgPath = join(dir, 'package.json');
|
|
97
|
+
if (existsSync(pkgPath)) {
|
|
98
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf8')) as { version?: string };
|
|
99
|
+
if (typeof pkg.version === 'string' && pkg.version) {
|
|
100
|
+
cachedVersion = pkg.version;
|
|
101
|
+
break;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
const parent = join(dir, '..');
|
|
105
|
+
if (parent === dir) {
|
|
106
|
+
break;
|
|
107
|
+
}
|
|
108
|
+
dir = parent;
|
|
109
|
+
}
|
|
110
|
+
} catch {
|
|
111
|
+
/* ignore — fall back to 'unknown' */
|
|
112
|
+
}
|
|
113
|
+
return cachedVersion ?? UNKNOWN_COMMIT;
|
|
114
|
+
}
|