Skip to main content

hessra_context_token/
mint.rs

1extern crate biscuit_auth as biscuit;
2
3use biscuit::macros::biscuit;
4use chrono::Utc;
5use hessra_token_core::{KeyPair, TokenTimeConfig};
6use std::error::Error;
7
8/// Builder for creating Hessra context tokens.
9///
10/// Context tokens identify a session and track data exposure (exposure labels)
11/// as append-only Biscuit blocks.
12///
13/// # Example
14/// ```rust
15/// use hessra_context_token::HessraContext;
16/// use hessra_token_core::{KeyPair, TokenTimeConfig};
17///
18/// let keypair = KeyPair::new();
19/// let token = HessraContext::new(
20///     "agent:openclaw".to_string(),
21///     TokenTimeConfig::default(),
22/// )
23/// .issue(&keypair)
24/// .expect("Failed to create context token");
25/// ```
26pub struct HessraContext {
27    subject: String,
28    time_config: TokenTimeConfig,
29}
30
31impl HessraContext {
32    /// Creates a new context token builder.
33    ///
34    /// # Arguments
35    /// * `subject` - The session owner identifier (e.g., "agent:openclaw")
36    /// * `time_config` - Time configuration for token validity
37    pub fn new(subject: String, time_config: TokenTimeConfig) -> Self {
38        Self {
39            subject,
40            time_config,
41        }
42    }
43
44    /// Issues (builds and signs) the context token.
45    ///
46    /// The authority block contains:
47    /// - `context({subject})` - identifies the session owner
48    /// - time expiration check
49    ///
50    /// # Arguments
51    /// * `keypair` - The keypair to sign the token with
52    ///
53    /// # Returns
54    /// Base64-encoded Biscuit token
55    pub fn issue(self, keypair: &KeyPair) -> Result<String, Box<dyn Error>> {
56        let start_time = self
57            .time_config
58            .start_time
59            .unwrap_or_else(|| Utc::now().timestamp());
60        let expiration = start_time + self.time_config.duration;
61        let subject = self.subject;
62
63        let builder = biscuit!(
64            r#"
65                context({subject});
66                check if time($time), $time < {expiration};
67            "#
68        );
69
70        let biscuit = builder.build(keypair)?;
71        let token = biscuit.to_base64()?;
72
73        Ok(token)
74    }
75}
76
77#[cfg(test)]
78mod tests {
79    use super::*;
80    use crate::verify::ContextVerifier;
81
82    #[test]
83    fn test_create_context_token() {
84        let keypair = KeyPair::new();
85        let public_key = keypair.public();
86
87        let token = HessraContext::new("agent:openclaw".to_string(), TokenTimeConfig::default())
88            .issue(&keypair)
89            .expect("Failed to create context token");
90
91        assert!(!token.is_empty());
92
93        // Should verify successfully
94        ContextVerifier::new(token, public_key)
95            .verify()
96            .expect("Should verify fresh context token");
97    }
98
99    #[test]
100    fn test_expired_context_token() {
101        let keypair = KeyPair::new();
102        let public_key = keypair.public();
103
104        let expired_config = TokenTimeConfig {
105            start_time: Some(0),
106            duration: 1,
107        };
108
109        let token = HessraContext::new("agent:test".to_string(), expired_config)
110            .issue(&keypair)
111            .expect("Failed to create expired context token");
112
113        let result = ContextVerifier::new(token, public_key).verify();
114        assert!(
115            result.is_err(),
116            "Expired context token should fail verification"
117        );
118    }
119
120    #[test]
121    fn test_custom_time_config() {
122        let keypair = KeyPair::new();
123        let public_key = keypair.public();
124
125        let config = TokenTimeConfig {
126            start_time: None,
127            duration: 7200, // 2 hours
128        };
129
130        let token = HessraContext::new("agent:test".to_string(), config)
131            .issue(&keypair)
132            .expect("Failed to create context token with custom config");
133
134        ContextVerifier::new(token, public_key)
135            .verify()
136            .expect("Should verify context token with custom duration");
137    }
138}