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("Domain mismatch: expected '{expected}', {}", match provided {
55 Some(p) => format!("got '{p}'"),
56 None => "no domain provided".to_string(),
57 })]
58 DomainMismatch {
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("Identity hierarchy violation: actor '{actual}' is not authorized for identity '{expected}' (delegatable: {delegatable})")]
92 HierarchyViolation {
93 expected: String,
95 actual: String,
97 delegatable: bool,
99 block_id: u32,
101 check_id: u32,
103 },
104
105 #[error("Token attenuation failed: {reason}")]
107 AttenuationFailed { reason: String },
108
109 #[error("Bearer token not allowed: {reason}")]
111 BearerNotAllowed { reason: String },
112
113 #[error("Authorization denied: subject '{subject}' does not have permission to perform '{operation}' on '{resource}'")]
116 RightsDenied {
117 subject: String,
119 resource: String,
121 operation: String,
123 },
124
125 #[error("Service chain verification failed for component '{component}': {reason}")]
127 ServiceChainFailed {
128 component: String,
130 reason: ServiceChainFailure,
132 },
133
134 #[error("Multi-party attestation missing for component '{component}' (expected key: {expected_key})")]
136 MultiPartyAttestationMissing {
137 component: String,
139 expected_key: String,
141 },
142
143 #[error("No matching authorization policy found. Failed checks: {}", format_check_failures(.failed_checks))]
145 NoMatchingPolicy {
146 failed_checks: Vec<CheckFailure>,
148 },
149
150 #[error("Authorization policy {policy_type} matched (index {policy_index}), but the following checks failed: {}", format_check_failures(.failed_checks))]
152 PolicyMatchedButChecksFailed {
153 policy_type: String,
155 policy_index: usize,
157 failed_checks: Vec<CheckFailure>,
159 },
160
161 #[error("Token verification exceeded execution limits: {reason}")]
164 ExecutionLimitReached { reason: String },
165
166 #[error("Token verification expression error: {reason}")]
168 ExpressionError { reason: String },
169
170 #[error("Invalid rule in block {block_id}: {rule}")]
172 InvalidBlockRule { block_id: u32, rule: String },
173
174 #[error("Internal error: {0}")]
177 Internal(String),
178
179 #[error("{0}")]
181 Generic(String),
182}
183
184#[derive(Debug, Clone)]
186pub struct CheckFailure {
187 pub block_id: Option<u32>,
189 pub check_id: u32,
191 pub rule: String,
193}
194
195impl std::fmt::Display for CheckFailure {
196 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
197 if let Some(block_id) = self.block_id {
198 write!(
199 f,
200 "Check #{} in block #{}: {}",
201 self.check_id, block_id, self.rule
202 )
203 } else {
204 write!(f, "Check #{} in authorizer: {}", self.check_id, self.rule)
205 }
206 }
207}
208
209fn format_check_failures(checks: &[CheckFailure]) -> String {
210 checks
211 .iter()
212 .map(|c| c.to_string())
213 .collect::<Vec<_>>()
214 .join("; ")
215}
216
217#[derive(Debug, Clone)]
219pub enum ServiceChainFailure {
220 ComponentNotFound,
222 MissingAttestation,
224 InvalidAttestation,
226 KeyMismatch { expected: String, actual: String },
228 Other(String),
230}
231
232impl std::fmt::Display for ServiceChainFailure {
233 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
234 match self {
235 ServiceChainFailure::ComponentNotFound => write!(f, "component not found in chain"),
236 ServiceChainFailure::MissingAttestation => write!(f, "attestation missing"),
237 ServiceChainFailure::InvalidAttestation => write!(f, "attestation signature invalid"),
238 ServiceChainFailure::KeyMismatch { expected, actual } => {
239 write!(f, "key mismatch (expected: {expected}, actual: {actual})")
240 }
241 ServiceChainFailure::Other(reason) => write!(f, "{reason}"),
242 }
243 }
244}
245
246impl TokenError {
247 pub fn is_expired(&self) -> bool {
251 matches!(self, TokenError::Expired { .. })
252 }
253
254 pub fn is_domain_mismatch(&self) -> bool {
256 matches!(self, TokenError::DomainMismatch { .. })
257 }
258
259 pub fn is_identity_mismatch(&self) -> bool {
261 matches!(
262 self,
263 TokenError::IdentityMismatch { .. } | TokenError::HierarchyViolation { .. }
264 )
265 }
266
267 pub fn is_rights_denied(&self) -> bool {
269 matches!(self, TokenError::RightsDenied { .. })
270 }
271
272 pub fn is_service_chain_failure(&self) -> bool {
274 matches!(self, TokenError::ServiceChainFailed { .. })
275 }
276
277 pub fn is_signature_error(&self) -> bool {
279 matches!(
280 self,
281 TokenError::InvalidSignature { .. }
282 | TokenError::UnknownPublicKey
283 | TokenError::DeserializationError { .. }
284 | TokenError::Base64DecodingError { .. }
285 )
286 }
287
288 pub fn get_expiration_time(&self) -> Option<i64> {
290 match self {
291 TokenError::Expired { expired_at, .. } => Some(*expired_at),
292 _ => None,
293 }
294 }
295
296 pub fn get_expected_domain(&self) -> Option<&str> {
298 match self {
299 TokenError::DomainMismatch { expected, .. } => Some(expected.as_str()),
300 _ => None,
301 }
302 }
303
304 pub fn get_denied_access(&self) -> Option<(&str, &str, &str)> {
306 match self {
307 TokenError::RightsDenied {
308 subject,
309 resource,
310 operation,
311 } => Some((subject.as_str(), resource.as_str(), operation.as_str())),
312 _ => None,
313 }
314 }
315
316 pub fn generic<S: Into<String>>(msg: S) -> Self {
320 TokenError::Generic(msg.into())
321 }
322
323 pub fn internal<S: Into<String>>(msg: S) -> Self {
325 TokenError::Internal(msg.into())
326 }
327
328 pub fn invalid_key_format<S: Into<String>>(reason: S) -> Self {
330 TokenError::InvalidKeyFormat {
331 reason: reason.into(),
332 }
333 }
334}
335
336impl From<biscuit_auth::error::Token> for TokenError {
339 fn from(err: biscuit_auth::error::Token) -> Self {
340 use biscuit_auth::error::{Logic, MatchedPolicy, Token};
341
342 match err {
343 Token::Format(format_err) => {
344 use biscuit_auth::error::Format;
345 match format_err {
346 Format::Signature(sig_err) => TokenError::InvalidSignature {
347 details: sig_err.to_string(),
348 },
349 Format::UnknownPublicKey => TokenError::UnknownPublicKey,
350 Format::DeserializationError(msg) | Format::BlockDeserializationError(msg) => {
351 TokenError::DeserializationError { reason: msg }
352 }
353 Format::SerializationError(msg) | Format::BlockSerializationError(msg) => {
354 TokenError::SerializationError { reason: msg }
355 }
356 Format::Version {
357 maximum,
358 minimum,
359 actual,
360 } => TokenError::UnsupportedVersion {
361 maximum,
362 minimum,
363 actual,
364 },
365 Format::InvalidKey(msg) => TokenError::InvalidKeyFormat { reason: msg },
366 other => TokenError::Generic(other.to_string()),
367 }
368 }
369 Token::Base64(base64_err) => TokenError::Base64DecodingError {
370 reason: base64_err.to_string(),
371 },
372 Token::FailedLogic(logic_err) => match logic_err {
373 Logic::Unauthorized { policy, checks } => {
374 let failed_checks = convert_failed_checks(checks);
375 let (policy_type, policy_index) = match policy {
376 MatchedPolicy::Allow(idx) => ("Allow", idx),
377 MatchedPolicy::Deny(idx) => ("Deny", idx),
378 };
379 TokenError::PolicyMatchedButChecksFailed {
380 policy_type: policy_type.to_string(),
381 policy_index,
382 failed_checks,
383 }
384 }
385 Logic::NoMatchingPolicy { checks } => {
386 let failed_checks = convert_failed_checks(checks);
387 TokenError::NoMatchingPolicy { failed_checks }
388 }
389 Logic::InvalidBlockRule(block_id, rule) => {
390 TokenError::InvalidBlockRule { block_id, rule }
391 }
392 Logic::AuthorizerNotEmpty => {
393 TokenError::Internal("Authorizer already contains a token".to_string())
394 }
395 },
396 Token::RunLimit(limit) => TokenError::ExecutionLimitReached {
397 reason: limit.to_string(),
398 },
399 Token::Execution(expr) => TokenError::ExpressionError {
400 reason: expr.to_string(),
401 },
402 Token::Language(lang_err) => TokenError::Generic(lang_err.to_string()),
403 other => TokenError::Generic(other.to_string()),
404 }
405 }
406}
407
408fn convert_failed_checks(checks: Vec<biscuit_auth::error::FailedCheck>) -> Vec<CheckFailure> {
410 checks
411 .into_iter()
412 .map(|check| match check {
413 biscuit_auth::error::FailedCheck::Block(block_check) => CheckFailure {
414 block_id: Some(block_check.block_id),
415 check_id: block_check.check_id,
416 rule: block_check.rule,
417 },
418 biscuit_auth::error::FailedCheck::Authorizer(auth_check) => CheckFailure {
419 block_id: None,
420 check_id: auth_check.check_id,
421 rule: auth_check.rule,
422 },
423 })
424 .collect()
425}
426
427impl From<hex::FromHexError> for TokenError {
428 fn from(err: hex::FromHexError) -> Self {
429 TokenError::InvalidKeyFormat {
430 reason: err.to_string(),
431 }
432 }
433}
434
435impl From<&str> for TokenError {
436 fn from(err: &str) -> Self {
437 TokenError::Generic(err.to_string())
438 }
439}
440
441impl From<String> for TokenError {
442 fn from(err: String) -> Self {
443 TokenError::Generic(err)
444 }
445}