Skip to main content

systemprompt_api/routes/oauth/error/
code.rs

1//! RFC 6749 §5.2 error codes plus the `WebAuthn` / RFC 7591 extensions this
2//! server emits, and their default HTTP status mapping.
3//!
4//! `Display` (via [`OAuthErrorCode::as_str`]) yields the wire string. The
5//! default status follows §5.2: token-endpoint errors return 400 except
6//! `invalid_client`, which RFC 6749 permits to return 401 to advertise
7//! authentication schemes — and so we do. `access_denied`, `invalid_token`,
8//! and `authentication_failed` retain 401 because they signal that the
9//! *caller* (not the request) was rejected (RFC 6750 §3.1).
10//!
11//! Copyright (c) systemprompt.io — Business Source License 1.1.
12//! See <https://systemprompt.io> for licensing details.
13
14use axum::http::StatusCode;
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17pub enum OAuthErrorCode {
18    InvalidRequest,
19    InvalidClient,
20    InvalidGrant,
21    UnauthorizedClient,
22    UnsupportedGrantType,
23    InvalidScope,
24    InvalidToken,
25    AccessDenied,
26    ServerError,
27    TemporarilyUnavailable,
28    InvalidClientMetadata,
29    AuthenticationFailed,
30    RegistrationFailed,
31    UsernameUnavailable,
32    EmailExists,
33    ExpiredChallenge,
34    InvalidCredential,
35    LinkFailed,
36    InvalidTarget,
37    NotFound,
38}
39
40impl OAuthErrorCode {
41    #[must_use]
42    pub const fn as_str(self) -> &'static str {
43        match self {
44            Self::InvalidRequest => "invalid_request",
45            Self::InvalidClient => "invalid_client",
46            Self::InvalidGrant => "invalid_grant",
47            Self::UnauthorizedClient => "unauthorized_client",
48            Self::UnsupportedGrantType => "unsupported_grant_type",
49            Self::InvalidScope => "invalid_scope",
50            Self::InvalidToken => "invalid_token",
51            Self::AccessDenied => "access_denied",
52            Self::ServerError => "server_error",
53            Self::TemporarilyUnavailable => "temporarily_unavailable",
54            Self::InvalidClientMetadata => "invalid_client_metadata",
55            Self::AuthenticationFailed => "authentication_failed",
56            Self::RegistrationFailed => "registration_failed",
57            Self::UsernameUnavailable => "username_unavailable",
58            Self::EmailExists => "email_exists",
59            Self::ExpiredChallenge => "expired_challenge",
60            Self::InvalidCredential => "invalid_credential",
61            Self::LinkFailed => "link_failed",
62            Self::InvalidTarget => "invalid_target",
63            Self::NotFound => "not_found",
64        }
65    }
66
67    #[must_use]
68    pub const fn default_status(self) -> StatusCode {
69        match self {
70            Self::InvalidRequest
71            | Self::InvalidGrant
72            | Self::UnauthorizedClient
73            | Self::UnsupportedGrantType
74            | Self::InvalidScope
75            | Self::InvalidClientMetadata
76            | Self::ExpiredChallenge
77            | Self::InvalidCredential
78            | Self::LinkFailed
79            | Self::InvalidTarget
80            | Self::RegistrationFailed => StatusCode::BAD_REQUEST,
81            Self::InvalidClient
82            | Self::AccessDenied
83            | Self::AuthenticationFailed
84            | Self::InvalidToken => StatusCode::UNAUTHORIZED,
85            Self::UsernameUnavailable | Self::EmailExists => StatusCode::CONFLICT,
86            Self::NotFound => StatusCode::NOT_FOUND,
87            Self::ServerError => StatusCode::INTERNAL_SERVER_ERROR,
88            Self::TemporarilyUnavailable => StatusCode::SERVICE_UNAVAILABLE,
89        }
90    }
91}