/** * Pairing (SH2) — how an unknown person is authorized to talk to a personal-line * agent. Fail-closed: an unknown DMer gets a one-time code; the owner approves it * once; then they're allowed. Codes are hashed (never stored in plaintext), * rate-limited per user, and brute-force is locked out per channel. * * Learned from Hermes' pairing.py — the elegant bit is "no static ID lists." */ export declare class PairingRateLimitError extends Error { constructor(message?: string); } export interface PairingStore { /** Is this user already approved on this channel? */ isApproved(channel: string, userId: string): Promise; /** Mint a one-time code for an unknown user (returns plaintext once). Throws {@link PairingRateLimitError} if on cooldown. */ requestCode(channel: string, userId: string): Promise; /** Owner approves a code → the bound user becomes approved. */ approveCode(channel: string, code: string): Promise<{ approved: boolean; userId?: string; locked?: boolean; }>; } export interface InMemoryPairingOptions { /** Injectable clock (tests). Default Date.now. */ readonly now?: () => number; /** Code lifetime. Default 1h. */ readonly codeTtlMs?: number; /** Per-user cooldown between code requests. Default 10min. */ readonly cooldownMs?: number; /** Failed approvals before a channel lockout. Default 5. */ readonly maxFailedApprovals?: number; /** Lockout duration after too many failures. Default 15min. */ readonly lockoutMs?: number; /** Injectable code generator (tests). */ readonly generateCode?: () => string; readonly codeLength?: number; } export declare class InMemoryPairingStore implements PairingStore { private readonly approved; private readonly pending; private readonly lastRequest; private readonly failCount; private readonly lockUntil; private readonly salt; private readonly now; private readonly codeTtlMs; private readonly cooldownMs; private readonly maxFailed; private readonly lockoutMs; private readonly gen; constructor(opts?: InMemoryPairingOptions); private key; private hash; private purgeExpired; isApproved(channel: string, userId: string): Promise; requestCode(channel: string, userId: string): Promise; approveCode(channel: string, code: string): Promise<{ approved: boolean; userId?: string; locked?: boolean; }>; } export interface FilePairingStoreOptions extends InMemoryPairingOptions { /** JSON state file (e.g. `~/.ownware/channels/pairing.json`). Created 0600. */ readonly file: string; } /** * Same rules as {@link InMemoryPairingStore} (hashed codes, cooldown, * lockout), persisted to a 0600 JSON file. Every operation reloads the * file first — the runner process and the `approve` CLI process both * mutate the same state, and message-rate traffic makes a per-op JSON * read/write a non-cost. */ export declare class FilePairingStore implements PairingStore { private readonly file; private readonly now; private readonly codeTtlMs; private readonly cooldownMs; private readonly maxFailed; private readonly lockoutMs; private readonly gen; constructor(opts: FilePairingStoreOptions); private load; private save; private hash; private purgeExpired; isApproved(channel: string, userId: string): Promise; requestCode(channel: string, userId: string): Promise; approveCode(channel: string, code: string): Promise<{ approved: boolean; userId?: string; locked?: boolean; }>; } //# sourceMappingURL=pairing.d.ts.map