Append-only signed event log; persist Dingtian input pushes
Implement the core anti-fraud primitive: an append-only, hash-chained, signed event log (the schema + types predated this; the writer/signer are new). - EventLog (apps/server): serialized append, monotonic index, prevHash chain, signature; verifyChain() detects tamper/reorder/delete. No update/delete paths. - Signer abstraction (packages/shared) over the ATECC608 secure element, with a SoftwareSigner (HMAC, EVENT_SIGNING_KEY) shipped now since the chip is still open-question #6. Documented: software signer is tamper-evident but NOT unforgeable-by-owner. - Add ParkingEventType "input_received" for raw device inputs (not yet a vehicle_entry, which the entry flow will append later). - Read API: GET /api/events; integrity self-check: GET /api/events/verify (admin). Verified on hardware: shorting the Dingtian inputs produced signed, chained input_received events; verifyChain ok; direct DB tamper/delete detected. NOTE: the log captures host-originated actions only. Out-of-band relay actuation (sniffed relay_pw, string protocol, ip_watchdog) produces no event by design -- the control is reconciliation vs. an independent witness, which is not yet built. See wiki/concepts/append-only-event-chain.md.
This commit is contained in:
@@ -3,9 +3,13 @@ import jwt from "@fastify/jwt";
|
||||
import Fastify, { type FastifyInstance } from "fastify";
|
||||
import { createDb, type Db } from "@parking/db";
|
||||
import { TOKEN_COOKIE, requireJwtSecret } from "./auth.js";
|
||||
import { deviceEvents } from "./device-events.js";
|
||||
import { EventLog } from "./event-log.js";
|
||||
import { PrinterMonitor } from "./printer-monitor.js";
|
||||
import { buildSigner } from "./signer.js";
|
||||
import { authRoutes } from "./routes/auth.js";
|
||||
import { deviceRoutes } from "./routes/devices.js";
|
||||
import { eventRoutes } from "./routes/events.js";
|
||||
import { printerRoutes } from "./routes/printers.js";
|
||||
import { setupRoutes } from "./routes/setup.js";
|
||||
|
||||
@@ -59,7 +63,27 @@ export async function buildServer(opts: BuildOptions = {}): Promise<FastifyInsta
|
||||
app.addHook("onReady", async () => printerMonitor.start());
|
||||
app.addHook("onClose", async () => printerMonitor.stop());
|
||||
|
||||
// TODO: entry flow (input event → signed event → print → relay), event-log routes.
|
||||
// Append-only signed event log. Subscribe device pushes (e.g. Dingtian button
|
||||
// presses) into the hash-chained, signed `events` table — the anti-fraud audit
|
||||
// trail. The device is NOT trusted; the host record is the source of truth, and
|
||||
// a relay open with no matching signed event is itself the anomaly. We record
|
||||
// the raw input faithfully as `input_received` (not yet a `vehicle_entry` — that
|
||||
// comes with the full entry flow). See wiki/concepts/append-only-event-chain.md.
|
||||
const eventLog = new EventLog(db, buildSigner(app.log));
|
||||
await eventRoutes(app, db, eventLog);
|
||||
const unsubscribeInput = deviceEvents.onInput((e) => {
|
||||
eventLog
|
||||
.append({
|
||||
type: "input_received",
|
||||
lane: 0, // lane mapping is a TODO — device->lane lookup arrives with setup/lane wiring
|
||||
identity: `${e.driverId}:${e.deviceId} input:${e.input}/${e.edge}`,
|
||||
occurredAt: e.at,
|
||||
})
|
||||
.catch((err) => app.log.error(`event-log append failed: ${(err as Error).message}`));
|
||||
});
|
||||
app.addHook("onClose", async () => unsubscribeInput());
|
||||
|
||||
// TODO: entry flow (input event → signed event → print → relay); map device→lane.
|
||||
|
||||
return app;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user