import type { DB } from '../db/client.js'; import { type AccountLinkRow } from '../db/schema/account_links.js'; /** * Match-only helper for command routers. Returns the raw code on match, else * null. Use this to short-circuit before doing any DB work. */ export declare function matchLinkCommand(text: string | null | undefined): string | null; export interface AccountLinkStoreOptions { db: DB; /** * Allowlist of `identity_kind` values the bot accepts. Codes whose stored * kind isn't in this set are refused — guards against a compromised web * side writing arbitrary identity kinds. Defaults to an empty allowlist * (everything refused) — callers must set this explicitly. */ allowedIdentityKinds?: string[]; /** Default TTL for issued codes, in seconds. Default 600 (10 minutes). */ defaultTtlSec?: number; /** Redeem attempts allowed per whatsapp per hour. Default 5. */ maxAttemptsPerHour?: number; } export type RedeemReason = 'rate_limited' | 'malformed' | 'not_found' | 'already_used' | 'expired' | 'invalid_identity_kind' | 'db_error'; export type RedeemResult = { ok: true; identityKind: string; identityValue: string; } | { ok: false; reason: RedeemReason; }; export interface IssueCodeArgs { code: string; identityKind: string; identityValue: string; ttlSec?: number; } export declare class AccountLinkStore { readonly db: DB; readonly allowed: ReadonlySet; readonly defaultTtlSec: number; readonly maxAttemptsPerHour: number; private readonly attempts; constructor({ db, allowedIdentityKinds, defaultTtlSec, maxAttemptsPerHour }: AccountLinkStoreOptions); /** * Web-side helper — store a hashed code so the bot can later redeem it. * Pass the raw digits; this hashes before persisting. * * @returns the inserted code row's id, useful for "your code: 123456" UI flows. */ issueCode({ code, identityKind, identityValue, ttlSec }: IssueCodeArgs): Promise; /** * Best-effort per-isolate sliding window rate limit. Cold starts reset it; * stick brute-forcers cross isolates land at the persistent Blocklist layer. * * @returns true when the caller has exceeded the per-hour cap. Caller should * short-circuit and reject without touching the DB. */ recordRedeemAttempt(whatsapp: string): boolean; /** * Verify + materialize the (identity → whatsapp) link. Idempotent: a * second redeem for the same identity overwrites the whatsapp number * (handy when a user replaces their phone). * * Codes are single-use; once redeemed, re-attempts fail with `already_used`. */ redeem(whatsapp: string, rawCode: string): Promise; /** Look up a single link by identity. */ findByIdentity(identityKind: string, identityValue: string): Promise; /** All identities linked to a single whatsapp. */ listByWhatsapp(whatsapp: string): Promise; /** Unlink one identity. Idempotent. */ unlink(identityKind: string, identityValue: string): Promise; /** Refresh `last_seen_at` — call when the linked identity is used. */ touch(identityKind: string, identityValue: string): Promise; /** * Periodic cleanup — drops expired-and-unused codes plus codes whose * `used_at` is older than `keepUsedFor` (default 30 days, kept for * audit). Run from cron. */ cleanup({ keepUsedForSec }?: { keepUsedForSec?: number; }): Promise; } //# sourceMappingURL=account_link_store.d.ts.map