clawdentity_core/runtime/
auth.rs1use base64::Engine;
2use base64::engine::general_purpose::URL_SAFE_NO_PAD;
3use ed25519_dalek::SigningKey;
4use getrandom::fill as getrandom_fill;
5
6use crate::error::{CoreError, Result};
7use crate::signing::{SignHttpRequestInput, sign_http_request};
8
9#[derive(Debug, Clone)]
10pub struct RelayConnectHeaders {
11 pub authorization: String,
12 pub signed_headers: Vec<(String, String)>,
13}
14
15pub fn build_relay_connect_headers(
17 relay_connect_url: &str,
18 ait: &str,
19 secret_key: &SigningKey,
20) -> Result<RelayConnectHeaders> {
21 let trimmed_ait = ait.trim();
22 if trimmed_ait.is_empty() {
23 return Err(CoreError::InvalidInput("AIT token is required".to_string()));
24 }
25
26 let parsed = url::Url::parse(relay_connect_url).map_err(|_| CoreError::InvalidUrl {
27 context: "relayConnectUrl",
28 value: relay_connect_url.to_string(),
29 })?;
30 let path_with_query = match parsed.query() {
31 Some(query) => format!("{}?{query}", parsed.path()),
32 None => parsed.path().to_string(),
33 };
34
35 let mut nonce_bytes = [0_u8; 16];
36 getrandom_fill(&mut nonce_bytes).map_err(|error| CoreError::InvalidInput(error.to_string()))?;
37 let nonce = URL_SAFE_NO_PAD.encode(nonce_bytes);
38 let timestamp = format!("{}", chrono::Utc::now().timestamp());
39 let signed = sign_http_request(&SignHttpRequestInput {
40 method: "GET",
41 path_with_query: &path_with_query,
42 timestamp: ×tamp,
43 nonce: &nonce,
44 body: &[],
45 secret_key,
46 })?;
47
48 Ok(RelayConnectHeaders {
49 authorization: format!("Claw {trimmed_ait}"),
50 signed_headers: signed.headers,
51 })
52}
53
54#[cfg(test)]
55mod tests {
56 use ed25519_dalek::SigningKey;
57
58 use super::build_relay_connect_headers;
59
60 #[test]
61 fn build_relay_headers_includes_authorization_and_claw_proof_headers() {
62 let key = SigningKey::from_bytes(&[7_u8; 32]);
63 let headers = build_relay_connect_headers(
64 "wss://proxy.clawdentity.com/v1/relay/connect",
65 "ait.jwt.value",
66 &key,
67 )
68 .expect("headers");
69 assert_eq!(headers.authorization, "Claw ait.jwt.value");
70 assert_eq!(headers.signed_headers.len(), 4);
71 }
72}