hessra_token_authz/
mint.rs

1extern crate biscuit_auth as biscuit;
2
3use crate::verify::{biscuit_key_from_string, ServiceNode};
4
5use biscuit::macros::{biscuit, check, rule};
6use biscuit::BiscuitBuilder;
7use chrono::Utc;
8use hessra_token_core::{Biscuit, KeyPair, TokenTimeConfig};
9use std::error::Error;
10use tracing::info;
11
12/// Builder for creating Hessra authorization tokens with flexible configuration.
13///
14/// Authorization tokens can be configured with the following capabilities:
15/// - **Service Chain**: Add service node attestations via `.service_chain(nodes)`
16/// - **Multi-Party**: Add multi-party attestation requirements via `.multi_party(nodes)`
17/// - **Domain Restriction**: Limit token to a specific domain via `.domain_restricted(domain)`
18///
19/// Service chain and multi-party capabilities can be combined in the same token
20/// (though this is not currently validated or actively used).
21///
22/// # Example
23/// ```rust
24/// use hessra_token_authz::HessraAuthorization;
25/// use hessra_token_core::{KeyPair, TokenTimeConfig};
26///
27/// let keypair = KeyPair::new();
28///
29/// // Authorization with domain restriction
30/// let token = HessraAuthorization::new(
31///     "alice".to_string(),
32///     "resource1".to_string(),
33///     "read".to_string(),
34///     TokenTimeConfig::default()
35/// )
36/// .domain_restricted("myapp.hessra.dev".to_string())
37/// .issue(&keypair)
38/// .expect("Failed to create token");
39/// ```
40///
41pub struct HessraAuthorization {
42    subject: Option<String>,
43    resource: Option<String>,
44    operation: Option<String>,
45    time_config: TokenTimeConfig,
46    service_chain_nodes: Option<Vec<ServiceNode>>,
47    multi_party_nodes: Option<Vec<ServiceNode>>,
48    domain: Option<String>,
49}
50
51impl HessraAuthorization {
52    /// Creates a new singleton capability authorization token builder.
53    ///
54    /// Singleton tokens grant a specific right to a specific subject for a specific resource
55    /// and operation. The token can be used immediately upon issuance.
56    ///
57    /// # Arguments
58    /// * `subject` - The subject (user) identifier
59    /// * `resource` - The resource identifier to grant access to
60    /// * `operation` - The operation to grant access to
61    /// * `time_config` - Time configuration for token validity
62    pub fn new(
63        subject: String,
64        resource: String,
65        operation: String,
66        time_config: TokenTimeConfig,
67    ) -> Self {
68        Self {
69            subject: Some(subject),
70            resource: Some(resource),
71            operation: Some(operation),
72            time_config,
73            service_chain_nodes: None,
74            multi_party_nodes: None,
75            domain: None,
76        }
77    }
78
79    /// Adds service chain attestation to the authorization token.
80    ///
81    /// Service chain tokens include attestations for each service node in the chain.
82    /// The token grants access and includes trusting relationships for the specified nodes.
83    ///
84    /// Can be combined with `.multi_party()` to create a token with both capabilities
85    /// (though this is not currently validated or actively used).
86    ///
87    /// # Arguments
88    /// * `nodes` - Vector of service nodes that will attest to the token
89    pub fn service_chain(mut self, nodes: Vec<ServiceNode>) -> Self {
90        self.service_chain_nodes = Some(nodes);
91        self
92    }
93
94    /// Adds multi-party attestation requirement to the authorization token.
95    ///
96    /// Multi-party tokens require attestation from all specified parties before becoming valid.
97    /// The key difference from service chain is that the token is invalid until all parties
98    /// have provided their attestations.
99    ///
100    /// Can be combined with `.service_chain()` to create a token with both capabilities
101    /// (though this is not currently validated or actively used).
102    ///
103    /// # Arguments
104    /// * `nodes` - Vector of multi-party nodes that must attest to the token
105    pub fn multi_party(mut self, nodes: Vec<ServiceNode>) -> Self {
106        self.multi_party_nodes = Some(nodes);
107        self
108    }
109
110    /// Restricts the authorization to a specific domain.
111    ///
112    /// Adds a domain restriction check to the authority block:
113    /// - `check if domain({domain})`
114    ///
115    /// This ensures the token can only be used within the specified domain.
116    ///
117    /// # Arguments
118    /// * `domain` - The domain to restrict to (e.g., "myapp.hessra.dev")
119    pub fn domain_restricted(mut self, domain: String) -> Self {
120        self.domain = Some(domain);
121        self
122    }
123
124    /// Issues (builds and signs) the authorization token.
125    ///
126    /// # Arguments
127    /// * `keypair` - The keypair to sign the token with
128    ///
129    /// # Returns
130    /// Base64-encoded biscuit token
131    pub fn issue(self, keypair: &KeyPair) -> Result<String, Box<dyn Error>> {
132        let start_time = self
133            .time_config
134            .start_time
135            .unwrap_or_else(|| Utc::now().timestamp());
136        let expiration = start_time + self.time_config.duration;
137
138        let domain = self.domain;
139
140        // Extract required fields
141        let subject = self.subject.ok_or("Token requires subject")?;
142        let resource = self.resource.ok_or("Token requires resource")?;
143        let operation = self.operation.ok_or("Token requires operation")?;
144
145        // Build authority block
146        let mut biscuit_builder = biscuit!(
147            r#"
148                right({subject}, {resource}, {operation});
149                check if subject($sub), resource($res), operation($op), right($sub, $res, $op);
150                check if time($time), $time < {expiration};
151            "#
152        );
153
154        // Add domain restriction if specified
155        if let Some(domain) = domain {
156            biscuit_builder = biscuit_builder.check(check!(
157                r#"
158                    check if domain({domain});
159                "#
160            ))?;
161        }
162
163        // Add service chain rules if specified (works for both token types)
164        if let Some(nodes) = self.service_chain_nodes {
165            for node in nodes {
166                let component = node.component;
167                let public_key = biscuit_key_from_string(node.public_key)?;
168                biscuit_builder = biscuit_builder.rule(rule!(
169                    r#"
170                        node($s, {component}) <- service($s) trusting {public_key};
171                    "#
172                ))?;
173            }
174        }
175
176        // Add multi-party checks if specified (works for both token types)
177        if let Some(nodes) = self.multi_party_nodes {
178            for node in nodes {
179                let component = node.component;
180                let public_key = biscuit_key_from_string(node.public_key)?;
181                biscuit_builder = biscuit_builder.check(check!(
182                    r#"
183                        check if namespace({component}) trusting {public_key};
184                    "#
185                ))?;
186            }
187        }
188
189        // Build and sign the biscuit
190        let biscuit = biscuit_builder.build(keypair)?;
191        info!("biscuit (authority): {}", biscuit);
192        let token = biscuit.to_base64()?;
193        Ok(token)
194    }
195}
196
197/// Creates a base biscuit builder with default time configuration.
198///
199/// This is a private helper function that creates a biscuit builder with the default
200/// time configuration (5 minutes duration from current time).
201///
202/// # Arguments
203///
204/// * `subject` - The subject (user) identifier
205/// * `resource` - The resource identifier
206/// * `operation` - The operation to be granted
207///
208/// # Returns
209///
210/// * `Ok(BiscuitBuilder)` - The configured biscuit builder if successful
211/// * `Err(Box<dyn Error>)` - If builder creation fails
212fn _create_base_biscuit_builder(
213    subject: String,
214    resource: String,
215    operation: String,
216) -> Result<BiscuitBuilder, Box<dyn Error>> {
217    create_base_biscuit_builder_with_time(subject, resource, operation, TokenTimeConfig::default())
218}
219
220/// Creates a base biscuit builder with custom time configuration.
221///
222/// This is a private helper function that creates a biscuit builder with custom
223/// time settings for token validity period.
224///
225/// # Arguments
226///
227/// * `subject` - The subject (user) identifier
228/// * `resource` - The resource identifier
229/// * `operation` - The operation to be granted
230/// * `time_config` - Time configuration for token validity
231///
232/// # Returns
233///
234/// * `Ok(BiscuitBuilder)` - The configured biscuit builder if successful
235/// * `Err(Box<dyn Error>)` - If builder creation fails
236fn create_base_biscuit_builder_with_time(
237    subject: String,
238    resource: String,
239    operation: String,
240    time_config: TokenTimeConfig,
241) -> Result<BiscuitBuilder, Box<dyn Error>> {
242    let start_time = time_config
243        .start_time
244        .unwrap_or_else(|| Utc::now().timestamp());
245    let expiration = start_time + time_config.duration;
246
247    let biscuit_builder = biscuit!(
248        r#"
249            right({subject}, {resource}, {operation});
250            check if subject($sub), resource($res), operation($op), right($sub, $res, $op);
251            check if time($time), $time < {expiration};
252        "#
253    );
254
255    Ok(biscuit_builder)
256}
257
258/// Creates a biscuit (not serialized, not base64 encoded) with custom time
259/// configuration.
260///
261/// This function creates a raw Biscuit object that can be further processed
262/// or converted to different formats. It grants the specified operation on
263/// the resource for the given subject.
264///
265/// # Arguments
266///
267/// * `subject` - The subject (user) identifier
268/// * `resource` - The resource identifier to grant access to
269/// * `operation` - The operation to grant access to
270/// * `key` - The key pair used to sign the token
271/// * `time_config` - Time configuration for token validity
272///
273/// # Returns
274///
275/// * `Ok(Biscuit)` - The raw biscuit if successful
276/// * `Err(Box<dyn Error>)` - If token creation fails
277pub fn create_raw_biscuit(
278    subject: String,
279    resource: String,
280    operation: String,
281    key: KeyPair,
282    time_config: TokenTimeConfig,
283) -> Result<Biscuit, Box<dyn Error>> {
284    let biscuit = create_base_biscuit_builder_with_time(subject, resource, operation, time_config)?
285        .build(&key)?;
286
287    info!("biscuit (authority): {}", biscuit);
288
289    Ok(biscuit)
290}
291
292/// Creates a new biscuit token with the specified subject and resource.
293///
294/// This function creates a token that grants read and write access to the specified resource
295/// for the given subject. The token will be valid for 5 minutes by default.
296///
297/// # Arguments
298///
299/// * `subject` - The subject (user) identifier
300/// * `resource` - The resource identifier to grant access to
301/// * `operation` - The operation to grant access to
302/// * `key` - The key pair used to sign the token
303/// * `time_config` - Optional time configuration for token validity
304///
305/// # Returns
306///
307/// * `Ok(Vec<u8>)` - The binary token data if successful
308/// * `Err(Box<dyn Error>)` - If token creation fails
309pub fn create_biscuit(
310    subject: String,
311    resource: String,
312    operation: String,
313    key: KeyPair,
314    time_config: TokenTimeConfig,
315) -> Result<Vec<u8>, Box<dyn Error>> {
316    let biscuit = create_raw_biscuit(subject, resource, operation, key, time_config)?;
317    let token = biscuit.to_vec()?;
318    Ok(token)
319}
320
321/// Creates a base64-encoded biscuit token with custom time configuration.
322///
323/// This is a private helper function that creates a biscuit token and returns
324/// it as a base64-encoded string for easy transmission and storage.
325///
326/// # Arguments
327///
328/// * `subject` - The subject (user) identifier
329/// * `resource` - The resource identifier to grant access to
330/// * `operation` - The operation to grant access to
331/// * `key` - The key pair used to sign the token
332/// * `time_config` - Time configuration for token validity
333///
334/// # Returns
335///
336/// * `Ok(String)` - The base64-encoded token if successful
337/// * `Err(Box<dyn Error>)` - If token creation fails
338fn create_base64_biscuit(
339    subject: String,
340    resource: String,
341    operation: String,
342    key: KeyPair,
343    time_config: TokenTimeConfig,
344) -> Result<String, Box<dyn Error>> {
345    let biscuit = create_raw_biscuit(subject, resource, operation, key, time_config)?;
346    let token = biscuit.to_base64()?;
347    Ok(token)
348}
349
350/// Creates a biscuit token with default time configuration.
351///
352/// This function creates a base64-encoded token string that grants the specified
353/// operation on the resource for the given subject. The token will be valid for
354/// 5 minutes by default.
355///
356/// # Arguments
357///
358/// * `subject` - The subject (user) identifier
359/// * `resource` - The resource identifier to grant access to
360/// * `operation` - The operation to grant access to
361/// * `key` - The key pair used to sign the token
362///
363/// # Returns
364///
365/// * `Ok(String)` - The base64-encoded token if successful
366/// * `Err(Box<dyn Error>)` - If token creation fails
367pub fn create_token(
368    subject: String,
369    resource: String,
370    operation: String,
371    key: KeyPair,
372) -> Result<String, Box<dyn Error>> {
373    create_base64_biscuit(
374        subject,
375        resource,
376        operation,
377        key,
378        TokenTimeConfig::default(),
379    )
380}
381
382/// Creates a biscuit token with custom time configuration.
383///
384/// This function creates a base64-encoded token string that grants the specified
385/// operation on the resource for the given subject with custom time settings.
386///
387/// # Arguments
388///
389/// * `subject` - The subject (user) identifier
390/// * `resource` - The resource identifier to grant access to
391/// * `operation` - The operation to grant access to
392/// * `key` - The key pair used to sign the token
393/// * `time_config` - Time configuration for token validity
394///
395/// # Returns
396///
397/// * `Ok(String)` - The base64-encoded token if successful
398/// * `Err(Box<dyn Error>)` - If token creation fails
399pub fn create_token_with_time(
400    subject: String,
401    resource: String,
402    operation: String,
403    key: KeyPair,
404    time_config: TokenTimeConfig,
405) -> Result<String, Box<dyn Error>> {
406    create_base64_biscuit(subject, resource, operation, key, time_config)
407}
408
409/// Creates a new biscuit token with service chain attestations.
410/// Creates a new biscuit token with service chain attestations.
411///
412/// This function creates a token that grants access to the specified resource for the given subject,
413/// and includes attestations for each service node in the chain. The token will be valid for 5 minutes by default.
414///
415/// # Arguments
416///
417/// * `subject` - The subject (user) identifier
418/// * `resource` - The resource identifier to grant access to
419/// * `operation` - The operation to grant access to
420/// * `key` - The key pair used to sign the token
421/// * `nodes` - Vector of service nodes that will attest to the token
422///
423/// # Returns
424///
425/// * `Ok(Vec<u8>)` - The binary token data if successful
426/// * `Err(Box<dyn Error>)` - If token creation fails
427pub fn create_service_chain_biscuit(
428    subject: String,
429    resource: String,
430    operation: String,
431    key: KeyPair,
432    nodes: &Vec<ServiceNode>,
433    time_config: TokenTimeConfig,
434) -> Result<Vec<u8>, Box<dyn Error>> {
435    let biscuit =
436        create_raw_service_chain_biscuit(subject, resource, operation, key, nodes, time_config)?;
437    let token = biscuit.to_vec()?;
438    Ok(token)
439}
440
441/// Creates a base64-encoded service chain biscuit token.
442///
443/// This is a private helper function that creates a service chain biscuit token
444/// and returns it as a base64-encoded string for easy transmission and storage.
445///
446/// # Arguments
447///
448/// * `subject` - The subject (user) identifier
449/// * `resource` - The resource identifier to grant access to
450/// * `operation` - The operation to grant access to
451/// * `key` - The key pair used to sign the token
452/// * `nodes` - Vector of service nodes that will attest to the token
453/// * `time_config` - Time configuration for token validity
454///
455/// # Returns
456///
457/// * `Ok(String)` - The base64-encoded token if successful
458/// * `Err(Box<dyn Error>)` - If token creation fails
459fn create_base64_service_chain_biscuit(
460    subject: String,
461    resource: String,
462    operation: String,
463    key: KeyPair,
464    nodes: &Vec<ServiceNode>,
465    time_config: TokenTimeConfig,
466) -> Result<String, Box<dyn Error>> {
467    let biscuit =
468        create_raw_service_chain_biscuit(subject, resource, operation, key, nodes, time_config)?;
469    let token = biscuit.to_base64()?;
470    Ok(token)
471}
472
473/// Creates a raw service chain biscuit token.
474///
475/// This function creates a raw Biscuit object with service chain attestations
476/// that can be further processed or converted to different formats. It delegates
477/// to the time-aware version with the provided configuration.
478///
479/// # Arguments
480///
481/// * `subject` - The subject (user) identifier
482/// * `resource` - The resource identifier to grant access to
483/// * `operation` - The operation to grant access to
484/// * `key` - The key pair used to sign the token
485/// * `nodes` - Vector of service nodes that will attest to the token
486/// * `time_config` - Time configuration for token validity
487///
488/// # Returns
489///
490/// * `Ok(Biscuit)` - The raw biscuit if successful
491/// * `Err(Box<dyn Error>)` - If token creation fails
492pub fn create_raw_service_chain_biscuit(
493    subject: String,
494    resource: String,
495    operation: String,
496    key: KeyPair,
497    nodes: &Vec<ServiceNode>,
498    time_config: TokenTimeConfig,
499) -> Result<Biscuit, Box<dyn Error>> {
500    create_service_chain_biscuit_with_time(subject, resource, operation, key, nodes, time_config)
501}
502
503/// Creates a service chain biscuit token with default time configuration.
504///
505/// This function creates a base64-encoded service chain token string that grants
506/// the specified operation on the resource for the given subject. The token will
507/// be valid for 5 minutes by default and includes attestations for each service
508/// node in the chain.
509///
510/// # Arguments
511///
512/// * `subject` - The subject (user) identifier
513/// * `resource` - The resource identifier to grant access to
514/// * `operation` - The operation to grant access to
515/// * `key` - The key pair used to sign the token
516/// * `nodes` - Vector of service nodes that will attest to the token
517///
518/// # Returns
519///
520/// * `Ok(String)` - The base64-encoded token if successful
521/// * `Err(Box<dyn Error>)` - If token creation fails
522pub fn create_service_chain_token(
523    subject: String,
524    resource: String,
525    operation: String,
526    key: KeyPair,
527    nodes: &Vec<ServiceNode>,
528) -> Result<String, Box<dyn Error>> {
529    create_base64_service_chain_biscuit(
530        subject,
531        resource,
532        operation,
533        key,
534        nodes,
535        TokenTimeConfig::default(),
536    )
537}
538
539/// Creates a new biscuit token with service chain attestations and custom time settings.
540///
541/// This function creates a token that grants access to the specified resource for the given subject,
542/// includes attestations for each service node in the chain, and allows custom time configuration.
543///
544/// # Arguments
545///
546/// * `subject` - The subject (user) identifier
547/// * `resource` - The resource identifier to grant access to
548/// * `operation` - The operation to grant access to
549/// * `key` - The key pair used to sign the token
550/// * `nodes` - Vector of service nodes that will attest to the token
551/// * `time_config` - Time configuration for token validity
552///
553/// # Returns
554///
555/// * `Ok(Vec<u8>)` - The binary token data if successful
556/// * `Err(Box<dyn Error>)` - If token creation fails
557pub fn create_service_chain_biscuit_with_time(
558    subject: String,
559    resource: String,
560    operation: String,
561    key: KeyPair,
562    nodes: &Vec<ServiceNode>,
563    time_config: TokenTimeConfig,
564) -> Result<Biscuit, Box<dyn Error>> {
565    let service = resource.clone();
566    let mut biscuit_builder =
567        create_base_biscuit_builder_with_time(subject, service, operation, time_config)?;
568
569    // Add each node in the service chain to the biscuit builder
570    for node in nodes {
571        let component = node.component.clone();
572        let public_key = biscuit_key_from_string(node.public_key.clone())?;
573        biscuit_builder = biscuit_builder.rule(rule!(
574            r#"
575                node($s, {component}) <- service($s) trusting {public_key};
576            "#
577        ))?;
578    }
579
580    let biscuit = biscuit_builder.build(&key)?;
581
582    info!("biscuit (authority): {}", biscuit);
583
584    Ok(biscuit)
585}
586
587/// Creates a service chain biscuit token with custom time configuration.
588///
589/// This function creates a base64-encoded service chain token string that grants
590/// the specified operation on the resource for the given subject with custom time
591/// settings. The token includes attestations for each service node in the chain.
592///
593/// # Arguments
594///
595/// * `subject` - The subject (user) identifier
596/// * `resource` - The resource identifier to grant access to
597/// * `operation` - The operation to grant access to
598/// * `key` - The key pair used to sign the token
599/// * `nodes` - Vector of service nodes that will attest to the token
600/// * `time_config` - Time configuration for token validity
601///
602/// # Returns
603///
604/// * `Ok(String)` - The base64-encoded token if successful
605/// * `Err(Box<dyn Error>)` - If token creation fails
606pub fn create_service_chain_token_with_time(
607    subject: String,
608    resource: String,
609    operation: String,
610    key: KeyPair,
611    nodes: &Vec<ServiceNode>,
612    time_config: TokenTimeConfig,
613) -> Result<String, Box<dyn Error>> {
614    let biscuit = create_service_chain_biscuit_with_time(
615        subject,
616        resource,
617        operation,
618        key,
619        nodes,
620        time_config,
621    )?;
622    let token = biscuit.to_base64()?;
623    Ok(token)
624}
625
626/// Creates a new biscuit token with multi-party attestations.
627///
628/// This function creates a token that grants access to the specified resource for the given subject,
629/// includes attestations for each multi-party node in the chain, and allows custom time configuration.
630///
631/// The key difference between a multi-party biscuit and a service chain biscuit is that a multi-party
632/// biscuit is not valid until it has been attested by all the parties.
633///
634/// # Arguments
635///
636/// * `subject` - The subject (user) identifier
637/// * `resource` - The resource identifier to grant access to
638/// * `operation` - The operation to grant access to
639/// * `key` - The key pair used to sign the token
640/// * `multi_party_nodes` - Vector of multi-party nodes that will attest to the token
641pub fn create_raw_multi_party_biscuit(
642    subject: String,
643    resource: String,
644    operation: String,
645    key: KeyPair,
646    multi_party_nodes: &Vec<ServiceNode>,
647) -> Result<Biscuit, Box<dyn Error>> {
648    create_multi_party_biscuit_with_time(
649        subject,
650        resource,
651        operation,
652        key,
653        multi_party_nodes,
654        TokenTimeConfig::default(),
655    )
656}
657
658/// Creates a new biscuit token with multi-party attestations.
659///
660/// This function creates a token that grants access to the specified resource for the given subject,
661/// includes attestations for each multi-party node in the chain. The token will be valid for 5 minutes by default.
662///
663/// The key difference between a multi-party biscuit and a service chain biscuit is that a multi-party
664/// biscuit is not valid until it has been attested by all the parties.
665///
666/// # Arguments
667///
668/// * `subject` - The subject (user) identifier
669/// * `resource` - The resource identifier to grant access to
670/// * `operation` - The operation to grant access to
671/// * `key` - The key pair used to sign the token
672/// * `multi_party_nodes` - Vector of multi-party nodes that will attest to the token
673///
674/// # Returns
675///
676/// * `Ok(Vec<u8>)` - The binary token data if successful
677/// * `Err(Box<dyn Error>)` - If token creation fails
678pub fn create_multi_party_biscuit(
679    subject: String,
680    resource: String,
681    operation: String,
682    key: KeyPair,
683    multi_party_nodes: &Vec<ServiceNode>,
684) -> Result<Vec<u8>, Box<dyn Error>> {
685    let biscuit =
686        create_raw_multi_party_biscuit(subject, resource, operation, key, multi_party_nodes)?;
687    let token = biscuit.to_vec()?;
688    Ok(token)
689}
690
691/// Creates a base64-encoded multi-party biscuit token.
692///
693/// This is a private helper function that creates a multi-party biscuit token
694/// and returns it as a base64-encoded string for easy transmission and storage.
695/// Multi-party tokens require attestation from all specified nodes before they
696/// become valid.
697///
698/// # Arguments
699///
700/// * `subject` - The subject (user) identifier
701/// * `resource` - The resource identifier to grant access to
702/// * `operation` - The operation to grant access to
703/// * `key` - The key pair used to sign the token
704/// * `multi_party_nodes` - Vector of multi-party nodes that must attest to the token
705/// * `time_config` - Time configuration for token validity
706///
707/// # Returns
708///
709/// * `Ok(String)` - The base64-encoded token if successful
710/// * `Err(Box<dyn Error>)` - If token creation fails
711fn create_base64_multi_party_biscuit(
712    subject: String,
713    resource: String,
714    operation: String,
715    key: KeyPair,
716    multi_party_nodes: &Vec<ServiceNode>,
717    time_config: TokenTimeConfig,
718) -> Result<String, Box<dyn Error>> {
719    let biscuit = create_multi_party_biscuit_with_time(
720        subject,
721        resource,
722        operation,
723        key,
724        multi_party_nodes,
725        time_config,
726    )?;
727    let token = biscuit.to_base64()?;
728    Ok(token)
729}
730
731/// Creates a new multi-party biscuit token with default time configuration.
732///
733/// This function creates a token that grants access to the specified resource for the given subject,
734/// includes attestations for each multi-party node, and uses the default time configuration (5 minutes).
735///
736/// The key difference between a multi-party biscuit and a service chain biscuit is that a multi-party
737/// biscuit is not valid until it has been attested by all the parties.
738///
739/// # Arguments
740///
741/// * `subject` - The subject (user) identifier
742/// * `resource` - The resource identifier to grant access to
743/// * `operation` - The operation to grant access to
744/// * `key` - The key pair used to sign the token
745/// * `multi_party_nodes` - Vector of multi-party nodes that will attest to the token
746///
747/// # Returns
748///
749/// * `Ok(String)` - The base64-encoded token if successful
750/// * `Err(Box<dyn Error>)` - If token creation fails
751pub fn create_multi_party_token(
752    subject: String,
753    resource: String,
754    operation: String,
755    key: KeyPair,
756    multi_party_nodes: &Vec<ServiceNode>,
757) -> Result<String, Box<dyn Error>> {
758    create_base64_multi_party_biscuit(
759        subject,
760        resource,
761        operation,
762        key,
763        multi_party_nodes,
764        TokenTimeConfig::default(),
765    )
766}
767
768/// Creates a new biscuit token with multi-party attestations and custom time settings.
769///
770/// This function creates a token that grants access to the specified resource for the given subject,
771/// includes attestations for each multi-party node in the chain, and allows custom time configuration.
772///
773/// The key difference between a multi-party biscuit and a service chain biscuit is that a multi-party
774/// biscuit is not valid until it has been attested by all the parties.
775///
776/// # Arguments
777///
778/// * `subject` - The subject (user) identifier
779/// * `resource` - The resource identifier to grant access to
780/// * `operation` - The operation to grant access to
781/// * `key` - The key pair used to sign the token
782/// * `multi_party_nodes` - Vector of multi-party nodes that will attest to the token
783/// * `time_config` - Time configuration for token validity
784///
785/// # Returns
786///
787/// * `Ok(Biscuit)` - The raw biscuit if successful
788/// * `Err(Box<dyn Error>)` - If token creation fails
789pub fn create_multi_party_biscuit_with_time(
790    subject: String,
791    resource: String,
792    operation: String,
793    key: KeyPair,
794    multi_party_nodes: &Vec<ServiceNode>,
795    time_config: TokenTimeConfig,
796) -> Result<Biscuit, Box<dyn Error>> {
797    let mut biscuit_builder =
798        create_base_biscuit_builder_with_time(subject, resource, operation, time_config)?;
799
800    for node in multi_party_nodes {
801        let component = node.component.clone();
802        let public_key = biscuit_key_from_string(node.public_key.clone())?;
803        biscuit_builder = biscuit_builder.check(check!(
804            r#"
805                check if namespace({component}) trusting {public_key};
806            "#
807        ))?;
808    }
809
810    let biscuit = biscuit_builder.build(&key)?;
811
812    info!("biscuit (authority): {}", biscuit);
813
814    Ok(biscuit)
815}
816
817pub fn create_multi_party_token_with_time(
818    subject: String,
819    resource: String,
820    operation: String,
821    key: KeyPair,
822    multi_party_nodes: &Vec<ServiceNode>,
823    time_config: TokenTimeConfig,
824) -> Result<String, Box<dyn Error>> {
825    let biscuit = create_multi_party_biscuit_with_time(
826        subject,
827        resource,
828        operation,
829        key,
830        multi_party_nodes,
831        time_config,
832    )?;
833    let token = biscuit.to_base64()?;
834    Ok(token)
835}
836
837#[cfg(test)]
838mod tests {
839    use super::*;
840    use crate::verify::{verify_biscuit_local, verify_service_chain_biscuit_local};
841    use biscuit::macros::block;
842    use biscuit::Biscuit;
843    #[test]
844    fn test_create_biscuit() {
845        let subject = "test@test.com".to_owned();
846        let resource: String = "res1".to_string();
847        let operation = "read".to_string();
848        let root = KeyPair::new();
849        let public_key = root.public();
850        let token = create_biscuit(
851            subject.clone(),
852            resource.clone(),
853            operation.clone(),
854            root,
855            TokenTimeConfig::default(),
856        )
857        .unwrap();
858
859        let res = verify_biscuit_local(token, public_key, subject, resource, operation);
860        assert!(res.is_ok());
861    }
862
863    #[test]
864    fn test_biscuit_operations() {
865        let subject = "test@test.com".to_owned();
866        let resource = "res1".to_string();
867        let operation = "read".to_string();
868        let root = KeyPair::new();
869        let public_key = root.public();
870
871        // Test read operation
872        let read_token = create_biscuit(
873            subject.clone(),
874            resource.clone(),
875            operation.clone(),
876            root,
877            TokenTimeConfig::default(),
878        )
879        .unwrap();
880
881        let res = verify_biscuit_local(
882            read_token.clone(),
883            public_key,
884            subject.clone(),
885            resource.clone(),
886            operation.clone(),
887        );
888        assert!(res.is_ok());
889
890        let root = KeyPair::new();
891        let public_key = root.public();
892
893        // Test write operation
894        let write_token = create_biscuit(
895            subject.clone(),
896            resource.clone(),
897            "write".to_string(),
898            root,
899            TokenTimeConfig::default(),
900        )
901        .unwrap();
902
903        let res = verify_biscuit_local(
904            write_token.clone(),
905            public_key,
906            subject.clone(),
907            resource.clone(),
908            "write".to_string(),
909        );
910        assert!(res.is_ok());
911
912        // Test that read token cannot be used for write
913        let res = verify_biscuit_local(
914            read_token,
915            public_key,
916            subject.clone(),
917            resource.clone(),
918            "write".to_string(),
919        );
920        assert!(res.is_err());
921
922        // Test that write token cannot be used for read
923        let res = verify_biscuit_local(
924            write_token,
925            public_key,
926            subject.clone(),
927            resource.clone(),
928            "read".to_string(),
929        );
930        assert!(res.is_err());
931    }
932
933    #[test]
934    fn test_biscuit_expiration() {
935        let subject = "test@test.com".to_owned();
936        let resource = "res1".to_string();
937        let operation = "read".to_string();
938        let root = KeyPair::new();
939        let public_key = root.public();
940        // Create a biscuit with a 5 minute expiration from now
941        let token = create_biscuit(
942            subject.clone(),
943            resource.clone(),
944            operation.clone(),
945            root,
946            TokenTimeConfig::default(),
947        )
948        .unwrap();
949
950        let res = verify_biscuit_local(
951            token.clone(),
952            public_key,
953            subject.clone(),
954            resource.clone(),
955            operation.clone(),
956        );
957        assert!(res.is_ok());
958
959        // Create a biscuit with a start time over 5 minutes ago
960        let root = KeyPair::new();
961        let token = create_biscuit(
962            subject.clone(),
963            resource.clone(),
964            operation.clone(),
965            root,
966            TokenTimeConfig {
967                start_time: Some(Utc::now().timestamp() - 301),
968                duration: 300,
969            },
970        )
971        .unwrap();
972        let res = verify_biscuit_local(token, public_key, subject, resource, operation);
973        assert!(res.is_err());
974    }
975
976    #[test]
977    fn test_custom_token_time_config() {
978        let subject = "test@test.com".to_owned();
979        let resource = "res1".to_string();
980        let operation = "read".to_string();
981        let root = KeyPair::new();
982        let public_key = root.public();
983
984        // Create token with custom start time (1 hour in the past) and longer duration (1 hour)
985        let past_time = Utc::now().timestamp() - 3600;
986        let time_config = TokenTimeConfig {
987            start_time: Some(past_time),
988            duration: 7200, // 2 hours
989        };
990
991        let token = create_biscuit(
992            subject.clone(),
993            resource.clone(),
994            operation.clone(),
995            root,
996            time_config,
997        )
998        .unwrap();
999
1000        // Token should be valid at a time between start and expiration
1001        let res = verify_biscuit_local(
1002            token.clone(),
1003            public_key,
1004            subject.clone(),
1005            resource.clone(),
1006            operation.clone(),
1007        );
1008        assert!(res.is_ok());
1009    }
1010
1011    #[test]
1012    fn test_service_chain_biscuit() {
1013        let subject = "test@test.com".to_owned();
1014        let resource = "res1".to_string();
1015        let operation = "read".to_string();
1016        let root = KeyPair::new();
1017        let public_key = root.public();
1018        let chain_key = KeyPair::new();
1019        let chain_public_key = hex::encode(chain_key.public().to_bytes());
1020        let chain_public_key = format!("ed25519/{}", chain_public_key);
1021        let chain_node = ServiceNode {
1022            component: "edge_function".to_string(),
1023            public_key: chain_public_key.clone(),
1024        };
1025        let nodes = vec![chain_node];
1026        let token = create_service_chain_biscuit(
1027            subject.clone(),
1028            resource.clone(),
1029            operation.clone(),
1030            root,
1031            &nodes,
1032            TokenTimeConfig::default(),
1033        );
1034        if let Err(e) = &token {
1035            println!("Error: {}", e);
1036        }
1037        assert!(token.is_ok());
1038        let token = token.unwrap();
1039        let res = verify_biscuit_local(
1040            token.clone(),
1041            public_key,
1042            subject.clone(),
1043            resource.clone(),
1044            operation.clone(),
1045        );
1046        assert!(res.is_ok());
1047        let biscuit = Biscuit::from(&token, public_key).unwrap();
1048        let third_party_request = biscuit.third_party_request().unwrap();
1049        let third_party_block = block!(
1050            r#"
1051            service("res1");
1052            "#
1053        );
1054        let third_party_block = third_party_request
1055            .create_block(&chain_key.private(), third_party_block)
1056            .unwrap();
1057        let attested_biscuit = biscuit
1058            .append_third_party(chain_key.public(), third_party_block)
1059            .unwrap();
1060        let attested_token = attested_biscuit.to_vec().unwrap();
1061        let res = verify_service_chain_biscuit_local(
1062            attested_token,
1063            public_key,
1064            subject.clone(),
1065            resource.clone(),
1066            operation.clone(),
1067            nodes,
1068            None,
1069        );
1070        assert!(res.is_ok());
1071    }
1072
1073    #[test]
1074    fn test_service_chain_biscuit_with_component_name() {
1075        let subject = "test@test.com".to_owned();
1076        let resource = "res1".to_string();
1077        let root = KeyPair::new();
1078        let public_key = root.public();
1079
1080        // Create two chain nodes
1081        let chain_key1 = KeyPair::new();
1082        let chain_public_key1 = hex::encode(chain_key1.public().to_bytes());
1083        let chain_public_key1 = format!("ed25519/{}", chain_public_key1);
1084        let chain_node1 = ServiceNode {
1085            component: "edge_function".to_string(),
1086            public_key: chain_public_key1.clone(),
1087        };
1088
1089        let chain_key2 = KeyPair::new();
1090        let chain_public_key2 = hex::encode(chain_key2.public().to_bytes());
1091        let chain_public_key2 = format!("ed25519/{}", chain_public_key2);
1092        let chain_node2 = ServiceNode {
1093            component: "middleware".to_string(),
1094            public_key: chain_public_key2.clone(),
1095        };
1096
1097        let nodes = vec![chain_node1.clone(), chain_node2.clone()];
1098
1099        // Create the initial token using the first node
1100        let token = create_service_chain_biscuit(
1101            subject.clone(),
1102            resource.clone(),
1103            "read".to_string(),
1104            root,
1105            &nodes,
1106            TokenTimeConfig::default(),
1107        );
1108        assert!(token.is_ok());
1109        let token = token.unwrap();
1110
1111        // Create the biscuit and add third-party blocks
1112        let biscuit = Biscuit::from(&token, public_key).unwrap();
1113        let third_party_request = biscuit.third_party_request().unwrap();
1114        let third_party_block = block!(
1115            r#"
1116                service("res1");
1117            "#
1118        );
1119        let third_party_block = third_party_request
1120            .create_block(&chain_key1.private(), third_party_block)
1121            .unwrap();
1122        let attested_biscuit = biscuit
1123            .append_third_party(chain_key1.public(), third_party_block)
1124            .unwrap();
1125        let attested_token = attested_biscuit.to_vec().unwrap();
1126
1127        // Test with the "edge_function" component name - should pass
1128        // the first node in the service chain checking itself is valid
1129        // since it is checking the base biscuit
1130        let res = verify_service_chain_biscuit_local(
1131            attested_token.clone(),
1132            public_key,
1133            subject.clone(),
1134            resource.clone(),
1135            "read".to_string(),
1136            nodes.clone(),
1137            Some("edge_function".to_string()),
1138        );
1139        // This should fail - since we're not verifying any nodes when checking up to but not including "edge_function"
1140        assert!(res.is_ok());
1141
1142        // Create a chain with two nodes
1143        let nodes = vec![chain_node1.clone(), chain_node2.clone()];
1144
1145        // Test with "middleware" component - should succeed verifying node1 only
1146        let res = verify_service_chain_biscuit_local(
1147            attested_token.clone(),
1148            public_key,
1149            subject.clone(),
1150            resource.clone(),
1151            "read".to_string(),
1152            nodes.clone(),
1153            Some("middleware".to_string()),
1154        );
1155        assert!(res.is_ok());
1156    }
1157
1158    #[test]
1159    fn test_service_chain_biscuit_with_nonexistent_component() {
1160        let subject = "test@test.com".to_owned();
1161        let resource = "res1".to_string();
1162        let root = KeyPair::new();
1163        let public_key = root.public();
1164        let chain_key = KeyPair::new();
1165        let chain_public_key = hex::encode(chain_key.public().to_bytes());
1166        let chain_public_key = format!("ed25519/{}", chain_public_key);
1167        let chain_node = ServiceNode {
1168            component: "edge_function".to_string(),
1169            public_key: chain_public_key.clone(),
1170        };
1171        let nodes = vec![chain_node];
1172        let token = create_service_chain_biscuit(
1173            subject.clone(),
1174            resource.clone(),
1175            "read".to_string(),
1176            root,
1177            &nodes,
1178            TokenTimeConfig::default(),
1179        );
1180        assert!(token.is_ok());
1181        let token = token.unwrap();
1182
1183        let biscuit = Biscuit::from(&token, public_key).unwrap();
1184        let third_party_request = biscuit.third_party_request().unwrap();
1185        let third_party_block = block!(
1186            r#"
1187            service("res1");
1188            "#
1189        );
1190        let third_party_block = third_party_request
1191            .create_block(&chain_key.private(), third_party_block)
1192            .unwrap();
1193        let attested_biscuit = biscuit
1194            .append_third_party(chain_key.public(), third_party_block)
1195            .unwrap();
1196        let attested_token = attested_biscuit.to_vec().unwrap();
1197
1198        // Test with a component name that doesn't exist in the chain
1199        let res = verify_service_chain_biscuit_local(
1200            attested_token,
1201            public_key,
1202            subject.clone(),
1203            resource.clone(),
1204            "read".to_string(),
1205            nodes.clone(),
1206            Some("nonexistent_component".to_string()),
1207        );
1208        assert!(res.is_err());
1209
1210        // Verify the error message contains the component name
1211        let err = res.unwrap_err().to_string();
1212        assert!(err.contains("nonexistent_component"));
1213    }
1214
1215    #[test]
1216    fn test_service_chain_biscuit_with_multiple_nodes() {
1217        let subject = "test@test.com".to_owned();
1218        let resource = "res1".to_string();
1219        let root = KeyPair::new();
1220        let public_key = root.public();
1221
1222        // Create three chain nodes
1223        let chain_key1 = KeyPair::new();
1224        let chain_public_key1 = hex::encode(chain_key1.public().to_bytes());
1225        let chain_public_key1 = format!("ed25519/{}", chain_public_key1);
1226        let chain_node1 = ServiceNode {
1227            component: "edge_function".to_string(),
1228            public_key: chain_public_key1.clone(),
1229        };
1230
1231        let chain_key2 = KeyPair::new();
1232        let chain_public_key2 = hex::encode(chain_key2.public().to_bytes());
1233        let chain_public_key2 = format!("ed25519/{}", chain_public_key2);
1234        let chain_node2 = ServiceNode {
1235            component: "middleware".to_string(),
1236            public_key: chain_public_key2.clone(),
1237        };
1238
1239        let chain_key3 = KeyPair::new();
1240        let chain_public_key3 = hex::encode(chain_key3.public().to_bytes());
1241        let chain_public_key3 = format!("ed25519/{}", chain_public_key3);
1242        let chain_node3 = ServiceNode {
1243            component: "backend".to_string(),
1244            public_key: chain_public_key3.clone(),
1245        };
1246
1247        // Create the initial token with the first node
1248        let nodes = vec![
1249            chain_node1.clone(),
1250            chain_node2.clone(),
1251            chain_node3.clone(),
1252        ];
1253        let token = create_service_chain_biscuit(
1254            subject.clone(),
1255            resource.clone(),
1256            "read".to_string(),
1257            root,
1258            &nodes,
1259            TokenTimeConfig::default(),
1260        );
1261        assert!(token.is_ok());
1262        let token = token.unwrap();
1263
1264        println!("Created initial token");
1265
1266        // Create the biscuit and add node1's block
1267        let biscuit = Biscuit::from(&token, public_key).unwrap();
1268        let third_party_request1 = biscuit.third_party_request().unwrap();
1269        let third_party_block1 = block!(
1270            r#"
1271                service("res1");
1272            "#
1273        );
1274        let third_party_block1 = third_party_request1
1275            .create_block(&chain_key1.private(), third_party_block1)
1276            .unwrap();
1277        let attested_biscuit1 = biscuit
1278            .append_third_party(chain_key1.public(), third_party_block1)
1279            .unwrap();
1280
1281        // Chain with all three nodes
1282        let all_nodes = vec![
1283            chain_node1.clone(),
1284            chain_node2.clone(),
1285            chain_node3.clone(),
1286        ];
1287        let attested_token1 = attested_biscuit1.to_vec().unwrap();
1288
1289        // Test 1: Verify up to but not including middleware
1290        // This should verify edge_function only
1291        let res = verify_service_chain_biscuit_local(
1292            attested_token1.clone(),
1293            public_key,
1294            subject.clone(),
1295            resource.clone(),
1296            "read".to_string(),
1297            all_nodes.clone(),
1298            Some("middleware".to_string()),
1299        );
1300        assert!(res.is_ok());
1301
1302        // Test 3: Verify up to but not including backend
1303        // This should try to verify both edge_function and middleware
1304        // but since the middleware attestation wasn't added, it will fail
1305        let res = verify_service_chain_biscuit_local(
1306            attested_token1.clone(),
1307            public_key,
1308            subject.clone(),
1309            resource.clone(),
1310            "read".to_string(),
1311            all_nodes.clone(),
1312            Some("backend".to_string()),
1313        );
1314        assert!(res.is_err());
1315    }
1316
1317    #[test]
1318    fn test_service_chain_biscuit_with_custom_time() {
1319        let subject = "test@test.com".to_owned();
1320        let resource = "res1".to_string();
1321        let root = KeyPair::new();
1322        let public_key = root.public();
1323        let chain_key = KeyPair::new();
1324        let chain_public_key = hex::encode(chain_key.public().to_bytes());
1325        let chain_public_key = format!("ed25519/{}", chain_public_key);
1326        let chain_node = ServiceNode {
1327            component: "edge_function".to_string(),
1328            public_key: chain_public_key.clone(),
1329        };
1330        let nodes = vec![chain_node];
1331
1332        // Create a valid token with default time configuration (5 minutes)
1333        let valid_token = create_service_chain_biscuit_with_time(
1334            subject.clone(),
1335            resource.clone(),
1336            "read".to_string(),
1337            root,
1338            &nodes,
1339            TokenTimeConfig::default(),
1340        );
1341        assert!(valid_token.is_ok());
1342        let valid_token = valid_token.unwrap().to_vec().unwrap();
1343
1344        // Verify the valid token works
1345        let res = verify_biscuit_local(
1346            valid_token.clone(),
1347            public_key,
1348            subject.clone(),
1349            resource.clone(),
1350            "read".to_string(),
1351        );
1352        assert!(res.is_ok());
1353
1354        // Create an expired token (start time 6 minutes ago with 5 minute duration)
1355        let expired_time_config = TokenTimeConfig {
1356            start_time: Some(Utc::now().timestamp() - 360), // 6 minutes ago
1357            duration: 300,                                  // 5 minutes
1358        };
1359
1360        // Create a new key pair for the expired token
1361        let root2 = KeyPair::new();
1362        let public_key2 = root2.public();
1363
1364        let expired_token = create_service_chain_biscuit_with_time(
1365            subject.clone(),
1366            resource.clone(),
1367            "read".to_string(),
1368            root2,
1369            &nodes,
1370            expired_time_config,
1371        );
1372        assert!(expired_token.is_ok());
1373        let expired_token = expired_token.unwrap().to_vec().unwrap();
1374
1375        // Verify expired token fails
1376        let res = verify_biscuit_local(
1377            expired_token,
1378            public_key2,
1379            subject,
1380            resource,
1381            "read".to_string(),
1382        );
1383        assert!(res.is_err());
1384    }
1385
1386    #[test]
1387    fn test_multi_party_biscuit_helper_functions() {
1388        let subject = "test@test.com".to_owned();
1389        let resource = "res1".to_string();
1390        let operation = "read".to_string();
1391        let root = KeyPair::new();
1392
1393        // Create a multi-party node
1394        let multi_party_key = KeyPair::new();
1395        let multi_party_public_key = hex::encode(multi_party_key.public().to_bytes());
1396        let multi_party_public_key = format!("ed25519/{}", multi_party_public_key);
1397        let multi_party_node = ServiceNode {
1398            component: "approval_service".to_string(),
1399            public_key: multi_party_public_key.clone(),
1400        };
1401        let nodes = vec![multi_party_node];
1402
1403        // Test create_multi_party_token (default time config)
1404        let token_string = create_multi_party_token(
1405            subject.clone(),
1406            resource.clone(),
1407            operation.clone(),
1408            root,
1409            &nodes,
1410        );
1411        assert!(token_string.is_ok());
1412
1413        // Test create_multi_party_biscuit (binary token with default time config)
1414        let root2 = KeyPair::new();
1415        let binary_token = create_multi_party_biscuit(
1416            subject.clone(),
1417            resource.clone(),
1418            operation.clone(),
1419            root2,
1420            &nodes,
1421        );
1422        assert!(binary_token.is_ok());
1423
1424        // Test create_raw_multi_party_biscuit (raw biscuit with default time config)
1425        let root3 = KeyPair::new();
1426        let raw_biscuit = create_raw_multi_party_biscuit(
1427            subject.clone(),
1428            resource.clone(),
1429            operation.clone(),
1430            root3,
1431            &nodes,
1432        );
1433        assert!(raw_biscuit.is_ok());
1434
1435        // Test create_multi_party_token_with_time (custom time config)
1436        let custom_time_config = TokenTimeConfig {
1437            start_time: Some(Utc::now().timestamp()),
1438            duration: 600, // 10 minutes
1439        };
1440        let root4 = KeyPair::new();
1441        let custom_time_token = create_multi_party_token_with_time(
1442            subject.clone(),
1443            resource.clone(),
1444            operation.clone(),
1445            root4,
1446            &nodes,
1447            custom_time_config,
1448        );
1449        assert!(custom_time_token.is_ok());
1450
1451        // Test create_multi_party_biscuit_with_time (raw biscuit with custom time config)
1452        let root5 = KeyPair::new();
1453        let custom_time_biscuit = create_multi_party_biscuit_with_time(
1454            subject.clone(),
1455            resource.clone(),
1456            operation.clone(),
1457            root5,
1458            &nodes,
1459            custom_time_config,
1460        );
1461        assert!(custom_time_biscuit.is_ok());
1462    }
1463
1464    #[test]
1465    fn test_basic_authorization_with_domain_restriction() {
1466        let subject = "alice".to_string();
1467        let resource = "resource1".to_string();
1468        let operation = "read".to_string();
1469        let domain = "myapp.hessra.dev".to_string();
1470        let keypair = KeyPair::new();
1471        let public_key = keypair.public();
1472
1473        // Create basic authorization token with domain restriction
1474        let token = HessraAuthorization::new(
1475            subject.clone(),
1476            resource.clone(),
1477            operation.clone(),
1478            TokenTimeConfig::default(),
1479        )
1480        .domain_restricted(domain.clone())
1481        .issue(&keypair);
1482
1483        assert!(token.is_ok(), "Failed to create domain-restricted token");
1484        let token = token.unwrap();
1485
1486        // Parse and verify the token
1487        let biscuit = Biscuit::from_base64(&token, public_key).unwrap();
1488
1489        // Build authorizer with domain fact - should succeed
1490        let authz = crate::verify::build_base_authorizer(
1491            subject.clone(),
1492            resource.clone(),
1493            operation.clone(),
1494            Some(domain.clone()),
1495        )
1496        .unwrap();
1497        assert!(
1498            authz.build(&biscuit).unwrap().authorize().is_ok(),
1499            "Token should verify with correct domain"
1500        );
1501
1502        // Build authorizer without domain fact - should fail
1503        let authz_no_domain = crate::verify::build_base_authorizer(
1504            subject.clone(),
1505            resource.clone(),
1506            operation.clone(),
1507            None,
1508        )
1509        .unwrap();
1510        assert!(
1511            authz_no_domain
1512                .build(&biscuit)
1513                .unwrap()
1514                .authorize()
1515                .is_err(),
1516            "Token should fail verification without domain fact"
1517        );
1518
1519        // Build authorizer with wrong domain - should fail
1520        let authz_wrong_domain = crate::verify::build_base_authorizer(
1521            subject,
1522            resource,
1523            operation,
1524            Some("wrongdomain.com".to_string()),
1525        )
1526        .unwrap();
1527        assert!(
1528            authz_wrong_domain
1529                .build(&biscuit)
1530                .unwrap()
1531                .authorize()
1532                .is_err(),
1533            "Token should fail verification with wrong domain"
1534        );
1535    }
1536
1537    #[test]
1538    fn test_service_chain_with_domain_restriction() {
1539        let subject = "alice".to_string();
1540        let resource = "resource1".to_string();
1541        let operation = "read".to_string();
1542        let domain = "myapp.hessra.dev".to_string();
1543        let keypair = KeyPair::new();
1544        let public_key = keypair.public();
1545
1546        // Create service node
1547        let chain_key = KeyPair::new();
1548        let chain_public_key = hex::encode(chain_key.public().to_bytes());
1549        let chain_public_key = format!("ed25519/{}", chain_public_key);
1550        let chain_node = ServiceNode {
1551            component: "edge_function".to_string(),
1552            public_key: chain_public_key.clone(),
1553        };
1554        let nodes = vec![chain_node];
1555
1556        // Create service chain token with domain restriction
1557        let token = HessraAuthorization::new(
1558            subject.clone(),
1559            resource.clone(),
1560            operation.clone(),
1561            TokenTimeConfig::default(),
1562        )
1563        .service_chain(nodes.clone())
1564        .domain_restricted(domain.clone())
1565        .issue(&keypair);
1566
1567        assert!(
1568            token.is_ok(),
1569            "Failed to create domain-restricted service chain token"
1570        );
1571        let token = token.unwrap();
1572
1573        // Parse and verify the token
1574        let biscuit = Biscuit::from_base64(&token, public_key).unwrap();
1575
1576        // Build authorizer with domain fact - should succeed
1577        let authz = crate::verify::build_base_authorizer(
1578            subject.clone(),
1579            resource.clone(),
1580            operation.clone(),
1581            Some(domain),
1582        )
1583        .unwrap();
1584        assert!(
1585            authz.build(&biscuit).unwrap().authorize().is_ok(),
1586            "Service chain token should verify with correct domain"
1587        );
1588
1589        // Build authorizer without domain fact - should fail
1590        let authz_no_domain =
1591            crate::verify::build_base_authorizer(subject, resource, operation, None).unwrap();
1592        assert!(
1593            authz_no_domain
1594                .build(&biscuit)
1595                .unwrap()
1596                .authorize()
1597                .is_err(),
1598            "Service chain token should fail verification without domain fact"
1599        );
1600    }
1601
1602    #[test]
1603    fn test_multi_party_with_domain_restriction() {
1604        let subject = "alice".to_string();
1605        let resource = "resource1".to_string();
1606        let operation = "read".to_string();
1607        let domain = "myapp.hessra.dev".to_string();
1608        let keypair = KeyPair::new();
1609        let public_key = keypair.public();
1610
1611        // Create multi-party node
1612        let party_key = KeyPair::new();
1613        let party_public_key = hex::encode(party_key.public().to_bytes());
1614        let party_public_key = format!("ed25519/{}", party_public_key);
1615        let party_node = ServiceNode {
1616            component: "approval_service".to_string(),
1617            public_key: party_public_key.clone(),
1618        };
1619        let nodes = vec![party_node];
1620
1621        // Create multi-party token with domain restriction
1622        let token = HessraAuthorization::new(
1623            subject.clone(),
1624            resource.clone(),
1625            operation.clone(),
1626            TokenTimeConfig::default(),
1627        )
1628        .multi_party(nodes.clone())
1629        .domain_restricted(domain.clone())
1630        .issue(&keypair);
1631
1632        assert!(
1633            token.is_ok(),
1634            "Failed to create domain-restricted multi-party token"
1635        );
1636        let token = token.unwrap();
1637
1638        // Parse and verify the token
1639        let biscuit = Biscuit::from_base64(&token, public_key).unwrap();
1640
1641        // Build authorizer with domain fact - token will still fail because multi-party needs attestations
1642        // But we're testing that domain check is present
1643        let _authz = crate::verify::build_base_authorizer(
1644            subject.clone(),
1645            resource.clone(),
1646            operation.clone(),
1647            Some(domain),
1648        )
1649        .unwrap();
1650        // Multi-party token needs attestation, so this will fail for that reason, not domain
1651        // We're just checking the token was created successfully with domain check
1652        assert!(biscuit.to_base64().is_ok(), "Token should be valid biscuit");
1653
1654        // Build authorizer without domain fact - should also fail
1655        let authz_no_domain =
1656            crate::verify::build_base_authorizer(subject, resource, operation, None).unwrap();
1657        assert!(
1658            authz_no_domain
1659                .build(&biscuit)
1660                .unwrap()
1661                .authorize()
1662                .is_err(),
1663            "Multi-party token should fail verification without domain fact or attestations"
1664        );
1665    }
1666
1667    #[test]
1668    fn test_authorization_verifier_with_domain() {
1669        use crate::verify::AuthorizationVerifier;
1670
1671        let subject = "alice".to_string();
1672        let resource = "resource1".to_string();
1673        let operation = "read".to_string();
1674        let domain = "myapp.hessra.dev".to_string();
1675        let keypair = KeyPair::new();
1676        let public_key = keypair.public();
1677
1678        // Create basic authorization token with domain restriction
1679        let token = HessraAuthorization::new(
1680            subject.clone(),
1681            resource.clone(),
1682            operation.clone(),
1683            TokenTimeConfig::default(),
1684        )
1685        .domain_restricted(domain.clone())
1686        .issue(&keypair)
1687        .expect("Failed to create domain-restricted token");
1688
1689        // Verify with matching domain using builder - should succeed
1690        assert!(
1691            AuthorizationVerifier::new(
1692                token.clone(),
1693                public_key,
1694                subject.clone(),
1695                resource.clone(),
1696                operation.clone(),
1697            )
1698            .with_domain(domain.clone())
1699            .verify()
1700            .is_ok(),
1701            "Token should verify with correct domain using builder"
1702        );
1703
1704        // Verify without domain context - should fail
1705        assert!(
1706            AuthorizationVerifier::new(
1707                token.clone(),
1708                public_key,
1709                subject.clone(),
1710                resource.clone(),
1711                operation.clone(),
1712            )
1713            .verify()
1714            .is_err(),
1715            "Token should fail verification without domain context"
1716        );
1717
1718        // Verify with wrong domain - should fail
1719        assert!(
1720            AuthorizationVerifier::new(
1721                token.clone(),
1722                public_key,
1723                subject.clone(),
1724                resource.clone(),
1725                operation.clone(),
1726            )
1727            .with_domain("wrongdomain.com".to_string())
1728            .verify()
1729            .is_err(),
1730            "Token should fail verification with wrong domain"
1731        );
1732
1733        // Test with non-domain-restricted token - extra domain context shouldn't break it
1734        let regular_token = HessraAuthorization::new(
1735            subject.clone(),
1736            resource.clone(),
1737            operation.clone(),
1738            TokenTimeConfig::default(),
1739        )
1740        .issue(&keypair)
1741        .expect("Failed to create regular token");
1742
1743        // Regular token should pass with or without domain context
1744        assert!(
1745            AuthorizationVerifier::new(
1746                regular_token.clone(),
1747                public_key,
1748                subject.clone(),
1749                resource.clone(),
1750                operation.clone(),
1751            )
1752            .verify()
1753            .is_ok(),
1754            "Regular token should verify without domain context"
1755        );
1756
1757        assert!(
1758            AuthorizationVerifier::new(regular_token, public_key, subject, resource, operation,)
1759                .with_domain(domain)
1760                .verify()
1761                .is_ok(),
1762            "Regular token should verify even with extra domain context"
1763        );
1764    }
1765
1766    #[test]
1767    fn test_service_chain_verifier_with_domain() {
1768        use crate::verify::{AuthorizationVerifier, ServiceNode};
1769
1770        let subject = "alice".to_string();
1771        let resource = "resource1".to_string();
1772        let operation = "read".to_string();
1773        let domain = "myapp.hessra.dev".to_string();
1774        let keypair = KeyPair::new();
1775        let public_key = keypair.public();
1776
1777        // Create service node
1778        let node_keypair = KeyPair::new();
1779        let node_public_key = node_keypair.public();
1780        let node_key_string = format!("ed25519/{}", hex::encode(node_public_key.to_bytes()));
1781
1782        let service_nodes = vec![ServiceNode {
1783            component: "api-gateway".to_string(),
1784            public_key: node_key_string,
1785        }];
1786
1787        // Create service chain token with domain restriction
1788        let token = HessraAuthorization::new(
1789            subject.clone(),
1790            resource.clone(),
1791            operation.clone(),
1792            TokenTimeConfig::default(),
1793        )
1794        .service_chain(service_nodes.clone())
1795        .domain_restricted(domain.clone())
1796        .issue(&keypair)
1797        .expect("Failed to create service chain token with domain");
1798
1799        // Convert token to bytes for attestation
1800        let token_bytes = crate::decode_token(&token).expect("Failed to decode token");
1801
1802        // Add service node attestation
1803        let attested_token_bytes = crate::attest::add_service_node_attestation(
1804            token_bytes,
1805            public_key,
1806            &resource,
1807            &node_keypair,
1808        )
1809        .expect("Failed to add attestation");
1810
1811        // Verify with service chain and domain - should succeed
1812        assert!(
1813            AuthorizationVerifier::from_bytes(
1814                attested_token_bytes.clone(),
1815                public_key,
1816                subject.clone(),
1817                resource.clone(),
1818                operation.clone(),
1819            )
1820            .expect("Failed to create verifier")
1821            .with_service_chain(service_nodes.clone(), Some("api-gateway".to_string()))
1822            .with_domain(domain.clone())
1823            .verify()
1824            .is_ok(),
1825            "Service chain token should verify with domain"
1826        );
1827
1828        // Verify with service chain but no domain - should fail
1829        assert!(
1830            AuthorizationVerifier::from_bytes(
1831                attested_token_bytes.clone(),
1832                public_key,
1833                subject.clone(),
1834                resource.clone(),
1835                operation.clone(),
1836            )
1837            .expect("Failed to create verifier")
1838            .with_service_chain(service_nodes.clone(), Some("api-gateway".to_string()))
1839            .verify()
1840            .is_err(),
1841            "Service chain token should fail without domain"
1842        );
1843
1844        // Verify with domain but no service chain checks
1845        // This should actually PASS because the attestation is valid and embedded in the token
1846        // The service chain checks are additional validation for specific component requirements
1847        assert!(
1848            AuthorizationVerifier::from_bytes(
1849                attested_token_bytes,
1850                public_key,
1851                subject,
1852                resource,
1853                operation,
1854            )
1855            .expect("Failed to create verifier")
1856            .with_domain(domain)
1857            .verify()
1858            .is_ok(),
1859            "Service chain token with valid attestation should pass basic verification"
1860        );
1861    }
1862
1863    #[test]
1864    fn test_verify_capability_token_basic() {
1865        let subject = "alice".to_string();
1866        let resource = "resource1".to_string();
1867        let operation = "read".to_string();
1868        let keypair = KeyPair::new();
1869        let public_key = keypair.public();
1870
1871        // Create token with subject
1872        let token = create_token(
1873            subject.clone(),
1874            resource.clone(),
1875            operation.clone(),
1876            keypair,
1877        )
1878        .unwrap();
1879
1880        // Verify without providing subject - should succeed
1881        let result =
1882            crate::verify::verify_capability_token_local(&token, public_key, &resource, &operation);
1883
1884        assert!(result.is_ok());
1885    }
1886
1887    #[test]
1888    fn test_verify_capability_token_wrong_resource() {
1889        let keypair = KeyPair::new();
1890        let public_key = keypair.public();
1891
1892        let token = create_token(
1893            "alice".to_string(),
1894            "resource1".to_string(),
1895            "read".to_string(),
1896            keypair,
1897        )
1898        .unwrap();
1899
1900        // Try to verify for wrong resource
1901        let result = crate::verify::verify_capability_token_local(
1902            &token,
1903            public_key,
1904            "resource2", // Wrong resource
1905            "read",
1906        );
1907
1908        assert!(result.is_err());
1909    }
1910
1911    #[test]
1912    fn test_verify_capability_token_wrong_operation() {
1913        let keypair = KeyPair::new();
1914        let public_key = keypair.public();
1915
1916        let token = create_token(
1917            "alice".to_string(),
1918            "resource1".to_string(),
1919            "read".to_string(),
1920            keypair,
1921        )
1922        .unwrap();
1923
1924        // Try to verify for wrong operation
1925        let result = crate::verify::verify_capability_token_local(
1926            &token,
1927            public_key,
1928            "resource1",
1929            "write", // Wrong operation
1930        );
1931
1932        assert!(result.is_err());
1933    }
1934
1935    #[test]
1936    fn test_verify_capability_with_domain() {
1937        let domain = "myapp.hessra.dev".to_string();
1938        let keypair = KeyPair::new();
1939        let public_key = keypair.public();
1940
1941        // Create token with domain restriction
1942        let token = HessraAuthorization::new(
1943            "alice".to_string(),
1944            "resource1".to_string(),
1945            "read".to_string(),
1946            TokenTimeConfig::default(),
1947        )
1948        .domain_restricted(domain.clone())
1949        .issue(&keypair)
1950        .unwrap();
1951
1952        // Verify capability with matching domain
1953        let result = crate::verify::AuthorizationVerifier::new_capability(
1954            token.clone(),
1955            public_key,
1956            "resource1".to_string(),
1957            "read".to_string(),
1958        )
1959        .with_domain(domain.clone())
1960        .verify();
1961
1962        assert!(result.is_ok());
1963
1964        // Verify capability without domain - should fail
1965        let result = crate::verify::AuthorizationVerifier::new_capability(
1966            token,
1967            public_key,
1968            "resource1".to_string(),
1969            "read".to_string(),
1970        )
1971        .verify();
1972
1973        assert!(result.is_err());
1974    }
1975
1976    #[test]
1977    fn test_verify_capability_with_service_chain() {
1978        let keypair = KeyPair::new();
1979        let public_key = keypair.public();
1980
1981        let chain_keypair = KeyPair::new();
1982        let chain_public_key =
1983            format!("ed25519/{}", hex::encode(chain_keypair.public().to_bytes()));
1984        let chain_node = ServiceNode {
1985            component: "edge_function".to_string(),
1986            public_key: chain_public_key,
1987        };
1988
1989        // Create service chain token
1990        let token = HessraAuthorization::new(
1991            "alice".to_string(),
1992            "resource1".to_string(),
1993            "read".to_string(),
1994            TokenTimeConfig::default(),
1995        )
1996        .service_chain(vec![chain_node.clone()])
1997        .issue(&keypair)
1998        .unwrap();
1999
2000        // Add attestation
2001        let token_bytes = crate::decode_token(&token).unwrap();
2002        let attested = crate::attest::add_service_node_attestation(
2003            token_bytes,
2004            public_key,
2005            "resource1",
2006            &chain_keypair,
2007        )
2008        .unwrap();
2009        let attested_token = crate::encode_token(&attested);
2010
2011        // Verify capability with service chain
2012        let result = crate::verify::verify_service_chain_capability_token_local(
2013            &attested_token,
2014            public_key,
2015            "resource1",
2016            "read",
2017            vec![chain_node],
2018            None,
2019        );
2020
2021        assert!(result.is_ok());
2022    }
2023
2024    #[test]
2025    fn test_capability_verifier_builder() {
2026        let keypair = KeyPair::new();
2027        let public_key = keypair.public();
2028        let domain = "example.com".to_string();
2029
2030        let token = HessraAuthorization::new(
2031            "alice".to_string(),
2032            "resource1".to_string(),
2033            "read".to_string(),
2034            TokenTimeConfig::default(),
2035        )
2036        .domain_restricted(domain.clone())
2037        .issue(&keypair)
2038        .unwrap();
2039
2040        // Test builder pattern
2041        let result = crate::verify::AuthorizationVerifier::new_capability(
2042            token,
2043            public_key,
2044            "resource1".to_string(),
2045            "read".to_string(),
2046        )
2047        .with_domain(domain)
2048        .verify();
2049
2050        assert!(result.is_ok());
2051    }
2052
2053    #[test]
2054    fn test_capability_vs_identity_verification() {
2055        let keypair = KeyPair::new();
2056        let public_key = keypair.public();
2057
2058        // Create token for alice
2059        let token = create_token(
2060            "alice".to_string(),
2061            "document_123".to_string(),
2062            "read".to_string(),
2063            keypair,
2064        )
2065        .unwrap();
2066
2067        // Identity-based: Must specify correct subject
2068        let result = crate::verify::verify_token_local(
2069            &token,
2070            public_key,
2071            "alice", // Must match
2072            "document_123",
2073            "read",
2074        );
2075        assert!(result.is_ok());
2076
2077        // Identity-based: Wrong subject fails
2078        let result = crate::verify::verify_token_local(
2079            &token,
2080            public_key,
2081            "bob", // Wrong subject
2082            "document_123",
2083            "read",
2084        );
2085        assert!(result.is_err());
2086
2087        // Capability-based: Don't care about subject
2088        let result = crate::verify::verify_capability_token_local(
2089            &token,
2090            public_key,
2091            "document_123", // No subject needed
2092            "read",
2093        );
2094        assert!(result.is_ok());
2095    }
2096}