/** * Live transcript, phase 2: ACTIVE-provider outcomes over a real socket. * Strategy: seed the REAL data files (/etc/dashcaddy/network-*.json — none * exist on this fresh install) in the exact on-disk format the stores read, * register a stub provider through the REAL registry (setActiveProvider), * boot the REAL app, and hit the REAL mounted route over HTTP. Only the * network edge (whois) is stubbed. Leaves no state behind that matters * (fresh-install files; removed on exit). */ 'use strict'; const http = require('http'); const fs = require('fs'); const { createApp } = require('../src/app'); const registry = require('../src/network/registry'); const DATA = '/etc/dashcaddy'; function req(port, method, reqPath, headers = {}) { return new Promise((resolve, reject) => { const r = http.request({ host: '127.0.0.1', port, method, path: reqPath, headers }, (res) => { let body = ''; res.on('data', (c) => { body += c; }); res.on('end', () => resolve({ status: res.statusCode, headers: res.headers, body })); }); r.on('error', reject); r.end(); }); } const CREATED = []; function writeIfAbsent(name, obj) { const p = `${DATA}/${name}`; if (!fs.existsSync(p)) { fs.writeFileSync(p, JSON.stringify(obj, null, 2)); CREATED.push(p); } } async function main() { // ── seed the real store files (fresh install: none exist) ── const now = new Date().toISOString(); writeIfAbsent('network-people.json', { people: { 'p-alice': { id: 'p-alice', displayName: 'Alice', providerUserId: '1001', groups: ['Admin'], status: 'active', devices: [], createdAt: now, updatedAt: now }, 'p-bob': { id: 'p-bob', displayName: 'Bob', providerUserId: '1002', groups: ['Family'], status: 'active', devices: [], createdAt: now, updatedAt: now }, }, }); writeIfAbsent('network-resources.json', { resources: [ { id: 'r-plex', name: 'plex', host: 'plex.internal', ports: ['*'], sensitive: false, createdAt: now, updatedAt: now }, { id: 'r-vault', name: 'vault', host: 'vault.internal', ports: ['*'], sensitive: true, createdAt: now, updatedAt: now }, ], }); writeIfAbsent('network-access.json', { grants: [ { principal: { type: 'person', id: 'p-alice' }, resource: 'plex', allowed: true, updatedAt: now }, { principal: { type: 'person', id: 'p-alice' }, resource: 'vault', allowed: true, updatedAt: now }, ], updatedAt: now, }); // ── register + activate a stub provider through the REAL registry ── const DEVICE_IP = '10.53.0.7'; const stub = { id: () => 'stub-live', whois: async (ip) => (ip === DEVICE_IP ? { personId: 'p-alice', deviceId: 'dev-1' } : (ip === '10.53.0.9' ? { personId: 'p-bob', deviceId: 'dev-2' } : { personId: null, deviceId: null })), listDevices: async () => [], listPeople: async () => [], capabilities: () => ({ id: 'stub-live' }), getStatus: async () => ({ ok: true }), createInvite: async () => { throw new Error('not needed for probe'); }, revokeInvite: async () => { throw new Error('not needed for probe'); }, blockPerson: async () => { throw new Error('not needed for probe'); }, unblockPerson: async () => { throw new Error('not needed for probe'); }, removePerson: async () => { throw new Error('not needed for probe'); }, revokeDevice: async () => { throw new Error('not needed for probe'); }, syncAccess: async () => { throw new Error('not needed for probe'); }, configureDns: async () => { throw new Error('not needed for probe'); }, }; registry.registerProvider('stub-live', () => stub); registry.setActiveProvider('stub-live'); const { app } = await createApp(); const server = app.listen(0, '127.0.0.1'); await new Promise((r) => server.on('listening', r)); const port = server.address().port; const FA = '/api/v1/network/forward-auth/'; const show = (label, res) => { console.log(`### ${label}`); console.log(`< HTTP ${res.status}`); if (res.headers['www-authenticate']) console.log(`< www-authenticate: ${res.headers['www-authenticate']}`); if (res.headers['x-forwarded-user']) console.log(`< x-forwarded-user: ${res.headers['x-forwarded-user']}`); console.log(`< body: ${res.body.slice(0, 240)}`); console.log(''); }; // loopback is in the default trustedProxies → forwarded headers honored show('ALLOWED non-sensitive (alice device → plex.internal)', await req(port, 'GET', FA, { 'X-Forwarded-For': DEVICE_IP, 'X-Forwarded-Host': 'plex.internal' })); show('DENIED (bob device → plex.internal, no grant)', await req(port, 'GET', FA, { 'X-Forwarded-For': '10.53.0.9', 'X-Forwarded-Host': 'plex.internal' })); show('UNKNOWN-DEVICE (unrecognized tailnet ip)', await req(port, 'GET', FA, { 'X-Forwarded-For': '10.99.99.99', 'X-Forwarded-Host': 'plex.internal' })); show('SENSITIVE step-up REQUIRED (alice → vault.internal, no session)', await req(port, 'GET', FA, { 'X-Forwarded-For': DEVICE_IP, 'X-Forwarded-Host': 'vault.internal' })); server.close(); console.log('LIVE-PROBE-PHASE2-DONE'); process.exit(0); } main().catch((e) => { console.error('FATAL', e && e.stack || e); process.exit(1); });