108 lines
7.3 KiB
TypeScript
108 lines
7.3 KiB
TypeScript
/** Fixed managed metadata comparison. This is NOT actor, Q or native authority.
|
|
* The trusted caller must check its genuine current original grant first.
|
|
* Passwords are private bound predicates, never result projections or hashes. */
|
|
import { types } from "node:util"
|
|
import { performance } from "node:perf_hooks"
|
|
import type { PoolClient, QueryConfig } from "pg"
|
|
import type { BandwidthPeerSourceSnapshot } from "./bandwidth-peer-observation.js"
|
|
import type { BandwidthNodeObservationScope } from "./bandwidth-node-observation.js"
|
|
|
|
export class BandwidthPeerMetadataSourceRefused extends Error {
|
|
readonly code = "BANDWIDTH_PEER_METADATA_SOURCE_REFUSED"
|
|
constructor() { super("Bandwidth peer metadata refused") }
|
|
}
|
|
const refuse = (): never => { throw new BandwidthPeerMetadataSourceRefused() }
|
|
const checkers = new WeakSet<object>()
|
|
function data(raw: unknown, names: readonly string[]): Record<string, unknown> {
|
|
if (!raw || typeof raw !== "object" || types.isProxy(raw) || Object.getPrototypeOf(raw) !== Object.prototype) return refuse()
|
|
const fields = Object.getOwnPropertyDescriptors(raw)
|
|
if (Reflect.ownKeys(raw).length !== names.length || names.some(name => !fields[name])
|
|
|| Object.values(fields).some(field => !("value" in field) || !field.enumerable)) return refuse()
|
|
return Object.fromEntries(names.map(name => [name, fields[name].value]))
|
|
}
|
|
const bounded = (raw: unknown, max: number): raw is string => typeof raw === "string" && Buffer.byteLength(raw) <= max
|
|
const SQL = `/* bandwidth-managed:metadata-current */ SELECT EXISTS (
|
|
SELECT 1 FROM public.servers
|
|
WHERE id=$1::bigint AND enabled IS TRUE AND host=$2::text AND port=$3::integer
|
|
AND use_ssl=$4::boolean AND verify_ssl=$5::boolean
|
|
AND username=$6::text AND password=$7::text
|
|
AND routing_endpoint_epoch=$8::uuid
|
|
) AS metadata_current`
|
|
|
|
export function assertManagedBandwidthPeerMetadataChecker(value: unknown): asserts value is ReturnType<typeof createManagedBandwidthPeerMetadataChecker> {
|
|
if (!value || typeof value !== "object" || !checkers.has(value)) refuse()
|
|
}
|
|
export function createManagedBandwidthPeerMetadataChecker() {
|
|
const checker = Object.freeze({
|
|
async assertMetadataCurrent(expected: Readonly<BandwidthPeerSourceSnapshot>, rawScope: BandwidthNodeObservationScope): Promise<void> {
|
|
const wall = Date.now(), mono = performance.now()
|
|
let client: PoolClient | undefined, active = true, released = false, success = false
|
|
let releaseFailed = false, socketFailed = false
|
|
let timer: ReturnType<typeof setTimeout> | undefined, signal: AbortSignal | undefined, cancel: (() => void) | undefined
|
|
const onError = () => { socketFailed = true; if (active) cancel?.() }
|
|
const release = (destroy: boolean) => {
|
|
if (!client || released) return
|
|
released = true
|
|
try {
|
|
client.release(destroy)
|
|
if (!destroy) client.removeListener("error", onError)
|
|
} catch { releaseFailed = true }
|
|
// A destroyed/uncertain client retains its listener as a late-error
|
|
// absorber. Never retry an uncertain release or expose its raw error.
|
|
}
|
|
try {
|
|
const original = data(expected, ["peerRef", "enabled", "sourceProof", "authorityGeneration", "endpoint"])
|
|
// External source has no persistent current metadata provider. Never
|
|
// invent a server alias, consult a supplied snapshot again or fall back.
|
|
const peer = data(original.peerRef, ["kind", "serverId"])
|
|
const endpoint = data(original.endpoint, ["host", "port", "useSsl", "verifySsl", "username", "password", "routingEndpointEpoch"])
|
|
if (peer.kind !== "server" || !Number.isSafeInteger(peer.serverId) || Number(peer.serverId) < 1 || original.enabled !== true
|
|
|| !bounded(original.sourceProof, 64) || !/^[a-f0-9]{64}$/.test(original.sourceProof)
|
|
|| !bounded(original.authorityGeneration, 200) || !original.authorityGeneration
|
|
|| !bounded(endpoint.host, 1000) || !endpoint.host || /[\s/\\\u0000-\u001f\u007f]/.test(endpoint.host)
|
|
|| !Number.isSafeInteger(endpoint.port) || Number(endpoint.port) < 1 || Number(endpoint.port) > 65535
|
|
|| typeof endpoint.useSsl !== "boolean" || typeof endpoint.verifySsl !== "boolean"
|
|
|| !bounded(endpoint.username, 1000) || !bounded(endpoint.password, 65536)
|
|
|| !bounded(endpoint.routingEndpointEpoch, 36) || !/^[a-f0-9]{8}(?:-[a-f0-9]{4}){3}-[a-f0-9]{12}$/i.test(endpoint.routingEndpointEpoch)) refuse()
|
|
if (!rawScope || typeof rawScope !== "object" || types.isProxy(rawScope) || Object.getPrototypeOf(rawScope) !== Object.prototype) refuse()
|
|
const fields = data(rawScope, Object.hasOwn(rawScope, "signal") ? ["deadlineAt", "signal"] : ["deadlineAt"])
|
|
if (!Number.isSafeInteger(fields.deadlineAt) || fields.signal !== undefined && (types.isProxy(fields.signal) || !(fields.signal instanceof AbortSignal))) refuse()
|
|
signal = fields.signal as AbortSignal | undefined
|
|
const deadline = Math.min(Number(fields.deadlineAt), wall + 5000), end = mono + Math.max(0, deadline - wall)
|
|
const aborted = Object.getOwnPropertyDescriptor(AbortSignal.prototype, "aborted")!.get!
|
|
const live = () => { const now = Date.now(); if (!active || now < wall || now >= deadline || performance.now() >= end || signal && Reflect.apply(aborted, signal, [])) refuse() }
|
|
const values = [peer.serverId, endpoint.host, endpoint.port, endpoint.useSsl, endpoint.verifySsl, endpoint.username, endpoint.password, endpoint.routingEndpointEpoch]
|
|
let rejectStopped!: (error: BandwidthPeerMetadataSourceRefused) => void
|
|
const stopped = new Promise<never>((_resolve, reject) => { rejectStopped = reject }); void stopped.catch(() => {})
|
|
cancel = () => { active = false; release(true); rejectStopped(new BandwidthPeerMetadataSourceRefused()) }
|
|
live(); timer = setTimeout(cancel, Math.max(1, Math.ceil(end - performance.now())))
|
|
if (signal) Reflect.apply(EventTarget.prototype.addEventListener, signal, ["abort", cancel, { once: true }])
|
|
await Promise.race([stopped, (async () => {
|
|
live(); const { pool } = await import("../db/index.js"); live()
|
|
const acquired = await pool.connect()
|
|
if (!active) {
|
|
acquired.on("error", () => {})
|
|
try { acquired.release(true) } catch { /* Original call already refused. */ }
|
|
return refuse()
|
|
}
|
|
client = acquired; client.on("error", onError); live()
|
|
const config: QueryConfig & { query_timeout: number } = { text: SQL, values, query_timeout: Math.max(1, Math.ceil(Math.min(deadline - Date.now(), end - performance.now()))) }
|
|
const result = await client.query(config)
|
|
live()
|
|
if (result.command !== "SELECT" || result.rowCount !== 1 || result.rows.length !== 1) refuse()
|
|
const row = data(result.rows[0], ["metadata_current"])
|
|
if (row.metadata_current !== true) refuse()
|
|
live(); success = true
|
|
})()]); live()
|
|
} catch { throw new BandwidthPeerMetadataSourceRefused() }
|
|
finally {
|
|
active = false; clearTimeout(timer)
|
|
if (signal && cancel) Reflect.apply(EventTarget.prototype.removeEventListener, signal, ["abort", cancel])
|
|
release(!success)
|
|
if (releaseFailed || socketFailed) throw new BandwidthPeerMetadataSourceRefused()
|
|
}
|
|
},
|
|
})
|
|
checkers.add(checker); return checker
|
|
}
|