import { AssertionError } from "./errors.js"; /** Default HTTP header name for the base64-encoded assertion. */ export declare const DEFAULT_ASSERTION_HEADER = "X-App-Attest-Assertion"; /** Default HTTP header name for the device identifier. */ export declare const DEFAULT_DEVICE_ID_HEADER = "X-App-Attest-Device-Id"; /** Stored device key material returned by the `getDeviceKey` callback. */ export type DeviceKey = { /** PEM-encoded ECDSA P-256 public key from attestation. */ publicKeyPem: string; /** Last verified sign count for this device. */ signCount: number; }; /** * Library-internal timing spans for an assertion verification, in * milliseconds. Exposed on {@linkcode AssertionContext.timings} so the * wrapped handler can emit them as part of its own Server-Timing response. */ export type AssertionTimings = { /** Parse request headers and read raw body bytes. */ extractMs: number; /** `getDeviceKey` callback wall-clock duration. */ getDeviceKeyMs: number; /** Cryptographic verification (CBOR decode, ECDSA verify, counter check). */ verifyMs: number; /** `commitSignCount` callback wall-clock duration. */ commitMs: number; }; /** Context passed to the inner handler after successful assertion verification. */ export type AssertionContext = { /** Device identifier from the request. */ deviceId: string; /** Updated sign count after verification. */ signCount: number; /** Raw request body bytes (the client data that was signed). */ rawBody: Uint8Array; /** Library-internal timings, ready to merge into Server-Timing. */ timings: AssertionTimings; }; /** Custom function to extract assertion data from an incoming request. */ export type ExtractAssertionFn = (req: Request) => Promise<{ assertion: string; deviceId: string; clientData: Uint8Array; }>; /** Configuration for the {@linkcode withAssertion} middleware. */ export type WithAssertionOptions = { /** Apple App ID in the format `TEAMID.bundleId`. */ appId: string; /** Retrieve the stored device key for a given device ID. Return `null` if not found. */ getDeviceKey: (deviceId: string) => Promise; /** * Atomically advance the stored sign count. MUST be implemented as a * compare-and-swap: only update if the currently stored value is strictly * less than `newSignCount`. Returns `true` if the row was advanced, * `false` if another concurrent request already advanced past this value. * * Recommended SQL pattern against Postgres: * * ```sql * UPDATE app_attest_devices * SET sign_count = $1, last_seen_at = now() * WHERE device_id = $2 AND sign_count < $1 * ``` * * Return `rowCount > 0`. The library converts `false` into * `AssertionError(SIGN_COUNT_STALE)`. */ commitSignCount: (deviceId: string, newSignCount: number) => Promise; /** Override the default header-based assertion extraction. */ extractAssertion?: ExtractAssertionFn; /** * Maximum request body size in bytes accepted by the default extractor * (default 1 MiB). Oversized bodies are rejected with `INVALID_FORMAT` * before buffering. Ignored when `extractAssertion` is provided. */ maxBodyBytes?: number; /** Custom error response handler. Defaults to JSON error responses. */ onError?: (error: AssertionError, req: Request) => Response | Promise; }; /** * Request handler middleware that verifies App Attest assertions. * * Wraps a handler function with automatic assertion verification, * device key lookup, and atomic sign-count commit. Returns a new handler * that rejects unauthenticated requests with appropriate HTTP error responses. * * The `commitSignCount` callback MUST implement compare-and-swap semantics * (see {@linkcode WithAssertionOptions.commitSignCount}) — a non-atomic * unconditional write will silently corrupt replay protection under * concurrent load. */ export declare function withAssertion(options: WithAssertionOptions, handler: (req: Request, context: AssertionContext) => Response | Promise): (req: Request) => Promise;