Skip to main content

systemprompt_api/routes/oauth/error/
conversions.rs

1//! `From` impls mapping domain errors onto [`OAuthHttpError`], keeping the
2//! variant-to-RFC-code mapping in one place so handlers use `?`.
3
4use systemprompt_config::SecretsBootstrapError;
5use systemprompt_oauth::OauthError;
6use systemprompt_traits::auth::AuthProviderError;
7
8use super::{OAuthErrorCode, OAuthHttpError};
9
10impl From<OauthError> for OAuthHttpError {
11    fn from(err: OauthError) -> Self {
12        match &err {
13            OauthError::InvalidClient(_) | OauthError::ClientNotFound(_) => {
14                Self::invalid_client(err.to_string())
15            },
16            OauthError::InvalidGrant(_)
17            | OauthError::CodeNotFound(_)
18            | OauthError::TokenNotFound(_)
19            | OauthError::PkceMismatch(_)
20            | OauthError::Expired(_) => Self::invalid_grant(err.to_string()),
21            OauthError::Validation(_) => Self::invalid_request(err.to_string()),
22            OauthError::Unauthorized(_) => Self::access_denied(err.to_string()),
23            OauthError::UsernameTaken(_) => Self::username_unavailable(
24                "Username is already taken. Please choose a different username.",
25            ),
26            OauthError::EmailRegistered(_) => {
27                Self::email_exists("An account with this email already exists.")
28            },
29            OauthError::UserNotFound(_) => Self::not_found(err.to_string()),
30            OauthError::RegistrationStateExpired => Self::expired_challenge(
31                "Registration challenge has expired. Please start the registration process again.",
32            ),
33            OauthError::WebAuthnVerificationFailed(_) => Self::invalid_credential(
34                "WebAuthn verification failed. Please ensure your authenticator and browser are \
35                 compatible.",
36            ),
37            OauthError::WebAuthn(_)
38            | OauthError::User(_)
39            | OauthError::Session(_)
40            | OauthError::TokenInvalid(_)
41            | OauthError::TokenAlgMismatch { .. }
42            | OauthError::TokenMissingKid
43            | OauthError::TokenUnknownKid { .. }
44            | OauthError::Provider(_)
45            | OauthError::Repository(_)
46            | OauthError::DatabaseRepository(_)
47            | OauthError::Config(_)
48            | OauthError::Crypto(_)
49            | OauthError::Internal(_) => Self::server_error(err.to_string()),
50        }
51    }
52}
53
54impl From<AuthProviderError> for OAuthHttpError {
55    fn from(err: AuthProviderError) -> Self {
56        match &err {
57            AuthProviderError::InvalidCredentials | AuthProviderError::InvalidToken => {
58                Self::invalid_client(err.to_string())
59            },
60            AuthProviderError::UserNotFound => Self::not_found(err.to_string()),
61            AuthProviderError::TokenExpired => Self::invalid_grant(err.to_string()),
62            AuthProviderError::InsufficientPermissions => Self::access_denied(err.to_string()),
63            _ => Self::server_error(err.to_string()),
64        }
65    }
66}
67
68impl From<SecretsBootstrapError> for OAuthHttpError {
69    fn from(err: SecretsBootstrapError) -> Self {
70        Self::server_error(err.to_string())
71    }
72}
73
74impl From<sqlx::Error> for OAuthHttpError {
75    fn from(err: sqlx::Error) -> Self {
76        if let sqlx::Error::Database(db_err) = &err
77            && db_err.is_unique_violation()
78        {
79            return Self::new(OAuthErrorCode::UsernameUnavailable, err.to_string());
80        }
81        Self::server_error(err.to_string())
82    }
83}
84
85impl From<anyhow::Error> for OAuthHttpError {
86    fn from(err: anyhow::Error) -> Self {
87        Self::server_error(err.to_string())
88    }
89}