/** * Parse a raw key without verifying it. Returns the segments, or * `null` when the shape is wrong. Never throws. * * Callers can use `parseApiKey(key).prefix` to route by key family * in a middleware before hitting the store — but the parse result * MUST NOT be trusted as authenticated. Only `verifyApiKey` proves * the holder possesses the real secret. * * @param {string} key * @returns {{ prefix: string, id: string, secret: string } | null} */ declare function parseApiKey(key: string): { prefix: string; id: string; secret: string; } | null; /** * Log-safe display for a key. Returns `_…` * so the value is recognisable in an audit log without disclosing the * secret. The default masks all but the first 6 chars of the id and * the last 4 chars of the secret. * * @param {string} key * @returns {string} */ declare function mask(key: string): string; /** * Shared base error class — the single error structure behind every * `@exortek/*` package's `errors.js`. * * Every package keeps its own class identity with a one-liner subclass; * codes stay per-package frozen maps, status mapping is declared as a * static field: * * import { BaseError } from '@exortek/shared/errors'; * * export const ErrorCode = Object.freeze({ * INVALID_ARGUMENT: 'INVALID_ARGUMENT', * INVALID_TOKEN: 'INVALID_TOKEN', * }); * * export class JwtError extends BaseError { * static statuses = { INVALID_ARGUMENT: 400, INVALID_TOKEN: 401 }; * static defaultStatus = 500; * } * * Instances carry a stable machine-readable `code` (branch on this, * never on the message), an optional HTTP `status`, an optional * `details` object, and the standard `cause` chain. */ declare class BaseError extends Error { /** * Optional `code → HTTP status` map declared on the subclass. When * absent the instance carries no `status` at all — for HTTP-agnostic * packages like `@exortek/crypto`. * * @type {Record | undefined} */ static statuses: Record | undefined; /** * Fallback status for codes missing from `statuses`. * * @type {number} */ static defaultStatus: number; /** * @param {string} code Stable machine-readable code; branch on this. * @param {string} message Human-readable diagnostic. Free-form; may * change across versions. * @param {{ cause?: unknown, status?: number, details?: Record }} [options] */ constructor(code: string, message: string, options?: { cause?: unknown; status?: number; details?: Record; }); /** @type {string} */ code: string; /** @type {number | undefined} */ status: number | undefined; /** @type {Record | undefined} */ details: Record | undefined; } declare const ErrorCode: Readonly<{ INVALID_ARGUMENT: "INVALID_ARGUMENT"; INVALID_PREFIX: "INVALID_PREFIX"; INVALID_PEPPER: "INVALID_PEPPER"; STORE_ERROR: "STORE_ERROR"; }>; /** * Every recoverable failure raised by this package. Carries a stable * `code` (from {@link ErrorCode}) and a `status` — the HTTP response * status a middleware layer would use when translating the error. */ declare class ApiKeyError extends BaseError { static statuses: { INVALID_ARGUMENT: number; INVALID_PREFIX: number; INVALID_PEPPER: number; STORE_ERROR: number; }; } /** * Does `granted` cover a single `required` scope? * @param {string[]} granted * @param {string} required * @returns {boolean} */ declare function covers(granted: string[], required: string): boolean; /** * All required scopes must be covered by granted. * @param {string[]} granted * @param {string[]} required * @returns {boolean} */ declare function hasAll(granted: string[], required: string[]): boolean; /** * At least one required scope must be covered. * @param {string[]} granted * @param {string[]} required * @returns {boolean} */ declare function hasAny(granted: string[], required: string[]): boolean; /** * @typedef {object} ApiKeyRecord * @property {string} id Plaintext lookup key (128-bit b64u). * @property {string} hash Storage hash of the secret (43-char b64u). * @property {string} prefix The prefix the key was minted with. * @property {string} userId * @property {string[]} scopes * @property {string} [name] Human label ("Production Backend"). * @property {string} [environment] `'live'` / `'test'` / caller-defined. * @property {Record} [metadata] * @property {number} createdAt ms epoch. * @property {number} [expiresAt] ms epoch; absent = no expiry. * @property {number} [revokedAt] ms epoch when the record was revoked. * @property {string} [revokedReason] * @property {number} [lastUsedAt] ms epoch of the last successful verify. * @property {number} [pepperVersion] Index into the peppers array that was used at mint. 0 = newest. */ /** * @typedef {object} ApiKeyStore * @property {(record: ApiKeyRecord) => Promise} put * @property {(id: string) => Promise} getById * @property {(id: string, patch: Partial) => Promise} update * @property {(id: string, reason?: string) => Promise} revoke * @property {(userId: string, reason?: string) => Promise} revokeAllForUser * @property {(userId: string) => Promise} listByUser */ /** * @typedef {object} CreateApiKeyOptions * @property {string} prefix Stripe-style; `sk_live`, `pk_test`, `svc_prod_v2`. * @property {string} userId * @property {string[]} scopes * @property {string} [name] * @property {string} [environment] * @property {Record} [metadata] * @property {string | number} [expiresIn] `'1y'` / `'30d'` / ms integer. Omit for no expiry. * @property {ApiKeyStore} store * @property {(Buffer | Uint8Array | string)[]} [peppers] * Newest first. Each ≥16 bytes. Omit for plain SHA-256 storage. * @property {number} [now] Override `Date.now()` for testing. */ /** * @typedef {object} CreateApiKeyResult * @property {string} key Wire token — show ONCE to the caller and never again. * @property {string} id Plaintext lookup key; safe to store / log / display. * @property {ApiKeyRecord} record What was persisted to the store. */ /** * @typedef {object} VerifyApiKeyOptions * @property {ApiKeyStore} store * @property {(Buffer | Uint8Array | string)[]} [peppers] * @property {string[]} [requiredScopes] * @property {string} [expectedPrefix] Reject a valid key whose prefix differs. * @property {boolean} [updateLastUsed=false] Bump `lastUsedAt` on success (one extra store write). * @property {number} [now] */ /** * @typedef {'malformed' | 'not_found' | 'expired' | 'revoked' * | 'bad_secret' | 'prefix_mismatch' | 'missing_scope' * | 'store_unavailable'} VerifyApiKeyFailureReason */ /** * @typedef {{ * valid: true, * id: string, * userId: string, * scopes: string[], * prefix: string, * name?: string, * environment?: string, * metadata?: Record, * needsRehash?: boolean, * } | { valid: false, reason: VerifyApiKeyFailureReason }} VerifyApiKeyResult */ /** * Mint a new API key. The wire `key` is returned once — the caller * MUST show it to the end user immediately and never persist it in * the clear (only the storage `hash` inside the returned `record` is * safe to keep). * * @param {CreateApiKeyOptions} options * @returns {Promise} */ declare function createApiKey(options: CreateApiKeyOptions): Promise; /** * Verify a raw API key. Returns `{ valid: true, ... }` on success, or * `{ valid: false, reason }` on any expected failure. Never throws on * a bad key — a wrong or stale key is a normal auth outcome. * * On success, callers get the stored `userId` / `scopes` / `metadata` * and can pass them straight to their app's auth context. * `needsRehash: true` in the success result signals the secret matched * an older pepper — call `rehashApiKey(id, options)` to silently * migrate storage to the newest pepper on the next natural chance. * * @param {string} rawKey * @param {VerifyApiKeyOptions} options * @returns {Promise} */ declare function verifyApiKey(rawKey: string, options: VerifyApiKeyOptions): Promise; /** * Revoke an API key by id or by the raw wire key. Returns `true` if * a record was actually revoked, `false` if the id was unknown or * the record was already revoked. * * @param {string} keyOrId Either the raw wire key or its id half. * @param {{ store: ApiKeyStore, reason?: string }} options * @returns {Promise} */ declare function revokeApiKey(keyOrId: string, options: { store: ApiKeyStore; reason?: string; }): Promise; /** * Revoke every non-revoked key belonging to `userId`. Useful on * password reset, account termination, etc. Returns the number of * records actually revoked. * * @param {string} userId * @param {{ store: ApiKeyStore, reason?: string }} options * @returns {Promise} */ declare function revokeAllForUser(userId: string, options: { store: ApiKeyStore; reason?: string; }): Promise; /** * List every key belonging to `userId` — for a "manage API keys" UI. * Callers should hide the storage `hash` before rendering; only the * plaintext `id` is safe to display. Returns most-recently-used first * (falling back to createdAt) so active keys float to the top. * * @param {string} userId * @param {{ store: ApiKeyStore }} options * @returns {Promise} */ declare function listApiKeys(userId: string, options: { store: ApiKeyStore; }): Promise; /** * Migrate the storage hash of an existing key to the newest pepper. * Requires the raw wire key (the plaintext secret is the input to the * new HMAC). Called opportunistically when `verifyApiKey` reports * `needsRehash: true`. * * @param {string} rawKey * @param {{ store: ApiKeyStore, peppers: (Buffer | Uint8Array | string)[] }} options * @returns {Promise} */ declare function rehashApiKey(rawKey: string, options: { store: ApiKeyStore; peppers: (Buffer | Uint8Array | string)[]; }): Promise; type ApiKeyRecord = { /** * Plaintext lookup key (128-bit b64u). */ id: string; /** * Storage hash of the secret (43-char b64u). */ hash: string; /** * The prefix the key was minted with. */ prefix: string; userId: string; scopes: string[]; /** * Human label ("Production Backend"). */ name?: string | undefined; /** * `'live'` / `'test'` / caller-defined. */ environment?: string | undefined; metadata?: Record | undefined; /** * ms epoch. */ createdAt: number; /** * ms epoch; absent = no expiry. */ expiresAt?: number | undefined; /** * ms epoch when the record was revoked. */ revokedAt?: number | undefined; revokedReason?: string | undefined; /** * ms epoch of the last successful verify. */ lastUsedAt?: number | undefined; /** * Index into the peppers array that was used at mint. 0 = newest. */ pepperVersion?: number | undefined; }; type ApiKeyStore = { put: (record: ApiKeyRecord) => Promise; getById: (id: string) => Promise; update: (id: string, patch: Partial) => Promise; revoke: (id: string, reason?: string) => Promise; revokeAllForUser: (userId: string, reason?: string) => Promise; listByUser: (userId: string) => Promise; }; type CreateApiKeyOptions = { /** * Stripe-style; `sk_live`, `pk_test`, `svc_prod_v2`. */ prefix: string; userId: string; scopes: string[]; name?: string | undefined; environment?: string | undefined; metadata?: Record | undefined; /** * `'1y'` / `'30d'` / ms integer. Omit for no expiry. */ expiresIn?: string | number | undefined; store: ApiKeyStore; /** * Newest first. Each ≥16 bytes. Omit for plain SHA-256 storage. */ peppers?: any[] | undefined; /** * Override `Date.now()` for testing. */ now?: number | undefined; }; type CreateApiKeyResult = { /** * Wire token — show ONCE to the caller and never again. */ key: string; /** * Plaintext lookup key; safe to store / log / display. */ id: string; /** * What was persisted to the store. */ record: ApiKeyRecord; }; type VerifyApiKeyOptions = { store: ApiKeyStore; peppers?: any[] | undefined; requiredScopes?: string[] | undefined; /** * Reject a valid key whose prefix differs. */ expectedPrefix?: string | undefined; /** * Bump `lastUsedAt` on success (one extra store write). */ updateLastUsed?: boolean | undefined; now?: number | undefined; }; type VerifyApiKeyFailureReason = "malformed" | "not_found" | "expired" | "revoked" | "bad_secret" | "prefix_mismatch" | "missing_scope" | "store_unavailable"; type VerifyApiKeyResult = { valid: true; id: string; userId: string; scopes: string[]; prefix: string; name?: string; environment?: string; metadata?: Record; needsRehash?: boolean; } | { valid: false; reason: VerifyApiKeyFailureReason; }; export { ApiKeyError, ErrorCode, covers, createApiKey, hasAll, hasAny, listApiKeys, mask, mask as maskApiKey, parseApiKey, rehashApiKey, revokeAllForUser, revokeApiKey, verifyApiKey }; export type { ApiKeyRecord, ApiKeyStore, CreateApiKeyOptions, CreateApiKeyResult, VerifyApiKeyFailureReason, VerifyApiKeyOptions, VerifyApiKeyResult };