Skip to main content

fabryk_auth/
error.rs

1//! Auth-specific error types.
2
3/// Errors that can occur during authentication.
4#[derive(Debug, thiserror::Error)]
5pub enum AuthError {
6    /// No Authorization header or bearer token present.
7    #[error("missing authentication token")]
8    MissingToken,
9
10    /// Token format is invalid (not a valid JWT).
11    #[error("invalid token format: {0}")]
12    InvalidFormat(String),
13
14    /// JWT signature verification failed.
15    #[error("invalid token signature: {0}")]
16    InvalidSignature(String),
17
18    /// Token has expired.
19    #[error("token has expired")]
20    Expired,
21
22    /// Token audience doesn't match configured client ID.
23    #[error("invalid audience")]
24    InvalidAudience,
25
26    /// Token domain doesn't match configured domain.
27    #[error("invalid domain: got '{domain}', expected '{expected}'")]
28    InvalidDomain { domain: String, expected: String },
29
30    /// Token is missing the email claim.
31    #[error("token missing email claim")]
32    MissingEmail,
33
34    /// Failed to fetch JWKS from the identity provider.
35    #[error("failed to fetch JWKS: {0}")]
36    JwksFetchError(String),
37
38    /// No key in the JWKS matches the token's kid.
39    #[error("no matching key for kid '{0}'")]
40    NoMatchingKey(String),
41}
42
43impl AuthError {
44    /// Whether this error should result in a 401 (vs. a 500).
45    pub fn is_client_error(&self) -> bool {
46        matches!(
47            self,
48            AuthError::MissingToken
49                | AuthError::InvalidFormat(_)
50                | AuthError::InvalidSignature(_)
51                | AuthError::Expired
52                | AuthError::InvalidAudience
53                | AuthError::InvalidDomain { .. }
54                | AuthError::MissingEmail
55                | AuthError::NoMatchingKey(_)
56        )
57    }
58}
59
60#[cfg(test)]
61mod tests {
62    use super::*;
63
64    #[test]
65    fn test_auth_error_display() {
66        let e = AuthError::MissingToken;
67        assert_eq!(e.to_string(), "missing authentication token");
68    }
69
70    #[test]
71    fn test_auth_error_invalid_domain_display() {
72        let e = AuthError::InvalidDomain {
73            domain: "other.com".to_string(),
74            expected: "banyan.com".to_string(),
75        };
76        assert_eq!(
77            e.to_string(),
78            "invalid domain: got 'other.com', expected 'banyan.com'"
79        );
80    }
81
82    #[test]
83    fn test_is_client_error() {
84        assert!(AuthError::MissingToken.is_client_error());
85        assert!(AuthError::Expired.is_client_error());
86        // JwksFetchError is a server-side issue, not a client error
87        assert!(!AuthError::JwksFetchError("err".into()).is_client_error());
88    }
89}