Skip to main content

spiral_contract/
auth.rs

1//! Authorization values passed to service implementations for verification.
2
3use std::fmt::{self, Debug, Formatter};
4
5/// An owned Authorization header value without parsing or authentication.
6///
7/// Service implementations interpret the authorization scheme and verify the
8/// credential. Debug output redacts the value; use [`Self::as_str`] explicitly.
9///
10/// The value is for the service's API unless the entry point that received it
11/// names another protected resource (RFC 8707), such as an MCP endpoint. The
12/// entry point sets that resource, never the caller, and verification requires
13/// a token issued for it.
14#[derive(Clone, Eq, PartialEq)]
15pub struct AuthHeader {
16    value: String,
17    resource: Option<String>,
18}
19
20impl AuthHeader {
21    /// Preserve the complete Authorization header value without interpreting it.
22    pub fn new(value: impl Into<String>) -> Self {
23        Self {
24            value: value.into(),
25            resource: None,
26        }
27    }
28
29    /// The same value, presented to `resource` rather than to the service's API.
30    pub fn for_resource(self, resource: impl Into<String>) -> Self {
31        Self {
32            resource: Some(resource.into()),
33            ..self
34        }
35    }
36
37    /// Expose the complete value for verification by the service implementation.
38    pub fn as_str(&self) -> &str {
39        &self.value
40    }
41
42    /// The protected resource the value was presented to, when it is not the service's API.
43    pub fn resource(&self) -> Option<&str> {
44        self.resource.as_deref()
45    }
46}
47
48impl From<String> for AuthHeader {
49    fn from(value: String) -> Self {
50        Self::new(value)
51    }
52}
53
54impl From<&str> for AuthHeader {
55    fn from(value: &str) -> Self {
56        Self::new(value)
57    }
58}
59
60impl Debug for AuthHeader {
61    fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
62        formatter.write_str("AuthHeader([REDACTED])")
63    }
64}
65
66#[cfg(test)]
67mod tests {
68    use super::AuthHeader;
69
70    #[test]
71    fn preserves_authorization_values_and_redacts_debug_output() {
72        let value = "CustomScheme  secret.token ";
73        let header = AuthHeader::new(value);
74        assert_eq!(header.as_str(), value);
75        assert_eq!(header.clone(), header);
76        assert_eq!(format!("{header:?}"), "AuthHeader([REDACTED])");
77        assert_eq!(AuthHeader::new("").as_str(), "");
78    }
79}