Files
dsh-remote/deploy/fake-edge.mjs
T
lyuksovannyy bfbc3c66e4 dsh-remote 0.5.0: node settings, session-cookie edge auth, sign-in popup
- Settings → Remote devices → Node: type master|slave (stored in state, beats
  config role; Restart DSH button under systemd/launchd), origin (read-only:
  own origin / paired master), 'Master has an auth middleware' toggle
- middleware on: a non-DSH answer on the link (login redirect, 401/403, inline
  HTML) raises auth-required; the UI opens <master>/dsh-remote/signin in a popup,
  the user signs in to the proxy, one click hands the proxy session cookie back
  (postMessage, origin-checked, trusted origins only), link retries
- edge mode 'cookie': pasted authentik_proxy_… (or a whole Cookie header),
  rotated proxy cookies persisted back; pair.mjs/install-mac --edge cookie
- connectMaster serialised; a failed attempt never clears a finished link
- tests: cookie seed/mode, HTML login wall; e2e-signin against a fake proxy
2026-10-12 09:37:21 +00:00

66 lines
2.9 KiB
JavaScript

#!/usr/bin/env node
// Test helper: an Authentik-like auth proxy in front of a DSH master.
// node deploy/fake-edge.mjs <listenPort> <upstream http://host:port> <user> <password>
// Unauthenticated → 302 to a fake login page; Basic user:password → session cookie +
// pass-through (HTTP and WebSocket upgrades); session cookie → pass-through.
import crypto from 'node:crypto'
import http from 'node:http'
import net from 'node:net'
const [port, upstream, user, pass] = process.argv.slice(2)
const up = new URL(upstream)
const sessions = new Set()
const want = `Basic ${Buffer.from(`${user}:${pass}`).toString('base64')}`
const stats = { redirects: 0, credentialLogins: 0, cookieHits: 0, upgrades: 0 }
function auth(req) {
const sid = /edge_session=([a-f0-9]+)/.exec(req.headers.cookie || '')?.[1]
if (sid && sessions.has(sid)) { stats.cookieHits++; return { ok: true } }
if (req.headers.authorization === want) {
stats.credentialLogins++
const id = crypto.randomBytes(12).toString('hex')
sessions.add(id)
return { ok: true, setCookie: `edge_session=${id}; Path=/; HttpOnly; Max-Age=86400` }
}
if (req.headers.authorization) return { ok: false, status: 401 }
return { ok: false, status: 302 }
}
const strip = (h) => { const o = { ...h }; delete o.authorization; return o }
const srv = http.createServer((req, res) => {
if (req.url === '/__stats') { res.end(JSON.stringify(stats)); return }
const a = auth(req)
if (!a.ok) {
if (a.status === 302) stats.redirects++
res.writeHead(a.status, a.status === 302 ? { location: 'https://auth.example/application/o/authorize/?client_id=x' } : {})
return res.end()
}
const p = http.request({ host: up.hostname, port: up.port, method: req.method, path: req.url, headers: { ...strip(req.headers), host: up.host } }, (r) => {
const h = { ...r.headers }
if (a.setCookie) h['set-cookie'] = [a.setCookie, ...(h['set-cookie'] || [])]
res.writeHead(r.statusCode, h)
r.pipe(res)
})
p.on('error', () => { res.writeHead(502); res.end() })
req.pipe(p)
})
srv.on('upgrade', (req, socket, head) => {
const a = auth(req)
if (!a.ok) {
if (a.status === 302) stats.redirects++
socket.end(a.status === 302 ? 'HTTP/1.1 302 Found\r\nLocation: https://auth.example/login\r\nContent-Length: 0\r\n\r\n' : 'HTTP/1.1 401 Unauthorized\r\nContent-Length: 0\r\n\r\n')
return
}
stats.upgrades++
const s = net.connect(Number(up.port), up.hostname, () => {
const lines = [`${req.method} ${req.url} HTTP/1.1`]
for (const [k, v] of Object.entries({ ...strip(req.headers), host: up.host })) for (const one of [].concat(v)) lines.push(`${k}: ${one}`)
s.write(lines.join('\r\n') + '\r\n\r\n')
if (head?.length) s.write(head)
s.pipe(socket); socket.pipe(s)
})
s.on('error', () => socket.destroy())
socket.on('error', () => s.destroy())
})
srv.listen(Number(port), '127.0.0.1', () => console.log(`fake edge on 127.0.0.1:${port} → ${upstream}`))