hessra_token_authz/
verify.rs

1extern crate biscuit_auth as biscuit;
2
3use biscuit::macros::{authorizer, check, fact};
4use biscuit::Algorithm;
5use chrono::Utc;
6use hessra_token_core::{
7    parse_authorization_failure, parse_check_failure, Biscuit, PublicKey, ServiceChainFailure,
8    TokenError,
9};
10use serde::Deserialize;
11
12#[derive(Debug, Deserialize, Clone)]
13pub struct ServiceNode {
14    pub component: String,
15    pub public_key: String,
16}
17
18/// Verification mode for authorization tokens.
19#[derive(Debug, Clone, PartialEq)]
20enum VerificationMode {
21    /// Identity-based verification: requires an explicit subject parameter.
22    /// The authorizer will check if the token grants rights to this specific subject.
23    Identity { subject: String },
24    /// Capability-based verification: derives the subject from the token's rights.
25    /// The authorizer will accept any token that grants the specified capability
26    /// (resource + operation), regardless of the subject.
27    Capability,
28}
29
30impl VerificationMode {
31    /// Returns the subject if in Identity mode, None for Capability mode.
32    fn subject(&self) -> Option<&str> {
33        match self {
34            VerificationMode::Identity { subject } => Some(subject),
35            VerificationMode::Capability => None,
36        }
37    }
38
39    /// Returns the subject if in Identity mode, "unknown" for Capability mode.
40    /// Used for error reporting.
41    fn subject_or_unknown(&self) -> &str {
42        match self {
43            VerificationMode::Identity { subject } => subject,
44            VerificationMode::Capability => "unknown",
45        }
46    }
47}
48
49/// Builder for verifying Hessra authorization tokens with flexible configuration.
50///
51/// This builder allows you to configure various verification parameters including
52/// optional domain restrictions and service chain attestation.
53///
54/// # Example
55/// ```no_run
56/// use hessra_token_authz::{AuthorizationVerifier, ServiceNode, create_token};
57/// use hessra_token_core::KeyPair;
58///
59/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
60/// // Create a token
61/// let keypair = KeyPair::new();
62/// let public_key = keypair.public();
63/// let token = create_token(
64///     "user123".to_string(),
65///     "resource456".to_string(),
66///     "read".to_string(),
67///     keypair,
68/// )?;
69///
70/// // Basic authorization verification
71/// AuthorizationVerifier::new(
72///     token.clone(),
73///     public_key,
74///     "user123".to_string(),
75///     "resource456".to_string(),
76///     "read".to_string(),
77/// )
78/// .verify()?;
79///
80/// // With domain restriction
81/// AuthorizationVerifier::new(
82///     token.clone(),
83///     public_key,
84///     "user123".to_string(),
85///     "resource456".to_string(),
86///     "read".to_string(),
87/// )
88/// .with_domain("example.com".to_string())
89/// .verify()?;
90///
91/// // With service chain attestation
92/// let service_nodes = vec![
93///     ServiceNode {
94///         component: "api-gateway".to_string(),
95///         public_key: "ed25519/abcd1234...".to_string(),
96///     }
97/// ];
98/// AuthorizationVerifier::new(
99///     token,
100///     public_key,
101///     "user123".to_string(),
102///     "resource456".to_string(),
103///     "read".to_string(),
104/// )
105/// .with_service_chain(service_nodes, Some("api-gateway".to_string()))
106/// .verify()?;
107/// # Ok(())
108/// # }
109/// ```
110pub struct AuthorizationVerifier {
111    token: String,
112    public_key: PublicKey,
113    mode: VerificationMode,
114    resource: String,
115    operation: String,
116    domain: Option<String>,
117    service_chain: Option<(Vec<ServiceNode>, Option<String>)>,
118}
119
120impl AuthorizationVerifier {
121    /// Creates a new authorization verifier for a base64-encoded token.
122    ///
123    /// # Arguments
124    /// * `token` - The base64-encoded authorization token to verify
125    /// * `public_key` - The public key used to verify the token signature
126    /// * `subject` - The subject (user) identifier to verify authorization for
127    /// * `resource` - The resource identifier to verify authorization against
128    /// * `operation` - The operation to verify authorization for
129    pub fn new(
130        token: String,
131        public_key: PublicKey,
132        subject: String,
133        resource: String,
134        operation: String,
135    ) -> Self {
136        Self {
137            token,
138            public_key,
139            mode: VerificationMode::Identity { subject },
140            resource,
141            operation,
142            domain: None,
143            service_chain: None,
144        }
145    }
146
147    /// Creates a new authorization verifier from raw token bytes.
148    ///
149    /// # Arguments
150    /// * `token` - The raw binary Biscuit token bytes
151    /// * `public_key` - The public key used to verify the token signature
152    /// * `subject` - The subject (user) identifier to verify authorization for
153    /// * `resource` - The resource identifier to verify authorization against
154    /// * `operation` - The operation to verify authorization for
155    pub fn from_bytes(
156        token: Vec<u8>,
157        public_key: PublicKey,
158        subject: String,
159        resource: String,
160        operation: String,
161    ) -> Result<Self, TokenError> {
162        // Convert bytes to base64 for internal storage
163        let biscuit = Biscuit::from(&token, public_key)?;
164        let token_string = biscuit
165            .to_base64()
166            .map_err(|e| TokenError::generic(format!("Failed to encode token: {e}")))?;
167        Ok(Self {
168            token: token_string,
169            public_key,
170            mode: VerificationMode::Identity { subject },
171            resource,
172            operation,
173            domain: None,
174            service_chain: None,
175        })
176    }
177
178    /// Creates a new capability-based verifier (no subject required).
179    ///
180    /// This verifier will accept any token that grants the specified capability
181    /// (resource + operation), regardless of the subject. The subject is derived
182    /// from the token's rights instead of being provided explicitly.
183    ///
184    /// # Arguments
185    /// * `token` - The base64-encoded authorization token to verify
186    /// * `public_key` - The public key used to verify the token signature
187    /// * `resource` - The resource identifier to verify authorization against
188    /// * `operation` - The operation to verify authorization for
189    pub fn new_capability(
190        token: String,
191        public_key: PublicKey,
192        resource: String,
193        operation: String,
194    ) -> Self {
195        Self {
196            token,
197            public_key,
198            mode: VerificationMode::Capability,
199            resource,
200            operation,
201            domain: None,
202            service_chain: None,
203        }
204    }
205
206    /// Creates a new capability-based verifier from raw token bytes.
207    ///
208    /// This is the binary token version of `new_capability`. It accepts any token
209    /// that grants the specified capability (resource + operation), regardless of
210    /// the subject.
211    ///
212    /// # Arguments
213    /// * `token` - The raw binary Biscuit token bytes
214    /// * `public_key` - The public key used to verify the token signature
215    /// * `resource` - The resource identifier to verify authorization against
216    /// * `operation` - The operation to verify authorization for
217    pub fn from_bytes_capability(
218        token: Vec<u8>,
219        public_key: PublicKey,
220        resource: String,
221        operation: String,
222    ) -> Result<Self, TokenError> {
223        let biscuit = Biscuit::from(&token, public_key)?;
224        let token_string = biscuit
225            .to_base64()
226            .map_err(|e| TokenError::generic(format!("Failed to encode token: {e}")))?;
227        Ok(Self {
228            token: token_string,
229            public_key,
230            mode: VerificationMode::Capability,
231            resource,
232            operation,
233            domain: None,
234            service_chain: None,
235        })
236    }
237
238    /// Adds a domain restriction to the verification.
239    ///
240    /// When set, adds a domain fact to the authorizer. This is required for
241    /// verifying domain-restricted tokens.
242    ///
243    /// # Arguments
244    /// * `domain` - The domain to verify against (e.g., "example.com")
245    pub fn with_domain(mut self, domain: String) -> Self {
246        self.domain = Some(domain);
247        self
248    }
249
250    /// Adds service chain attestation verification.
251    ///
252    /// When set, verifies that the token has been properly attested by the
253    /// specified service chain nodes.
254    ///
255    /// # Arguments
256    /// * `service_nodes` - The list of service nodes in the chain
257    /// * `component` - Optional specific component to verify in the chain
258    pub fn with_service_chain(
259        mut self,
260        service_nodes: Vec<ServiceNode>,
261        component: Option<String>,
262    ) -> Self {
263        self.service_chain = Some((service_nodes, component));
264        self
265    }
266
267    /// Performs the token verification with the configured parameters.
268    ///
269    /// # Returns
270    /// * `Ok(())` - If the token is valid and meets all verification requirements
271    /// * `Err(TokenError)` - If verification fails for any reason
272    ///
273    /// # Errors
274    /// Returns an error if:
275    /// - The token is malformed or cannot be parsed
276    /// - The token signature is invalid
277    /// - The token has expired
278    /// - The token does not grant the required access rights
279    /// - The domain doesn't match (if domain restriction is set on token)
280    /// - Service chain attestation fails (if service chain is configured)
281    pub fn verify(self) -> Result<(), TokenError> {
282        let biscuit = Biscuit::from_base64(&self.token, self.public_key)?;
283
284        if let Some((service_nodes, component)) = self.service_chain {
285            // Service chain verification
286            verify_raw_service_chain_biscuit(
287                biscuit,
288                self.mode,
289                self.resource,
290                self.operation,
291                service_nodes,
292                component,
293                self.domain,
294            )
295        } else {
296            // Basic verification
297            verify_raw_biscuit(
298                biscuit,
299                self.mode,
300                self.resource,
301                self.operation,
302                self.domain,
303            )
304        }
305    }
306}
307
308pub(crate) fn build_base_authorizer(
309    subject: String,
310    resource: String,
311    operation: String,
312    domain: Option<String>,
313) -> Result<biscuit::AuthorizerBuilder, TokenError> {
314    let now = Utc::now().timestamp();
315
316    let mut authz = authorizer!(
317        r#"
318            time({now});
319            resource({resource});
320            subject({subject});
321            operation({operation});
322            allow if true;
323        "#
324    );
325
326    // Add domain fact if specified
327    if let Some(domain) = domain {
328        authz = authz.fact(fact!(r#"domain({domain});"#))?;
329    }
330
331    Ok(authz)
332}
333
334/// Build a capability-based authorizer that derives the subject from the token's rights.
335///
336/// Unlike `build_base_authorizer`, this function does not require a subject parameter.
337/// Instead, it uses a Datalog rule to derive the subject from any `right(subject, resource, operation)`
338/// facts present in the token. This allows verification based solely on capability (resource + operation)
339/// without needing to know the identity.
340pub(crate) fn build_capability_authorizer(
341    resource: String,
342    operation: String,
343    domain: Option<String>,
344) -> Result<biscuit::AuthorizerBuilder, TokenError> {
345    let now = Utc::now().timestamp();
346
347    let mut authz = authorizer!(
348        r#"
349            time({now});
350            resource({resource});
351            operation({operation});
352            // Derive subject from the token's rights instead of providing it explicitly
353            subject($sub) <- right($sub, {resource}, {operation});
354            allow if true;
355        "#
356    );
357
358    // Add domain fact if specified
359    if let Some(domain) = domain {
360        authz = authz.fact(fact!(r#"domain({domain});"#))?;
361    }
362
363    Ok(authz)
364}
365
366fn verify_raw_biscuit(
367    biscuit: Biscuit,
368    mode: VerificationMode,
369    resource: String,
370    operation: String,
371    domain: Option<String>,
372) -> Result<(), TokenError> {
373    let authz = match &mode {
374        VerificationMode::Identity { subject } => build_base_authorizer(
375            subject.clone(),
376            resource.clone(),
377            operation.clone(),
378            domain.clone(),
379        )?,
380        VerificationMode::Capability => {
381            build_capability_authorizer(resource.clone(), operation.clone(), domain.clone())?
382        }
383    };
384
385    match authz.build(&biscuit)?.authorize() {
386        Ok(_) => Ok(()),
387        Err(e) => Err(convert_authorization_error(
388            e,
389            mode.subject(),
390            Some(&resource),
391            Some(&operation),
392            domain.as_deref(),
393        )),
394    }
395}
396
397/// Verifies a Biscuit authorization token locally without contacting the authorization server.
398///
399/// This function performs local verification of a Biscuit token using the provided public key.
400/// It validates that the token grants access to the specified resource for the given subject.
401///
402/// # Arguments
403///
404/// * `token` - The binary Biscuit token bytes (typically decoded from Base64)
405/// * `public_key` - The public key used to verify the token signature
406/// * `subject` - The subject (user) identifier to verify authorization for
407/// * `resource` - The resource identifier to verify authorization against
408/// * `operation` - The operation to verify authorization for
409///
410/// # Returns
411///
412/// * `Ok(())` - If the token is valid and grants access to the resource
413/// * `Err(TokenError)` - If verification fails for any reason
414///
415/// # Errors
416///
417/// Returns an error if:
418/// - The token is malformed or cannot be parsed
419/// - The token signature is invalid
420/// - The token does not grant the required access rights
421/// - The token has expired or other authorization checks fail
422pub fn verify_biscuit_local(
423    token: Vec<u8>,
424    public_key: PublicKey,
425    subject: String,
426    resource: String,
427    operation: String,
428) -> Result<(), TokenError> {
429    AuthorizationVerifier::from_bytes(token, public_key, subject, resource, operation)?.verify()
430}
431
432/// Verifies a Biscuit authorization token locally without contacting the authorization server.
433///
434/// This function performs local verification of a Biscuit token using the provided public key.
435/// It validates that the token grants access to the specified resource for the given subject.
436///
437/// # Arguments
438///
439/// * `token` - The base64-encoded Biscuit token string
440/// * `public_key` - The public key used to verify the token signature
441/// * `subject` - The subject (user) identifier to verify authorization for
442/// * `resource` - The resource identifier to verify authorization against
443/// * `operation` - The operation to verify authorization for
444///
445/// # Returns
446///
447/// * `Ok(())` - If the token is valid and grants access to the resource
448/// * `Err(TokenError)` - If verification fails for any reason
449///
450/// # Errors
451///
452/// Returns an error if:
453/// - The token is malformed or cannot be parsed
454/// - The token signature is invalid
455/// - The token does not grant the required access rights
456/// - The token has expired or other authorization checks fail
457pub fn verify_token_local(
458    token: &str,
459    public_key: PublicKey,
460    subject: &str,
461    resource: &str,
462    operation: &str,
463) -> Result<(), TokenError> {
464    AuthorizationVerifier::new(
465        token.to_string(),
466        public_key,
467        subject.to_string(),
468        resource.to_string(),
469        operation.to_string(),
470    )
471    .verify()
472}
473
474/// Takes a public key encoded as a string in the format "ed25519/..." or "secp256r1/..."
475/// and returns a PublicKey.
476pub fn biscuit_key_from_string(key: String) -> Result<PublicKey, TokenError> {
477    let parts = key.split('/').collect::<Vec<&str>>();
478    if parts.len() != 2 {
479        return Err(TokenError::invalid_key_format(
480            "Key must be in format 'algorithm/hexkey'",
481        ));
482    }
483
484    let alg = match parts[0] {
485        "ed25519" => Algorithm::Ed25519,
486        "secp256r1" => Algorithm::Secp256r1,
487        _ => {
488            return Err(TokenError::invalid_key_format(
489                "Unsupported algorithm, must be ed25519 or secp256r1",
490            ))
491        }
492    };
493
494    // decode the key from hex
495    let key_bytes = hex::decode(parts[1])?;
496
497    // construct the public key
498    let key = PublicKey::from_bytes(&key_bytes, alg)
499        .map_err(|e| TokenError::invalid_key_format(e.to_string()))?;
500
501    Ok(key)
502}
503
504fn verify_raw_service_chain_biscuit(
505    biscuit: Biscuit,
506    mode: VerificationMode,
507    resource: String,
508    operation: String,
509    service_nodes: Vec<ServiceNode>,
510    component: Option<String>,
511    domain: Option<String>,
512) -> Result<(), TokenError> {
513    let mut authz = match &mode {
514        VerificationMode::Identity { subject } => build_base_authorizer(
515            subject.clone(),
516            resource.clone(),
517            operation.clone(),
518            domain.clone(),
519        )?,
520        VerificationMode::Capability => {
521            build_capability_authorizer(resource.clone(), operation.clone(), domain.clone())?
522        }
523    };
524
525    let mut component_found = false;
526    if component.is_none() {
527        component_found = true;
528    }
529    for service_node in &service_nodes {
530        if let Some(ref component) = component {
531            if component == &service_node.component {
532                component_found = true;
533                break;
534            }
535        }
536        let service = resource.clone();
537        let node_name = service_node.component.clone();
538        let node_key = biscuit_key_from_string(service_node.public_key.clone())?;
539        authz = authz.check(check!(
540            r#"
541                check if node({service}, {node_name}) trusting authority, {node_key};
542            "#
543        ))?;
544    }
545
546    if let Some(ref component_name) = component {
547        if !component_found {
548            return Err(TokenError::ServiceChainFailed {
549                component: component_name.clone(),
550                reason: ServiceChainFailure::ComponentNotFound,
551            });
552        }
553    }
554
555    match authz.build(&biscuit)?.authorize() {
556        Ok(_) => Ok(()),
557        Err(e) => Err(convert_service_chain_error(
558            e,
559            mode.subject_or_unknown(),
560            &resource,
561            &operation,
562            service_nodes,
563            domain.as_deref(),
564        )),
565    }
566}
567
568pub fn verify_service_chain_biscuit_local(
569    token: Vec<u8>,
570    public_key: PublicKey,
571    subject: String,
572    resource: String,
573    operation: String,
574    service_nodes: Vec<ServiceNode>,
575    component: Option<String>,
576) -> Result<(), TokenError> {
577    AuthorizationVerifier::from_bytes(token, public_key, subject, resource, operation)?
578        .with_service_chain(service_nodes, component)
579        .verify()
580}
581
582pub fn verify_service_chain_token_local(
583    token: &str,
584    public_key: PublicKey,
585    subject: &str,
586    resource: &str,
587    operation: &str,
588    service_nodes: Vec<ServiceNode>,
589    component: Option<String>,
590) -> Result<(), TokenError> {
591    AuthorizationVerifier::new(
592        token.to_string(),
593        public_key,
594        subject.to_string(),
595        resource.to_string(),
596        operation.to_string(),
597    )
598    .with_service_chain(service_nodes, component)
599    .verify()
600}
601
602/// Verifies a Biscuit authorization token based on capability (resource + operation) only.
603///
604/// This function performs capability-based verification without requiring a subject parameter.
605/// The subject is derived from the token's rights - any subject that has the specified right
606/// for the resource and operation will satisfy verification.
607///
608/// This is useful for services that only care about authorization for an action, not identity.
609/// For example, a telemetry service that only needs to verify write permission, not who is writing.
610///
611/// # Arguments
612///
613/// * `token` - The base64-encoded Biscuit token string
614/// * `public_key` - The public key used to verify the token signature
615/// * `resource` - The resource identifier to verify authorization against
616/// * `operation` - The operation to verify authorization for
617///
618/// # Returns
619///
620/// * `Ok(())` - If the token is valid and grants access to the resource+operation
621/// * `Err(TokenError)` - If verification fails
622///
623/// # Example
624///
625/// ```no_run
626/// use hessra_token_authz::{verify_capability_token_local, create_token};
627/// use hessra_token_core::{KeyPair, TokenError};
628///
629/// let keypair = KeyPair::new();
630/// let public_key = keypair.public();
631///
632/// // Create a token for alice to read resource1
633/// let token = create_token(
634///     "alice".to_string(),
635///     "resource1".to_string(),
636///     "read".to_string(),
637///     keypair,
638/// )
639/// .map_err(|e| TokenError::Generic(e.to_string()))?;
640///
641/// // Verify capability without caring about the subject
642/// verify_capability_token_local(&token, public_key, "resource1", "read")?;
643/// # Ok::<(), hessra_token_core::TokenError>(())
644/// ```
645pub fn verify_capability_token_local(
646    token: &str,
647    public_key: PublicKey,
648    resource: &str,
649    operation: &str,
650) -> Result<(), TokenError> {
651    AuthorizationVerifier::new_capability(
652        token.to_string(),
653        public_key,
654        resource.to_string(),
655        operation.to_string(),
656    )
657    .verify()
658}
659
660/// Verifies a Biscuit authorization token based on capability (resource + operation) only.
661///
662/// This is the binary token version of `verify_capability_token_local`.
663///
664/// # Arguments
665///
666/// * `token` - The binary Biscuit token bytes
667/// * `public_key` - The public key used to verify the token signature
668/// * `resource` - The resource identifier to verify authorization against
669/// * `operation` - The operation to verify authorization for
670///
671/// # Returns
672///
673/// * `Ok(())` - If the token is valid and grants access to the resource+operation
674/// * `Err(TokenError)` - If verification fails
675pub fn verify_capability_biscuit_local(
676    token: Vec<u8>,
677    public_key: PublicKey,
678    resource: String,
679    operation: String,
680) -> Result<(), TokenError> {
681    AuthorizationVerifier::from_bytes_capability(token, public_key, resource, operation)?.verify()
682}
683
684/// Verifies a service chain token based on capability without requiring subject.
685///
686/// This combines service chain attestation verification with capability-based verification.
687/// The token must have the required service chain attestations and grant the specified
688/// capability (resource + operation), but the subject is derived from the token rather
689/// than being provided explicitly.
690///
691/// # Arguments
692///
693/// * `token` - The base64-encoded Biscuit token string
694/// * `public_key` - The public key used to verify the token signature
695/// * `resource` - The resource identifier to verify authorization against
696/// * `operation` - The operation to verify authorization for
697/// * `service_nodes` - The list of service nodes in the chain
698/// * `component` - Optional specific component to verify in the chain
699///
700/// # Returns
701///
702/// * `Ok(())` - If the token is valid and grants access
703/// * `Err(TokenError)` - If verification fails
704pub fn verify_service_chain_capability_token_local(
705    token: &str,
706    public_key: PublicKey,
707    resource: &str,
708    operation: &str,
709    service_nodes: Vec<ServiceNode>,
710    component: Option<String>,
711) -> Result<(), TokenError> {
712    AuthorizationVerifier::new_capability(
713        token.to_string(),
714        public_key,
715        resource.to_string(),
716        operation.to_string(),
717    )
718    .with_service_chain(service_nodes, component)
719    .verify()
720}
721
722/// Binary version of `verify_service_chain_capability_token_local`.
723///
724/// Verifies a service chain token from raw bytes based on capability without requiring subject.
725///
726/// # Arguments
727///
728/// * `token` - The binary Biscuit token bytes
729/// * `public_key` - The public key used to verify the token signature
730/// * `resource` - The resource identifier to verify authorization against
731/// * `operation` - The operation to verify authorization for
732/// * `service_nodes` - The list of service nodes in the chain
733/// * `component` - Optional specific component to verify in the chain
734///
735/// # Returns
736///
737/// * `Ok(())` - If the token is valid and grants access
738/// * `Err(TokenError)` - If verification fails
739pub fn verify_service_chain_capability_biscuit_local(
740    token: Vec<u8>,
741    public_key: PublicKey,
742    resource: String,
743    operation: String,
744    service_nodes: Vec<ServiceNode>,
745    component: Option<String>,
746) -> Result<(), TokenError> {
747    AuthorizationVerifier::from_bytes_capability(token, public_key, resource, operation)?
748        .with_service_chain(service_nodes, component)
749        .verify()
750}
751
752/// Convert biscuit authorization errors to detailed authorization errors
753fn convert_authorization_error(
754    err: biscuit::error::Token,
755    subject: Option<&str>,
756    resource: Option<&str>,
757    operation: Option<&str>,
758    domain: Option<&str>,
759) -> TokenError {
760    use biscuit::error::{Logic, Token};
761
762    match err {
763        Token::FailedLogic(logic_err) => match &logic_err {
764            Logic::Unauthorized { checks, .. } | Logic::NoMatchingPolicy { checks } => {
765                // Try to parse each failed check for more specific errors
766                for failed_check in checks.iter() {
767                    let (block_id, check_id, rule) = match failed_check {
768                        biscuit::error::FailedCheck::Block(block_check) => (
769                            block_check.block_id,
770                            block_check.check_id,
771                            block_check.rule.clone(),
772                        ),
773                        biscuit::error::FailedCheck::Authorizer(auth_check) => {
774                            (0, auth_check.check_id, auth_check.rule.clone())
775                        }
776                    };
777
778                    // Try to parse the check for specific error types
779                    let parsed_error = parse_check_failure(block_id, check_id, &rule);
780
781                    // Enhance domain errors with context we have
782                    match parsed_error {
783                        TokenError::DomainMismatch {
784                            expected,
785                            block_id,
786                            check_id,
787                            ..
788                        } => {
789                            return TokenError::DomainMismatch {
790                                expected,
791                                provided: domain.map(|s| s.to_string()),
792                                block_id,
793                                check_id,
794                            };
795                        }
796                        TokenError::Expired { .. } => return parsed_error,
797                        _ => {}
798                    }
799                }
800
801                // Check if this looks like a rights denial (no matching policy)
802                if matches!(logic_err, Logic::NoMatchingPolicy { .. }) {
803                    return parse_authorization_failure(
804                        subject,
805                        resource,
806                        operation,
807                        &format!("{checks:?}"),
808                    );
809                }
810
811                // If we couldn't parse any specific error, use generic conversion
812                TokenError::from(Token::FailedLogic(logic_err))
813            }
814            other => TokenError::from(Token::FailedLogic(other.clone())),
815        },
816        other => TokenError::from(other),
817    }
818}
819
820/// Convert biscuit authorization errors to service chain specific errors
821fn convert_service_chain_error(
822    err: biscuit::error::Token,
823    subject: &str,
824    resource: &str,
825    operation: &str,
826    service_nodes: Vec<ServiceNode>,
827    domain: Option<&str>,
828) -> TokenError {
829    use biscuit::error::{Logic, Token};
830
831    match err {
832        Token::FailedLogic(logic_err) => match &logic_err {
833            Logic::Unauthorized { checks, .. } | Logic::NoMatchingPolicy { checks } => {
834                // Check if any failure is service chain related
835                for failed_check in checks.iter() {
836                    let (block_id, check_id, rule) = match failed_check {
837                        biscuit::error::FailedCheck::Block(block_check) => (
838                            block_check.block_id,
839                            block_check.check_id,
840                            block_check.rule.clone(),
841                        ),
842                        biscuit::error::FailedCheck::Authorizer(auth_check) => {
843                            (0, auth_check.check_id, auth_check.rule.clone())
844                        }
845                    };
846
847                    // Check if this is a service chain check (contains "node(")
848                    if rule.contains("node(") {
849                        // Try to extract component name from the rule
850                        for service_node in &service_nodes {
851                            if rule.contains(&service_node.component) {
852                                return TokenError::ServiceChainFailed {
853                                    component: service_node.component.clone(),
854                                    reason: ServiceChainFailure::MissingAttestation,
855                                };
856                            }
857                        }
858
859                        // Generic service chain failure
860                        return TokenError::ServiceChainFailed {
861                            component: resource.to_string(),
862                            reason: ServiceChainFailure::Other(
863                                "Service chain attestation check failed".to_string(),
864                            ),
865                        };
866                    }
867
868                    // Check for other specific error types
869                    let parsed_error = parse_check_failure(block_id, check_id, &rule);
870                    match parsed_error {
871                        TokenError::DomainMismatch {
872                            expected,
873                            block_id,
874                            check_id,
875                            ..
876                        } => {
877                            return TokenError::DomainMismatch {
878                                expected,
879                                provided: domain.map(|s| s.to_string()),
880                                block_id,
881                                check_id,
882                            };
883                        }
884                        TokenError::Expired { .. } => return parsed_error,
885                        _ => {}
886                    }
887                }
888
889                // Fallback to authorization error
890                parse_authorization_failure(
891                    Some(subject),
892                    Some(resource),
893                    Some(operation),
894                    &format!("{checks:?}"),
895                )
896            }
897            other => TokenError::from(Token::FailedLogic(other.clone())),
898        },
899        other => TokenError::from(other),
900    }
901}