Something went wrong. Try again.
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259//! Error types, one enum per domain.//!//! Every message starts with a greppable code, `error-aip-lite-<domain>-<n>`.//! A number is never reused: when a variant goes, its number is retired.
use thiserror::Error;
/// Configuration loading and validation.#[derive(Debug, Error)]pub enum ConfigError { /// The config file could not be read. #[error("error-aip-lite-config-1 Could not read config file: {path}: {details}")] Read { /// Path that was read. path: String, /// Underlying error. details: String, }, /// The config file is not valid TOML for this schema. #[error("error-aip-lite-config-2 Invalid config file: {0}")] Parse(String), /// A value failed validation. #[error("error-aip-lite-config-3 Invalid configuration: {0}")] Invalid(String), /// A secret reference could not be resolved. #[error("error-aip-lite-config-4 Secret {reference} could not be read: {details}")] Secret { /// The `env:` or `file:` reference. reference: String, /// Why it failed. details: String, }, /// A duration string could not be parsed. #[error( "error-aip-lite-config-5 Invalid duration {0:?}: use a number followed by s, m, h or d" )] Duration(String),}
/// Key loading, signing and sealing.#[derive(Debug, Error)]pub enum KeyError { /// A configured signing key is not a usable P-256 private key. #[error("error-aip-lite-keys-1 Invalid signing key in {set}: {details}")] InvalidSigningKey { /// Which key set. set: &'static str, /// Why. details: String, }, /// A sealing key is malformed. #[error("error-aip-lite-keys-2 Invalid sealing key: {0}")] InvalidSealingKey(String), /// A sealed value could not be opened. #[error("error-aip-lite-keys-3 Sealed value could not be opened: {0}")] Unseal(String), /// Encryption failed. #[error("error-aip-lite-keys-4 Sealing failed")] Seal, /// A key set has no keys. #[error("error-aip-lite-keys-5 Key set {0} is empty")] Empty(&'static str), /// JWT minting failed. #[error("error-aip-lite-keys-6 Could not sign token: {0}")] Sign(String),}
/// Database access.#[derive(Debug, Error)]pub enum StorageError { /// Opening or migrating the database failed. #[error("error-aip-lite-storage-1 Could not open database: {0}")] Open(String), /// A query failed. #[error("error-aip-lite-storage-2 Database query failed: {0}")] Query(#[from] sqlx::Error), /// A stored value could not be decoded. #[error("error-aip-lite-storage-3 Stored value is corrupt: {0}")] Corrupt(String),}
/// Identity resolution and authorization-server discovery.#[derive(Debug, Error)]pub enum IdentityError { /// The sign-in input is not a handle, DID or HTTPS URL. #[error("error-aip-lite-identity-1 Not a handle, DID or server URL: {0}")] InvalidInput(String), /// Resolution failed. #[error("error-aip-lite-identity-2 Could not resolve {subject}: {details}")] Resolve { /// What was being resolved. subject: String, /// Why it failed. details: String, }, /// The DID document names no usable PDS. #[error("error-aip-lite-identity-3 {0} has no usable PDS")] NoPds(String), /// Discovery of the authorization server failed. #[error( "error-aip-lite-identity-4 Could not discover an authorization server for {subject}: {details}" )] Discovery { /// The PDS or URL being discovered. subject: String, /// Why it failed. details: String, }, /// The authorization server lacks a capability aip-lite requires (UP-8). #[error("error-aip-lite-identity-5 Authorization server {issuer} is not usable: {reason}")] Unsupported { /// The server's issuer. issuer: String, /// What it is missing. reason: String, }, /// Resolution could not reach the directory or server it needed: a /// refused connection, a timeout or a 5xx, not a missing account. #[error("error-aip-lite-identity-6 Could not reach what resolves {subject}: {details}")] Unreachable { /// What was being resolved. subject: String, /// Why it failed. details: String, },}
/// The upstream AT Protocol OAuth client.#[derive(Debug, Error)]pub enum UpstreamError { /// The callback's state is unknown, used or expired (UP-20). #[error("error-aip-lite-upstream-1 Unknown or expired sign-in state")] UnknownState, /// The browser binding cookie is missing or does not match (UP-20). #[error("error-aip-lite-upstream-2 This sign-in was started in a different browser")] BindingMismatch, /// The authorization server returned an error to the callback (UP-19). #[error("error-aip-lite-upstream-3 Authorization server refused: {error}")] Refused { /// OAuth error code. error: String, /// Optional description. description: Option<String>, }, /// PAR, token exchange, refresh or binding failed in atproto-oauth. #[error("error-aip-lite-upstream-4 AT Protocol OAuth failed: {0}")] Protocol(String), /// The token response is not acceptable (UP-22, UP-23). #[error("error-aip-lite-upstream-5 Unacceptable token response: {0}")] BadTokenResponse(String), /// The upstream session has ended (UP-28). #[error("error-aip-lite-upstream-6 Upstream session has ended: {0}")] SessionEnded(String), /// The DID is not permitted by the allow-list (OP-6). #[error("error-aip-lite-upstream-7 {0} is not permitted to sign in here")] NotAllowed(String), /// A required callback parameter is missing. #[error("error-aip-lite-upstream-8 Callback is missing {0}")] MissingParameter(&'static str),}
/// The downstream OIDC provider. These carry the OAuth error code returned to/// the relying party.#[derive(Debug, Error)]pub enum OidcError { /// `invalid_request`. #[error("error-aip-lite-oidc-1 invalid_request: {0}")] InvalidRequest(String), /// `invalid_client`. #[error("error-aip-lite-oidc-2 invalid_client: {0}")] InvalidClient(String), /// `invalid_grant`. #[error("error-aip-lite-oidc-3 invalid_grant: {0}")] InvalidGrant(String), /// `unauthorized_client`. #[error("error-aip-lite-oidc-4 unauthorized_client: {0}")] UnauthorizedClient(String), /// `unsupported_grant_type`. #[error("error-aip-lite-oidc-5 unsupported_grant_type: {0}")] UnsupportedGrantType(String), /// `invalid_scope`. #[error("error-aip-lite-oidc-6 invalid_scope: {0}")] InvalidScope(String), /// `invalid_token`. #[error("error-aip-lite-oidc-7 invalid_token: {0}")] InvalidToken(String), /// `server_error`. #[error("error-aip-lite-oidc-8 server_error: {0}")] Server(String), /// `invalid_dpop_proof`. #[error("error-aip-lite-oidc-9 invalid_dpop_proof: {0}")] InvalidDpopProof(String), /// `slow_down` / too many requests. #[error("error-aip-lite-oidc-10 rate_limited: {0}")] RateLimited(String), /// `request_not_supported` (OIDC Core 3.1.2.6): a `request` object was /// sent and aip-lite does not take them. #[error("error-aip-lite-oidc-12 request_not_supported: {0}")] RequestNotSupported(String), /// `temporarily_unavailable`: the user's server could not be reached; the /// same request may succeed later. #[error("error-aip-lite-oidc-11 temporarily_unavailable: {0}")] Unavailable(String),}
impl OidcError { /// The OAuth `error` code for the response body. #[must_use] pub fn code(&self) -> &'static str { match self { Self::InvalidRequest(_) => "invalid_request", Self::InvalidClient(_) => "invalid_client", Self::InvalidGrant(_) => "invalid_grant", Self::UnauthorizedClient(_) => "unauthorized_client", Self::UnsupportedGrantType(_) => "unsupported_grant_type", Self::InvalidScope(_) => "invalid_scope", Self::InvalidToken(_) => "invalid_token", Self::Server(_) => "server_error", Self::InvalidDpopProof(_) => "invalid_dpop_proof", Self::RateLimited(_) => "slow_down", Self::Unavailable(_) => "temporarily_unavailable", Self::RequestNotSupported(_) => "request_not_supported", } }
/// The description, without the code prefix. #[must_use] pub fn description(&self) -> &str { match self { Self::InvalidRequest(d) | Self::InvalidClient(d) | Self::InvalidGrant(d) | Self::UnauthorizedClient(d) | Self::UnsupportedGrantType(d) | Self::InvalidScope(d) | Self::InvalidToken(d) | Self::Server(d) | Self::InvalidDpopProof(d) | Self::RateLimited(d) | Self::Unavailable(d) | Self::RequestNotSupported(d) => d, } }}
impl From<StorageError> for OidcError { fn from(error: StorageError) -> Self { tracing::error!(error = %error, "storage failure"); Self::Server("storage failure".to_string()) }}
impl From<KeyError> for OidcError { fn from(error: KeyError) -> Self { tracing::error!(error = %error, "key failure"); Self::Server("key failure".to_string()) }}