Files
web-terminal/control-panel/src/pairing.ts
Yaojia Wang 675de771c7 feat(control-panel): web admin UI for the zero-touch tunnel
Loopback Fastify auth-broker + esbuild SPA. Operator password login (constant-time,
signed HttpOnly session cookie, per-forwarded-IP rate-limit) → session-gated proxy
that mints a fresh 60s manage capability token per call to the control-plane admin
API: list hosts, mint pairing codes (with QR + pair command), revoke hosts. Security
headers + CSP, CP_URL pinned loopback (anti-SSRF), hostId dot-segment guard. 55 tests
pass; security-reviewed. Deployed behind nginx panel.terminal.yaojia.wang.
2026-07-19 19:47:51 +02:00

38 lines
1.5 KiB
TypeScript

/**
* Operator-facing pairing artifacts derived from a freshly-issued pairing code:
* - the ready-to-run `web-terminal-agent pair <code> --install --zone <zone>` command line, and
* - a QR PNG data: URL of the code (rendered with the `qrcode` dep) for phone scanning.
* Pure/deterministic given the code + zone (except the QR, which is a stable render of the code).
*/
import QRCode from 'qrcode'
/** Build the exact command an operator runs on the new host to enroll it into the tunnel. */
export function buildPairCommand(code: string, zone: string): string {
return `web-terminal-agent pair ${code} --install --zone ${zone}`
}
/** Render a QR PNG data: URL encoding the pairing code (for scanning on the phone client). */
export async function buildQrDataUrl(code: string): Promise<string> {
return QRCode.toDataURL(code, { errorCorrectionLevel: 'M', margin: 1, width: 256 })
}
export interface PairingArtifacts {
readonly code: string
readonly expiresAt: string
readonly pairCommand: string
readonly qrDataUrl: string
}
/** Combine an issued code with its operator-facing command + QR into the /api/pairing-codes payload. */
export async function buildPairingArtifacts(
issued: { readonly code: string; readonly expiresAt: string },
zone: string,
): Promise<PairingArtifacts> {
return {
code: issued.code,
expiresAt: issued.expiresAt,
pairCommand: buildPairCommand(issued.code, zone),
qrDataUrl: await buildQrDataUrl(issued.code),
}
}