1#[derive(Debug, thiserror::Error)]
5pub enum AuthError {
6 #[error("missing authentication token")]
8 MissingToken,
9
10 #[error("invalid token format: {0}")]
12 InvalidFormat(String),
13
14 #[error("invalid token signature: {0}")]
16 InvalidSignature(String),
17
18 #[error("token has expired")]
20 Expired,
21
22 #[error("invalid audience")]
24 InvalidAudience,
25
26 #[error("invalid domain: got '{domain}', expected '{expected}'")]
28 InvalidDomain { domain: String, expected: String },
29
30 #[error("token missing email claim")]
32 MissingEmail,
33
34 #[error("failed to fetch JWKS: {0}")]
36 JwksFetchError(String),
37
38 #[error("no matching key for kid '{0}'")]
40 NoMatchingKey(String),
41}
42
43impl AuthError {
44 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 assert!(!AuthError::JwksFetchError("err".into()).is_client_error());
88 }
89}