@openparachute/hub 0.7.4-rc.14 → 0.7.4-rc.16
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/package.json +1 -1
- package/src/__tests__/api-account-2fa.test.ts +72 -0
- package/src/__tests__/doctor.test.ts +464 -0
- package/src/api-account-2fa.ts +23 -1
- package/src/cli.ts +18 -0
- package/src/commands/doctor.ts +894 -0
- package/src/help.ts +38 -0
- package/src/rate-limit.ts +28 -0
package/src/help.ts
CHANGED
|
@@ -17,6 +17,7 @@ Usage:
|
|
|
17
17
|
parachute install <service> install and register a service
|
|
18
18
|
services: ${services}
|
|
19
19
|
parachute status show installed services, run state, health
|
|
20
|
+
parachute doctor run health checks + tell you the one thing to fix
|
|
20
21
|
parachute start [service] start a module via the supervisor (or ensure the hub is up)
|
|
21
22
|
parachute stop [service] stop a module via the supervisor (or stop the hub unit)
|
|
22
23
|
parachute restart [service] restart a module via the supervisor (or restart the hub unit)
|
|
@@ -367,6 +368,43 @@ Example:
|
|
|
367
368
|
`;
|
|
368
369
|
}
|
|
369
370
|
|
|
371
|
+
export function doctorHelp(): string {
|
|
372
|
+
return `parachute doctor — health / diagnostics for your Parachute install
|
|
373
|
+
|
|
374
|
+
Usage:
|
|
375
|
+
parachute doctor [--json]
|
|
376
|
+
|
|
377
|
+
What it does:
|
|
378
|
+
Runs a set of independent health checks and prints a grouped report
|
|
379
|
+
(✓ pass / ⚠ warn / ✗ fail), each with a one-line detail and — where there
|
|
380
|
+
is one — a copy-pasteable fix-it command. The single command that answers
|
|
381
|
+
"is my Parachute healthy, and if not, what's the one thing to fix?"
|
|
382
|
+
|
|
383
|
+
Checks (each PASSES on a fresh / fully-current install — doctor positively
|
|
384
|
+
detects a known-bad condition and never treats "not configured" as broken):
|
|
385
|
+
- Hub supervisor reachable on :1939 (/health).
|
|
386
|
+
- Each CONFIGURED module alive via its loopback /health (2xx or 401 = live).
|
|
387
|
+
- services.json parses + required fields valid (a missing file is the
|
|
388
|
+
fresh pre-install state, not a failure).
|
|
389
|
+
- operator.token exists, parses, and its issuer matches the hub (the
|
|
390
|
+
recurring "not signed in to the hub" / issuer-mismatch class).
|
|
391
|
+
- Each first-party module bin is executable (catches the lost-+x-bit
|
|
392
|
+
start-failure class).
|
|
393
|
+
- Migration: legacy detached install? known cruft at the ecosystem root?
|
|
394
|
+
(allowlist detectors only — a fresh root flags nothing).
|
|
395
|
+
- Exposure: if exposed, is the public origin reachable? If not exposed,
|
|
396
|
+
"loopback only" is reported as benign info, never a warning.
|
|
397
|
+
- Version freshness (cosmetic) — drift is WARN at most, never a failure.
|
|
398
|
+
|
|
399
|
+
Flags:
|
|
400
|
+
--json emit a single JSON object instead of the human report
|
|
401
|
+
|
|
402
|
+
Exit codes:
|
|
403
|
+
0 no failures (warnings are advisory and still exit 0)
|
|
404
|
+
1 one or more checks failed
|
|
405
|
+
`;
|
|
406
|
+
}
|
|
407
|
+
|
|
370
408
|
export function exposeHelp(): string {
|
|
371
409
|
return `parachute expose — route your services behind HTTPS on a network layer
|
|
372
410
|
|
package/src/rate-limit.ts
CHANGED
|
@@ -87,6 +87,21 @@ export const CHANGE_PASSWORD_WINDOW_MS = 5 * 60 * 1000;
|
|
|
87
87
|
* cookie attacker shouldn't get a 5-shot grind window.
|
|
88
88
|
*/
|
|
89
89
|
export const CHANGE_PASSWORD_MAX_ATTEMPTS = 3;
|
|
90
|
+
/**
|
|
91
|
+
* `/api/account/2fa/confirm` (TOTP enrollment seal) window: 15 minutes. This is
|
|
92
|
+
* the SELF-only, already-session-authenticated enrollment step — the operator is
|
|
93
|
+
* typing the first live code off their own authenticator while it drifts into
|
|
94
|
+
* sync, so legitimate mistypes are common and must not be punished. The threat
|
|
95
|
+
* is only a hijacked session grinding the (client-held, not-yet-persisted)
|
|
96
|
+
* in-flight secret, which the 10^6 code space + replay cache already make
|
|
97
|
+
* effectively non-exploitable — so this is defense-in-depth, deliberately MORE
|
|
98
|
+
* generous than the 3/5-min change-password bucket. NOT the `/login/2fa` bucket:
|
|
99
|
+
* that one is the strict, pre-auth brute-force door (5/15-min); enrollment is a
|
|
100
|
+
* different, lower-risk surface and gets its own lenient bucket.
|
|
101
|
+
*/
|
|
102
|
+
export const TOTP_ENROLL_CONFIRM_WINDOW_MS = 15 * 60 * 1000;
|
|
103
|
+
/** `/api/account/2fa/confirm` attempts allowed per window. 11th is denied. */
|
|
104
|
+
export const TOTP_ENROLL_CONFIRM_MAX_ATTEMPTS = 10;
|
|
90
105
|
/**
|
|
91
106
|
* `/login/2fa` window length: 15 minutes — same as `/login`. The second-
|
|
92
107
|
* factor step (hub#473) sits behind a verified password + a short-lived
|
|
@@ -289,6 +304,18 @@ export const changePasswordRateLimiter = new RateLimiter(
|
|
|
289
304
|
*/
|
|
290
305
|
export const totpRateLimiter = new RateLimiter(TOTP_MAX_ATTEMPTS, TOTP_WINDOW_MS);
|
|
291
306
|
|
|
307
|
+
/**
|
|
308
|
+
* `/api/account/2fa/confirm` enrollment-seal bucket. Lenient (10 / 15 min),
|
|
309
|
+
* keyed by `user.id` (the session already establishes identity). Separate from
|
|
310
|
+
* `totpRateLimiter` so an enrollment mistype and a `/login/2fa` failure never
|
|
311
|
+
* share a window — different surfaces, different threat models (see the const
|
|
312
|
+
* docs above).
|
|
313
|
+
*/
|
|
314
|
+
export const totpEnrollConfirmRateLimiter = new RateLimiter(
|
|
315
|
+
TOTP_ENROLL_CONFIRM_MAX_ATTEMPTS,
|
|
316
|
+
TOTP_ENROLL_CONFIRM_WINDOW_MS,
|
|
317
|
+
);
|
|
318
|
+
|
|
292
319
|
/**
|
|
293
320
|
* Coarse per-IP CEILING rate limiter — 60 attempts / 15 min, keyed by client
|
|
294
321
|
* IP ONLY. Shared by all interactive auth doors (`/login`, the
|
|
@@ -342,6 +369,7 @@ export function __resetForTests(): void {
|
|
|
342
369
|
loginRateLimiter.reset();
|
|
343
370
|
changePasswordRateLimiter.reset();
|
|
344
371
|
totpRateLimiter.reset();
|
|
372
|
+
totpEnrollConfirmRateLimiter.reset();
|
|
345
373
|
vaultTokenMintRateLimiter.reset();
|
|
346
374
|
signupRateLimiter.reset();
|
|
347
375
|
authIpCeilingRateLimiter.reset();
|