New e2e/ package wires the REAL P5(auth)+P4(crypto)+P2(agent) exports through in-memory seams and an untrusted-relay attacker vantage (RelaySpy). Dynamically re-validates F1–F4/F6 plus MITM/reflection/replay/reorder/INV2/cross-tenant/ single-use — every assertion runs the production code path. 21 tests green, tsc clean. node_modules via symlinks (gitignored); no npm install needed.
133 lines
3.6 KiB
TypeScript
133 lines
3.6 KiB
TypeScript
/**
|
|
* In-memory port fakes — the ONLY seams that stand in for the true I/O boundaries P1/P3 own
|
|
* (host registry / session registry / revocation store / token buckets / audit sink / revocation
|
|
* bus). Every security decision still runs through the REAL relay-auth exports; these fakes only
|
|
* supply storage. Adapted from `relay-auth/test/_helpers.ts` (same shapes, bare import paths).
|
|
*/
|
|
import type { HostRecord, KillSignal, RevocationBus } from 'relay-contracts'
|
|
import type {
|
|
AuthenticatedPrincipal,
|
|
AuditEvent,
|
|
AuditSink,
|
|
HostRegistryPort,
|
|
SessionRegistryPort,
|
|
RevocationStore,
|
|
TokenBucketStore,
|
|
} from 'relay-auth'
|
|
|
|
/** An authenticated principal (the ONLY source of accountId, INV3). */
|
|
export function principal(
|
|
accountId: string,
|
|
overrides: Partial<AuthenticatedPrincipal> = {},
|
|
): AuthenticatedPrincipal {
|
|
return {
|
|
kind: 'human',
|
|
accountId,
|
|
principalId: 'cred-' + accountId,
|
|
amr: ['passkey'],
|
|
authAt: 1000,
|
|
stepUpAt: null,
|
|
...overrides,
|
|
}
|
|
}
|
|
|
|
/** A minimal online HostRecord for the authz registry (agentPubkey here is unused by authz). */
|
|
export function makeHostRecord(
|
|
accountId: string,
|
|
hostId: string,
|
|
status: HostRecord['status'] = 'online',
|
|
): HostRecord {
|
|
return {
|
|
hostId,
|
|
accountId,
|
|
subdomain: 'sub-' + hostId,
|
|
agentPubkey: new Uint8Array(32),
|
|
enrollFpr: 'fpr-' + hostId,
|
|
status,
|
|
lastSeen: '2026-01-01T00:00:00.000Z',
|
|
createdAt: '2026-01-01T00:00:00.000Z',
|
|
revokedAt: null,
|
|
}
|
|
}
|
|
|
|
export function fakeHostRegistry(hosts: readonly HostRecord[]): HostRegistryPort {
|
|
const map = new Map(hosts.map((h) => [h.hostId, h]))
|
|
return { getById: async (id) => map.get(id) ?? null }
|
|
}
|
|
|
|
export interface MutableSessionRegistry extends SessionRegistryPort {
|
|
add(entry: { sessionId: string; hostId: string; accountId: string }): void
|
|
}
|
|
|
|
export function fakeSessionRegistry(
|
|
seed: readonly { sessionId: string; hostId: string; accountId: string }[] = [],
|
|
): MutableSessionRegistry {
|
|
const map = new Map(seed.map((s) => [s.sessionId, { hostId: s.hostId, accountId: s.accountId }]))
|
|
return {
|
|
getById: async (id) => map.get(id) ?? null,
|
|
add: (e) => {
|
|
map.set(e.sessionId, { hostId: e.hostId, accountId: e.accountId })
|
|
},
|
|
}
|
|
}
|
|
|
|
export interface RevocationFake extends RevocationStore {
|
|
readonly revoked: Set<string>
|
|
readonly consumed: Set<string>
|
|
}
|
|
|
|
export function fakeRevocationStore(): RevocationFake {
|
|
const revoked = new Set<string>()
|
|
const consumed = new Set<string>()
|
|
return {
|
|
revoked,
|
|
consumed,
|
|
isRevoked: async (jti) => revoked.has(jti),
|
|
revokeJti: async (jti) => {
|
|
revoked.add(jti)
|
|
},
|
|
consumeOnce: async (jti) => {
|
|
if (consumed.has(jti)) return false
|
|
consumed.add(jti)
|
|
return true
|
|
},
|
|
}
|
|
}
|
|
|
|
export interface TokenBucketFake extends TokenBucketStore {
|
|
readonly blocked: Set<string>
|
|
readonly calls: string[]
|
|
}
|
|
|
|
/** Token bucket that always allows unless a key is added to `blocked`. */
|
|
export function fakeTokenBucket(): TokenBucketFake {
|
|
const blocked = new Set<string>()
|
|
const calls: string[] = []
|
|
return {
|
|
blocked,
|
|
calls,
|
|
take: async (key) => {
|
|
calls.push(key)
|
|
return !blocked.has(key)
|
|
},
|
|
}
|
|
}
|
|
|
|
export interface AuditFake extends AuditSink {
|
|
readonly events: AuditEvent[]
|
|
}
|
|
|
|
export function fakeAuditSink(): AuditFake {
|
|
const events: AuditEvent[] = []
|
|
return { events, append: async (e) => void events.push(e) }
|
|
}
|
|
|
|
export interface RevocationBusFake extends RevocationBus {
|
|
readonly published: KillSignal[]
|
|
}
|
|
|
|
export function fakeRevocationBus(): RevocationBusFake {
|
|
const published: KillSignal[] = []
|
|
return { published, publish: async (s) => void published.push(s) }
|
|
}
|