import net from 'node:net'; /** * Minimal, dependency-free Redis client implementing just enough of the RESP * protocol to issue a single SET command. Used for the best-effort live * kill-switch (tunnel:active:). Every failure mode is swallowed — * Redis being unavailable must NEVER break an admin operation. * * REDIS_URL format: redis://[:password@]host:port[/db] */ type RedisTarget = { host: string; port: number; password?: string; db?: number; }; function parseRedisUrl(raw: string): RedisTarget | null { try { const u = new URL(raw); if (u.protocol !== 'redis:' && u.protocol !== 'rediss:') return null; const host = u.hostname || '127.0.0.1'; const port = u.port ? Number(u.port) : 6379; const password = u.password ? decodeURIComponent(u.password) : undefined; const dbStr = u.pathname.replace(/^\//, ''); const db = dbStr ? Number(dbStr) : undefined; if (!Number.isFinite(port)) return null; return { host, port, password, db: Number.isFinite(db) ? db : undefined }; } catch { return null; } } function encodeCommand(args: string[]): string { let out = `*${args.length}\r\n`; for (const a of args) { out += `$${Buffer.byteLength(a)}\r\n${a}\r\n`; } return out; } /** * Best-effort execution of a single RESP command (preceded by optional * AUTH/SELECT). Resolves true when the final reply is a non-error (`+...` or * `:...`), false otherwise. Never rejects. */ function runCommand(args: string[]): Promise { const url = process.env.REDIS_URL; if (!url) return Promise.resolve(false); const target = parseRedisUrl(url); if (!target) return Promise.resolve(false); return new Promise((resolve) => { let settled = false; const done = (result: boolean) => { if (settled) return; settled = true; try { socket.destroy(); } catch { /* ignore */ } resolve(result); }; const commands: string[] = []; if (target.password) commands.push(encodeCommand(['AUTH', target.password])); if (target.db !== undefined && target.db > 0) { commands.push(encodeCommand(['SELECT', String(target.db)])); } commands.push(encodeCommand(args)); const socket = net.createConnection( { host: target.host, port: target.port }, () => { try { socket.write(commands.join('')); } catch { done(false); } }, ); socket.setTimeout(1500, () => done(false)); socket.on('error', () => done(false)); let buf = ''; const expectedReplies = commands.length; socket.on('data', (chunk) => { buf += chunk.toString('utf8'); // Count complete simple replies (lines terminated by \r\n). const lines = buf.split('\r\n').filter((l) => l.length > 0); if (lines.length >= expectedReplies) { const last = lines[lines.length - 1]; // +OK / +... (simple string) or :N (integer, e.g. DEL count) = success. done(last.startsWith('+') || last.startsWith(':')); } }); }); } /** * Best-effort SET. When `ttlSeconds` is a positive integer the command is * issued as `SET key val EX `, otherwise a plain `SET key val`. Resolves * true on apparent success, false otherwise. Never rejects. Backward * compatible: existing two-arg callers are unaffected. */ export function redisSet( key: string, value: string, ttlSeconds?: number, ): Promise { const args = ['SET', key, value]; if ( typeof ttlSeconds === 'number' && Number.isFinite(ttlSeconds) && ttlSeconds > 0 ) { args.push('EX', String(Math.floor(ttlSeconds))); } return runCommand(args); } /** * Best-effort DEL. Resolves true when Redis acknowledges the command (reply is * an integer, even 0), false otherwise. Never rejects. */ export function redisDel(key: string): Promise { return runCommand(['DEL', key]); } /** * TTL (seconds) written alongside the tunnel:active flag. Must match the edge * `tunnel-active` forward_auth gate's TTL_SECONDS (currently 30) so the flag * naturally expires in lock-step with the gate's cache. Overridable via * TUNNEL_ACTIVE_TTL; defaults to 30. */ function tunnelActiveTtl(): number { const raw = process.env.TUNNEL_ACTIVE_TTL; if (raw) { const n = Number(raw); if (Number.isFinite(n) && n > 0) return Math.floor(n); } return 30; } /** * Live kill-switch primitive. Writes the SAME key the edge gate reads: * SET tunnel:active: <"1"|"0"> EX * "1" = ALLOW, "0" = DENY. A currently-connected tunnel is dropped within ~1s * when "0" is written; "1" re-allows it. No-op (returns false) when REDIS_URL * is unset — behavior then falls back to the gate's Postgres `is_active` read, * exactly as before this wiring existed. Never throws. */ export async function setTunnelActive( subdomain: string, active: boolean, ): Promise { if (!process.env.REDIS_URL) return false; if (!subdomain) return false; try { return await redisSet( `tunnel:active:${subdomain}`, active ? '1' : '0', tunnelActiveTtl(), ); } catch { return false; } }