@lenne.tech/nest-server 11.31.1 → 11.31.3
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.md +28 -1
- package/FRAMEWORK-API.md +4 -1
- 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/interfaces/server-options.interface.d.ts +9 -0
- 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/ai/core-ai.controller.d.ts +1 -1
- package/dist/core/modules/ai/core-ai.controller.js +4 -5
- package/dist/core/modules/ai/core-ai.controller.js.map +1 -1
- package/dist/core/modules/ai/core-ai.resolver.d.ts +1 -1
- package/dist/core/modules/ai/core-ai.resolver.js +7 -6
- package/dist/core/modules/ai/core-ai.resolver.js.map +1 -1
- package/dist/core/modules/ai/models/core-ai-conversation.model.d.ts +6 -0
- package/dist/core/modules/ai/models/core-ai-conversation.model.js +13 -1
- package/dist/core/modules/ai/models/core-ai-conversation.model.js.map +1 -1
- package/dist/core/modules/ai/services/core-ai-conversation.service.d.ts +8 -2
- package/dist/core/modules/ai/services/core-ai-conversation.service.js +53 -2
- package/dist/core/modules/ai/services/core-ai-conversation.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 +1 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/docs/REQUEST-LIFECYCLE.md +1 -0
- package/migration-guides/11.31.1-to-11.31.2.md +127 -0
- package/migration-guides/11.31.2-to-11.31.3.md +135 -0
- package/package.json +5 -4
- 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/interfaces/server-options.interface.ts +46 -0
- package/src/core/common/services/email.service.ts +33 -1
- package/src/core/common/services/template.service.ts +21 -16
- package/src/core/modules/ai/core-ai.controller.ts +12 -9
- package/src/core/modules/ai/core-ai.resolver.ts +14 -9
- package/src/core/modules/ai/models/core-ai-conversation.model.ts +18 -1
- package/src/core/modules/ai/services/core-ai-conversation.service.ts +110 -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 +6 -0
|
@@ -213,6 +213,7 @@ JWT-based authentication for existing projects:
|
|
|
213
213
|
| **Health Check Module** | `GET /health` + GraphQL `healthCheck` query |
|
|
214
214
|
| **Error Code Module** | Centralized error registry with unique IDs |
|
|
215
215
|
| **Permissions Report** | Interactive HTML dashboard, JSON, and Markdown reports |
|
|
216
|
+
| **Hub (Operator Cockpit)** | Build-free ADMIN-gated dashboard at `/hub` (config-gated per environment). Adds an optional HTTP trace middleware (registered by `CoreHubModule.configure()` only when traces are enabled), a chaining `Logger.overrideLogger()` delegate for the log buffer, an optional `EmailService` capture hook (`HUB_EMAIL_CAPTURE` token) for the mailbox, and — when the query profiler is enabled — opts the MongoDB driver into `monitorCommands` from `core.module.ts`. See `src/core/modules/hub/README.md`. |
|
|
216
217
|
| **System Setup Module** | Initial admin creation for fresh deployments |
|
|
217
218
|
| **Cron Jobs** | `CoreCronJobsService` with timezone/UTC offset support |
|
|
218
219
|
| **Model Documentation** | Auto-generated model docs via `ModelDocService` |
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# Migration Guide: 11.31.1 → 11.31.2
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
| Category | Details |
|
|
6
|
+
|----------|---------|
|
|
7
|
+
| **Breaking Changes** | None (one behavior change for admins — see below) |
|
|
8
|
+
| **New Features** | Admin cross-user conversation listing (`?all=true`), creator attribution (`createdByUser`) |
|
|
9
|
+
| **Bugfixes** | Non-admins can list their own AI conversations again (was HTTP 403 / GraphQL "Access denied") |
|
|
10
|
+
| **Migration Effort** | None for most projects — no code changes required |
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## Quick Migration
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
# Update package
|
|
18
|
+
pnpm update @lenne.tech/nest-server@11.31.2
|
|
19
|
+
|
|
20
|
+
# Verify
|
|
21
|
+
pnpm run build && pnpm test
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
No configuration changes are required. Everything below is automatic (the
|
|
25
|
+
bugfix) or opt-in (the two new features).
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## What's New in 11.31.2
|
|
30
|
+
|
|
31
|
+
### 1. Non-admins can list their own AI conversations again (bugfix)
|
|
32
|
+
|
|
33
|
+
`GET /ai/conversations` and the GraphQL `findAiConversations` query returned
|
|
34
|
+
`403` / "Access denied" for every non-admin user, so their own conversations
|
|
35
|
+
never loaded. The list endpoints passed the per-document roles
|
|
36
|
+
`[ADMIN, S_CREATOR, S_SELF]`, which a LIST operation can never satisfy (there is
|
|
37
|
+
no single object to check ownership against), so non-admins were rejected before
|
|
38
|
+
ownership scoping ran.
|
|
39
|
+
|
|
40
|
+
Both endpoints now authorize the list with `S_USER` and scope the result to the
|
|
41
|
+
caller via a server-side `createdBy` filter plus the model's `securityCheck`.
|
|
42
|
+
Cross-user isolation is unchanged — a non-admin still only ever sees their own
|
|
43
|
+
conversations. **No action required**; it simply works after the update.
|
|
44
|
+
|
|
45
|
+
### 2. Admins can opt in to a cross-user view (`?all=true`)
|
|
46
|
+
|
|
47
|
+
By default every user — **admins included** — now receives only their own
|
|
48
|
+
conversations. An admin can list every user's conversations on demand:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
# REST
|
|
52
|
+
GET /ai/conversations?all=true
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
```graphql
|
|
56
|
+
# GraphQL
|
|
57
|
+
query { findAiConversations(all: true) { id title createdBy createdByUser } }
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
The flag is ignored for non-admins, so it can never widen a regular user's
|
|
61
|
+
scope. Results are ordered newest-first.
|
|
62
|
+
|
|
63
|
+
### 3. Creator attribution in the admin cross-user view (`createdByUser`)
|
|
64
|
+
|
|
65
|
+
In the admin cross-user view (`all: true`) each conversation additionally
|
|
66
|
+
carries a resolved `createdByUser` object so the list is attributable to a named
|
|
67
|
+
user:
|
|
68
|
+
|
|
69
|
+
```jsonc
|
|
70
|
+
{
|
|
71
|
+
"id": "…",
|
|
72
|
+
"title": "…",
|
|
73
|
+
"createdBy": "665f…", // owner user id (always present)
|
|
74
|
+
"createdByUser": { // only in the admin all-view
|
|
75
|
+
"id": "665f…",
|
|
76
|
+
"email": "user@example.com",
|
|
77
|
+
"firstName": "…",
|
|
78
|
+
"lastName": "…",
|
|
79
|
+
"username": "…"
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
On a normal own-only fetch `createdByUser` is left unset (the owner is the
|
|
85
|
+
caller, so no lookup is needed).
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
## Behavior Change (admins only)
|
|
90
|
+
|
|
91
|
+
Before 11.31.2, `GET /ai/conversations` returned **all** users' conversations
|
|
92
|
+
for an admin by default. It now returns the **admin's own** conversations by
|
|
93
|
+
default, matching every other role. If your admin UI relied on the unscoped
|
|
94
|
+
list, request the cross-user view explicitly with `?all=true` (REST) or
|
|
95
|
+
`all: true` (GraphQL). Non-admin behavior is unchanged.
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
## Compatibility Notes
|
|
100
|
+
|
|
101
|
+
- `getConversation` / `deleteConversation` (single-object operations) are
|
|
102
|
+
unchanged — they still use `[ADMIN, S_CREATOR, S_SELF]`, which is correct
|
|
103
|
+
because a single loaded object exists to check ownership against.
|
|
104
|
+
- Projects that extend `CoreAiConversationService` and override its constructor
|
|
105
|
+
must forward the new `@InjectConnection()` parameter to `super(...)` (used to
|
|
106
|
+
resolve creators for the admin view). Projects that do not subclass the
|
|
107
|
+
service are unaffected.
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## Module Documentation
|
|
112
|
+
|
|
113
|
+
### AI Module
|
|
114
|
+
|
|
115
|
+
- **README:** [src/core/modules/ai/README.md](../src/core/modules/ai/README.md)
|
|
116
|
+
- **Integration Checklist:** [src/core/modules/ai/INTEGRATION-CHECKLIST.md](../src/core/modules/ai/INTEGRATION-CHECKLIST.md)
|
|
117
|
+
- **Reference Implementation:** `src/server/modules/ai/`
|
|
118
|
+
- **Key Files:**
|
|
119
|
+
- `core-ai.controller.ts` / `core-ai.resolver.ts` — list endpoints delegate to the shared service method
|
|
120
|
+
- `services/core-ai-conversation.service.ts` — `findForCurrentUser()` (shared owner-scoped list + admin all-scope + creator attribution)
|
|
121
|
+
- `models/core-ai-conversation.model.ts` — `createdByUser` field, ownership `securityCheck`
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
## References
|
|
126
|
+
|
|
127
|
+
- [nest-server-starter](https://github.com/lenneTech/nest-server-starter) (reference implementation)
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
# Migration Guide: 11.31.2 → 11.31.3
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
| Category | Effort | Required? |
|
|
6
|
+
|----------|--------|-----------|
|
|
7
|
+
| **New Feature** | Opt-in | No — purely additive |
|
|
8
|
+
| Breaking Changes | None | — |
|
|
9
|
+
| Config Changes | Optional | Only if you enable the Hub |
|
|
10
|
+
|
|
11
|
+
This release adds the **Hub** — a build-free, ADMIN-gated operator cockpit served directly by the
|
|
12
|
+
framework at `/hub`. It is **disabled by default** and must be switched on per environment, so
|
|
13
|
+
existing projects are unaffected until they opt in. No code changes are required to update.
|
|
14
|
+
|
|
15
|
+
It also includes a **security patch**: three moderate `axios` advisories (recursion DoS + prototype
|
|
16
|
+
pollution, transitive via the Brevo and Mailjet mail SDKs) are closed by pinning `axios` to `1.18.1`.
|
|
17
|
+
Nothing to do on your side — just update.
|
|
18
|
+
|
|
19
|
+
## Quick Migration
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
# Update package
|
|
23
|
+
pnpm update @lenne.tech/nest-server
|
|
24
|
+
|
|
25
|
+
# Verify build
|
|
26
|
+
pnpm run build
|
|
27
|
+
|
|
28
|
+
# Run tests
|
|
29
|
+
pnpm test
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Nothing else is required — the Hub stays dormant (every `/hub` route answers 404) until you add a
|
|
33
|
+
`hub` block to a config environment.
|
|
34
|
+
|
|
35
|
+
## What's New in 11.31.3
|
|
36
|
+
|
|
37
|
+
### Hub — admin area / operator cockpit
|
|
38
|
+
|
|
39
|
+
A dependency-free dashboard of runtime information and admin tools, rendered as server-side HTML +
|
|
40
|
+
a vanilla-JS SPA (no React, no build step), so it works identically in npm- and vendor-mode.
|
|
41
|
+
|
|
42
|
+
**16 panels:** Dashboard, Diagnostics, Logs, Request Traces, Query Performance, Cron Jobs, Database,
|
|
43
|
+
Models/ERD, Migrations, Files (GridFS), Config (secrets masked), Auth Migration (Legacy→IAM), Error
|
|
44
|
+
Codes, Email Preview, Mailbox, AI. Each has a `*.json` sidecar (the stable data contract) the client
|
|
45
|
+
polls; optional sources degrade to an "unavailable" state instead of erroring.
|
|
46
|
+
|
|
47
|
+
**3 runtime collectors** (in-memory ring buffers, per-app-instance, parallel-test-safe):
|
|
48
|
+
- **Logs** — a chaining `Logger.overrideLogger()` delegate (no main.ts change), secrets redacted.
|
|
49
|
+
- **Traces** — an Express middleware bound only when the collector is enabled (zero cost otherwise).
|
|
50
|
+
- **Queries** — MongoDB driver command monitoring; records value-free query SHAPES (N+1 templates),
|
|
51
|
+
never values. Enabling it opts the driver into `monitorCommands`.
|
|
52
|
+
|
|
53
|
+
**Built-in Mailbox** — a Mailpit-style capture of outgoing mail hooked into `EmailService`. In
|
|
54
|
+
`mode: 'capture'` mail is recorded and NOT sent (dev/test); in `mode: 'copy'` it is sent AND a copy
|
|
55
|
+
is recorded (with tokens/links redacted in the stored copy). `capture` throws at startup in
|
|
56
|
+
`production`/`staging` so it can never silently swallow real mail.
|
|
57
|
+
|
|
58
|
+
**Admin actions** (opt-out via `hub.actions: false`) — migrations run/rollback, GridFS file delete,
|
|
59
|
+
cron start/stop/trigger, collector-buffer clear, test mail. Every mutating request requires the
|
|
60
|
+
`X-Hub-Request: 1` header (CSRF defense) and destructive ones a server-validated type-to-confirm
|
|
61
|
+
keyword; each writes a `[HUB-ACTION] <action> by user <id>` audit line.
|
|
62
|
+
|
|
63
|
+
#### Enable it
|
|
64
|
+
|
|
65
|
+
The Hub is **never enabled implicitly** — switch it on per environment in `config.env.ts`:
|
|
66
|
+
|
|
67
|
+
```typescript
|
|
68
|
+
// config.env.ts (development / local)
|
|
69
|
+
hub: {
|
|
70
|
+
collectors: { queries: true }, // opt-in query profiler
|
|
71
|
+
mailbox: { mode: 'capture' }, // capture outgoing mail locally (Mailpit replacement)
|
|
72
|
+
},
|
|
73
|
+
|
|
74
|
+
// production — usually omit `hub` entirely. If you do want it (still ADMIN-gated),
|
|
75
|
+
// never use mailbox capture mode there:
|
|
76
|
+
// hub: { mailbox: false },
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Boolean shorthand `hub: true` enables it at `/hub`, admin-only, with default collectors. See
|
|
80
|
+
`IHubConfig` for every option. Env-var equivalents exist via `NSC__HUB__*` (see `.env.example`).
|
|
81
|
+
|
|
82
|
+
Then sign in as a user with `RoleEnum.ADMIN` and open `/hub`. The shell is self-sufficient: if you
|
|
83
|
+
are not authenticated it shows a login form that posts to `loginEndpoint` (default
|
|
84
|
+
`/iam/sign-in/email`), so an admin can sign in directly at the API without the frontend. In a
|
|
85
|
+
fullstack setup, a cross-subdomain session cookie from the app login already authenticates `/hub`.
|
|
86
|
+
|
|
87
|
+
#### Customization (optional)
|
|
88
|
+
|
|
89
|
+
Replace any Hub controller/service with a project subclass via `CoreModule.forRoot`:
|
|
90
|
+
|
|
91
|
+
```typescript
|
|
92
|
+
CoreModule.forRoot(envConfig, {
|
|
93
|
+
hub: {
|
|
94
|
+
controller: MyHubController, // extends CoreHubController
|
|
95
|
+
actionsController: MyHubActionsController, // extends CoreHubActionsController
|
|
96
|
+
service: MyHubService, // extends CoreHubService
|
|
97
|
+
htmlService: MyHubHtmlService, // extends CoreHubHtmlService
|
|
98
|
+
actionsService: MyHubActionsService, // extends CoreHubActionsService
|
|
99
|
+
},
|
|
100
|
+
});
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Breaking Changes
|
|
104
|
+
|
|
105
|
+
None. The Hub is opt-in and disabled by default.
|
|
106
|
+
|
|
107
|
+
## Compatibility Notes
|
|
108
|
+
|
|
109
|
+
| Pattern | Status | Notes |
|
|
110
|
+
|---------|--------|-------|
|
|
111
|
+
| Projects that never set `hub` | Unaffected | Every `/hub` route answers 404 |
|
|
112
|
+
| Custom `EmailService` subclass | Compatible | The mailbox capture hook is injected optionally (`@Optional()`) — absent when the Hub/mailbox is off, so there is zero cost and no behavior change |
|
|
113
|
+
| No auth system enabled | ⚠️ Action required | The Hub relies on the app-wide `RolesGuard`/`BetterAuthRolesGuard` to enforce `@Roles(ADMIN)`. With BetterAuth and legacy auth both disabled, no guard runs and the gate is inert — keep an auth module enabled or leave the Hub disabled |
|
|
114
|
+
| `queries` collector enabled | Compatible | Opts the MongoDB driver into `monitorCommands` from `core.module.ts` when the consumer has not set it |
|
|
115
|
+
|
|
116
|
+
## Troubleshooting
|
|
117
|
+
|
|
118
|
+
| Issue | Fix |
|
|
119
|
+
|-------|-----|
|
|
120
|
+
| `/hub` → 404 everywhere | `hub` is not set in that environment's config block — add `hub: true` (or an options object) |
|
|
121
|
+
| Startup error mentioning mailbox `capture` | `mailbox.mode: 'capture'` is forbidden in `production`/`staging` — use `copy` or disable the mailbox there |
|
|
122
|
+
| Query panel empty | The queries collector is opt-in — set `hub.collectors.queries: true` |
|
|
123
|
+
| Cron panel empty | Import `ScheduleModule.forRoot()` in your ServerModule |
|
|
124
|
+
| Hub reachable without login | You disabled all auth systems, so no roles guard runs — see the Compatibility note above |
|
|
125
|
+
|
|
126
|
+
## Module Documentation
|
|
127
|
+
|
|
128
|
+
- `src/core/modules/hub/README.md` — full panel/action/collector/security reference
|
|
129
|
+
- `src/core/modules/hub/INTEGRATION-CHECKLIST.md` — config-only setup + common mistakes
|
|
130
|
+
- `.claude/rules/configurable-features.md` — Hub row in the configurable-features table
|
|
131
|
+
|
|
132
|
+
## References
|
|
133
|
+
|
|
134
|
+
- Configurable-features pattern: `.claude/rules/configurable-features.md`
|
|
135
|
+
- Request lifecycle (middleware + logger override + email hook): `docs/REQUEST-LIFECYCLE.md`
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lenne.tech/nest-server",
|
|
3
|
-
"version": "11.31.
|
|
3
|
+
"version": "11.31.3",
|
|
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,9 +23,9 @@
|
|
|
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 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 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 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
31
|
"cf": "pnpm run check:fix",
|
|
@@ -65,6 +65,7 @@
|
|
|
65
65
|
"vitest:unit:cov": "vitest run --coverage --config vitest.config.ts",
|
|
66
66
|
"test:unit:watch": "vitest --config vitest.config.ts",
|
|
67
67
|
"test:types": "tsc --noEmit --skipLibCheck -p tests/types/tsconfig.json",
|
|
68
|
+
"typecheck:tests": "tsc --noEmit -p tsconfig.tests.json",
|
|
68
69
|
"test:cleanup": "find tests -type f \\( -name '*.txt' -o -name '*.bin' \\) -not -name '.gitkeep' -delete && echo 'Test artifacts cleaned up'",
|
|
69
70
|
"watch": "npm-watch"
|
|
70
71
|
},
|
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
|
+
}
|
|
@@ -18,6 +18,7 @@ import * as SMTPTransport from 'nodemailer/lib/smtp-transport';
|
|
|
18
18
|
import type * as StreamTransport from 'nodemailer/lib/stream-transport';
|
|
19
19
|
|
|
20
20
|
import { Falsy } from '../types/falsy.type';
|
|
21
|
+
import { IHubConfig } from '../../modules/hub/interfaces/hub-config.interface';
|
|
21
22
|
import { IPermissions } from '../../modules/permissions/interfaces/permissions.interface';
|
|
22
23
|
import { CronJobConfigWithTimeZone } from './cron-job-config-with-time-zone.interface';
|
|
23
24
|
import { CronJobConfigWithUtcOffset } from './cron-job-config-with-utc-offset.interface';
|
|
@@ -1847,6 +1848,24 @@ export interface IServerOptions {
|
|
|
1847
1848
|
*/
|
|
1848
1849
|
hostname?: string;
|
|
1849
1850
|
|
|
1851
|
+
/**
|
|
1852
|
+
* Hub admin area (operator cockpit).
|
|
1853
|
+
*
|
|
1854
|
+
* A build-free, ADMIN-gated dashboard of runtime information and admin tools (dashboard,
|
|
1855
|
+
* diagnostics, logs, request traces, query profiler, migrations, DB stats, models/ERD, files,
|
|
1856
|
+
* cron jobs, error codes, masked config, auth-migration progress, email preview, built-in mailbox,
|
|
1857
|
+
* AI panel).
|
|
1858
|
+
*
|
|
1859
|
+
* Follows "presence implies enabled" — but is NEVER enabled implicitly. It must be switched on per
|
|
1860
|
+
* environment (here or via `NSC__HUB__*`); no environment inherits it.
|
|
1861
|
+
* - `true` / `{}`: enabled at `/hub`, admin-only, default collectors (logs + traces on, queries off)
|
|
1862
|
+
* - `{ path: 'admin/hub', collectors: { queries: true } }`: custom path + query profiling
|
|
1863
|
+
* - `{ enabled: false }` / `undefined`: disabled — every Hub route answers 404
|
|
1864
|
+
*
|
|
1865
|
+
* @default undefined (disabled)
|
|
1866
|
+
*/
|
|
1867
|
+
hub?: boolean | IHubConfig;
|
|
1868
|
+
|
|
1850
1869
|
/**
|
|
1851
1870
|
* Ignore selections in fieldSelection
|
|
1852
1871
|
* [ConfigService must be integrated in ModuleService]
|
|
@@ -3312,4 +3331,31 @@ export interface ICoreModuleOverrides {
|
|
|
3312
3331
|
controller?: Type<any>;
|
|
3313
3332
|
service?: Type<any>;
|
|
3314
3333
|
};
|
|
3334
|
+
|
|
3335
|
+
/**
|
|
3336
|
+
* Override Hub controllers and/or services with project-specific subclasses.
|
|
3337
|
+
*
|
|
3338
|
+
* - `actionsController` must extend `CoreHubActionsController`
|
|
3339
|
+
* - `actionsService` must extend `CoreHubActionsService`
|
|
3340
|
+
* - `controller` must extend `CoreHubController`
|
|
3341
|
+
* - `htmlService` must extend `CoreHubHtmlService`
|
|
3342
|
+
* - `service` must extend `CoreHubService`
|
|
3343
|
+
*
|
|
3344
|
+
* @example
|
|
3345
|
+
* ```typescript
|
|
3346
|
+
* {
|
|
3347
|
+
* hub: {
|
|
3348
|
+
* service: MyHubService,
|
|
3349
|
+
* htmlService: MyHubHtmlService,
|
|
3350
|
+
* },
|
|
3351
|
+
* }
|
|
3352
|
+
* ```
|
|
3353
|
+
*/
|
|
3354
|
+
hub?: {
|
|
3355
|
+
actionsController?: Type<any>;
|
|
3356
|
+
actionsService?: Type<any>;
|
|
3357
|
+
controller?: Type<any>;
|
|
3358
|
+
htmlService?: Type<any>;
|
|
3359
|
+
service?: Type<any>;
|
|
3360
|
+
};
|
|
3315
3361
|
}
|