66 lines
1.9 KiB
TypeScript
66 lines
1.9 KiB
TypeScript
/**
|
|
* Authentication helpers for remote-control.
|
|
*
|
|
* 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);
|
|
if (a.length !== b.length) return false;
|
|
return timingSafeEqual(a, b);
|
|
}
|
|
|
|
/** Name of the cookie that grants access after initial token validation */
|
|
export const SESSION_COOKIE = "pi_rc_session";
|
|
|
|
export function generateSessionId(): string {
|
|
return randomBytes(24).toString("base64url");
|
|
}
|
|
|
|
export function parseCookies(
|
|
header: string | undefined,
|
|
): Record<string, string> {
|
|
const cookies: Record<string, string> = {};
|
|
if (!header) return cookies;
|
|
for (const pair of header.split(";")) {
|
|
const idx = pair.indexOf("=");
|
|
if (idx < 0) continue;
|
|
const name = pair.slice(0, idx).trim();
|
|
const raw = pair.slice(idx + 1).trim();
|
|
let value = raw;
|
|
try {
|
|
value = decodeURIComponent(raw);
|
|
} catch {
|
|
/* keep raw */
|
|
}
|
|
cookies[name] = value;
|
|
}
|
|
return cookies;
|
|
}
|