Skip to main content

laterite_auth/
error.rs

1//! Auth error taxonomy.
2
3use laterite_core::CoreError;
4use thiserror::Error;
5
6#[derive(Debug, Error)]
7pub enum AuthError {
8    /// Username unknown, or password did not match. Deliberately does not
9    /// distinguish the two, so the response cannot be used to enumerate users.
10    #[error("invalid credentials")]
11    InvalidCredentials,
12
13    /// Too many recent failures for this identity; login is temporarily locked.
14    #[error("too many attempts")]
15    TooManyAttempts,
16
17    /// No such session, or it has expired.
18    #[error("session invalid")]
19    SessionInvalid,
20
21    /// Credentials were correct but the account is disabled.
22    #[error("account is inactive")]
23    InactiveAccount,
24
25    /// The authenticated backend user lacks the required permission.
26    #[error("permission denied: {0}")]
27    PermissionDenied(String),
28
29    /// A deactivation that would leave the panel without a way in, or that an
30    /// operator aimed at their own account. Carries the reason, which is shown
31    /// to the operator rather than logged and swallowed.
32    #[error("{0}")]
33    Refused(String),
34
35    /// Password hashing or verification failed at the cryptographic layer.
36    #[error("password hashing failure")]
37    PasswordHash(String),
38
39    #[error("database error")]
40    Store(#[from] sqlx::Error),
41
42    /// A stored value could not be parsed back into its Rust type (corrupt data
43    /// or a schema mismatch).
44    #[error("corrupt stored data: {0}")]
45    Data(String),
46}
47
48impl From<AuthError> for CoreError {
49    fn from(err: AuthError) -> Self {
50        match err {
51            AuthError::InvalidCredentials
52            | AuthError::TooManyAttempts
53            | AuthError::SessionInvalid => CoreError::Unauthorized,
54            AuthError::InactiveAccount => CoreError::Forbidden("account is inactive".to_string()),
55            AuthError::PermissionDenied(perm) => CoreError::Forbidden(perm),
56            // The operator asked for something the rules refuse; the reason is
57            // theirs to read, so it travels rather than becoming a bare 403.
58            AuthError::Refused(reason) => CoreError::Forbidden(reason),
59            AuthError::PasswordHash(msg) => CoreError::Internal(msg),
60            AuthError::Store(e) => CoreError::Database(e),
61            AuthError::Data(msg) => CoreError::Internal(msg),
62        }
63    }
64}