@lamalibre/create-lamaste 4.0.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/LICENSE.md +118 -0
- package/README.md +114 -0
- package/bin/create-lamaste.js +15 -0
- package/package.json +54 -0
- package/scripts/bundle-vendor.js +110 -0
- package/scripts/lamaste-cert-info +62 -0
- package/scripts/lamaste-certbot +96 -0
- package/scripts/lamaste-priv +693 -0
- package/src/index.js +646 -0
- package/src/lib/accounts.js +86 -0
- package/src/lib/binaries.js +137 -0
- package/src/lib/cert-help-page.js +160 -0
- package/src/lib/env.js +132 -0
- package/src/lib/http-redirect.js +38 -0
- package/src/lib/ownership.js +231 -0
- package/src/lib/relay-services.js +103 -0
- package/src/lib/secrets.js +19 -0
- package/src/lib/service-config.js +311 -0
- package/src/lib/summary.js +131 -0
- package/src/lib/wrappers.js +63 -0
- package/src/tasks/gatekeeper.js +233 -0
- package/src/tasks/harden.js +293 -0
- package/src/tasks/mtls.js +193 -0
- package/src/tasks/nginx.js +265 -0
- package/src/tasks/node.js +110 -0
- package/src/tasks/panel.js +408 -0
- package/src/tasks/redeploy.js +438 -0
- package/vendor/docs/00-introduction/how-it-works.md +447 -0
- package/vendor/docs/00-introduction/quickstart.md +472 -0
- package/vendor/docs/00-introduction/what-is-lamaste.md +112 -0
- package/vendor/docs/01-concepts/authentication.md +464 -0
- package/vendor/docs/01-concepts/authorization.md +242 -0
- package/vendor/docs/01-concepts/certificates.md +452 -0
- package/vendor/docs/01-concepts/dns-and-domains.md +343 -0
- package/vendor/docs/01-concepts/mtls.md +490 -0
- package/vendor/docs/01-concepts/nginx-reverse-proxy.md +540 -0
- package/vendor/docs/01-concepts/security-model.md +600 -0
- package/vendor/docs/01-concepts/tickets.md +414 -0
- package/vendor/docs/01-concepts/tunneling.md +367 -0
- package/vendor/docs/02-guides/agent-setup.md +194 -0
- package/vendor/docs/02-guides/certificate-management.md +341 -0
- package/vendor/docs/02-guides/cloud-provisioning.md +406 -0
- package/vendor/docs/02-guides/desktop-app-setup.md +226 -0
- package/vendor/docs/02-guides/disaster-recovery.md +464 -0
- package/vendor/docs/02-guides/first-tunnel.md +230 -0
- package/vendor/docs/02-guides/installation.md +393 -0
- package/vendor/docs/02-guides/managing-users.md +255 -0
- package/vendor/docs/02-guides/onboarding.md +244 -0
- package/vendor/docs/02-guides/standalone-plugin-with-tickets.md +375 -0
- package/vendor/docs/02-guides/static-sites.md +364 -0
- package/vendor/docs/03-architecture/e2e-three-vm-sequences.md +457 -0
- package/vendor/docs/03-architecture/installer-flow.md +151 -0
- package/vendor/docs/03-architecture/installer.md +445 -0
- package/vendor/docs/03-architecture/management-flow.md +288 -0
- package/vendor/docs/03-architecture/nginx-configuration.md +830 -0
- package/vendor/docs/03-architecture/onboarding-flow.md +164 -0
- package/vendor/docs/03-architecture/overview.md +541 -0
- package/vendor/docs/03-architecture/panel-client.md +416 -0
- package/vendor/docs/03-architecture/panel-server.md +695 -0
- package/vendor/docs/03-architecture/state-management.md +529 -0
- package/vendor/docs/03-architecture/system-overview.md +167 -0
- package/vendor/docs/04-api-reference/certificates.md +1141 -0
- package/vendor/docs/04-api-reference/gatekeeper.md +592 -0
- package/vendor/docs/04-api-reference/onboarding.md +473 -0
- package/vendor/docs/04-api-reference/overview.md +404 -0
- package/vendor/docs/04-api-reference/services.md +267 -0
- package/vendor/docs/04-api-reference/settings.md +343 -0
- package/vendor/docs/04-api-reference/sites.md +716 -0
- package/vendor/docs/04-api-reference/system.md +268 -0
- package/vendor/docs/04-api-reference/tickets.md +618 -0
- package/vendor/docs/04-api-reference/tunnels.md +417 -0
- package/vendor/docs/04-api-reference/users.md +333 -0
- package/vendor/docs/05-operations/backup-and-restore.md +342 -0
- package/vendor/docs/05-operations/monitoring.md +285 -0
- package/vendor/docs/05-operations/uninstalling.md +344 -0
- package/vendor/docs/05-operations/upgrades.md +270 -0
- package/vendor/docs/06-reference/config-files.md +1080 -0
- package/vendor/docs/06-reference/glossary.md +528 -0
- package/vendor/docs/06-reference/installer-flags.md +260 -0
- package/vendor/docs/06-reference/ports-and-services.md +244 -0
- package/vendor/docs/06-reference/troubleshooting.md +665 -0
- package/vendor/docs/_index.json +233 -0
- package/vendor/docs/gatekeeper-design.md +953 -0
- package/vendor/docs/package.json +15 -0
- package/vendor/docs/todo/lib-cli-desktop-refactor.md +641 -0
- package/vendor/gatekeeper/dist/boot.d.ts +1 -0
- package/vendor/gatekeeper/dist/boot.d.ts.map +1 -0
- package/vendor/gatekeeper/dist/boot.js +17 -0
- package/vendor/gatekeeper/dist/boot.js.map +1 -0
- package/vendor/gatekeeper/dist/cli/commands/access.d.ts +2 -0
- package/vendor/gatekeeper/dist/cli/commands/access.d.ts.map +1 -0
- package/vendor/gatekeeper/dist/cli/commands/access.js +33 -0
- package/vendor/gatekeeper/dist/cli/commands/access.js.map +1 -0
- package/vendor/gatekeeper/dist/cli/commands/grants.d.ts +2 -0
- package/vendor/gatekeeper/dist/cli/commands/grants.d.ts.map +1 -0
- package/vendor/gatekeeper/dist/cli/commands/grants.js +110 -0
- package/vendor/gatekeeper/dist/cli/commands/grants.js.map +1 -0
- package/vendor/gatekeeper/dist/cli/commands/groups.d.ts +2 -0
- package/vendor/gatekeeper/dist/cli/commands/groups.d.ts.map +1 -0
- package/vendor/gatekeeper/dist/cli/commands/groups.js +98 -0
- package/vendor/gatekeeper/dist/cli/commands/groups.js.map +1 -0
- package/vendor/gatekeeper/dist/cli/index.d.ts +3 -0
- package/vendor/gatekeeper/dist/cli/index.d.ts.map +1 -0
- package/vendor/gatekeeper/dist/cli/index.js +45 -0
- package/vendor/gatekeeper/dist/cli/index.js.map +1 -0
- package/vendor/gatekeeper/dist/index.d.ts +9 -0
- package/vendor/gatekeeper/dist/index.d.ts.map +1 -0
- package/vendor/gatekeeper/dist/index.js +13 -0
- package/vendor/gatekeeper/dist/index.js.map +1 -0
- package/vendor/gatekeeper/dist/lib/authz.d.ts +16 -0
- package/vendor/gatekeeper/dist/lib/authz.d.ts.map +1 -0
- package/vendor/gatekeeper/dist/lib/authz.js +42 -0
- package/vendor/gatekeeper/dist/lib/authz.js.map +1 -0
- package/vendor/gatekeeper/dist/lib/cache-bust.d.ts +15 -0
- package/vendor/gatekeeper/dist/lib/cache-bust.d.ts.map +1 -0
- package/vendor/gatekeeper/dist/lib/cache-bust.js +19 -0
- package/vendor/gatekeeper/dist/lib/cache-bust.js.map +1 -0
- package/vendor/gatekeeper/dist/lib/constants.d.ts +82 -0
- package/vendor/gatekeeper/dist/lib/constants.d.ts.map +1 -0
- package/vendor/gatekeeper/dist/lib/constants.js +79 -0
- package/vendor/gatekeeper/dist/lib/constants.js.map +1 -0
- package/vendor/gatekeeper/dist/lib/grants.d.ts +32 -0
- package/vendor/gatekeeper/dist/lib/grants.d.ts.map +1 -0
- package/vendor/gatekeeper/dist/lib/grants.js +314 -0
- package/vendor/gatekeeper/dist/lib/grants.js.map +1 -0
- package/vendor/gatekeeper/dist/lib/groups.d.ts +10 -0
- package/vendor/gatekeeper/dist/lib/groups.d.ts.map +1 -0
- package/vendor/gatekeeper/dist/lib/groups.js +247 -0
- package/vendor/gatekeeper/dist/lib/groups.js.map +1 -0
- package/vendor/gatekeeper/dist/lib/migration.d.ts +17 -0
- package/vendor/gatekeeper/dist/lib/migration.d.ts.map +1 -0
- package/vendor/gatekeeper/dist/lib/migration.js +79 -0
- package/vendor/gatekeeper/dist/lib/migration.js.map +1 -0
- package/vendor/gatekeeper/dist/lib/migrations/gatekeeper/0001_groups.sql +7 -0
- package/vendor/gatekeeper/dist/lib/migrations/gatekeeper/0002_access_grants.sql +14 -0
- package/vendor/gatekeeper/dist/lib/migrations/gatekeeper/0003_access_request_log.sql +9 -0
- package/vendor/gatekeeper/dist/lib/migrations/migrations/gatekeeper/0001_groups.sql +7 -0
- package/vendor/gatekeeper/dist/lib/migrations/migrations/gatekeeper/0002_access_grants.sql +14 -0
- package/vendor/gatekeeper/dist/lib/migrations/migrations/gatekeeper/0003_access_request_log.sql +9 -0
- package/vendor/gatekeeper/dist/lib/state-db.d.ts +16 -0
- package/vendor/gatekeeper/dist/lib/state-db.d.ts.map +1 -0
- package/vendor/gatekeeper/dist/lib/state-db.js +154 -0
- package/vendor/gatekeeper/dist/lib/state-db.js.map +1 -0
- package/vendor/gatekeeper/dist/lib/templates.d.ts +21 -0
- package/vendor/gatekeeper/dist/lib/templates.d.ts.map +1 -0
- package/vendor/gatekeeper/dist/lib/templates.js +151 -0
- package/vendor/gatekeeper/dist/lib/templates.js.map +1 -0
- package/vendor/gatekeeper/dist/lib/types.d.ts +178 -0
- package/vendor/gatekeeper/dist/lib/types.d.ts.map +1 -0
- package/vendor/gatekeeper/dist/lib/types.js +2 -0
- package/vendor/gatekeeper/dist/lib/types.js.map +1 -0
- package/vendor/gatekeeper/dist/server/index.d.ts +15 -0
- package/vendor/gatekeeper/dist/server/index.d.ts.map +1 -0
- package/vendor/gatekeeper/dist/server/index.js +251 -0
- package/vendor/gatekeeper/dist/server/index.js.map +1 -0
- package/vendor/gatekeeper/dist/server/routes/access-request.d.ts +7 -0
- package/vendor/gatekeeper/dist/server/routes/access-request.d.ts.map +1 -0
- package/vendor/gatekeeper/dist/server/routes/access-request.js +17 -0
- package/vendor/gatekeeper/dist/server/routes/access-request.js.map +1 -0
- package/vendor/gatekeeper/dist/server/routes/authz.d.ts +3 -0
- package/vendor/gatekeeper/dist/server/routes/authz.d.ts.map +1 -0
- package/vendor/gatekeeper/dist/server/routes/authz.js +279 -0
- package/vendor/gatekeeper/dist/server/routes/authz.js.map +1 -0
- package/vendor/gatekeeper/dist/server/routes/diagnostic.d.ts +13 -0
- package/vendor/gatekeeper/dist/server/routes/diagnostic.d.ts.map +1 -0
- package/vendor/gatekeeper/dist/server/routes/diagnostic.js +249 -0
- package/vendor/gatekeeper/dist/server/routes/diagnostic.js.map +1 -0
- package/vendor/gatekeeper/dist/server/routes/grants.d.ts +3 -0
- package/vendor/gatekeeper/dist/server/routes/grants.d.ts.map +1 -0
- package/vendor/gatekeeper/dist/server/routes/grants.js +112 -0
- package/vendor/gatekeeper/dist/server/routes/grants.js.map +1 -0
- package/vendor/gatekeeper/dist/server/routes/groups.d.ts +3 -0
- package/vendor/gatekeeper/dist/server/routes/groups.d.ts.map +1 -0
- package/vendor/gatekeeper/dist/server/routes/groups.js +139 -0
- package/vendor/gatekeeper/dist/server/routes/groups.js.map +1 -0
- package/vendor/gatekeeper/package.json +49 -0
- package/vendor/server/bin/lamaste-server.js +204 -0
- package/vendor/server/package.json +31 -0
- package/vendor/server/src/commands/certs.js +224 -0
- package/vendor/server/src/commands/chisel.js +132 -0
- package/vendor/server/src/commands/logs.js +49 -0
- package/vendor/server/src/commands/plugins.js +321 -0
- package/vendor/server/src/commands/reset-admin.js +478 -0
- package/vendor/server/src/commands/restart.js +94 -0
- package/vendor/server/src/commands/sites.js +257 -0
- package/vendor/server/src/commands/status.js +158 -0
- package/vendor/server/src/commands/tunnels.js +365 -0
- package/vendor/server/src/commands/uninstall.js +326 -0
- package/vendor/server/src/config.js +38 -0
- package/vendor/server/src/exec.js +26 -0
- package/vendor/server/src/logger.js +39 -0
- package/vendor/server/src/ndjson.js +56 -0
- package/vendor/server/src/panel-api.js +85 -0
- package/vendor/server/src/state.js +45 -0
- package/vendor/server-ui/dist/assets/index-B0nWuUyL.js +257 -0
- package/vendor/server-ui/dist/assets/index-BqWWFlTU.css +1 -0
- package/vendor/server-ui/dist/index.html +13 -0
- package/vendor/serverd/package.json +44 -0
- package/vendor/serverd/src/boot.js +13 -0
- package/vendor/serverd/src/index.js +191 -0
- package/vendor/serverd/src/lib/app-error.js +14 -0
- package/vendor/serverd/src/lib/authelia.js +85 -0
- package/vendor/serverd/src/lib/certbot.js +74 -0
- package/vendor/serverd/src/lib/chisel-args.js +6 -0
- package/vendor/serverd/src/lib/chisel-reconcile.js +232 -0
- package/vendor/serverd/src/lib/chisel-runtime.js +46 -0
- package/vendor/serverd/src/lib/chisel-users.js +109 -0
- package/vendor/serverd/src/lib/chisel.js +81 -0
- package/vendor/serverd/src/lib/config.js +190 -0
- package/vendor/serverd/src/lib/constants.js +8 -0
- package/vendor/serverd/src/lib/csr-signing.js +331 -0
- package/vendor/serverd/src/lib/enrollment.js +302 -0
- package/vendor/serverd/src/lib/files.js +19 -0
- package/vendor/serverd/src/lib/gatekeeper-client.js +135 -0
- package/vendor/serverd/src/lib/invite-page.js +281 -0
- package/vendor/serverd/src/lib/ip-utils.js +79 -0
- package/vendor/serverd/src/lib/migrations/state/0001_enrollment_tokens.sql +17 -0
- package/vendor/serverd/src/lib/migrations/state/0002_tickets.sql +70 -0
- package/vendor/serverd/src/lib/migrations/state/0003_plugins.sql +15 -0
- package/vendor/serverd/src/lib/migrations/state/0004_agents.sql +19 -0
- package/vendor/serverd/src/lib/mtls.js +795 -0
- package/vendor/serverd/src/lib/nginx.js +1090 -0
- package/vendor/serverd/src/lib/pki-paths.js +41 -0
- package/vendor/serverd/src/lib/pki-sign.js +103 -0
- package/vendor/serverd/src/lib/plugins.js +538 -0
- package/vendor/serverd/src/lib/push-install.js +390 -0
- package/vendor/serverd/src/lib/revocation.js +112 -0
- package/vendor/serverd/src/lib/services.js +131 -0
- package/vendor/serverd/src/lib/session.js +151 -0
- package/vendor/serverd/src/lib/state-db.js +130 -0
- package/vendor/serverd/src/lib/state.js +140 -0
- package/vendor/serverd/src/lib/storage.js +63 -0
- package/vendor/serverd/src/lib/system-stats.js +58 -0
- package/vendor/serverd/src/lib/tickets.js +1556 -0
- package/vendor/serverd/src/lib/totp.js +76 -0
- package/vendor/serverd/src/lib/tunnel-deps.js +41 -0
- package/vendor/serverd/src/lib/user-access-session.js +142 -0
- package/vendor/serverd/src/lib/user-access.js +52 -0
- package/vendor/serverd/src/middleware/errors.js +63 -0
- package/vendor/serverd/src/middleware/mtls.js +120 -0
- package/vendor/serverd/src/middleware/onboarding-guard.js +30 -0
- package/vendor/serverd/src/middleware/role-guard.js +58 -0
- package/vendor/serverd/src/middleware/twofa-session.js +65 -0
- package/vendor/serverd/src/middleware/user-access-session.js +52 -0
- package/vendor/serverd/src/routes/enrollment.js +120 -0
- package/vendor/serverd/src/routes/health.js +22 -0
- package/vendor/serverd/src/routes/invite.js +163 -0
- package/vendor/serverd/src/routes/management/agents.js +182 -0
- package/vendor/serverd/src/routes/management/certs.js +911 -0
- package/vendor/serverd/src/routes/management/gatekeeper-proxy.js +160 -0
- package/vendor/serverd/src/routes/management/identity.js +107 -0
- package/vendor/serverd/src/routes/management/invitations.js +166 -0
- package/vendor/serverd/src/routes/management/logs.js +218 -0
- package/vendor/serverd/src/routes/management/plugins.js +560 -0
- package/vendor/serverd/src/routes/management/services.js +63 -0
- package/vendor/serverd/src/routes/management/settings.js +325 -0
- package/vendor/serverd/src/routes/management/sites.js +522 -0
- package/vendor/serverd/src/routes/management/storage.js +192 -0
- package/vendor/serverd/src/routes/management/system.js +201 -0
- package/vendor/serverd/src/routes/management/tickets.js +457 -0
- package/vendor/serverd/src/routes/management/tunnels.js +709 -0
- package/vendor/serverd/src/routes/management/users.js +377 -0
- package/vendor/serverd/src/routes/management.js +36 -0
- package/vendor/serverd/src/routes/onboarding/dns.js +101 -0
- package/vendor/serverd/src/routes/onboarding/domain.js +42 -0
- package/vendor/serverd/src/routes/onboarding/index.js +18 -0
- package/vendor/serverd/src/routes/onboarding/provision.js +327 -0
- package/vendor/serverd/src/routes/onboarding/status.js +12 -0
- package/vendor/serverd/src/routes/plugin-router.js +79 -0
- package/vendor/serverd/src/routes/user-access.js +389 -0
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
# Services API
|
|
2
|
+
|
|
3
|
+
> Monitor and control the systemd services that make up the Lamaste stack, with live log streaming via WebSocket.
|
|
4
|
+
|
|
5
|
+
## In Plain English
|
|
6
|
+
|
|
7
|
+
Lamaste runs several background services on the droplet: nginx (the reverse proxy), Chisel (the tunnel server), Authelia (the login system for tunneled apps), and the panel server itself. The services API lets you check whether each service is running, start or stop them, restart them, and watch their log output in real time.
|
|
8
|
+
|
|
9
|
+
Think of it as a simplified version of the `systemctl` and `journalctl` commands, accessible from your browser.
|
|
10
|
+
|
|
11
|
+
## Authentication
|
|
12
|
+
|
|
13
|
+
All service endpoints require a valid mTLS client certificate and a completed onboarding. See the [API Overview](./overview.md) for details.
|
|
14
|
+
|
|
15
|
+
If onboarding is not complete, all endpoints return `503 Service Unavailable`.
|
|
16
|
+
|
|
17
|
+
In addition to admin certificates, agent certificates with the appropriate capabilities can access these endpoints:
|
|
18
|
+
|
|
19
|
+
- `services:read` — grants access to `GET /api/services` (list statuses)
|
|
20
|
+
- `services:write` — grants access to `POST /api/services/:name/:action` (control services)
|
|
21
|
+
|
|
22
|
+
## Service Whitelist
|
|
23
|
+
|
|
24
|
+
Only these services can be managed through the API:
|
|
25
|
+
|
|
26
|
+
| Service Name | Description |
|
|
27
|
+
| --------------------------- | ------------------------------------------------ |
|
|
28
|
+
| `nginx` | Reverse proxy, TLS termination, mTLS enforcement |
|
|
29
|
+
| `chisel` | WebSocket tunnel server |
|
|
30
|
+
| `authelia` | TOTP two-factor authentication |
|
|
31
|
+
| `lamalibre-lamaste-serverd` | The panel server itself (Fastify) |
|
|
32
|
+
|
|
33
|
+
Requests for any other service name are rejected with a 400 error. This whitelist prevents arbitrary systemd service manipulation.
|
|
34
|
+
|
|
35
|
+
## Safety Rules
|
|
36
|
+
|
|
37
|
+
- **Cannot stop the panel from the UI.** Calling `POST /api/services/lamalibre-lamaste-serverd/stop` returns a 400 error because stopping the panel would terminate the API session — you would lose the ability to restart it without SSH access.
|
|
38
|
+
- **All actions use sudo.** The panel runs as the `lamaste` user. Service control commands use `sudo systemctl` via sudoers rules that restrict which services and actions are permitted.
|
|
39
|
+
|
|
40
|
+
## Endpoints
|
|
41
|
+
|
|
42
|
+
### `GET /api/services`
|
|
43
|
+
|
|
44
|
+
Returns the status and uptime of all managed services. Statuses are queried in parallel from systemd.
|
|
45
|
+
|
|
46
|
+
**Request:**
|
|
47
|
+
|
|
48
|
+
No request body.
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
curl -s --cert client.p12:password \
|
|
52
|
+
https://203.0.113.42:9292/api/services | jq
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
**Response (200):**
|
|
56
|
+
|
|
57
|
+
```json
|
|
58
|
+
{
|
|
59
|
+
"services": [
|
|
60
|
+
{
|
|
61
|
+
"name": "nginx",
|
|
62
|
+
"status": "active",
|
|
63
|
+
"uptime": "5d 3h 20m"
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
"name": "chisel",
|
|
67
|
+
"status": "active",
|
|
68
|
+
"uptime": "5d 3h 19m"
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
"name": "authelia",
|
|
72
|
+
"status": "active",
|
|
73
|
+
"uptime": "5d 3h 18m"
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
"name": "lamalibre-lamaste-serverd",
|
|
77
|
+
"status": "active",
|
|
78
|
+
"uptime": "2h 45m"
|
|
79
|
+
}
|
|
80
|
+
]
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
| Field | Type | Description |
|
|
85
|
+
| -------- | ---------------- | -------------------------------------------------------------------- |
|
|
86
|
+
| `name` | `string` | systemd service name |
|
|
87
|
+
| `status` | `string` | One of: `active`, `inactive`, `failed`, `unknown` |
|
|
88
|
+
| `uptime` | `string \| null` | Human-readable uptime (e.g., `"2d 5h 30m"`), or `null` if not active |
|
|
89
|
+
|
|
90
|
+
The uptime is calculated from the `ActiveEnterTimestamp` reported by systemd and formatted as a human-readable duration. If the timestamp cannot be read, uptime is `null` even for active services.
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
### `POST /api/services/:name/:action`
|
|
95
|
+
|
|
96
|
+
Executes a systemctl action on a managed service. The command runs with a 30-second timeout.
|
|
97
|
+
|
|
98
|
+
**URL parameters:**
|
|
99
|
+
|
|
100
|
+
| Parameter | Type | Validation | Description |
|
|
101
|
+
| --------- | -------- | -------------------------------------------- | ------------------ |
|
|
102
|
+
| `:name` | `string` | Must be in the service whitelist | Service to control |
|
|
103
|
+
| `:action` | `string` | One of: `start`, `stop`, `restart`, `reload` | Action to execute |
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
curl -s --cert client.p12:password \
|
|
107
|
+
-X POST \
|
|
108
|
+
https://203.0.113.42:9292/api/services/nginx/restart | jq
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
**Response (200):**
|
|
112
|
+
|
|
113
|
+
```json
|
|
114
|
+
{
|
|
115
|
+
"ok": true,
|
|
116
|
+
"name": "nginx",
|
|
117
|
+
"action": "restart"
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
**Errors:**
|
|
122
|
+
|
|
123
|
+
| Status | Body | When |
|
|
124
|
+
| ------ | ----------------------------------------------------------------------------------------- | ---------------------------------------------- |
|
|
125
|
+
| 400 | `{"error":"Unknown service"}` | Service name not in whitelist |
|
|
126
|
+
| 400 | `{"error":"Invalid action"}` | Action not one of start/stop/restart/reload |
|
|
127
|
+
| 400 | `{"error":"Cannot stop the panel service from the UI — it would terminate this session"}` | Attempting to stop `lamalibre-lamaste-serverd` |
|
|
128
|
+
| 400 | `{"error":"chisel does not support reload — use restart"}` | `reload` for `chisel` or the panel service |
|
|
129
|
+
| 400 | `{"error":"Validation failed","details":{"issues":[...]}}` | Zod validation of params failed |
|
|
130
|
+
| 500 | `{"error":"Failed to restart nginx","details":"..."}` | systemctl command failed |
|
|
131
|
+
|
|
132
|
+
### Available Actions
|
|
133
|
+
|
|
134
|
+
| Action | systemctl Command | Notes |
|
|
135
|
+
| --------- | ------------------------------- | --------------------------------------------------------------------------- |
|
|
136
|
+
| `start` | `sudo systemctl start <name>` | Start a stopped service |
|
|
137
|
+
| `stop` | `sudo systemctl stop <name>` | Stop a running service (blocked for `lamalibre-lamaste-serverd`) |
|
|
138
|
+
| `restart` | `sudo systemctl restart <name>` | Stop then start a service |
|
|
139
|
+
| `reload` | `sudo systemctl reload <name>` | Reload configuration without full restart (supported by nginx and authelia) |
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
### `WS /api/services/:name/logs`
|
|
144
|
+
|
|
145
|
+
WebSocket endpoint that streams live logs from journalctl for a specific service. The connection tails the last 100 log lines and then follows new output in real time.
|
|
146
|
+
|
|
147
|
+
**URL parameters:**
|
|
148
|
+
|
|
149
|
+
| Parameter | Type | Validation | Description |
|
|
150
|
+
| --------- | -------- | -------------------------------- | ------------------------ |
|
|
151
|
+
| `:name` | `string` | Must be in the service whitelist | Service to tail logs for |
|
|
152
|
+
|
|
153
|
+
If the service name is not in the whitelist, the WebSocket is closed immediately with code 1008 ("Policy Violation") and the reason "Unknown service".
|
|
154
|
+
|
|
155
|
+
**Connection:**
|
|
156
|
+
|
|
157
|
+
```javascript
|
|
158
|
+
const ws = new WebSocket('wss://203.0.113.42:9292/api/services/nginx/logs');
|
|
159
|
+
|
|
160
|
+
ws.onmessage = (event) => {
|
|
161
|
+
const entry = JSON.parse(event.data);
|
|
162
|
+
console.log(`[${entry.timestamp}] ${entry.message}`);
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
ws.onclose = (event) => {
|
|
166
|
+
console.log(`Log stream closed: ${event.reason}`);
|
|
167
|
+
};
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
**Message format:**
|
|
171
|
+
|
|
172
|
+
Each message is a JSON object with a timestamp and message:
|
|
173
|
+
|
|
174
|
+
```json
|
|
175
|
+
{
|
|
176
|
+
"timestamp": "2026-03-13T14:30:45+0000",
|
|
177
|
+
"message": "hostname nginx[1234]: 203.0.113.42 - - [13/Mar/2026:14:30:45 +0000] \"GET /api/health HTTP/1.1\" 200 32"
|
|
178
|
+
}
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
| Field | Type | Description |
|
|
182
|
+
| ----------- | -------- | -------------------------------------------------------------- |
|
|
183
|
+
| `timestamp` | `string` | ISO timestamp from journalctl `--output=short-iso` format |
|
|
184
|
+
| `message` | `string` | The rest of the log line (hostname, process, PID, and message) |
|
|
185
|
+
|
|
186
|
+
If the timestamp cannot be parsed from the log line, `timestamp` is an empty string and `message` contains the entire raw line.
|
|
187
|
+
|
|
188
|
+
**Special messages:**
|
|
189
|
+
|
|
190
|
+
```json
|
|
191
|
+
{
|
|
192
|
+
"timestamp": "2026-03-13T14:30:45.000Z",
|
|
193
|
+
"message": "[Error: Failed to start log stream for nginx]"
|
|
194
|
+
}
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
```json
|
|
198
|
+
{
|
|
199
|
+
"timestamp": "2026-03-13T14:30:45.000Z",
|
|
200
|
+
"message": "[Log stream ended]"
|
|
201
|
+
}
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
**Close codes:**
|
|
205
|
+
|
|
206
|
+
| Code | Reason | When |
|
|
207
|
+
| ---- | -------------------------- | -------------------------------------- |
|
|
208
|
+
| 1000 | Log stream ended | journalctl process exited normally |
|
|
209
|
+
| 1008 | Unknown service | Service name not in whitelist |
|
|
210
|
+
| 1011 | Failed to spawn journalctl | Could not start the journalctl process |
|
|
211
|
+
|
|
212
|
+
**Implementation details:**
|
|
213
|
+
|
|
214
|
+
- Uses `journalctl -u <name> -f -n 100 --output=short-iso`
|
|
215
|
+
- The `-n 100` flag sends the last 100 log lines on connect (backfill)
|
|
216
|
+
- The `-f` flag follows new output in real time
|
|
217
|
+
- The journalctl process is killed (SIGTERM) when the WebSocket closes
|
|
218
|
+
- All active journalctl processes are cleaned up on server shutdown
|
|
219
|
+
|
|
220
|
+
## Quick Reference
|
|
221
|
+
|
|
222
|
+
| Method | Path | Description |
|
|
223
|
+
| ------ | ----------------------------- | ------------------------------------- |
|
|
224
|
+
| GET | `/api/services` | List all service statuses and uptimes |
|
|
225
|
+
| POST | `/api/services/:name/:action` | Execute start/stop/restart/reload |
|
|
226
|
+
| WS | `/api/services/:name/logs` | Stream live logs via WebSocket |
|
|
227
|
+
|
|
228
|
+
### Service Status Object Shape
|
|
229
|
+
|
|
230
|
+
```json
|
|
231
|
+
{
|
|
232
|
+
"name": "nginx",
|
|
233
|
+
"status": "active",
|
|
234
|
+
"uptime": "5d 3h 20m"
|
|
235
|
+
}
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
### Allowed Combinations
|
|
239
|
+
|
|
240
|
+
| Service | start | stop | restart | reload |
|
|
241
|
+
| --------------------------- | ----- | ------ | ------- | ------ |
|
|
242
|
+
| `nginx` | Yes | Yes | Yes | Yes |
|
|
243
|
+
| `chisel` | Yes | Yes | Yes | **No** |
|
|
244
|
+
| `authelia` | Yes | Yes | Yes | Yes |
|
|
245
|
+
| `lamalibre-lamaste-serverd` | Yes | **No** | Yes | **No** |
|
|
246
|
+
|
|
247
|
+
### curl Cheat Sheet
|
|
248
|
+
|
|
249
|
+
```bash
|
|
250
|
+
# List service statuses
|
|
251
|
+
curl -s --cert client.p12:password \
|
|
252
|
+
https://203.0.113.42:9292/api/services | jq
|
|
253
|
+
|
|
254
|
+
# Restart nginx
|
|
255
|
+
curl -s --cert client.p12:password \
|
|
256
|
+
-X POST \
|
|
257
|
+
https://203.0.113.42:9292/api/services/nginx/restart | jq
|
|
258
|
+
|
|
259
|
+
# Reload Authelia (after manual config changes)
|
|
260
|
+
curl -s --cert client.p12:password \
|
|
261
|
+
-X POST \
|
|
262
|
+
https://203.0.113.42:9292/api/services/authelia/reload | jq
|
|
263
|
+
|
|
264
|
+
# Stream nginx logs (requires wscat or similar)
|
|
265
|
+
wscat -c wss://203.0.113.42:9292/api/services/nginx/logs \
|
|
266
|
+
--cert client.pem --key client-key.pem
|
|
267
|
+
```
|
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
# Settings API
|
|
2
|
+
|
|
3
|
+
> Two-factor authentication (2FA) configuration for the admin panel. All endpoints are admin-only and fall under the management route group.
|
|
4
|
+
|
|
5
|
+
## In Plain English
|
|
6
|
+
|
|
7
|
+
The settings API lets you enable, configure, and manage TOTP-based two-factor authentication for the admin panel. When 2FA is enabled, every admin request must carry both a valid mTLS client certificate and a valid session cookie. The session cookie is issued after the admin verifies a TOTP code and lasts for 12 hours (or 2 hours of inactivity, whichever comes first).
|
|
8
|
+
|
|
9
|
+
The lifecycle is:
|
|
10
|
+
|
|
11
|
+
1. **Check status** — `GET /api/settings/2fa` tells you whether 2FA is enabled.
|
|
12
|
+
2. **Setup** — `POST /api/settings/2fa/setup` generates a TOTP secret and returns a `otpauth://` URI for scanning with an authenticator app.
|
|
13
|
+
3. **Confirm** — `POST /api/settings/2fa/confirm` verifies the first code from the authenticator app, enabling 2FA and disabling the IP-based vhost.
|
|
14
|
+
4. **Verify (ongoing)** — `POST /api/settings/2fa/verify` is called at the start of each admin session to exchange a TOTP code for a session cookie.
|
|
15
|
+
5. **Disable** — `POST /api/settings/2fa/disable` turns off 2FA and re-enables the IP-based vhost. Requires a valid 2FA session and a TOTP code.
|
|
16
|
+
|
|
17
|
+
## Authentication
|
|
18
|
+
|
|
19
|
+
All endpoints require a valid mTLS admin certificate. The **status** (`GET /api/settings/2fa`) and **verify** (`POST /api/settings/2fa/verify`) endpoints are exempt from the 2FA session requirement — they must be accessible before the admin has a session cookie. All other endpoints require a valid 2FA session when 2FA is enabled.
|
|
20
|
+
|
|
21
|
+
**Note:** Agent certificates bypass 2FA entirely — the 2FA session middleware skips all requests where `certRole` is `agent`. Only admin certificate holders are subject to the 2FA requirement.
|
|
22
|
+
|
|
23
|
+
## Rate Limiting
|
|
24
|
+
|
|
25
|
+
The `/confirm`, `/verify`, and `/disable` endpoints enforce per-IP rate limiting:
|
|
26
|
+
|
|
27
|
+
- **5 attempts** per 2-minute sliding window
|
|
28
|
+
- **5-minute ban** once the limit is exceeded
|
|
29
|
+
|
|
30
|
+
Rate-limited requests receive:
|
|
31
|
+
|
|
32
|
+
**Response (429):**
|
|
33
|
+
|
|
34
|
+
```json
|
|
35
|
+
{
|
|
36
|
+
"error": "Too many attempts. Try again later.",
|
|
37
|
+
"retryAfter": 300
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
| Field | Type | Description |
|
|
42
|
+
| ------------ | -------- | ----------------------------- |
|
|
43
|
+
| `retryAfter` | `number` | Seconds until the ban expires |
|
|
44
|
+
|
|
45
|
+
## Session Cookie
|
|
46
|
+
|
|
47
|
+
When `POST /api/settings/2fa/verify` succeeds, the server sets a `lamaste_2fa_session` cookie:
|
|
48
|
+
|
|
49
|
+
| Property | Value |
|
|
50
|
+
| ------------ | --------------------- |
|
|
51
|
+
| Name | `lamaste_2fa_session` |
|
|
52
|
+
| Signing | HMAC-SHA256 |
|
|
53
|
+
| HttpOnly | Yes |
|
|
54
|
+
| Secure | Yes |
|
|
55
|
+
| SameSite | Strict |
|
|
56
|
+
| Absolute TTL | 12 hours |
|
|
57
|
+
| Inactivity | 2 hours |
|
|
58
|
+
|
|
59
|
+
If a request arrives with a valid mTLS certificate but without a valid 2FA session cookie (when 2FA is enabled), the API returns:
|
|
60
|
+
|
|
61
|
+
**Response (401):**
|
|
62
|
+
|
|
63
|
+
```json
|
|
64
|
+
{
|
|
65
|
+
"error": "2fa_required"
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Endpoints
|
|
70
|
+
|
|
71
|
+
### `GET /api/settings/2fa`
|
|
72
|
+
|
|
73
|
+
Returns the current 2FA status. This endpoint is exempt from the 2FA session requirement, so the UI can check whether to show the verification prompt.
|
|
74
|
+
|
|
75
|
+
**Request:**
|
|
76
|
+
|
|
77
|
+
No request body.
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
curl -s --cert client.p12:password \
|
|
81
|
+
https://203.0.113.42:9292/api/settings/2fa | jq
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
**Response (200):**
|
|
85
|
+
|
|
86
|
+
```json
|
|
87
|
+
{
|
|
88
|
+
"enabled": false,
|
|
89
|
+
"setupComplete": false
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
| Field | Type | Description |
|
|
94
|
+
| --------------- | --------- | -------------------------------------------------- |
|
|
95
|
+
| `enabled` | `boolean` | Whether 2FA is currently active |
|
|
96
|
+
| `setupComplete` | `boolean` | Whether the initial setup and confirmation is done |
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
### `POST /api/settings/2fa/setup`
|
|
101
|
+
|
|
102
|
+
Generates a new TOTP secret and returns the provisioning URI for scanning with an authenticator app (Google Authenticator, 1Password, etc.) and a manual entry key for apps that do not support QR scanning.
|
|
103
|
+
|
|
104
|
+
Requires that a domain has been configured on the panel (enabling 2FA disables IP:9292 access, so a domain is required for continued access).
|
|
105
|
+
|
|
106
|
+
**Request:**
|
|
107
|
+
|
|
108
|
+
No request body.
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
curl -s -X POST --cert client.p12:password \
|
|
112
|
+
https://203.0.113.42:9292/api/settings/2fa/setup | jq
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
**Response (200):**
|
|
116
|
+
|
|
117
|
+
```json
|
|
118
|
+
{
|
|
119
|
+
"uri": "otpauth://totp/Lamaste%20Panel:admin?secret=JBSWY3DPEHPK3PXP&issuer=Lamaste%20Panel",
|
|
120
|
+
"manualKey": "JBSWY3DPEHPK3PXP"
|
|
121
|
+
}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
| Field | Type | Description |
|
|
125
|
+
| ----------- | -------- | ------------------------------------------------------ |
|
|
126
|
+
| `uri` | `string` | `otpauth://` URI — encode as QR code for scanning |
|
|
127
|
+
| `manualKey` | `string` | Base32-encoded secret for manual entry in the auth app |
|
|
128
|
+
|
|
129
|
+
**Errors:**
|
|
130
|
+
|
|
131
|
+
| Status | Body | When |
|
|
132
|
+
| ------ | ---------------------------------------------------------------------------------------- | ------------------------------ |
|
|
133
|
+
| 400 | `{"error": "Domain must be configured before enabling 2FA", "details": {"hint": "..."}}` | No domain has been provisioned |
|
|
134
|
+
| 409 | `{"error": "2FA is already enabled"}` | 2FA is already active |
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
### `POST /api/settings/2fa/confirm`
|
|
139
|
+
|
|
140
|
+
Verifies the first TOTP code after setup, completing the 2FA activation. Once confirmed, 2FA is enabled and the IP-based vhost is disabled (all access goes through the domain vhost). This is a one-time operation during setup.
|
|
141
|
+
|
|
142
|
+
**Request:**
|
|
143
|
+
|
|
144
|
+
```json
|
|
145
|
+
{
|
|
146
|
+
"code": "123456"
|
|
147
|
+
}
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
| Field | Type | Required | Description |
|
|
151
|
+
| ------ | -------- | -------- | ---------------------------------------- |
|
|
152
|
+
| `code` | `string` | Yes | 6-digit TOTP code from authenticator app |
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
curl -s -X POST --cert client.p12:password \
|
|
156
|
+
-H 'Content-Type: application/json' \
|
|
157
|
+
-d '{"code":"123456"}' \
|
|
158
|
+
https://203.0.113.42:9292/api/settings/2fa/confirm | jq
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
**Response (200):**
|
|
162
|
+
|
|
163
|
+
```json
|
|
164
|
+
{
|
|
165
|
+
"enabled": true
|
|
166
|
+
}
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
The response also sets the `lamaste_2fa_session` cookie (see [Session Cookie](#session-cookie)), so no separate `/verify` call is needed for the initial session.
|
|
170
|
+
|
|
171
|
+
**Errors:**
|
|
172
|
+
|
|
173
|
+
| Status | Body | When |
|
|
174
|
+
| ------ | ----------------------------------------------------------------------------- | --------------------------------- |
|
|
175
|
+
| 400 | `{"error": "Code must be exactly 6 digits"}` | Code format invalid |
|
|
176
|
+
| 400 | `{"error": "No 2FA setup in progress. Call POST /settings/2fa/setup first."}` | No secret has been generated yet |
|
|
177
|
+
| 401 | `{"error": "Invalid TOTP code"}` | TOTP code is incorrect or expired |
|
|
178
|
+
| 409 | `{"error": "2FA is already enabled"}` | 2FA is already active |
|
|
179
|
+
| 429 | `{"error": "Too many attempts. Try again later."}` | Rate limit exceeded |
|
|
180
|
+
| 500 | `{"error": "Failed to disable IP vhost. 2FA was not enabled."}` | nginx config update failed |
|
|
181
|
+
|
|
182
|
+
---
|
|
183
|
+
|
|
184
|
+
### `POST /api/settings/2fa/verify`
|
|
185
|
+
|
|
186
|
+
Verifies a TOTP code and issues a session cookie. Called at the start of each admin session when 2FA is enabled. This endpoint is exempt from the 2FA session requirement.
|
|
187
|
+
|
|
188
|
+
**Request:**
|
|
189
|
+
|
|
190
|
+
```json
|
|
191
|
+
{
|
|
192
|
+
"code": "123456"
|
|
193
|
+
}
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
| Field | Type | Required | Description |
|
|
197
|
+
| ------ | -------- | -------- | ---------------------------------------- |
|
|
198
|
+
| `code` | `string` | Yes | 6-digit TOTP code from authenticator app |
|
|
199
|
+
|
|
200
|
+
```bash
|
|
201
|
+
curl -s -X POST --cert client.p12:password \
|
|
202
|
+
-H 'Content-Type: application/json' \
|
|
203
|
+
-d '{"code":"123456"}' \
|
|
204
|
+
https://203.0.113.42:9292/api/settings/2fa/verify | jq
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
**Response (200):**
|
|
208
|
+
|
|
209
|
+
```json
|
|
210
|
+
{
|
|
211
|
+
"verified": true
|
|
212
|
+
}
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
The response also sets the `lamaste_2fa_session` cookie (see [Session Cookie](#session-cookie) above).
|
|
216
|
+
|
|
217
|
+
**Errors:**
|
|
218
|
+
|
|
219
|
+
| Status | Body | When |
|
|
220
|
+
| ------ | -------------------------------------------------- | --------------------------------- |
|
|
221
|
+
| 400 | `{"error": "Code must be exactly 6 digits"}` | Code format invalid |
|
|
222
|
+
| 400 | `{"error": "2FA is not enabled"}` | 2FA has not been activated |
|
|
223
|
+
| 401 | `{"error": "Invalid TOTP code"}` | TOTP code is incorrect or expired |
|
|
224
|
+
| 429 | `{"error": "Too many attempts. Try again later."}` | Rate limit exceeded |
|
|
225
|
+
|
|
226
|
+
---
|
|
227
|
+
|
|
228
|
+
### `POST /api/settings/2fa/disable`
|
|
229
|
+
|
|
230
|
+
Disables 2FA. Requires both a valid 2FA session cookie and a TOTP code for confirmation. After disabling, the IP-based vhost is re-enabled and the session cookie requirement is removed.
|
|
231
|
+
|
|
232
|
+
**Note:** If re-enabling the IP vhost fails (e.g., nginx config error), 2FA is still disabled and the endpoint returns `200`. The failure is logged server-side. The admin would need to manually fix the nginx configuration or run `sudo lamaste-reset-admin` to restore the IP vhost.
|
|
233
|
+
|
|
234
|
+
**Request:**
|
|
235
|
+
|
|
236
|
+
```json
|
|
237
|
+
{
|
|
238
|
+
"code": "123456"
|
|
239
|
+
}
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
| Field | Type | Required | Description |
|
|
243
|
+
| ------ | -------- | -------- | ---------------------------------------- |
|
|
244
|
+
| `code` | `string` | Yes | 6-digit TOTP code from authenticator app |
|
|
245
|
+
|
|
246
|
+
```bash
|
|
247
|
+
curl -s -X POST --cert client.p12:password \
|
|
248
|
+
-H 'Content-Type: application/json' \
|
|
249
|
+
-d '{"code":"123456"}' \
|
|
250
|
+
https://203.0.113.42:9292/api/settings/2fa/disable | jq
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
**Response (200):**
|
|
254
|
+
|
|
255
|
+
```json
|
|
256
|
+
{
|
|
257
|
+
"enabled": false
|
|
258
|
+
}
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
**Errors:**
|
|
262
|
+
|
|
263
|
+
| Status | Body | When |
|
|
264
|
+
| ------ | -------------------------------------------------- | --------------------------------- |
|
|
265
|
+
| 400 | `{"error": "Code must be exactly 6 digits"}` | Code format invalid |
|
|
266
|
+
| 400 | `{"error": "2FA is not enabled"}` | 2FA was not active |
|
|
267
|
+
| 401 | `{"error": "Invalid TOTP code"}` | TOTP code is incorrect or expired |
|
|
268
|
+
| 401 | `{"error": "2fa_required"}` | No valid 2FA session cookie |
|
|
269
|
+
| 429 | `{"error": "Too many attempts. Try again later."}` | Rate limit exceeded |
|
|
270
|
+
|
|
271
|
+
## Quick Reference
|
|
272
|
+
|
|
273
|
+
| Method | Path | Auth | Rate Limited | Description |
|
|
274
|
+
| ------ | --------------------------- | ------------------------------------ | ------------ | --------------------------------- |
|
|
275
|
+
| GET | `/api/settings/2fa` | mTLS (exempt from 2FA session) | No | Get 2FA status |
|
|
276
|
+
| POST | `/api/settings/2fa/setup` | mTLS (2FA not yet active during use) | No | Generate TOTP secret |
|
|
277
|
+
| POST | `/api/settings/2fa/confirm` | mTLS (2FA not yet active during use) | Yes | Confirm initial code, enable 2FA |
|
|
278
|
+
| POST | `/api/settings/2fa/verify` | mTLS (exempt from 2FA session) | Yes | Verify code, issue session cookie |
|
|
279
|
+
| POST | `/api/settings/2fa/disable` | mTLS + 2FA session | Yes | Disable 2FA |
|
|
280
|
+
|
|
281
|
+
### Response Shapes
|
|
282
|
+
|
|
283
|
+
**Status:**
|
|
284
|
+
|
|
285
|
+
```json
|
|
286
|
+
{ "enabled": false, "setupComplete": false }
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
**Setup:**
|
|
290
|
+
|
|
291
|
+
```json
|
|
292
|
+
{ "uri": "otpauth://totp/...", "manualKey": "JBSWY3DPEHPK3PXP" }
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
**Confirm:**
|
|
296
|
+
|
|
297
|
+
```json
|
|
298
|
+
{ "enabled": true }
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
**Verify:**
|
|
302
|
+
|
|
303
|
+
```json
|
|
304
|
+
{ "verified": true }
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
**Disable:**
|
|
308
|
+
|
|
309
|
+
```json
|
|
310
|
+
{ "enabled": false }
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
### curl Cheat Sheet
|
|
314
|
+
|
|
315
|
+
```bash
|
|
316
|
+
# Check 2FA status
|
|
317
|
+
curl -s --cert client.p12:password \
|
|
318
|
+
https://203.0.113.42:9292/api/settings/2fa | jq
|
|
319
|
+
|
|
320
|
+
# Start 2FA setup
|
|
321
|
+
curl -s -X POST --cert client.p12:password \
|
|
322
|
+
https://203.0.113.42:9292/api/settings/2fa/setup | jq
|
|
323
|
+
|
|
324
|
+
# Confirm setup with first code
|
|
325
|
+
curl -s -X POST --cert client.p12:password \
|
|
326
|
+
-H 'Content-Type: application/json' \
|
|
327
|
+
-d '{"code":"123456"}' \
|
|
328
|
+
https://203.0.113.42:9292/api/settings/2fa/confirm | jq
|
|
329
|
+
|
|
330
|
+
# Verify code to get session cookie
|
|
331
|
+
curl -s -X POST --cert client.p12:password \
|
|
332
|
+
-H 'Content-Type: application/json' \
|
|
333
|
+
-d '{"code":"123456"}' \
|
|
334
|
+
-c cookies.txt \
|
|
335
|
+
https://203.0.113.42:9292/api/settings/2fa/verify | jq
|
|
336
|
+
|
|
337
|
+
# Disable 2FA (requires session cookie)
|
|
338
|
+
curl -s -X POST --cert client.p12:password \
|
|
339
|
+
-H 'Content-Type: application/json' \
|
|
340
|
+
-d '{"code":"123456"}' \
|
|
341
|
+
-b cookies.txt \
|
|
342
|
+
https://203.0.113.42:9292/api/settings/2fa/disable | jq
|
|
343
|
+
```
|