1use thiserror::Error;
2
3#[derive(Error, Debug, Clone)]
5pub enum TokenError {
6 #[error("Invalid token signature: {details}")]
9 InvalidSignature { details: String },
10
11 #[error("Unknown public key - token was not signed by a recognized authority")]
13 UnknownPublicKey,
14
15 #[error("Invalid key format: {reason}")]
17 InvalidKeyFormat { reason: String },
18
19 #[error("Failed to deserialize token: {reason}")]
21 DeserializationError { reason: String },
22
23 #[error("Failed to serialize token: {reason}")]
25 SerializationError { reason: String },
26
27 #[error("Failed to decode base64 token: {reason}")]
29 Base64DecodingError { reason: String },
30
31 #[error("Unsupported token version: expected {maximum}-{minimum}, got {actual}")]
33 UnsupportedVersion {
34 maximum: u32,
35 minimum: u32,
36 actual: u32,
37 },
38
39 #[error("Token expired at {expired_at}, current time is {current_time}")]
42 Expired {
43 expired_at: i64,
45 current_time: i64,
47 block_id: u32,
49 check_id: u32,
51 },
52
53 #[error("Namespace mismatch: expected '{expected}', {}", match provided {
55 Some(p) => format!("got '{p}'"),
56 None => "no namespace provided".to_string(),
57 })]
58 NamespaceMismatch {
59 expected: String,
61 provided: Option<String>,
63 block_id: u32,
65 check_id: u32,
67 },
68
69 #[error("Verification check failed in block {block_id}, check {check_id}: {rule}")]
71 CheckFailed {
72 block_id: u32,
74 check_id: u32,
76 rule: String,
78 },
79
80 #[error("Identity mismatch: expected '{expected}', got '{actual}'")]
83 IdentityMismatch {
84 expected: String,
86 actual: String,
88 },
89
90 #[error(
92 "Identity hierarchy violation: actor '{actual}' is not authorized for identity '{expected}' (delegatable: {delegatable})"
93 )]
94 HierarchyViolation {
95 expected: String,
97 actual: String,
99 delegatable: bool,
101 block_id: u32,
103 check_id: u32,
105 },
106
107 #[error("Token attenuation failed: {reason}")]
109 AttenuationFailed { reason: String },
110
111 #[error("Bearer token not allowed: {reason}")]
113 BearerNotAllowed { reason: String },
114
115 #[error("Authorization denied: {subject} does not have permission to perform '{operation}' on '{resource}'", subject = subject.as_deref().unwrap_or("<capability bearer>"))]
118 RightsDenied {
119 subject: Option<String>,
121 resource: String,
123 operation: String,
125 },
126
127 #[error("No matching authorization policy found. Failed checks: {}", format_check_failures(.failed_checks))]
129 NoMatchingPolicy {
130 failed_checks: Vec<CheckFailure>,
132 },
133
134 #[error("Authorization policy {policy_type} matched (index {policy_index}), but the following checks failed: {}", format_check_failures(.failed_checks))]
136 PolicyMatchedButChecksFailed {
137 policy_type: String,
139 policy_index: usize,
141 failed_checks: Vec<CheckFailure>,
143 },
144
145 #[error("Token verification exceeded execution limits: {reason}")]
148 ExecutionLimitReached { reason: String },
149
150 #[error("Token verification expression error: {reason}")]
152 ExpressionError { reason: String },
153
154 #[error("Invalid rule in block {block_id}: {rule}")]
156 InvalidBlockRule { block_id: u32, rule: String },
157
158 #[error("Internal error: {0}")]
161 Internal(String),
162
163 #[error("{0}")]
165 Generic(String),
166}
167
168#[derive(Debug, Clone)]
170pub struct CheckFailure {
171 pub block_id: Option<u32>,
173 pub check_id: u32,
175 pub rule: String,
177}
178
179impl std::fmt::Display for CheckFailure {
180 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
181 if let Some(block_id) = self.block_id {
182 write!(
183 f,
184 "Check #{} in block #{}: {}",
185 self.check_id, block_id, self.rule
186 )
187 } else {
188 write!(f, "Check #{} in authorizer: {}", self.check_id, self.rule)
189 }
190 }
191}
192
193fn format_check_failures(checks: &[CheckFailure]) -> String {
194 checks
195 .iter()
196 .map(|c| c.to_string())
197 .collect::<Vec<_>>()
198 .join("; ")
199}
200
201impl TokenError {
202 pub fn is_expired(&self) -> bool {
206 matches!(self, TokenError::Expired { .. })
207 }
208
209 pub fn is_namespace_mismatch(&self) -> bool {
211 matches!(self, TokenError::NamespaceMismatch { .. })
212 }
213
214 pub fn is_identity_mismatch(&self) -> bool {
216 matches!(
217 self,
218 TokenError::IdentityMismatch { .. } | TokenError::HierarchyViolation { .. }
219 )
220 }
221
222 pub fn is_rights_denied(&self) -> bool {
224 matches!(self, TokenError::RightsDenied { .. })
225 }
226
227 pub fn is_signature_error(&self) -> bool {
229 matches!(
230 self,
231 TokenError::InvalidSignature { .. }
232 | TokenError::UnknownPublicKey
233 | TokenError::DeserializationError { .. }
234 | TokenError::Base64DecodingError { .. }
235 )
236 }
237
238 pub fn get_expiration_time(&self) -> Option<i64> {
240 match self {
241 TokenError::Expired { expired_at, .. } => Some(*expired_at),
242 _ => None,
243 }
244 }
245
246 pub fn get_expected_namespace(&self) -> Option<&str> {
248 match self {
249 TokenError::NamespaceMismatch { expected, .. } => Some(expected.as_str()),
250 _ => None,
251 }
252 }
253
254 pub fn get_denied_access(&self) -> Option<(Option<&str>, &str, &str)> {
256 match self {
257 TokenError::RightsDenied {
258 subject,
259 resource,
260 operation,
261 } => Some((subject.as_deref(), resource.as_str(), operation.as_str())),
262 _ => None,
263 }
264 }
265
266 pub fn generic<S: Into<String>>(msg: S) -> Self {
270 TokenError::Generic(msg.into())
271 }
272
273 pub fn internal<S: Into<String>>(msg: S) -> Self {
275 TokenError::Internal(msg.into())
276 }
277
278 pub fn invalid_key_format<S: Into<String>>(reason: S) -> Self {
280 TokenError::InvalidKeyFormat {
281 reason: reason.into(),
282 }
283 }
284}
285
286impl From<biscuit_auth::error::Token> for TokenError {
289 fn from(err: biscuit_auth::error::Token) -> Self {
290 use biscuit_auth::error::{Logic, MatchedPolicy, Token};
291
292 match err {
293 Token::Format(format_err) => {
294 use biscuit_auth::error::Format;
295 match format_err {
296 Format::Signature(sig_err) => TokenError::InvalidSignature {
297 details: sig_err.to_string(),
298 },
299 Format::UnknownPublicKey => TokenError::UnknownPublicKey,
300 Format::DeserializationError(msg) | Format::BlockDeserializationError(msg) => {
301 TokenError::DeserializationError { reason: msg }
302 }
303 Format::SerializationError(msg) | Format::BlockSerializationError(msg) => {
304 TokenError::SerializationError { reason: msg }
305 }
306 Format::Version {
307 maximum,
308 minimum,
309 actual,
310 } => TokenError::UnsupportedVersion {
311 maximum,
312 minimum,
313 actual,
314 },
315 Format::InvalidKey(msg) => TokenError::InvalidKeyFormat { reason: msg },
316 other => TokenError::Generic(other.to_string()),
317 }
318 }
319 Token::Base64(base64_err) => TokenError::Base64DecodingError {
320 reason: base64_err.to_string(),
321 },
322 Token::FailedLogic(logic_err) => match logic_err {
323 Logic::Unauthorized { policy, checks } => {
324 let failed_checks = convert_failed_checks(checks);
325 let (policy_type, policy_index) = match policy {
326 MatchedPolicy::Allow(idx) => ("Allow", idx),
327 MatchedPolicy::Deny(idx) => ("Deny", idx),
328 };
329 TokenError::PolicyMatchedButChecksFailed {
330 policy_type: policy_type.to_string(),
331 policy_index,
332 failed_checks,
333 }
334 }
335 Logic::NoMatchingPolicy { checks } => {
336 let failed_checks = convert_failed_checks(checks);
337 TokenError::NoMatchingPolicy { failed_checks }
338 }
339 Logic::InvalidBlockRule(block_id, rule) => {
340 TokenError::InvalidBlockRule { block_id, rule }
341 }
342 Logic::AuthorizerNotEmpty => {
343 TokenError::Internal("Authorizer already contains a token".to_string())
344 }
345 },
346 Token::RunLimit(limit) => TokenError::ExecutionLimitReached {
347 reason: limit.to_string(),
348 },
349 Token::Execution(expr) => TokenError::ExpressionError {
350 reason: expr.to_string(),
351 },
352 Token::Language(lang_err) => TokenError::Generic(lang_err.to_string()),
353 other => TokenError::Generic(other.to_string()),
354 }
355 }
356}
357
358fn convert_failed_checks(checks: Vec<biscuit_auth::error::FailedCheck>) -> Vec<CheckFailure> {
360 checks
361 .into_iter()
362 .map(|check| match check {
363 biscuit_auth::error::FailedCheck::Block(block_check) => CheckFailure {
364 block_id: Some(block_check.block_id),
365 check_id: block_check.check_id,
366 rule: block_check.rule,
367 },
368 biscuit_auth::error::FailedCheck::Authorizer(auth_check) => CheckFailure {
369 block_id: None,
370 check_id: auth_check.check_id,
371 rule: auth_check.rule,
372 },
373 })
374 .collect()
375}
376
377impl From<hex::FromHexError> for TokenError {
378 fn from(err: hex::FromHexError) -> Self {
379 TokenError::InvalidKeyFormat {
380 reason: err.to_string(),
381 }
382 }
383}
384
385impl From<&str> for TokenError {
386 fn from(err: &str) -> Self {
387 TokenError::Generic(err.to_string())
388 }
389}
390
391impl From<String> for TokenError {
392 fn from(err: String) -> Self {
393 TokenError::Generic(err)
394 }
395}