/** * Stable, machine-readable error codes returned by the auth handlers alongside * the human-readable English `error` prose. A localized client maps the `code` * to its own translated string (see `errorMessageFromCode` in the client utils) * and falls back to the raw `error` when a code is unknown — so non-i18n * consumers keep working unchanged while localized pages stop rendering English. * * Codes are an append-only contract: never repurpose an existing value, only add * new ones. The set is intentionally semantic (not 1:1 with HTTP status) so the * same code can carry across handlers that share a meaning (e.g. every * re-auth-gated mutation returns `current_password_incorrect`). * * Validation failures (`validation_error`) keep their field-level `error` * message — the client's fallback surfaces it verbatim — because the precise * "email is invalid" / "password too short" text is more useful than a generic * localized string. */ export declare const AUTH_ERROR_CODES: { readonly invitation_required: "invitation_required"; readonly invitation_used: "invitation_used"; readonly invitation_expired: "invitation_expired"; readonly email_taken: "email_taken"; readonly email_invited: "email_invited"; readonly invalid_credentials: "invalid_credentials"; readonly account_locked: "account_locked"; readonly email_unverified: "email_unverified"; readonly invalid_token: "invalid_token"; readonly current_password_incorrect: "current_password_incorrect"; readonly not_authenticated: "not_authenticated"; readonly forbidden: "forbidden"; readonly invalid_code: "invalid_code"; readonly two_factor_setup_code_invalid: "two_factor_setup_code_invalid"; readonly no_2fa_challenge: "no_2fa_challenge"; readonly two_factor_challenge_expired: "two_factor_challenge_expired"; readonly two_factor_already_enabled: "two_factor_already_enabled"; readonly two_factor_setup_required: "two_factor_setup_required"; readonly totp_secret_unreadable: "totp_secret_unreadable"; readonly session_not_found: "session_not_found"; readonly missing_refresh_token: "missing_refresh_token"; readonly invalid_refresh_token: "invalid_refresh_token"; readonly feature_unavailable: "feature_unavailable"; readonly validation_error: "validation_error"; readonly rate_limited: "rate_limited"; readonly connection_limit: "connection_limit"; readonly csrf_failed: "csrf_failed"; readonly passkey_verification_failed: "passkey_verification_failed"; readonly passkey_registration_verification_failed: "passkey_registration_verification_failed"; readonly passkey_credential_deleted: "passkey_credential_deleted"; readonly passkey_not_found: "passkey_not_found"; readonly push_endpoint_conflict: "push_endpoint_conflict"; readonly push_subscription_limit: "push_subscription_limit"; readonly server_error: "server_error"; }; /** Union of every machine error code the auth handlers may return. */ export type AuthErrorCode = (typeof AUTH_ERROR_CODES)[keyof typeof AUTH_ERROR_CODES]; /** * The HTTP status each code answers under. The status is a property of the * code, not of the call site: two handlers returning the same code answer * under the same status because they read it from here rather than repeating * it. * * `Record` and not `Partial` is the whole enforcement — * a new code with no status here fails to compile, so there is no default to * fall back to and no way to reach a status this table does not name. * * **Choosing between 400 and 401 for a new code.** 401 means the request was an * attempt to authenticate and the credential it carried was missing or refused. * Everything else about a request is 400: a payload the server cannot read, a * request that does not match the server's state, and — the case that decides * the split pairs below — a wrong secret on a request that was not * authenticating anyone. Being signed in is not itself the test: a wrong TOTP * during enrolment is 400 because nothing was being authenticated. * * **What counts as "the credential it carried" needs saying, because two * server-issued cookies answer differently.** A refresh token *is* the * credential: the request offers it and asks for a session in exchange, so a * missing or rejected one is 401. The pending-2FA cookie is only a handle * naming which challenge is open — the credential is the TOTP or backup code * in the body — so a missing or expired handle is a request that does not * match server state, 400. The seam is that `verifySignedToken` returns null * for an expired token and a forged one alike, so a forged pending cookie * answers 400 where a forged refresh token answers 401. Both are refusals * either way; only the class differs, and nothing downstream reads it. * * The rule reaches only that pair. Every other status is chosen by its own HTTP * meaning: 403 when an authenticated caller is refused the action (a failed * re-auth gate included), 409 for a conflict, 423, 429, 404, 500 for a fault. * * Not re-exported from `./index.js`: a consumer reads the status off the * `Response`, and pinning the table as API would freeze numbers that belong to * the handlers. */ export declare const AUTH_ERROR_STATUS: Record; interface AuthErrorOptions { /** Override the default English prose (e.g. a field-level validation message). */ message?: string; /** Extra fields to merge into the JSON body (e.g. `errors` for validation). */ extra?: Record; /** * `Retry-After`, on the two rate-limit codes. Typed as that one header rather * than `HeadersInit` because it is the only one any refusal has ever carried, * and because the narrower type makes the case worth warning about — * a caller passing `Cache-Control`, which {@link authError} would override — * impossible to write instead of merely documented. */ headers?: { 'Retry-After': string; }; } /** * Build a handler error response carrying BOTH the human `error` prose and the * machine `code`. Drop-in for the old `json({ error }, { status })`: * * ```ts * return authError('invitation_required'); * // → 403 { error: 'An invitation is required to register.', code: 'invitation_required' } * ``` * * The status comes from {@link AUTH_ERROR_STATUS} keyed by the code — callers * do not pass one. Pass `message` to override the prose (validation field * messages), `extra` to add body fields (`{ errors }`), and `headers` for the * one header a refusal carries beyond the directive below (`Retry-After`). * * **`Cache-Control: no-store` is a property of the refusal, not of the call * site.** Every code here names an account, a credential state or a rate-limit * verdict, and none of it may be replayed to a second caller; `session_not_found` * answers 404, which is heuristically storable on its own (RFC 9111 §4.2.2), * and `json()` emits nothing but `content-type` and `content-length`. Set once * here, no call site can forget it and none has to spell it. The header is put * on the response `json()` already built rather than merged into a fresh * `Headers`, so a caller's own header cannot be dropped in the process. */ export declare function authError(code: AuthErrorCode, opts?: AuthErrorOptions): Response; export {};