feat: persist auth token across server restarts
Token is stored in ~/.pi/remote-control/token (mode 600) on first start and reused on subsequent starts — saved URLs stay valid indefinitely.
This commit is contained in:
parent
9f8b2cc987
commit
74fc22ddfb
|
|
@ -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<string> {
|
||||
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);
|
||||
|
|
|
|||
|
|
@ -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<WsClient>();
|
||||
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<string, number>();
|
||||
|
|
|
|||
Loading…
Reference in New Issue