SPB Git

Repository Public

setup-token.ts 1.5 KB 38 lines typescript

1/**2 * First-run setup token: 32 random bytes (base64url) in `$AOS_DATA_DIR/setup-token`, mode 0600.3 * It is never printed or logged; the operator reads the file on the host. Deleted once consumed.4 */5import fs from 'node:fs/promises';6import path from 'node:path';7import type { Queryable } from '../db.js';8import { randomToken, safeEqual } from './crypto.js';910export const setupTokenPath = (dataDir: string): string => path.join(dataDir, 'setup-token');1112export async function userExists(db: Queryable): Promise<boolean> {13  return (await db.one('SELECT 1 AS x FROM users LIMIT 1')) !== null;14}1516/** Called at startup: (re)generates the token while no administrator exists, removes it otherwise. */17export async function ensureSetupToken(db: Queryable, dataDir: string): Promise<void> {18  const file = setupTokenPath(dataDir);19  if (await userExists(db)) {20    await fs.rm(file, { force: true });21    return;22  }23  await fs.mkdir(dataDir, { recursive: true, mode: 0o700 });24  await fs.rm(file, { force: true });25  await fs.writeFile(file, randomToken(32), { mode: 0o600, flag: 'wx' });26}2728export async function checkSetupToken(dataDir: string, candidate: string): Promise<boolean> {29  let expected: string;30  try {31    expected = (await fs.readFile(setupTokenPath(dataDir), 'utf8')).trim();32  } catch {33    return false;34  }35  return expected.length >= 32 && safeEqual(expected, candidate);36}37export const deleteSetupToken = (dataDir: string): Promise<void> => fs.rm(setupTokenPath(dataDir), { force: true });38
Files main
Menu

Theme