From 74fc22ddfbbd8308634b90af421958cc35dfe106 Mon Sep 17 00:00:00 2001 From: jay Date: Thu, 14 May 2026 19:00:31 +0200 Subject: [PATCH] feat: persist auth token across server restarts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Token is stored in ~/.pi/remote-control/token (mode 600) on first start and reused on subsequent starts — saved URLs stay valid indefinitely. --- extensions/remote-control/auth.ts | 19 +++++++++++++++++++ extensions/remote-control/server.ts | 4 ++-- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/extensions/remote-control/auth.ts b/extensions/remote-control/auth.ts index 7765fd3..9d7ac7b 100644 --- a/extensions/remote-control/auth.ts +++ b/extensions/remote-control/auth.ts @@ -4,12 +4,31 @@ * Provides one-time token generation/validation and session cookie management. */ +import fs from "node:fs/promises"; +import os from "node:os"; +import path from "node:path"; import { randomBytes, timingSafeEqual } from "node:crypto"; export function generateToken(): string { return randomBytes(24).toString("base64url"); // 32 chars, URL-safe } +const TOKEN_FILE = path.join(os.homedir(), ".pi", "remote-control", "token"); + +/** Load persisted token, or generate + save a new one. */ +export async function loadOrCreateToken(): Promise { + try { + const token = (await fs.readFile(TOKEN_FILE, "utf8")).trim(); + if (token.length > 0) return token; + } catch { + /* file doesn't exist yet */ + } + const token = generateToken(); + await fs.mkdir(path.dirname(TOKEN_FILE), { recursive: true }); + await fs.writeFile(TOKEN_FILE, token, { encoding: "utf8", mode: 0o600 }); + return token; +} + export function validateToken(provided: string, expected: string): boolean { const a = Buffer.from(provided); const b = Buffer.from(expected); diff --git a/extensions/remote-control/server.ts b/extensions/remote-control/server.ts index e4fc503..a9f52cc 100644 --- a/extensions/remote-control/server.ts +++ b/extensions/remote-control/server.ts @@ -16,7 +16,7 @@ import type { } from "@earendil-works/pi-coding-agent"; import { generateSessionId, - generateToken, + loadOrCreateToken, parseCookies, SESSION_COOKIE, validateToken, @@ -75,7 +75,7 @@ export async function startServer( : { host: "127.0.0.1", port: 0 }; const clientChangeListeners: Array<() => void> = []; const clients = new Set(); - const token = generateToken(); + const token = await loadOrCreateToken(); // Map of valid session IDs → expiry timestamp (ms since epoch) const SESSION_TTL_MS = 86_400_000; // 24 h — matches cookie Max-Age const validSessions = new Map();