1use thiserror::Error;
2
3#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
5pub enum AuthErrorCode {
6 TokenMissing,
8 TokenExpired,
10 TokenInvalidSignature,
12 TokenInvalidFormat,
14 TokenInvalidIssuer,
16 TokenInvalidAudience,
18 TokenMissingClaim,
20 TokenKeyNotFound,
22 OriginMismatch,
24 OriginRequired,
26 RateLimitExceeded,
28 ConnectionLimitExceeded,
30 SubscriptionLimitExceeded,
32 SnapshotLimitExceeded,
34 EgressLimitExceeded,
36 InvalidStaticToken,
38 InternalError,
40}
41
42impl AuthErrorCode {
43 pub fn as_str(&self) -> &'static str {
45 match self {
46 AuthErrorCode::TokenMissing => "token-missing",
47 AuthErrorCode::TokenExpired => "token-expired",
48 AuthErrorCode::TokenInvalidSignature => "token-invalid-signature",
49 AuthErrorCode::TokenInvalidFormat => "token-invalid-format",
50 AuthErrorCode::TokenInvalidIssuer => "token-invalid-issuer",
51 AuthErrorCode::TokenInvalidAudience => "token-invalid-audience",
52 AuthErrorCode::TokenMissingClaim => "token-missing-claim",
53 AuthErrorCode::TokenKeyNotFound => "token-key-not-found",
54 AuthErrorCode::OriginMismatch => "origin-mismatch",
55 AuthErrorCode::OriginRequired => "origin-required",
56 AuthErrorCode::RateLimitExceeded => "rate-limit-exceeded",
57 AuthErrorCode::ConnectionLimitExceeded => "connection-limit-exceeded",
58 AuthErrorCode::SubscriptionLimitExceeded => "subscription-limit-exceeded",
59 AuthErrorCode::SnapshotLimitExceeded => "snapshot-limit-exceeded",
60 AuthErrorCode::EgressLimitExceeded => "egress-limit-exceeded",
61 AuthErrorCode::InvalidStaticToken => "invalid-static-token",
62 AuthErrorCode::InternalError => "internal-error",
63 }
64 }
65
66 pub fn should_retry(&self) -> bool {
68 matches!(
69 self,
70 AuthErrorCode::RateLimitExceeded | AuthErrorCode::InternalError
71 )
72 }
73
74 pub fn should_refresh_token(&self) -> bool {
76 matches!(
77 self,
78 AuthErrorCode::TokenExpired
79 | AuthErrorCode::TokenInvalidSignature
80 | AuthErrorCode::TokenInvalidFormat
81 | AuthErrorCode::TokenInvalidIssuer
82 | AuthErrorCode::TokenInvalidAudience
83 | AuthErrorCode::TokenKeyNotFound
84 )
85 }
86
87 pub fn http_status(&self) -> u16 {
89 match self {
90 AuthErrorCode::TokenMissing => 401,
91 AuthErrorCode::TokenExpired => 401,
92 AuthErrorCode::TokenInvalidSignature => 401,
93 AuthErrorCode::TokenInvalidFormat => 400,
94 AuthErrorCode::TokenInvalidIssuer => 401,
95 AuthErrorCode::TokenInvalidAudience => 401,
96 AuthErrorCode::TokenMissingClaim => 400,
97 AuthErrorCode::TokenKeyNotFound => 401,
98 AuthErrorCode::OriginMismatch => 403,
99 AuthErrorCode::OriginRequired => 403,
100 AuthErrorCode::RateLimitExceeded => 429,
101 AuthErrorCode::ConnectionLimitExceeded => 429,
102 AuthErrorCode::SubscriptionLimitExceeded => 429,
103 AuthErrorCode::SnapshotLimitExceeded => 429,
104 AuthErrorCode::EgressLimitExceeded => 429,
105 AuthErrorCode::InvalidStaticToken => 401,
106 AuthErrorCode::InternalError => 500,
107 }
108 }
109
110 pub fn default_retry_policy(&self) -> RetryPolicy {
112 use std::time::Duration;
113
114 match self {
115 AuthErrorCode::TokenExpired
117 | AuthErrorCode::TokenInvalidSignature
118 | AuthErrorCode::TokenInvalidFormat
119 | AuthErrorCode::TokenInvalidIssuer
120 | AuthErrorCode::TokenInvalidAudience
121 | AuthErrorCode::TokenKeyNotFound => RetryPolicy::RetryWithFreshToken,
122
123 AuthErrorCode::RateLimitExceeded
125 | AuthErrorCode::ConnectionLimitExceeded
126 | AuthErrorCode::SubscriptionLimitExceeded
127 | AuthErrorCode::SnapshotLimitExceeded
128 | AuthErrorCode::EgressLimitExceeded => RetryPolicy::RetryWithBackoff {
129 initial: Duration::from_secs(1),
130 max: Duration::from_secs(60),
131 },
132
133 AuthErrorCode::InternalError => RetryPolicy::RetryWithBackoff {
135 initial: Duration::from_secs(1),
136 max: Duration::from_secs(30),
137 },
138
139 _ => RetryPolicy::NoRetry,
141 }
142 }
143}
144
145#[derive(Debug, Clone, Copy, PartialEq, Eq)]
147pub enum RetryPolicy {
148 NoRetry,
150 RetryImmediately,
152 RetryAfter(std::time::Duration),
154 RetryWithBackoff {
156 initial: std::time::Duration,
158 max: std::time::Duration,
160 },
161 RetryWithFreshToken,
163}
164
165impl std::fmt::Display for AuthErrorCode {
166 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
167 write!(f, "{}", self.as_str())
168 }
169}
170
171impl From<&VerifyError> for AuthErrorCode {
173 fn from(err: &VerifyError) -> Self {
174 match err {
175 VerifyError::Expired => AuthErrorCode::TokenExpired,
176 VerifyError::NotYetValid => AuthErrorCode::TokenInvalidFormat,
177 VerifyError::InvalidSignature => AuthErrorCode::TokenInvalidSignature,
178 VerifyError::InvalidIssuer => AuthErrorCode::TokenInvalidIssuer,
179 VerifyError::InvalidAudience => AuthErrorCode::TokenInvalidAudience,
180 VerifyError::MissingClaim(_) => AuthErrorCode::TokenMissingClaim,
181 VerifyError::OriginMismatch { .. } => AuthErrorCode::OriginMismatch,
182 VerifyError::OriginRequired => AuthErrorCode::OriginRequired,
183 VerifyError::DecodeError(_) => AuthErrorCode::TokenInvalidFormat,
184 VerifyError::KeyNotFound(_) => AuthErrorCode::TokenKeyNotFound,
185 VerifyError::InvalidFormat(_) => AuthErrorCode::TokenInvalidFormat,
186 VerifyError::Revoked => AuthErrorCode::TokenExpired,
187 }
188 }
189}
190
191#[derive(Debug, Error)]
193pub enum AuthError {
194 #[error("invalid key format: {0}")]
195 InvalidKeyFormat(String),
196
197 #[error("key loading failed: {0}")]
198 KeyLoadingFailed(String),
199
200 #[error("signing failed: {0}")]
201 SigningFailed(String),
202
203 #[error("IO error: {0}")]
204 Io(#[from] std::io::Error),
205}
206
207#[derive(Debug, Error, Clone, PartialEq)]
209pub enum VerifyError {
210 #[error("token has expired")]
211 Expired,
212
213 #[error("token is not yet valid")]
214 NotYetValid,
215
216 #[error("invalid signature")]
217 InvalidSignature,
218
219 #[error("invalid issuer")]
220 InvalidIssuer,
221
222 #[error("invalid audience")]
223 InvalidAudience,
224
225 #[error("missing required claim: {0}")]
226 MissingClaim(String),
227
228 #[error("origin mismatch: expected {expected}, got {actual}")]
229 OriginMismatch { expected: String, actual: String },
230
231 #[error("origin required but not provided")]
232 OriginRequired,
233
234 #[error("decode error: {0}")]
235 DecodeError(String),
236
237 #[error("key not found: {0}")]
238 KeyNotFound(String),
239
240 #[error("invalid token format: {0}")]
241 InvalidFormat(String),
242
243 #[error("token has been revoked")]
244 Revoked,
245}