/** * Redis-backed jti-replay store contract. * * The lib OWNS the interface; consumers ship the Redis-talking impl * (typically a thin wrapper around `ioredis` / `node-redis`). Per R1, * the lib does NOT ship an in-memory default — a missing `replayStore` * at bootstrap (`configureGrpcAuth`) is a hard error. * * Reference Redis op: * `SET nodii:grpc-auth:jti: 1 PX NX` * * Implementations MUST be a single round-trip atomic `SET NX PX` so two * concurrent verify calls of the same jti produce exactly one `true` * return. A naive `EXISTS`-then-`SET` is RACE-VULNERABLE and is NOT * doctrine-compliant. */ interface ReplayStore { /** * Attempt to record a jti as "seen" with a TTL. * * @param jti - the JWT id claim. MUST be non-empty. * @param ttlMs - the TTL of the entry in milliseconds. Implementations * SHOULD set the underlying Redis key with `PX ttlMs NX` so the * entry self-expires at the same time as the token. * @returns `true` if the jti was newly stored (token may proceed); * `false` if the jti was already present (token MUST be rejected * as a replay). */ reserve(jti: string, ttlMs: number): Promise; } /** * Verifier-thrown error hierarchy, extracted from `s2s-verify.ts` so * peer types in `../types/` (e.g. `JtiReplayed`) can extend the base * without importing the full verifier module (which would form a * cycle: verifier → types/jti-replayed → verifier). * * Public surface (re-exported from `./verify` + the package root) is * unchanged; this is a pure refactor of module-load order. */ declare class S2SVerifyError extends Error { constructor(message: string, cause?: unknown); } declare class S2STokenInvalid extends S2SVerifyError { constructor(message?: string, cause?: unknown); } declare class S2STokenExpired extends S2SVerifyError { constructor(message?: string, cause?: unknown); } declare class S2SIssuerMismatch extends S2SVerifyError { constructor(message?: string, cause?: unknown); } declare class S2SAudienceMismatch extends S2SVerifyError { constructor(message?: string, cause?: unknown); } /** * The jti-replay store could not answer, so the verifier could not decide * whether this jti is fresh. * * ⚠️ NOT A REJECTION OF THE TOKEN. Nothing about the presented token is known * to be wrong; an external dependency is down. The correct caller response is * RETRY; the correct response to `S2STokenInvalid` is "your token was bad, do * not retry it". A caller cannot make that choice unless the two are distinct * classes, which is the entire reason this one exists. * * WHAT IT REPLACES. `verifyS2SToken` used to `await opts.replayStore.reserve( * jti, ttlMs)` bare, so a raw driver error (an ioredis `ConnectionError`, a * `MaxRetriesPerRequestError`) escaped the verifier untouched. Downstream * `catch (err)` blocks that map by class — including this package's own * synthetic consumer — fell through to their default arm and reported a Redis * blip as `S2STokenInvalid` / HTTP 401, i.e. an infra outage indistinguishable * from a forged token, on the exact typed-error axis parity-fence fixtures * 04/06/07 exist to fence. * * This package's OWN sibling path already got it right — `gateway-hmac/bound.ts` * catches around `replayStore.reserve` and returns a typed * `REPLAY_STORE_ERROR` — so the S2S verifier was the outlier inside a single * package, not a missing idea. Go (`ReplayStoreUnavailableError`) and Python * (`ReplayStoreUnavailableError`) are fixed in the same change: this is a class * across three ports. * * FAIL-CLOSED. Like `bound.ts`, this is thrown rather than swallowed: an * unreachable store means the verifier cannot prove the jti is unseen, so it * must not return success. */ declare class S2SReplayStoreUnavailable extends S2SVerifyError { constructor(message?: string, cause?: unknown); } /** * Typed error thrown when an incoming token's `jti` claim has already * been seen within the replay-store TTL window. * * Extends `S2SVerifyError` so downstream consumers can `instanceof`- * discriminate replay rejections from other verifier failures (e.g. * `S2STokenExpired`, `S2SAudienceMismatch`). * * Wire-stable detail code: `auth_jti_replayed` (auth-sdk middleware lens). */ declare class JtiReplayed extends S2SVerifyError { readonly jti: string; constructor(jti: string, cause?: unknown); } export { JtiReplayed as J, type ReplayStore as R, S2SAudienceMismatch as S, S2SIssuerMismatch as a, S2SReplayStoreUnavailable as b, S2STokenExpired as c, S2STokenInvalid as d, S2SVerifyError as e };