Add a printer through the gateway without the cloud keeping its credential
POST /farms/:id/printers (owner) is the console's add-printer call. It needs a connected gateway and relays the printer's access code to it in a single provision_printer frame; the gateway connects to the printer to prove the code, saves it in its own database, and only then does the cloud create the printer row. A refused or timed-out add leaves no row, and a row insert that fails after the gateway said yes tells the gateway to forget the printer. The access code is held in the request and the one frame and nowhere else: not in any table, not in a log line, not in a response or an error (the gateway's error text is scrubbed of it as a second line of defence). Deleting a printer now also tells the gateway to forget it and its stored credential, best effort, so an unplugged gateway cannot block an owner. Only Bambu is accepted, because it is the only vendor with a working driver; the rest are found by discovery but cannot yet be driven. e2e-provision.sh drives a stand-in gateway over the real socket and, after adding a printer, searches every table of the database and the backend's log for the code (checked to fail when the code is logged).
This commit is contained in:
@@ -0,0 +1,40 @@
|
||||
/**
|
||||
* Pure helpers for adding a printer through a gateway, with no imports so
|
||||
* they test without a database - same reasoning as `dispatch/job_status.ts`.
|
||||
*/
|
||||
|
||||
/** Vendors a gateway can actually drive today. Everything else is found by discovery but not yet controllable. */
|
||||
export const PROVISIONABLE_VENDORS = ["bambu"] as const;
|
||||
|
||||
/**
|
||||
* The id the gateway will know this printer by, chosen here so telemetry
|
||||
* arrives under an id the cloud already holds. A readable slug of the name
|
||||
* plus a random suffix: readable because it shows up in the gateway's logs
|
||||
* and certificate-pin file names, random because two printers may share a
|
||||
* name. Constrained to what the gateway accepts (`^[A-Za-z0-9_-]{1,64}$`).
|
||||
*/
|
||||
export function gatewayIdFor(name: string, random: () => number = Math.random): string {
|
||||
const slug =
|
||||
name
|
||||
.toLowerCase()
|
||||
.replace(/[^a-z0-9]+/g, "-")
|
||||
.replace(/^-+|-+$/g, "")
|
||||
.slice(0, 40) || "printer";
|
||||
const suffix = Math.floor(random() * 0xffff)
|
||||
.toString(16)
|
||||
.padStart(4, "0");
|
||||
return `${slug}-${suffix}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* What to tell the operator when the gateway says no, without ever repeating
|
||||
* the credential back. The gateway's messages do not contain it by design,
|
||||
* and this is the second line of that defence: anything that does is dropped.
|
||||
*/
|
||||
export function safeGatewayError(error: string | undefined, secrets: string[]): string {
|
||||
let message = error ?? "the gateway did not say why";
|
||||
for (const secret of secrets) {
|
||||
if (secret) message = message.split(secret).join("<hidden>");
|
||||
}
|
||||
return message;
|
||||
}
|
||||
Reference in New Issue
Block a user