RELAY-PHASE1 Wave A2/B/C/D/E (12-agent workflow, all tsc-clean, 314/314 tests pass): - A2: control-plane server.ts entry + boot/redis.ts revocation-bus wiring + start script. - B1: relay-run shared-store EnforceDeps (relay-auth ports over the SAME Postgres+Redis as P3). - B2: registry-backed MtlsVerifier (verifyAgentCert, fail-closed, INV14). - B3: store-backed RouteResolver (subdomain->hostId). - B4: Redis relay:revocations subscriber -> tunnel teardown (INV12). - B5: main-phase1.ts production entry (public bind, real TLS, async-mTLS prefetch bridge) + staging /auth/mint. - B6 (PARTIAL): relay-web operator login + browser DPoP; proof offered via term.dpop.<b64u> subprotocol. - C: agent dist/cli.js build (esbuild) + runTunnel run-loop + CliDeps. - D1: same-origin static serve of relay-web/public from the browser WSS. - E: systemd units + gen-ca/gen-capability-key/issue-tls-cert scripts + deploy/RUNBOOK.md. Adversarial review: all hard invariants PASS. Follow-ups (B7): close DPoP-subprotocol read on browser-server (blocks browser connect); rate-limit /auth/mint (F1); wire activeSessionCount (F2); scrub error logs (F5). Excludes unrelated public/style.css (concurrent iOS job).
82 lines
3.0 KiB
TypeScript
82 lines
3.0 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
|
import { APP_SUBPROTOCOL, encodeTokenSubprotocol } from 'relay-contracts'
|
|
import { readConfig } from '../src/config'
|
|
import { createPassthroughTransport, type WebSocketLike } from '../src/ws-transport'
|
|
import { encodeDpopSubprotocol } from '../src/dpop'
|
|
|
|
const cfg = readConfig({
|
|
protocol: 'https:',
|
|
host: 'alice.term.example.com',
|
|
hostname: 'alice.term.example.com',
|
|
})
|
|
|
|
class MockWs implements WebSocketLike {
|
|
static last: MockWs | null = null
|
|
binaryType = ''
|
|
protocol = ''
|
|
readonly url: string
|
|
readonly protocols: readonly string[]
|
|
closed: { code?: number; reason?: string } | null = null
|
|
onopen: ((ev: unknown) => void) | null = null
|
|
onmessage: ((ev: { data: unknown }) => void) | null = null
|
|
onclose: ((ev: { code?: number; reason?: string }) => void) | null = null
|
|
onerror: ((ev: unknown) => void) | null = null
|
|
constructor(url: string, protocols?: string | readonly string[]) {
|
|
this.url = url
|
|
this.protocols = protocols === undefined ? [] : typeof protocols === 'string' ? [protocols] : protocols
|
|
MockWs.last = this
|
|
}
|
|
send(): void {}
|
|
close(code?: number, reason?: string): void {
|
|
const ev: { code?: number; reason?: string } = {}
|
|
if (code !== undefined) ev.code = code
|
|
if (reason !== undefined) ev.reason = reason
|
|
this.closed = ev
|
|
this.onclose?.(ev)
|
|
}
|
|
fireOpen(echoedProtocol: string): void {
|
|
this.protocol = echoedProtocol
|
|
this.onopen?.({})
|
|
}
|
|
}
|
|
|
|
describe('createPassthroughTransport — B6 DPoP proof attachment', () => {
|
|
const PROOF = 'aGVhZGVy.cGF5bG9hZA.c2ln'
|
|
|
|
it('token + DPoP both ride Sec-WebSocket-Protocol in the frozen order, NEVER the URL', async () => {
|
|
const t = createPassthroughTransport(cfg, {
|
|
wsCtor: MockWs,
|
|
capabilityToken: 'RAWTOK',
|
|
dpopProof: PROOF,
|
|
})
|
|
const opened = t.open()
|
|
MockWs.last!.fireOpen(APP_SUBPROTOCOL)
|
|
await opened
|
|
expect(MockWs.last!.protocols).toEqual([
|
|
APP_SUBPROTOCOL,
|
|
encodeTokenSubprotocol('RAWTOK'),
|
|
encodeDpopSubprotocol(PROOF),
|
|
])
|
|
expect(MockWs.last!.url).not.toContain('RAWTOK')
|
|
expect(MockWs.last!.url).not.toContain(PROOF)
|
|
expect(MockWs.last!.url).not.toContain(encodeDpopSubprotocol(PROOF))
|
|
expect(MockWs.last!.url).not.toMatch(/\?/)
|
|
})
|
|
|
|
it('echo rule holds with a DPoP bearer: only APP_SUBPROTOCOL back is accepted', async () => {
|
|
const t = createPassthroughTransport(cfg, { wsCtor: MockWs, dpopProof: PROOF })
|
|
const opened = t.open()
|
|
MockWs.last!.fireOpen(encodeDpopSubprotocol(PROOF)) // relay wrongly echoes the DPoP entry
|
|
await expect(opened).rejects.toThrow()
|
|
expect(MockWs.last!.closed).not.toBeNull()
|
|
})
|
|
|
|
it('no dpopProof → protocols unchanged from the v0.9 token-only path (backward compatible)', async () => {
|
|
const t = createPassthroughTransport(cfg, { wsCtor: MockWs, capabilityToken: 'RAWTOK' })
|
|
const opened = t.open()
|
|
MockWs.last!.fireOpen(APP_SUBPROTOCOL)
|
|
await opened
|
|
expect(MockWs.last!.protocols).toEqual([APP_SUBPROTOCOL, encodeTokenSubprotocol('RAWTOK')])
|
|
})
|
|
})
|