dsh-remote 0.4.0: edge auth – slaves log in to an auth proxy in front of the master
- lib/edge-auth.mjs: basic / bearer / oauth2 client_credentials (Authentik M2M) / custom headers (Cloudflare Access), proxy session cookie jar, retry with fresh token, clear edge-refusal vs DSH-refusal errors - link + enroll go through the edge; master.edgeAuth stored at pair time, secrets as DSH credential refs only; config edgeAuth override - /dsh-remote/edge-auth: show/change/probe; Settings UI section + pair form - pair.mjs / install-mac.sh: --edge … flags, secrets prompted via /dev/tty - tests: fake Authentik-like proxy for all modes + WebSocket through the edge
This commit is contained in:
1 parent
5dff41a3d0
commit
78fce8e1ce
12 files changed
+833
-40
No files matched your search
@@ -0,0 +1,65 @@
|
||||
#!/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'] = [...(h['set-cookie'] || []), a.setCookie]
|
||||
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}`))
|
||||
Reference in new issue
Block a user