Skip to main content

fraiseql_error/
auth.rs

1/// Errors that arise during authentication and authorisation flows.
2#[derive(Debug, thiserror::Error)]
3pub enum AuthError {
4    /// The supplied username/password (or API key) did not match any account.
5    #[error("Invalid credentials")]
6    InvalidCredentials,
7
8    /// The access token has passed its expiry time and must be refreshed.
9    #[error("Token expired")]
10    TokenExpired,
11
12    /// The access token is structurally invalid or has been tampered with.
13    #[error("Invalid token: {reason}")]
14    InvalidToken {
15        /// Reason the token was rejected (kept server-side; not forwarded to clients).
16        reason: String,
17    },
18
19    /// An upstream OAuth / OIDC provider returned an error during the flow.
20    #[error("Provider error: {provider} - {message}")]
21    ProviderError {
22        /// Name of the provider (e.g. `"google"`, `"github"`).
23        provider: String,
24        /// Provider-supplied error message (kept server-side; not forwarded to clients).
25        message:  String,
26    },
27
28    /// The OAuth `state` parameter did not match the stored value, indicating a
29    /// possible CSRF attack or a stale/replayed authorisation request.
30    #[error("Invalid OAuth state")]
31    InvalidState,
32
33    /// The resource owner explicitly declined the authorisation request at the
34    /// provider's consent screen.
35    #[error("User denied authorization")]
36    UserDenied,
37
38    /// No active session exists for the supplied session identifier.
39    #[error("Session not found")]
40    SessionNotFound,
41
42    /// The session existed but has expired and can no longer be used.
43    #[error("Session expired")]
44    SessionExpired,
45
46    /// The authenticated principal does not have the scopes or roles required
47    /// to perform the requested operation.
48    #[error("Insufficient permissions: requires {required}")]
49    InsufficientPermissions {
50        /// The permission or scope that was required but not granted.
51        required: String,
52    },
53
54    /// The refresh token has been revoked, used more than once, or has expired.
55    #[error("Refresh token invalid or expired")]
56    RefreshTokenInvalid,
57
58    /// The account has been administratively locked and cannot be used.
59    #[error("Account locked: {reason}")]
60    AccountLocked {
61        /// Reason the account was locked.
62        reason: String,
63    },
64}
65
66impl AuthError {
67    /// Returns a short, stable error code string suitable for API responses and
68    /// structured logging.
69    pub const fn error_code(&self) -> &'static str {
70        match self {
71            Self::InvalidCredentials => "invalid_credentials",
72            Self::TokenExpired => "token_expired",
73            Self::InvalidToken { .. } => "invalid_token",
74            Self::ProviderError { .. } => "auth_provider_error",
75            Self::InvalidState => "invalid_oauth_state",
76            Self::UserDenied => "user_denied",
77            Self::SessionNotFound => "session_not_found",
78            Self::SessionExpired => "session_expired",
79            Self::InsufficientPermissions { .. } => "insufficient_permissions",
80            Self::RefreshTokenInvalid => "refresh_token_invalid",
81            Self::AccountLocked { .. } => "account_locked",
82        }
83    }
84}