08919752e3
Checkpoint multi-chantiers (arbre vert : tsc 0 erreur, 70 tests, build OK). - tâche 1.9 Phase 1 : schéma socle (machine_state/events/reports/raw_artifacts/ hardware/metrics + colonnes étendues) + wiring refresh/execute. Migration 0002. - tâche 1.9 Phase 2 : machine_credentials + machine_host_keys (non destructif, dual-read + backfill). Migration 0003. Fix séquence journal de migration. - tâche 2 : SJ-0 (types étendus rétro-compatibles, réducteur Docker, resolveTemplate), SJ-1 (update-analyze enrichi), SJ-2 (apply + diff dpkg + timeout inactivité SSH), SJ-3 (reboot vérifié boot_id). - WIP parallèle inclus : /api/capabilities, auth/apiTokens/apiClients, system metrics, scaffold app_rust, ajustements frontend. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
56 lines
2.2 KiB
TypeScript
56 lines
2.2 KiB
TypeScript
// server/services/credentials.ts
|
|
import { eq } from "drizzle-orm";
|
|
import { db, schema } from "../db/client.js";
|
|
|
|
interface EncPair { encPassword: string | null; encSudoPassword: string | null; }
|
|
|
|
/** Résout la source des secrets : machine_credentials prioritaire, sinon legacy machines (fonction pure). */
|
|
export function resolveCreds(legacy: EncPair, creds: EncPair | null): EncPair {
|
|
if (creds && creds.encPassword) return { encPassword: creds.encPassword, encSudoPassword: creds.encSudoPassword };
|
|
return { encPassword: legacy.encPassword, encSudoPassword: legacy.encSudoPassword };
|
|
}
|
|
|
|
/** Écrit (insert/replace) la ligne machine_credentials pour une machine (secrets déjà chiffrés). */
|
|
export function writeCredentials(input: {
|
|
machineId: string;
|
|
encPassword: string | null;
|
|
encSudoPassword: string | null;
|
|
}): void {
|
|
const now = new Date().toISOString();
|
|
db.insert(schema.machineCredentials)
|
|
.values({
|
|
machineId: input.machineId,
|
|
authMethod: "password",
|
|
encPassword: input.encPassword,
|
|
encSudoPassword: input.encSudoPassword,
|
|
sudoMode: input.encSudoPassword ? "separate" : "same_as_ssh",
|
|
createdAt: now,
|
|
updatedAt: now,
|
|
status: "unknown",
|
|
})
|
|
.onConflictDoUpdate({
|
|
target: schema.machineCredentials.machineId,
|
|
set: { encPassword: input.encPassword, encSudoPassword: input.encSudoPassword, updatedAt: now },
|
|
})
|
|
.run();
|
|
}
|
|
|
|
/** Lit la ligne machine_credentials (ou null). */
|
|
export function readCredentials(machineId: string): EncPair | null {
|
|
const row = db.select().from(schema.machineCredentials)
|
|
.where(eq(schema.machineCredentials.machineId, machineId)).get();
|
|
return row ? { encPassword: row.encPassword, encSudoPassword: row.encSudoPassword } : null;
|
|
}
|
|
|
|
/** Backfill idempotent : crée une ligne machine_credentials pour chaque machine qui n'en a pas. */
|
|
export function backfillCredentials(): number {
|
|
const machines = db.select().from(schema.machines).all();
|
|
let created = 0;
|
|
for (const m of machines) {
|
|
if (readCredentials(m.id)) continue;
|
|
writeCredentials({ machineId: m.id, encPassword: m.encPassword, encSudoPassword: m.encSudoPassword });
|
|
created++;
|
|
}
|
|
return created;
|
|
}
|