tact_memory/server/
credential.rs1use super::protocol::{self, RemoteRole};
7use sha2::{Digest, Sha256};
8use std::{fmt, mem};
9use thiserror::Error;
10use zeroize::{Zeroize, Zeroizing};
11
12const MAX_BEARER_TOKEN_BYTES: usize = 4096;
13
14struct BearerToken(Zeroizing<String>);
15
16impl BearerToken {
17 fn new(token: String) -> Result<Self, CredentialError> {
18 let token = Zeroizing::new(token);
19 if token.is_empty()
20 || token.len() > MAX_BEARER_TOKEN_BYTES
21 || !token.bytes().all(is_bearer_token_byte)
22 {
23 return Err(CredentialError::InvalidBearerToken);
24 }
25 Ok(Self(token))
26 }
27
28 fn expose(&self) -> &str {
29 self.0.as_str()
30 }
31}
32
33impl fmt::Debug for BearerToken {
34 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
35 formatter.write_str("BearerToken([REDACTED])")
36 }
37}
38
39impl Zeroize for BearerToken {
40 fn zeroize(&mut self) {
41 self.0.zeroize();
42 }
43}
44
45impl Drop for BearerToken {
46 fn drop(&mut self) {
47 self.zeroize();
48 }
49}
50
51pub struct Credential {
56 namespace: String,
57 role: RemoteRole,
58 token: BearerToken,
59}
60
61impl Credential {
62 pub fn new(
64 namespace: String,
65 role: RemoteRole,
66 bearer_token: String,
67 ) -> Result<Self, CredentialError> {
68 let token = BearerToken::new(bearer_token)?;
69 if !protocol::is_valid_namespace(&namespace) {
70 return Err(CredentialError::InvalidNamespace);
71 }
72 Ok(Self {
73 namespace,
74 role,
75 token,
76 })
77 }
78
79 pub(crate) fn into_hashed_principal(mut self) -> ([u8; 32], Principal) {
80 let token_hash = hash_token(self.token.expose());
81 let principal = Principal {
82 namespace: mem::take(&mut self.namespace),
83 role: self.role,
84 };
85 (token_hash, principal)
86 }
87}
88
89impl fmt::Debug for Credential {
90 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
91 formatter
92 .debug_struct("Credential")
93 .field("namespace", &self.namespace)
94 .field("role", &self.role)
95 .field("token", &"[REDACTED]")
96 .finish()
97 }
98}
99
100impl Zeroize for Credential {
101 fn zeroize(&mut self) {
102 self.token.zeroize();
103 }
104}
105
106impl Drop for Credential {
107 fn drop(&mut self) {
108 self.zeroize();
109 }
110}
111
112#[derive(Debug, Error)]
114pub enum CredentialError {
115 #[error("memory namespace is invalid")]
117 InvalidNamespace,
118 #[error("bearer token is invalid")]
120 InvalidBearerToken,
121}
122
123#[derive(Clone)]
124pub(crate) struct Principal {
125 pub(crate) namespace: String,
126 pub(crate) role: RemoteRole,
127}
128
129pub(crate) fn is_bearer_token_byte(byte: u8) -> bool {
130 byte.is_ascii_alphanumeric() || matches!(byte, b'-' | b'.' | b'_' | b'~' | b'+' | b'/' | b'=')
131}
132
133pub(crate) fn hash_token(token: &str) -> [u8; 32] {
134 Sha256::digest(token.as_bytes()).into()
135}