Skip to main content

hessra_cap_token/
attenuate.rs

1extern crate biscuit_auth as biscuit;
2
3use biscuit::macros::block;
4use hessra_token_core::{Biscuit, PublicKey, TokenError, utils};
5
6/// Builder for adding designation blocks to capability tokens.
7///
8/// Designations are standard Biscuit attenuation blocks that narrow the scope
9/// of a capability token by specifying which specific object/resource instance
10/// the token applies to. Unlike prefix restrictions (which required third-party
11/// blocks), designations use regular append-only blocks and do not require a
12/// signing key.
13///
14/// # Example
15/// ```rust,no_run
16/// use hessra_cap_token::DesignationBuilder;
17/// use hessra_token_core::PublicKey;
18///
19/// # fn example(token: String, public_key: PublicKey) -> Result<(), Box<dyn std::error::Error>> {
20/// let attenuated = DesignationBuilder::from_base64(token, public_key)?
21///     .designate("tenant_id".to_string(), "t-123".to_string())
22///     .designate("user_id".to_string(), "u-456".to_string())
23///     .attenuate_base64()?;
24/// # Ok(())
25/// # }
26/// ```
27pub struct DesignationBuilder {
28    token: Vec<u8>,
29    public_key: PublicKey,
30    designations: Vec<(String, String)>,
31}
32
33impl DesignationBuilder {
34    /// Create a new DesignationBuilder from raw token bytes.
35    ///
36    /// # Arguments
37    /// * `token` - The binary token data
38    /// * `public_key` - The public key to verify the token
39    pub fn new(token: Vec<u8>, public_key: PublicKey) -> Self {
40        Self {
41            token,
42            public_key,
43            designations: Vec::new(),
44        }
45    }
46
47    /// Create a new DesignationBuilder from a base64-encoded token string.
48    ///
49    /// # Arguments
50    /// * `token` - The base64-encoded token string
51    /// * `public_key` - The public key to verify the token
52    pub fn from_base64(token: String, public_key: PublicKey) -> Result<Self, TokenError> {
53        let token_bytes = utils::decode_token(&token)?;
54        Ok(Self::new(token_bytes, public_key))
55    }
56
57    /// Add a designation (label, value) pair to narrow the token's scope.
58    ///
59    /// Each designation adds a `check if designation(label, value)` to the token,
60    /// requiring the verifier to provide matching `designation(label, value)` facts.
61    ///
62    /// # Arguments
63    /// * `label` - The designation dimension (e.g., "tenant_id", "user_id", "region")
64    /// * `value` - The specific value for this dimension (e.g., "t-123", "u-456", "us-east-1")
65    pub fn designate(mut self, label: String, value: String) -> Self {
66        self.designations.push((label, value));
67        self
68    }
69
70    /// Attenuate the token with all accumulated designations.
71    ///
72    /// Returns the attenuated token as binary bytes.
73    pub fn attenuate(self) -> Result<Vec<u8>, TokenError> {
74        let biscuit = Biscuit::from(&self.token, self.public_key)?;
75
76        let mut block_builder = block!(r#""#);
77
78        for (label, value) in &self.designations {
79            let label = label.clone();
80            let value = value.clone();
81            block_builder = block_builder
82                .check(biscuit::macros::check!(
83                    r#"check if designation({label}, {value});"#
84                ))
85                .map_err(|e| TokenError::AttenuationFailed {
86                    reason: format!("Failed to add designation check: {e}"),
87                })?;
88        }
89
90        let attenuated =
91            biscuit
92                .append(block_builder)
93                .map_err(|e| TokenError::AttenuationFailed {
94                    reason: format!("Failed to append designation block: {e}"),
95                })?;
96
97        attenuated
98            .to_vec()
99            .map_err(|e| TokenError::AttenuationFailed {
100                reason: format!("Failed to serialize attenuated token: {e}"),
101            })
102    }
103
104    /// Attenuate the token and return as a base64-encoded string.
105    pub fn attenuate_base64(self) -> Result<String, TokenError> {
106        let bytes = self.attenuate()?;
107        Ok(utils::encode_token(&bytes))
108    }
109}