@lenne.tech/nest-server 12.3.2 → 12.4.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/configurable-features.md +1 -1
- package/.claude/rules/testing.md +4 -4
- package/FRAMEWORK-API.md +1 -1
- package/dist/core/common/helpers/swagger.helper.js +6 -0
- package/dist/core/common/helpers/swagger.helper.js.map +1 -1
- package/dist/core/common/interfaces/server-options.interface.d.ts +5 -0
- package/dist/core/modules/error-code/error-codes.d.ts +18 -0
- package/dist/core/modules/error-code/error-codes.js +16 -0
- package/dist/core/modules/error-code/error-codes.js.map +1 -1
- package/dist/core/modules/system-setup/core-system-setup.controller.d.ts +2 -2
- package/dist/core/modules/system-setup/core-system-setup.controller.js +17 -7
- package/dist/core/modules/system-setup/core-system-setup.controller.js.map +1 -1
- package/dist/core/modules/system-setup/core-system-setup.service.d.ts +18 -0
- package/dist/core/modules/system-setup/core-system-setup.service.js +118 -15
- package/dist/core/modules/system-setup/core-system-setup.service.js.map +1 -1
- package/dist/core/modules/system-setup/system-setup-http-init.helper.d.ts +11 -0
- package/dist/core/modules/system-setup/system-setup-http-init.helper.js +45 -0
- package/dist/core/modules/system-setup/system-setup-http-init.helper.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/server/modules/error-code/error-codes.d.ts +2 -0
- package/migration-guides/11.13.x-to-11.14.0.md +5 -5
- package/migration-guides/12.3.x-to-12.4.x.md +169 -0
- package/package.json +1 -1
- package/src/core/common/helpers/swagger.helper.ts +7 -0
- package/src/core/common/interfaces/server-options.interface.ts +59 -5
- package/src/core/modules/error-code/error-codes.ts +27 -0
- package/src/core/modules/system-setup/INTEGRATION-CHECKLIST.md +51 -16
- package/src/core/modules/system-setup/README.md +144 -28
- package/src/core/modules/system-setup/core-system-setup.controller.ts +30 -8
- package/src/core/modules/system-setup/core-system-setup.service.ts +246 -23
- package/src/core/modules/system-setup/system-setup-http-init.helper.ts +130 -0
- package/src/index.ts +1 -0
|
@@ -3611,7 +3611,10 @@ export interface IServerOptions {
|
|
|
3611
3611
|
*
|
|
3612
3612
|
* Auto-creation via config/ENV (no REST call needed):
|
|
3613
3613
|
* - `{ initialAdmin: { email: '...', password: '...' } }`
|
|
3614
|
-
* - `
|
|
3614
|
+
* - `NSC__SYSTEM_SETUP__INITIAL_ADMIN__EMAIL` + `NSC__SYSTEM_SETUP__INITIAL_ADMIN__PASSWORD`
|
|
3615
|
+
*
|
|
3616
|
+
* The anonymous `POST /system-setup/init` is open only in `local`, `ci` and `e2e` unless `httpInit` opens it
|
|
3617
|
+
* (12.4.0+, see {@link ISystemSetup.httpInit}).
|
|
3615
3618
|
*
|
|
3616
3619
|
* @since 11.14.0
|
|
3617
3620
|
*
|
|
@@ -4048,23 +4051,74 @@ export interface ISystemSetup {
|
|
|
4048
4051
|
*/
|
|
4049
4052
|
enabled?: boolean;
|
|
4050
4053
|
|
|
4054
|
+
/**
|
|
4055
|
+
* Whether the anonymous `POST /system-setup/init` may create the first administrator.
|
|
4056
|
+
*
|
|
4057
|
+
* Fail-closed outside the local environments: whoever reaches an API without users through that route becomes its
|
|
4058
|
+
* administrator, so a deployment provisions the first admin through {@link initialAdmin} and opens the HTTP route
|
|
4059
|
+
* only on purpose.
|
|
4060
|
+
*
|
|
4061
|
+
* | Value | `local` / `ci` / `e2e` | every other environment (`develop`, `test`, `production`, …) |
|
|
4062
|
+
* |-------|------------------------|---------------------------------------------------------------|
|
|
4063
|
+
* | not set | open | **closed** (403 `LTNS_0053`) |
|
|
4064
|
+
* | `true` | open | open, without a token |
|
|
4065
|
+
* | `{ token: '<at least 16 characters>' }` | open, token required | open, token required |
|
|
4066
|
+
* | `false` / `{ enabled: false, … }` | closed | closed |
|
|
4067
|
+
*
|
|
4068
|
+
* The token is sent in the `X-System-Setup-Token` header; a missing or wrong one answers 403 `LTNS_0054`. An object
|
|
4069
|
+
* whose token is missing, empty, shorter than 16 characters or not a string keeps the route CLOSED and logs an
|
|
4070
|
+
* error at boot — `{ token: process.env.SETUP_TOKEN }` with the variable unset must not open it. A value that is
|
|
4071
|
+
* neither a boolean nor an object (the string `'true'` from `NEST_SERVER_CONFIG`) counts as not set.
|
|
4072
|
+
*
|
|
4073
|
+
* Environment variables: `NSC__SYSTEM_SETUP__HTTP_INIT=true`, or `NSC__SYSTEM_SETUP__HTTP_INIT__TOKEN=<secret>` (a
|
|
4074
|
+
* token of digits only is converted to a number by the environment reader and refused; set one of the two, not
|
|
4075
|
+
* both).
|
|
4076
|
+
*
|
|
4077
|
+
* `GET /system-setup/status` reports the result as `httpInitEnabled` / `httpInitTokenRequired`.
|
|
4078
|
+
*
|
|
4079
|
+
* @default undefined (open only in `local`, `ci` and `e2e`)
|
|
4080
|
+
* @since 12.4.0
|
|
4081
|
+
*/
|
|
4082
|
+
httpInit?: boolean | ISystemSetupHttpInit;
|
|
4083
|
+
|
|
4051
4084
|
/**
|
|
4052
4085
|
* Pre-configured initial admin credentials for automatic creation on server start.
|
|
4053
4086
|
*
|
|
4054
4087
|
* When set, the service will automatically create the initial admin user
|
|
4055
4088
|
* during application bootstrap if zero users exist. This is useful for
|
|
4056
4089
|
* automated deployments (Docker, CI/CD) where no manual REST call is possible.
|
|
4090
|
+
* It is not affected by {@link httpInit}.
|
|
4057
4091
|
*
|
|
4058
|
-
* Can be provided via environment variables
|
|
4059
|
-
*
|
|
4060
|
-
* - `
|
|
4061
|
-
* - `
|
|
4092
|
+
* Can be provided via environment variables (SCREAMING_SNAKE segments — a camelCase segment such as
|
|
4093
|
+
* `NSC__systemSetup__…` is NOT read, the boot warns about it):
|
|
4094
|
+
* - `NSC__SYSTEM_SETUP__INITIAL_ADMIN__EMAIL`
|
|
4095
|
+
* - `NSC__SYSTEM_SETUP__INITIAL_ADMIN__PASSWORD`
|
|
4096
|
+
* - `NSC__SYSTEM_SETUP__INITIAL_ADMIN__NAME` (optional)
|
|
4062
4097
|
*
|
|
4063
4098
|
* Security: Same zero-user guard applies - only works when no users exist.
|
|
4064
4099
|
*/
|
|
4065
4100
|
initialAdmin?: ISystemSetupInitialAdmin;
|
|
4066
4101
|
}
|
|
4067
4102
|
|
|
4103
|
+
/**
|
|
4104
|
+
* Object form of {@link ISystemSetup.httpInit}: the HTTP init, protected by a setup token
|
|
4105
|
+
*
|
|
4106
|
+
* @since 12.4.0
|
|
4107
|
+
*/
|
|
4108
|
+
export interface ISystemSetupHttpInit {
|
|
4109
|
+
/**
|
|
4110
|
+
* `false` keeps the HTTP init closed while the token stays configured
|
|
4111
|
+
* @default true
|
|
4112
|
+
*/
|
|
4113
|
+
enabled?: boolean;
|
|
4114
|
+
|
|
4115
|
+
/**
|
|
4116
|
+
* The secret `POST /system-setup/init` must carry in the `X-System-Setup-Token` header. At least 16 characters;
|
|
4117
|
+
* a missing, shorter or non-string value keeps the HTTP init closed.
|
|
4118
|
+
*/
|
|
4119
|
+
token?: string;
|
|
4120
|
+
}
|
|
4121
|
+
|
|
4068
4122
|
/**
|
|
4069
4123
|
* System setup configuration interface
|
|
4070
4124
|
*
|
|
@@ -637,6 +637,33 @@ export const LtnsErrors = {
|
|
|
637
637
|
},
|
|
638
638
|
},
|
|
639
639
|
|
|
640
|
+
/**
|
|
641
|
+
* `POST /system-setup/init` is closed in this environment (12.4.0+): by default it is open only in `local`, `ci`
|
|
642
|
+
* and `e2e`. A deployment provisions its first admin through `systemSetup.initialAdmin`, or opens the HTTP init
|
|
643
|
+
* explicitly with `systemSetup.httpInit`.
|
|
644
|
+
*/
|
|
645
|
+
SYSTEM_SETUP_HTTP_INIT_DISABLED: {
|
|
646
|
+
code: 'LTNS_0053',
|
|
647
|
+
message: 'Initial admin setup over HTTP is not enabled in this environment',
|
|
648
|
+
translations: {
|
|
649
|
+
de: 'Die Ersteinrichtung über die Oberfläche ist in dieser Umgebung nicht freigegeben.',
|
|
650
|
+
en: 'Initial admin setup over HTTP is not enabled in this environment.',
|
|
651
|
+
},
|
|
652
|
+
},
|
|
653
|
+
|
|
654
|
+
/**
|
|
655
|
+
* `POST /system-setup/init` requires the setup token (`systemSetup.httpInit.token`) in the `X-System-Setup-Token`
|
|
656
|
+
* header, and the request sent none or a different one (12.4.0+).
|
|
657
|
+
*/
|
|
658
|
+
SYSTEM_SETUP_TOKEN_INVALID: {
|
|
659
|
+
code: 'LTNS_0054',
|
|
660
|
+
message: 'Missing or invalid system setup token',
|
|
661
|
+
translations: {
|
|
662
|
+
de: 'Der Einrichtungs-Token fehlt oder ist falsch.',
|
|
663
|
+
en: 'The system setup token is missing or invalid.',
|
|
664
|
+
},
|
|
665
|
+
},
|
|
666
|
+
|
|
640
667
|
// =====================================================
|
|
641
668
|
// AI Errors (LTNS_0600-LTNS_0699)
|
|
642
669
|
// =====================================================
|
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
**For initial admin creation on fresh deployments in projects using `@lenne.tech/nest-server`.**
|
|
4
4
|
|
|
5
|
-
> **Note:** System setup is **enabled by default** when BetterAuth is active.
|
|
5
|
+
> **Note:** System setup is **enabled by default** when BetterAuth is active. Locally (`local`, `ci`, `e2e`) the REST
|
|
6
|
+
> endpoints work without configuration. **In a deployed environment `POST /system-setup/init` is closed since 12.4.0**:
|
|
7
|
+
> provision the first admin via ENV (Step 1), or open the HTTP init on purpose (Step 1b).
|
|
6
8
|
|
|
7
9
|
---
|
|
8
10
|
|
|
@@ -10,8 +12,9 @@
|
|
|
10
12
|
|
|
11
13
|
| Scenario | Action Needed |
|
|
12
14
|
| ---------------------------------------------------- | ----------------------------------------------- |
|
|
13
|
-
|
|
|
14
|
-
|
|
|
15
|
+
| Local development / e2e tests create the first admin | None - the HTTP init is open there by default |
|
|
16
|
+
| Deployed instance needs its first admin | Set ENV variables (Step 1) - recommended |
|
|
17
|
+
| Deployed instance must be set up through the UI | Open the HTTP init with a token (Step 1b) |
|
|
15
18
|
| Want to disable system setup | Set `systemSetup: { enabled: false }` in config |
|
|
16
19
|
| Custom setup logic (extra fields, notifications) | Step 2 (Custom Controller) |
|
|
17
20
|
|
|
@@ -35,12 +38,16 @@ https://github.com/lenneTech/nest-server/tree/develop/src/core/modules/system-se
|
|
|
35
38
|
For automated deployments where no manual REST call is possible:
|
|
36
39
|
|
|
37
40
|
```bash
|
|
38
|
-
# .env or Docker environment
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
41
|
+
# .env or Docker environment — every segment in SCREAMING_SNAKE_CASE
|
|
42
|
+
NSC__SYSTEM_SETUP__INITIAL_ADMIN__EMAIL=admin@example.com
|
|
43
|
+
NSC__SYSTEM_SETUP__INITIAL_ADMIN__PASSWORD=YourSecurePassword123!
|
|
44
|
+
NSC__SYSTEM_SETUP__INITIAL_ADMIN__NAME=Admin # optional
|
|
42
45
|
```
|
|
43
46
|
|
|
47
|
+
**WHY SCREAMING_SNAKE_CASE?** The environment reader lowercases every segment it does not split at `_`. The camelCase
|
|
48
|
+
form `NSC__systemSetup__initialAdmin__email` (shown in this checklist until 12.4.0) is therefore never read, and no
|
|
49
|
+
admin is created. Since 12.4.0 the boot warns about such a variable.
|
|
50
|
+
|
|
44
51
|
Or in `config.env.ts`:
|
|
45
52
|
|
|
46
53
|
```typescript
|
|
@@ -58,6 +65,26 @@ The admin is created automatically on server start when zero users exist.
|
|
|
58
65
|
|
|
59
66
|
---
|
|
60
67
|
|
|
68
|
+
## Step 1b: Open the HTTP init on a deployed instance (Optional, 12.4.0+)
|
|
69
|
+
|
|
70
|
+
Only when the first admin must be created through the UI on a deployed stage:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
NSC__SYSTEM_SETUP__HTTP_INIT__TOKEN=<secret of at least 16 characters> # recommended
|
|
74
|
+
# or, without a token (whoever reaches the API first becomes admin):
|
|
75
|
+
NSC__SYSTEM_SETUP__HTTP_INIT=true
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
The frontend sends the token in the `X-System-Setup-Token` header; `GET /system-setup/status` tells it with
|
|
79
|
+
`httpInitEnabled` / `httpInitTokenRequired` whether to show the form and a token field.
|
|
80
|
+
|
|
81
|
+
**WHY closed by default?** The init is anonymous. On a deployment without `initialAdmin` it made whoever called it
|
|
82
|
+
first the administrator, until the first sign-up.
|
|
83
|
+
|
|
84
|
+
Remove the setting once the admin exists — the init is locked from then on anyway.
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
61
88
|
## Step 2: Custom Controller (Optional)
|
|
62
89
|
|
|
63
90
|
Only needed if you want to add extra validation, logging, or custom fields.
|
|
@@ -76,6 +103,9 @@ export class SystemSetupController extends CoreSystemSetupController {
|
|
|
76
103
|
```
|
|
77
104
|
|
|
78
105
|
> **Note:** The SystemSetup module auto-registers its controller via CoreModule. To use a custom controller, you would need to disable auto-registration and register your own module.
|
|
106
|
+
>
|
|
107
|
+
> **An override of `createInitialAdmin()` must call `this.systemSetupService.assertHttpInitAllowed(token)` first**
|
|
108
|
+
> (the token from the `x-system-setup-token` header), or it opens the anonymous init in every environment.
|
|
79
109
|
|
|
80
110
|
---
|
|
81
111
|
|
|
@@ -84,7 +114,9 @@ export class SystemSetupController extends CoreSystemSetupController {
|
|
|
84
114
|
- [ ] `npm run build` succeeds
|
|
85
115
|
- [ ] `npm test` passes
|
|
86
116
|
- [ ] `GET /system-setup/status` returns `{ needsSetup: true }` on empty database
|
|
87
|
-
- [ ] `POST /system-setup/init` creates admin user with correct role
|
|
117
|
+
- [ ] Locally: `httpInitEnabled: true`, and `POST /system-setup/init` creates admin user with correct role
|
|
118
|
+
- [ ] Deployed stage without `httpInit`: `httpInitEnabled: false`, and the init answers 403 `LTNS_0053`
|
|
119
|
+
- [ ] Deployed stage with `initialAdmin`: the admin exists after the first start
|
|
88
120
|
- [ ] `GET /system-setup/status` returns `{ needsSetup: false }` after init
|
|
89
121
|
- [ ] `POST /system-setup/init` returns 403 when users already exist
|
|
90
122
|
|
|
@@ -92,14 +124,17 @@ export class SystemSetupController extends CoreSystemSetupController {
|
|
|
92
124
|
|
|
93
125
|
## Common Mistakes
|
|
94
126
|
|
|
95
|
-
| Mistake
|
|
96
|
-
|
|
|
97
|
-
| BetterAuth not enabled
|
|
98
|
-
| Calling init with existing users
|
|
99
|
-
| Re-running init after a successful setup
|
|
100
|
-
| Password too short
|
|
101
|
-
| Missing ENV password
|
|
102
|
-
|
|
|
127
|
+
| Mistake | Symptom | Fix |
|
|
128
|
+
| --------------------------------------------- | -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
129
|
+
| BetterAuth not enabled | 404 on endpoints or 403 on init | Ensure `betterAuth` is configured |
|
|
130
|
+
| Calling init with existing users | 403 "System setup not available" | Init requires an empty `users` collection |
|
|
131
|
+
| Re-running init after a successful setup | 403, even with an empty `users` | The claim marker in `system-setup-locks` outlives a SUCCESSFUL setup (it is released only on failure) — drop that collection too, or wait 5 min for it to go stale |
|
|
132
|
+
| Password too short | 400 validation error | Password must be at least 8 characters |
|
|
133
|
+
| Missing ENV password | Auto-creation silently skipped | Set both `email` and `password` ENV vars |
|
|
134
|
+
| camelCase ENV names (`NSC__systemSetup__…`) | No admin, boot warning | Write every segment in SCREAMING_SNAKE_CASE: `NSC__SYSTEM_SETUP__INITIAL_ADMIN__EMAIL` |
|
|
135
|
+
| Setting up a deployed stage through the UI | 403 `LTNS_0053` | Set `initialAdmin` via ENV, or open the init with `NSC__SYSTEM_SETUP__HTTP_INIT__TOKEN` |
|
|
136
|
+
| `httpInit: { token: process.env.X }`, X unset | 403 `LTNS_0053`, boot error | A missing, empty or short token keeps the init closed on purpose — set the variable |
|
|
137
|
+
| `systemSetup: { enabled: false }` in config | 404 on endpoints | Remove the explicit disable |
|
|
103
138
|
|
|
104
139
|
---
|
|
105
140
|
|
|
@@ -4,13 +4,20 @@ Initial admin user creation for fresh deployments of @lenne.tech/nest-server.
|
|
|
4
4
|
|
|
5
5
|
## TL;DR
|
|
6
6
|
|
|
7
|
-
System setup is **enabled by default** when BetterAuth is active. No configuration needed.
|
|
7
|
+
System setup is **enabled by default** when BetterAuth is active. No configuration needed locally.
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
**In a deployed environment** (anything but `local`, `ci` and `e2e`) the anonymous `POST /system-setup/init` is
|
|
10
|
+
**closed** since 12.4.0. Provision the first admin via ENV instead — it is created on the next start:
|
|
10
11
|
|
|
11
12
|
```bash
|
|
12
|
-
|
|
13
|
-
|
|
13
|
+
NSC__SYSTEM_SETUP__INITIAL_ADMIN__EMAIL=admin@example.com
|
|
14
|
+
NSC__SYSTEM_SETUP__INITIAL_ADMIN__PASSWORD=YourSecurePassword123!
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Or open the HTTP init on purpose, preferably with a setup token (see [HTTP init](#http-init-systemsetuphttpinit)):
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
NSC__SYSTEM_SETUP__HTTP_INIT__TOKEN=<secret of at least 16 characters> # sent as X-System-Setup-Token
|
|
14
21
|
```
|
|
15
22
|
|
|
16
23
|
**Quick Links:** [Integration Checklist](./INTEGRATION-CHECKLIST.md) | [Endpoints](#endpoints) | [Configuration](#configuration) | [Security](#security)
|
|
@@ -22,6 +29,7 @@ NSC__systemSetup__initialAdmin__password=YourSecurePassword123!
|
|
|
22
29
|
- [Purpose](#purpose)
|
|
23
30
|
- [Endpoints](#endpoints)
|
|
24
31
|
- [Configuration](#configuration)
|
|
32
|
+
- [HTTP init (`systemSetup.httpInit`)](#http-init-systemsetuphttpinit)
|
|
25
33
|
- [Auto-Creation via Config/ENV](#auto-creation-via-configenv)
|
|
26
34
|
- [Security](#security)
|
|
27
35
|
- [Frontend Integration](#frontend-integration)
|
|
@@ -33,8 +41,8 @@ NSC__systemSetup__initialAdmin__password=YourSecurePassword123!
|
|
|
33
41
|
|
|
34
42
|
When a system is freshly deployed (zero users in the database), there is no way to create an initial admin user - especially when BetterAuth's `disableSignUp` is enabled. This module provides:
|
|
35
43
|
|
|
36
|
-
1. **REST endpoints** - Manual admin creation via API call
|
|
37
|
-
2. **Auto-creation** - Automatic admin creation on server start via config/ENV
|
|
44
|
+
1. **REST endpoints** - Manual admin creation via API call — open by default only in `local`, `ci` and `e2e`
|
|
45
|
+
2. **Auto-creation** - Automatic admin creation on server start via config/ENV — the way for a deployment
|
|
38
46
|
|
|
39
47
|
Once any user exists, the init endpoint is permanently locked (returns 403) and auto-creation is skipped.
|
|
40
48
|
|
|
@@ -56,13 +64,28 @@ Returns the current setup status.
|
|
|
56
64
|
```json
|
|
57
65
|
{
|
|
58
66
|
"needsSetup": true,
|
|
59
|
-
"betterAuthEnabled": true
|
|
67
|
+
"betterAuthEnabled": true,
|
|
68
|
+
"httpInitEnabled": true,
|
|
69
|
+
"httpInitTokenRequired": false
|
|
60
70
|
}
|
|
61
71
|
```
|
|
62
72
|
|
|
73
|
+
| Field | Meaning |
|
|
74
|
+
| ----------------------- | ------------------------------------------------------------------------------------------------------- |
|
|
75
|
+
| `needsSetup` | No user exists yet |
|
|
76
|
+
| `betterAuthEnabled` | BetterAuth is active (required for the init) |
|
|
77
|
+
| `httpInitEnabled` | `POST /system-setup/init` is open in this environment (12.4.0+). Independent of `needsSetup` |
|
|
78
|
+
| `httpInitTokenRequired` | The init needs the `X-System-Setup-Token` header (12.4.0+). Only `true` together with `httpInitEnabled` |
|
|
79
|
+
|
|
80
|
+
A frontend offers the setup form only when `needsSetup && httpInitEnabled`, with a token field when
|
|
81
|
+
`httpInitTokenRequired`. A backend older than 12.4.0 sends neither field; its init is open as before.
|
|
82
|
+
|
|
63
83
|
### POST /system-setup/init
|
|
64
84
|
|
|
65
|
-
Creates the initial admin user. Only works when zero users exist.
|
|
85
|
+
Creates the initial admin user. Only works when zero users exist, and only where `systemSetup.httpInit` opens it
|
|
86
|
+
(by default in `local`, `ci` and `e2e`).
|
|
87
|
+
|
|
88
|
+
**Request Header** (only when a token is configured): `X-System-Setup-Token: <setup token>`
|
|
66
89
|
|
|
67
90
|
**Request Body:**
|
|
68
91
|
|
|
@@ -98,17 +121,21 @@ Creates the initial admin user. Only works when zero users exist.
|
|
|
98
121
|
}
|
|
99
122
|
```
|
|
100
123
|
|
|
124
|
+
The refusals are checked in this order: `LTNS_0053` (HTTP init not enabled here), `LTNS_0054` (setup token missing
|
|
125
|
+
or wrong), then `LTNS_0050` and `LTNS_0052` as before. See [Error Codes](#error-codes).
|
|
126
|
+
|
|
101
127
|
---
|
|
102
128
|
|
|
103
129
|
## Configuration
|
|
104
130
|
|
|
105
131
|
System setup is **enabled by default** when BetterAuth is active:
|
|
106
132
|
|
|
107
|
-
| Config | Effect
|
|
108
|
-
| ---------------------------------------- |
|
|
109
|
-
| _(not set)_ | Enabled (when BetterAuth is active) |
|
|
110
|
-
| `systemSetup: { enabled: false }` | Disabled explicitly
|
|
111
|
-
| `systemSetup: { initialAdmin: { ... } }` | Enabled with auto-creation
|
|
133
|
+
| Config | Effect |
|
|
134
|
+
| ---------------------------------------- | --------------------------------------------------------------------- |
|
|
135
|
+
| _(not set)_ | Enabled (when BetterAuth is active); HTTP init open only locally |
|
|
136
|
+
| `systemSetup: { enabled: false }` | Disabled explicitly (no endpoints) |
|
|
137
|
+
| `systemSetup: { initialAdmin: { ... } }` | Enabled with auto-creation |
|
|
138
|
+
| `systemSetup: { httpInit: ... }` | Opens or closes `POST /system-setup/init` explicitly (12.4.0+, below) |
|
|
112
139
|
|
|
113
140
|
```typescript
|
|
114
141
|
// config.env.ts
|
|
@@ -129,6 +156,54 @@ systemSetup: {
|
|
|
129
156
|
|
|
130
157
|
---
|
|
131
158
|
|
|
159
|
+
## HTTP init (`systemSetup.httpInit`)
|
|
160
|
+
|
|
161
|
+
`POST /system-setup/init` is anonymous: on an API without users, whoever calls it becomes the administrator. Since
|
|
162
|
+
12.4.0 it is therefore **fail-closed** outside the local environments.
|
|
163
|
+
|
|
164
|
+
| `httpInit` | `local` / `ci` / `e2e` | every other environment (`develop`, `test`, `production`, …) |
|
|
165
|
+
| --------------------------------------- | ---------------------- | ------------------------------------------------------------ |
|
|
166
|
+
| _(not set)_ | open | **closed** — 403 `LTNS_0053` |
|
|
167
|
+
| `true` | open | open, without a token |
|
|
168
|
+
| `{ token: '<at least 16 characters>' }` | open, token required | open, token required — 403 `LTNS_0054` without it |
|
|
169
|
+
| `false` / `{ enabled: false, token }` | closed | closed |
|
|
170
|
+
|
|
171
|
+
The environment is the configured `env` (else `NODE_ENV`), compared with the framework's one list of local
|
|
172
|
+
environments (`isLocalEnv()`). The deployed `develop` and `test` stages of a starter-based project, `production`,
|
|
173
|
+
`staging` and any unknown or missing name count as deployed.
|
|
174
|
+
|
|
175
|
+
```typescript
|
|
176
|
+
// config.env.ts — open with a token (recommended when the setup must happen over the UI)
|
|
177
|
+
systemSetup: {
|
|
178
|
+
httpInit: { token: process.env.SETUP_TOKEN },
|
|
179
|
+
},
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
```bash
|
|
183
|
+
# the same via ENV — set ONE of the two
|
|
184
|
+
NSC__SYSTEM_SETUP__HTTP_INIT__TOKEN=<secret of at least 16 characters>
|
|
185
|
+
NSC__SYSTEM_SETUP__HTTP_INIT=true # open WITHOUT a token: whoever is first becomes admin
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
**The token** travels in the `X-System-Setup-Token` header, never in the body, and is compared in constant time.
|
|
189
|
+
Hand it to the person who sets the system up, the same way as a password.
|
|
190
|
+
|
|
191
|
+
**Misconfigurations close, they never open:**
|
|
192
|
+
|
|
193
|
+
- an object whose token is missing, empty, shorter than 16 characters or not a string keeps the init closed and logs
|
|
194
|
+
an error at boot. `{ token: process.env.SETUP_TOKEN }` with the variable unset must not become an open init.
|
|
195
|
+
- an empty or all-digit `NSC__SYSTEM_SETUP__HTTP_INIT__TOKEN` is converted to a number by the environment reader, and
|
|
196
|
+
a long digit string loses digits on the way — such a value is refused. Use a token with letters.
|
|
197
|
+
- a value that is neither a boolean nor an object (the string `"true"` from `NEST_SERVER_CONFIG`) counts as not set.
|
|
198
|
+
|
|
199
|
+
**Boot report.** A deployed instance that starts with zero users, no usable `initialAdmin` and a closed HTTP init logs
|
|
200
|
+
an error naming both ways in. With the init open without a token it logs a warning. Local environments stay silent.
|
|
201
|
+
|
|
202
|
+
The Swagger document leaves `POST /system-setup/init` out while the init is closed, like every operation that would
|
|
203
|
+
refuse every caller.
|
|
204
|
+
|
|
205
|
+
---
|
|
206
|
+
|
|
132
207
|
## Auto-Creation via Config/ENV
|
|
133
208
|
|
|
134
209
|
For automated deployments where no manual REST call is possible (Docker, CI/CD, Kubernetes), configure initial admin credentials via environment variables:
|
|
@@ -136,11 +211,16 @@ For automated deployments where no manual REST call is possible (Docker, CI/CD,
|
|
|
136
211
|
### Via NSC Environment Variables
|
|
137
212
|
|
|
138
213
|
```bash
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
214
|
+
NSC__SYSTEM_SETUP__INITIAL_ADMIN__EMAIL=admin@example.com
|
|
215
|
+
NSC__SYSTEM_SETUP__INITIAL_ADMIN__PASSWORD=YourSecurePassword123!
|
|
216
|
+
NSC__SYSTEM_SETUP__INITIAL_ADMIN__NAME=Admin # optional
|
|
142
217
|
```
|
|
143
218
|
|
|
219
|
+
Every segment in SCREAMING_SNAKE_CASE. The environment reader lowercases a segment it does not split at `_`, so the
|
|
220
|
+
camelCase form `NSC__systemSetup__initialAdmin__email` — which this README showed until 12.4.0 — lands under
|
|
221
|
+
`systemsetup.initialadmin` and is never read. Since 12.4.0 the boot warns about such a variable and names the correct
|
|
222
|
+
spelling.
|
|
223
|
+
|
|
144
224
|
### Via config.env.ts
|
|
145
225
|
|
|
146
226
|
```typescript
|
|
@@ -162,6 +242,7 @@ NEST_SERVER_CONFIG='{ "systemSetup": { "initialAdmin": { "email": "admin@example
|
|
|
162
242
|
### Behavior
|
|
163
243
|
|
|
164
244
|
- The admin is created automatically during application bootstrap (`OnApplicationBootstrap`)
|
|
245
|
+
- Not affected by `systemSetup.httpInit`: the configured admin is created in every environment
|
|
165
246
|
- Same zero-user guard applies: only works when no users exist
|
|
166
247
|
- If users already exist, auto-creation is silently skipped (no error)
|
|
167
248
|
- Race conditions between multiple instances are handled gracefully: the zero-user check alone is
|
|
@@ -187,14 +268,19 @@ NEST_SERVER_CONFIG='{ "systemSetup": { "initialAdmin": { "email": "admin@example
|
|
|
187
268
|
|
|
188
269
|
## Security
|
|
189
270
|
|
|
190
|
-
1. **
|
|
271
|
+
1. **Fail-closed HTTP init (12.4.0)** - Outside `local`, `ci` and `e2e` the anonymous init answers 403
|
|
272
|
+
`LTNS_0053` unless `systemSetup.httpInit` opens it, preferably with a setup token. Before 12.4.0 a
|
|
273
|
+
deployment without `initialAdmin` could be taken over by whoever called the init first
|
|
274
|
+
2. **Zero-user guard** - Init requires `countDocuments({}) === 0`. Necessary but not sufficient:
|
|
191
275
|
the atomic claim below must also be winnable, so an empty `users` collection alone does not
|
|
192
276
|
re-open setup
|
|
193
|
-
|
|
194
|
-
|
|
277
|
+
3. **Rollback on failure (12.4.0)** - When a step after creating the user fails (account, sync, role),
|
|
278
|
+
the user is removed again, so `needsSetup` stays `true` and the setup can be retried. Before,
|
|
279
|
+
the deployment kept a user without the admin role and was locked for good
|
|
280
|
+
4. **Race condition protection** - MongoDB unique email index prevents duplicates; auto-creation on
|
|
195
281
|
bootstrap is additionally claimed atomically via the `system-setup-locks` marker
|
|
196
|
-
|
|
197
|
-
|
|
282
|
+
5. **Permanent lock** - Once any user exists, init returns 403
|
|
283
|
+
6. **BetterAuth required** - Returns 403 if BetterAuth is not enabled
|
|
198
284
|
|
|
199
285
|
### How It Bypasses disableSignUp
|
|
200
286
|
|
|
@@ -207,15 +293,19 @@ The service uses BetterAuth's `$context.internalAdapter.createUser()` directly,
|
|
|
207
293
|
Typical frontend flow:
|
|
208
294
|
|
|
209
295
|
```typescript
|
|
210
|
-
// 1. Check if setup is needed
|
|
296
|
+
// 1. Check if setup is needed AND possible over HTTP
|
|
211
297
|
const status = await fetch('/system-setup/status');
|
|
212
|
-
const { needsSetup } = await status.json();
|
|
298
|
+
const { needsSetup, httpInitEnabled = true, httpInitTokenRequired = false } = await status.json();
|
|
299
|
+
// (the defaults keep a backend older than 12.4.0 working, where both fields are missing)
|
|
213
300
|
|
|
214
|
-
if (needsSetup) {
|
|
215
|
-
// 2. Show setup form and submit
|
|
301
|
+
if (needsSetup && httpInitEnabled) {
|
|
302
|
+
// 2. Show setup form (with a token field when httpInitTokenRequired) and submit
|
|
216
303
|
const result = await fetch('/system-setup/init', {
|
|
217
304
|
method: 'POST',
|
|
218
|
-
headers: {
|
|
305
|
+
headers: {
|
|
306
|
+
'Content-Type': 'application/json',
|
|
307
|
+
...(httpInitTokenRequired ? { 'X-System-Setup-Token': setupToken } : {}),
|
|
308
|
+
},
|
|
219
309
|
body: JSON.stringify({
|
|
220
310
|
email: 'admin@example.com',
|
|
221
311
|
password: 'SecurePassword123!',
|
|
@@ -232,6 +322,29 @@ if (needsSetup) {
|
|
|
232
322
|
|
|
233
323
|
## Troubleshooting
|
|
234
324
|
|
|
325
|
+
### Init returns 403 `LTNS_0053` "not enabled in this environment"
|
|
326
|
+
|
|
327
|
+
**Cause:** the environment is not `local`, `ci` or `e2e`, and `systemSetup.httpInit` does not open the init — the
|
|
328
|
+
default since 12.4.0.
|
|
329
|
+
|
|
330
|
+
**Solutions:**
|
|
331
|
+
|
|
332
|
+
1. Set `NSC__SYSTEM_SETUP__INITIAL_ADMIN__EMAIL` / `__PASSWORD` and restart — the admin is created at boot
|
|
333
|
+
2. Or open the init with a token: `NSC__SYSTEM_SETUP__HTTP_INIT__TOKEN=<secret>` (sent as `X-System-Setup-Token`)
|
|
334
|
+
3. Check the boot log: an unusable token configuration is logged as an error naming what is wrong
|
|
335
|
+
|
|
336
|
+
### Init returns 403 `LTNS_0054` "setup token is missing or invalid"
|
|
337
|
+
|
|
338
|
+
**Cause:** the init requires a token and the request sent none or a different one. Send the configured value in the
|
|
339
|
+
`X-System-Setup-Token` header. A frontend learns that a token is needed from `httpInitTokenRequired`.
|
|
340
|
+
|
|
341
|
+
### `needsSetup` is `false`, but nobody can sign in as admin
|
|
342
|
+
|
|
343
|
+
**Cause (before 12.4.0):** a creation failed after the user was written (for example a failing account link), and
|
|
344
|
+
the user stayed behind without the admin role. Since 12.4.0 such a user is removed again. On an older version, or if
|
|
345
|
+
the server log says the partially created user "could not be removed", delete that user and its `account` rows and
|
|
346
|
+
retry.
|
|
347
|
+
|
|
235
348
|
### Init returns 403 "System setup not available"
|
|
236
349
|
|
|
237
350
|
**Two different guards raise this, and the response cannot tell you which.**
|
|
@@ -290,11 +403,12 @@ DELETE FROM lt_locks WHERE id = 'system-setup:initial-admin';
|
|
|
290
403
|
|
|
291
404
|
### Auto-creation not working
|
|
292
405
|
|
|
293
|
-
**Cause:** Missing or
|
|
406
|
+
**Cause:** Missing, incomplete or misspelled ENV variables.
|
|
294
407
|
|
|
295
408
|
**Solutions:**
|
|
296
409
|
|
|
297
|
-
1. Verify both `email` and `password` are set
|
|
410
|
+
1. Verify both `email` and `password` are set, spelled `NSC__SYSTEM_SETUP__INITIAL_ADMIN__EMAIL` /
|
|
411
|
+
`__PASSWORD` — a camelCase spelling (`NSC__systemSetup__…`) is not read; the boot warns about it
|
|
298
412
|
2. Check server logs for `Auto-created initial admin on startup` or warning messages
|
|
299
413
|
3. Ensure BetterAuth is fully initialized (check startup logs)
|
|
300
414
|
|
|
@@ -316,6 +430,8 @@ DELETE FROM lt_locks WHERE id = 'system-setup:initial-admin';
|
|
|
316
430
|
| LTNS_0050 | `SYSTEM_SETUP_NOT_AVAILABLE` | Users already exist, OR the initial-admin setup is claimed |
|
|
317
431
|
| LTNS_0051 | `SYSTEM_SETUP_DISABLED` | System setup is disabled in config |
|
|
318
432
|
| LTNS_0052 | `SYSTEM_SETUP_BETTERAUTH_REQUIRED` | BetterAuth must be enabled |
|
|
433
|
+
| LTNS_0053 | `SYSTEM_SETUP_HTTP_INIT_DISABLED` | HTTP init not enabled in this environment (12.4.0+) |
|
|
434
|
+
| LTNS_0054 | `SYSTEM_SETUP_TOKEN_INVALID` | Setup token missing or wrong (12.4.0+) |
|
|
319
435
|
|
|
320
436
|
---
|
|
321
437
|
|
|
@@ -1,10 +1,15 @@
|
|
|
1
|
-
import { Body, Controller, Get, Post } from '@nestjs/common';
|
|
2
|
-
import { ApiBody, ApiOperation, ApiProperty, ApiResponse, ApiTags } from '@nestjs/swagger';
|
|
1
|
+
import { Body, Controller, Get, Headers, Post } from '@nestjs/common';
|
|
2
|
+
import { ApiBody, ApiHeader, ApiOperation, ApiProperty, ApiResponse, ApiTags } from '@nestjs/swagger';
|
|
3
3
|
import { IsEmail, IsNotEmpty, IsOptional, IsString, MinLength } from 'class-validator';
|
|
4
4
|
|
|
5
5
|
import { Roles } from '../../common/decorators/roles.decorator.js';
|
|
6
6
|
import { RoleEnum } from '../../common/enums/role.enum.js';
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
CoreSystemSetupService,
|
|
9
|
+
type SystemSetupInitResult,
|
|
10
|
+
type SystemSetupStatus,
|
|
11
|
+
} from './core-system-setup.service.js';
|
|
12
|
+
import { SYSTEM_SETUP_TOKEN_HEADER } from './system-setup-http-init.helper.js';
|
|
8
13
|
|
|
9
14
|
/**
|
|
10
15
|
* DTO for system setup init request
|
|
@@ -31,8 +36,12 @@ export class SystemSetupInitDto {
|
|
|
31
36
|
* CoreSystemSetupController provides REST endpoints for initial system setup.
|
|
32
37
|
*
|
|
33
38
|
* These endpoints are public (S_EVERYONE) to allow first-time admin creation
|
|
34
|
-
* on a fresh system with zero users. The service enforces security
|
|
35
|
-
*
|
|
39
|
+
* on a fresh system with zero users. The service enforces security: the init is
|
|
40
|
+
* refused where `systemSetup.httpInit` does not open it (by default every
|
|
41
|
+
* environment except `local`, `ci` and `e2e`), and once any user exists.
|
|
42
|
+
*
|
|
43
|
+
* A subclass that overrides `createInitialAdmin()` must call
|
|
44
|
+
* `systemSetupService.assertHttpInitAllowed(token)` itself.
|
|
36
45
|
*/
|
|
37
46
|
@ApiTags('System Setup')
|
|
38
47
|
@Controller('system-setup')
|
|
@@ -55,18 +64,31 @@ export class CoreSystemSetupController {
|
|
|
55
64
|
* Create the initial admin user (only works when zero users exist)
|
|
56
65
|
*/
|
|
57
66
|
@ApiBody({ type: SystemSetupInitDto })
|
|
67
|
+
@ApiHeader({
|
|
68
|
+
description: 'The setup token, required when systemSetup.httpInit configures one (see GET /system-setup/status)',
|
|
69
|
+
name: SYSTEM_SETUP_TOKEN_HEADER,
|
|
70
|
+
required: false,
|
|
71
|
+
})
|
|
58
72
|
@ApiOperation({
|
|
59
|
-
description:
|
|
73
|
+
description:
|
|
74
|
+
'Creates the initial admin user. Only works when zero users exist in the database, and only where ' +
|
|
75
|
+
'systemSetup.httpInit opens it (by default in the local, ci and e2e environments).',
|
|
60
76
|
summary: 'Create initial admin',
|
|
61
77
|
})
|
|
62
78
|
@ApiResponse({ description: 'Initial admin created', status: 201 })
|
|
63
79
|
@ApiResponse({
|
|
64
|
-
description:
|
|
80
|
+
description:
|
|
81
|
+
'LTNS_0053: HTTP init not enabled in this environment; LTNS_0054: setup token missing or invalid; ' +
|
|
82
|
+
'LTNS_0050: users already exist, or the initial-admin setup is claimed',
|
|
65
83
|
status: 403,
|
|
66
84
|
})
|
|
67
85
|
@Post('init')
|
|
68
86
|
@Roles(RoleEnum.S_EVERYONE)
|
|
69
|
-
async createInitialAdmin(
|
|
87
|
+
async createInitialAdmin(
|
|
88
|
+
@Body() input: SystemSetupInitDto,
|
|
89
|
+
@Headers(SYSTEM_SETUP_TOKEN_HEADER) token?: string,
|
|
90
|
+
): Promise<SystemSetupInitResult> {
|
|
91
|
+
this.systemSetupService.assertHttpInitAllowed(token);
|
|
70
92
|
return this.systemSetupService.createInitialAdmin(input);
|
|
71
93
|
}
|
|
72
94
|
}
|