// The hosted-deployment gate, and where an account's index file lives. // // The standalone Next.js server in `electron/main.ts` is the SAME artifact the // production `Dockerfile` ships to multi-tenant deployments. An index that // activated unconditionally would have a shared server start writing every // user's mail into a server-side SQLite file. So activation is keyed on an env // var that ONLY `electron/main.ts` sets, and that same var supplies the path - // one variable doing both jobs, so they cannot drift apart. import { createHash } from 'node:crypto'; import path from 'node:path'; /** Set by electron/main.ts on spawn. Absent => the feature does not exist. */ export const STORE_DIR_ENV = 'VNCMAIL_DESKTOP_STORE_DIR'; /** * The index root, or `null` when this process is not the desktop shell's * server. Every route must 404 on `null` - not 403, since nothing should learn * the routes exist in a deployment that doesn't have the feature. */ export function getStoreDir(): string | null { const dir = process.env[STORE_DIR_ENV]?.trim(); if (!dir) return null; // Must be absolute: a relative path would resolve against the server's cwd, // which differs between `electron:dev` and a packaged build. if (!path.isAbsolute(dir)) return null; return dir; } /** * Filenames are a hash, not `username@host`, so a directory listing is not a * plaintext inventory of the user's accounts. The account id itself lives only * inside the encrypted file (and in the renderer's own `account-registry`, * which already stores it in plain localStorage). */ export function accountFileToken(accountId: string): string { return createHash('sha256').update(accountId, 'utf8').digest('hex').slice(0, 32); } export function indexDbPath(storeDir: string, accountId: string): string { return path.join(storeDir, 'index', `${accountFileToken(accountId)}.db`); } export function keyFilePath(storeDir: string, accountId: string): string { return path.join(storeDir, 'keys', `${accountFileToken(accountId)}.bin`); } /** WAL siblings must be removed with the database, or a purge leaks readable pages. */ export function dbSiblings(dbPath: string): string[] { return [dbPath, `${dbPath}-wal`, `${dbPath}-shm`]; }