mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-08-06 08:58:05 +00:00
c6d1631afe
* feat(recovery): add safe-mode recovery surface and emergency CLI Add a read-only Recovery tab under Settings (admin-only) backed by a new GET /api/diagnostics endpoint reporting app version, database integrity, encryption-key status, Docker reachability, account and SSO counts, and non-secret configuration. The endpoint loads without Docker or live metrics so it stays available when the dashboard does not, requires a genuine admin session, and builds its config block from a non-secret allowlist so no credentials are ever exposed. Expand the emergency command-line toolkit beyond the two-factor reset with seven host-level commands: reset-password, create-emergency-admin, clear-sessions, disable-sso, diagnostics, validate-db, and backup-data. Each prints its result, exits with a meaningful status code, and writes an audit entry where it changes state. Document the toolkit in a new operator guide and link it from the recovery and two-factor pages. * feat(recovery): download the emergency command reference as a text file The recovery commands are needed exactly when the dashboard is unreachable, so reading them only in-app is a chicken-and-egg problem. Add a Download button to the command-line section that saves the full `docker compose exec sencho ...` reference as a text file, letting operators keep it on hand before they need it. Reuses a shared download helper with the existing diagnostics export. * fix(recovery): harden diagnostics, backup, and emergency-admin against edge cases Address findings from an independent review of the recovery toolkit: - DiagnosticsService now degrades instead of throwing when a queried table is missing or corrupt: each read falls back and is folded into database.ok, so a broken database reports "problem detected" rather than failing the whole endpoint or showing a misleading healthy state with zeroed counts. - backup-data refuses a destination that resolves to the live database, which would otherwise report success while producing no separate copy. - create-emergency-admin now applies the same username rule as the user- management route, extracted to a shared helper so both stay in sync. Adds tests for a missing read table, a malformed emergency-admin username, and the backup same-target rejection.
57 lines
2.0 KiB
TypeScript
57 lines
2.0 KiB
TypeScript
/**
|
|
* Shared helpers for the emergency recovery CLI commands in this directory.
|
|
* Each command exports a testable function and a thin `main()` wrapper; these
|
|
* helpers carry the audit-write and exit-code conventions so the individual
|
|
* commands stay focused on their own logic.
|
|
*/
|
|
import { DatabaseService } from '../services/DatabaseService';
|
|
|
|
export interface CliResult {
|
|
ok: boolean;
|
|
message: string;
|
|
}
|
|
|
|
/**
|
|
* Record a CLI action in the audit log with the conventional `cli` actor, then
|
|
* flush immediately. The buffer's 1s flush timer never fires because the CLI
|
|
* process exits first, so the explicit flush is what persists the entry. An
|
|
* audit-write failure is logged but never aborts the action it was recording.
|
|
*/
|
|
export function auditCli(db: DatabaseService, path: string, summary: string): void {
|
|
try {
|
|
db.insertAuditLog({
|
|
timestamp: Date.now(),
|
|
username: 'cli',
|
|
method: 'POST',
|
|
path,
|
|
status_code: 200,
|
|
node_id: null,
|
|
ip_address: 'cli',
|
|
summary,
|
|
});
|
|
// Flush inside the try: the buffer's 1s timer never fires before the
|
|
// CLI exits, so this is what persists the entry. Keeping it under the
|
|
// same catch means a broken DB handle cannot throw a second, uncaught
|
|
// error after the recovery action it records has already succeeded.
|
|
db.flushAuditLogBuffer();
|
|
} catch (err) {
|
|
console.warn(`[cli] audit log write failed: ${(err as Error).message}`);
|
|
}
|
|
}
|
|
|
|
/** Print the result and exit 0 (ok) or 1 (error), matching resetMfa.ts. */
|
|
export function exitWith(result: CliResult): never {
|
|
if (result.ok) {
|
|
console.log(result.message);
|
|
process.exit(0);
|
|
}
|
|
console.error(result.message);
|
|
process.exit(1);
|
|
}
|
|
|
|
/** Print a usage error to stderr and exit 2 (matches resetMfa.ts). */
|
|
export function usage(message: string): never {
|
|
console.error(message);
|
|
process.exit(2);
|
|
}
|