import type { AuthCredentialStore } from "../auth/store.js"; import { type AuthCredential, type DisabledCredentialSummary, type OAuthCredential, type OAuthRefreshReason, type StoredAuthCredential, type StoredCredentialBlock } from "../auth/types.js"; import type { OAuthCredentials } from "../registry/oauth/types.js"; import type { Provider } from "../types.js"; import type { ClientUsageIdentity, ObservedUsageEntry, UsageReport } from "../usage.js"; import { type AuthBrokerClient } from "./client.js"; import type { SnapshotResponse } from "./types.js"; /** * Per-provider OAuth identities visible to this trusted broker client. * Missing providers are unrestricted; an empty set excludes that provider's * OAuth credentials. API keys are never filtered. */ export type AuthBrokerAccountPool = ReadonlyMap>; export interface RemoteAuthCredentialStoreOptions { client: AuthBrokerClient; /** * Initial snapshot. When omitted, callers must call * {@link RemoteAuthCredentialStore.refreshSnapshot} before the first read. */ initialSnapshot?: SnapshotResponse; /** * Subscribe to the broker's SSE snapshot stream when available. Falls back * to long-poll permanently when the broker returns 404. Default `true`. */ streamSnapshots?: boolean; /** * Called with each broker-sourced raw full snapshot after the filtered * public view is applied. The constructor's initial snapshot intentionally * does not trigger this hook. */ onSnapshot?: (snapshot: SnapshotResponse, generation: number) => void; /** * OAuth identities visible through this store. This is a trusted-client * routing policy, not broker authorization. */ accountPool?: AuthBrokerAccountPool; /** Flush cadence for batched observed-usage reports. Default 10s. */ observedUsageFlushMs?: number; /** * Idle window after the last foreground store use before background * snapshot sync (SSE stream / long-poll) disconnects and parks. A parked * store holds no timers or sockets, so an unclosed store never keeps the * process alive longer than one idle window. Sync resumes transparently on * the next use. Default 20s. */ backgroundIdleMs?: number; } export declare class RemoteAuthCredentialStore implements AuthCredentialStore { #private; constructor(opts: RemoteAuthCredentialStoreOptions); get client(): AuthBrokerClient; get snapshot(): SnapshotResponse; /** Re-hydrate the in-memory snapshot from the broker. */ refreshSnapshot(): Promise; /** * Stateful probe for broker-side credential changes, mirroring * {@link SqliteAuthCredentialStore.pollExternalChanges} so long-lived broker * clients (notably `auth-gateway serve`) pick up logins/logouts made by * another process without a restart. * * Compares a local content revision, not the broker's numeric generation: * generation is an in-memory counter that resets when the broker process * restarts, so a reconnecting stream can deliver a different credential set * under a repeated (or lower) generation. {@link #refreshCredentialRevision} * bumps the revision whenever the applied credential material actually * changes, catching those cases too. Records foreground activity first: a * low-traffic client's background sync parks after `#backgroundIdleMs`, and * without this wakeup it would never fetch the new snapshot to report in the * first place. */ pollExternalChanges(): boolean; listAuthCredentials(provider?: string): StoredAuthCredential[]; /** Broker-backed disabled tombstones; empty against brokers predating the endpoint. */ listDisabledCredentials(provider?: string, signal?: AbortSignal): Promise; getCredentialBlock(credentialId: number, providerKey: string, blockScope: string): number | undefined; getCredentialBlockReconcileAfter(credentialId: number, providerKey: string, blockScope: string): number | undefined; listCredentialBlocks(credentialIds: readonly number[]): StoredCredentialBlock[]; upsertCredentialBlock(block: StoredCredentialBlock): void; deleteCredentialBlock(credentialId: number, providerKey: string, blockScope: string): void; deleteCredentialBlocks(credentialId: number): void; cleanExpiredCredentialBlocks(nowMs: number): void; /** * In-memory update from a successful refresh through the broker. AuthStorage * calls this after `#replaceCredentialAt`; the broker already persisted the * authoritative row, so we just mirror it. */ updateAuthCredential(id: number, credential: AuthCredential): void; deleteAuthCredential(id: number, disabledCause: string): Promise; tryDisableAuthCredentialIfMatches(id: number, _expectedData: string, disabledCause: string): boolean; waitForFreshSnapshot(maxWaitMs: number, opts?: { signal?: AbortSignal; }): Promise; prepareForRequest(credentialId: number, opts?: { signal?: AbortSignal; }): Promise; markCredentialSuspect(credentialId: number, opts?: { signal?: AbortSignal; }): Promise; /** * Upsert a single credential through the broker. The broker server is the * canonical writer — see `POST /v1/credential`. The redacted snapshot * entries returned by the server replace the provider's rows in our local * snapshot, and the global snapshot is then refreshed in the background so * any concurrent peer (refresh, generation bump) stays in sync. */ upsertAuthCredential(provider: string, credential: AuthCredential): Promise; /** * Replace-all semantics: disable every active credential for the provider, * then upload each of the new credentials. Used by API-key login so a new * key clobbers any previously stored key for the same provider. */ replaceAuthCredentials(provider: string, credentials: AuthCredential[]): Promise; /** * Logout: disable every active credential for the provider on the broker, * then drop them from the local snapshot. Refresh fetches the authoritative * post-state in the background. */ deleteAuthCredentials(provider: string, disabledCause: string): Promise; getCache(key: string): string | null; setCache(key: string, value: string, expiresAtSec: number): void; /** Drop all cache rows whose keys start with the supplied prefix. */ deleteCachePrefix(prefix: string): void; cleanExpiredCache(): void; invalidateUsageCache(signal?: AbortSignal): Promise; /** * Store-level hook consumed by `AuthStorage` — routes refresh through the * broker so the actual refresh token never leaves the broker host. Returns * the broker-redacted credential with {@link REMOTE_REFRESH_SENTINEL} in * the `refresh` slot. */ refreshOAuthCredential(_provider: Provider, credentialId: number, _credential: OAuthCredential, signal?: AbortSignal, reason?: OAuthRefreshReason): Promise; /** * Store-level hook consumed by `AuthStorage.usage.reports()` — proxies * to the broker's `/v1/usage` endpoint. Shared per-credential caches and * cooldowns keep separate clients from multiplying provider probes. */ fetchUsageReports(signal?: AbortSignal): Promise; /** * Per-credential usage hook consumed by `UsageService.report`. Pulls * the aggregate broker `/v1/usage` once and serves all callers from the * same response (coalesced + cached), then overlays any client-observed * header hints for the matching credential. * * The broker caches each credential independently; the short client TTL * also folds sequential consumers into one broker round-trip. */ getUsageReport(provider: Provider, credential: OAuthCredential, signal?: AbortSignal): Promise; ingestUsageReport(provider: Provider, credential: OAuthCredential, report: UsageReport): boolean; /** * Fold locally observed request usage into the pending report and schedule * a flush. One `POST /v1/usage/observed` at most per flush interval; on * failure the batch is retained and retried with the next flush. A 404 * (pre-endpoint broker) disables reporting for the life of this store. * * `client` overrides the reporting identity — the auth-gateway attributes * each request to the originating install/app instead of the gateway host. */ recordObservedUsage(entries: ObservedUsageEntry[], client?: ClientUsageIdentity): void; close(): void; }