pusher_rs/
auth.rs

1use crate::error::{PusherError, PusherResult};
2use base64::{engine::general_purpose, Engine as _};
3use hmac::{Hmac, Mac};
4use log::debug;
5use serde_json::{json, Value};
6use sha2::Sha256;
7use std::collections::BTreeMap;
8use std::time::{SystemTime, UNIX_EPOCH};
9
10type HmacSha256 = Hmac<Sha256>;
11
12pub struct PusherAuth {
13    key: String,
14    secret: String,
15}
16
17impl PusherAuth {
18    pub fn new(key: &str, secret: &str) -> Self {
19        Self {
20            key: key.to_string(),
21            secret: secret.to_string(),
22        }
23    }
24
25    pub fn authenticate_socket(&self, socket_id: &str, channel_name: &str) -> PusherResult<String> {
26        let auth_signature = self.sign_socket(socket_id, channel_name)?;
27        Ok(format!("{}:{}", self.key, auth_signature))
28    }
29
30    pub fn authenticate_presence_channel(
31        &self,
32        socket_id: &str,
33        channel_name: &str,
34        user_id: &str,
35        user_info: Option<&Value>,
36    ) -> PusherResult<String> {
37        let mut channel_data = json!({
38            "user_id": user_id,
39        });
40
41        if let Some(info) = user_info {
42            channel_data["user_info"] = info.clone();
43        }
44
45        let channel_data_str = serde_json::to_string(&channel_data)?;
46        let auth_signature =
47            self.sign_socket_with_channel_data(socket_id, channel_name, &channel_data_str)?;
48
49        Ok(format!(
50            "{}:{}:{}",
51            self.key, auth_signature, channel_data_str
52        ))
53    }
54
55    pub fn authenticate_private_encrypted_channel(
56        &self,
57        socket_id: &str,
58        channel_name: &str,
59    ) -> PusherResult<String> {
60        let shared_secret = self.generate_shared_secret(channel_name);
61        let auth_signature = self.sign_socket(socket_id, channel_name)?;
62        Ok(format!(
63            "{}:{}:{}",
64            self.key,
65            auth_signature,
66            general_purpose::STANDARD.encode(shared_secret)
67        ))
68    }
69
70    pub fn authenticate_request(
71        &self,
72        method: &str,
73        path: &str,
74        body: &serde_json::Value,
75    ) -> PusherResult<BTreeMap<String, String>> {
76        let mut params = BTreeMap::new();
77        let timestamp = SystemTime::now()
78            .duration_since(UNIX_EPOCH)
79            .map_err(|e| PusherError::AuthError(e.to_string()))?
80            .as_secs()
81            .to_string();
82
83        params.insert("auth_key".to_string(), self.key.clone());
84        params.insert("auth_timestamp".to_string(), timestamp);
85        params.insert("auth_version".to_string(), "1.0".to_string());
86
87        let body_md5 = format!("{:x}", md5::compute(serde_json::to_string(body)?));
88        params.insert("body_md5".to_string(), body_md5);
89        let to_sign = self.create_signing_string(method, path, &params)?;
90
91        let signature = self.sign(&to_sign)?;
92
93        params.insert("auth_signature".to_string(), signature);
94
95        Ok(params)
96    }
97
98    fn sign_socket(&self, socket_id: &str, channel_name: &str) -> PusherResult<String> {
99        let to_sign = format!("{}:{}", socket_id, channel_name);
100        self.sign(&to_sign)
101    }
102
103    fn sign_socket_with_channel_data(
104        &self,
105        socket_id: &str,
106        channel_name: &str,
107        channel_data: &str,
108    ) -> PusherResult<String> {
109        let to_sign = format!("{}:{}:{}", socket_id, channel_name, channel_data);
110        self.sign(&to_sign)
111    }
112
113    fn sign(&self, to_sign: &str) -> PusherResult<String> {
114        let mut mac = Hmac::<Sha256>::new_from_slice(self.secret.as_bytes())
115            .map_err(|e| PusherError::AuthError(e.to_string()))?;
116        mac.update(to_sign.as_bytes());
117        let result = mac.finalize();
118        Ok(hex::encode(result.into_bytes()))
119    }
120
121    fn create_signing_string(
122        &self,
123        method: &str,
124        path: &str,
125        params: &BTreeMap<String, String>,
126    ) -> PusherResult<String> {
127        let mut query_string: Vec<String> = params
128            .iter()
129            .map(|(k, v)| format!("{}={}", k, v))
130            .collect();
131        query_string.sort(); // Sort alphabetically
132        let query_string = query_string.join("&");
133        debug!("path: {}", path);
134        Ok(format!("{}\n{}\n{}", method, path, query_string))
135    }
136
137    fn generate_shared_secret(&self, channel_name: &str) -> Vec<u8> {
138        use sha2::Digest;
139        let mut hasher = Sha256::new();
140        hasher.update(self.secret.as_bytes());
141        hasher.update(channel_name.as_bytes());
142        hasher.finalize().to_vec()
143    }
144}
145
146#[cfg(test)]
147mod tests {
148    use super::*;
149
150    #[test]
151    fn test_authenticate_socket() {
152        let auth = PusherAuth::new("key", "secret");
153        let result = auth.authenticate_socket("socket_id", "channel_name");
154        assert!(result.is_ok());
155    }
156
157    #[test]
158    fn test_authenticate_presence_channel() {
159        let auth = PusherAuth::new("key", "secret");
160        let result = auth.authenticate_presence_channel(
161            "socket_id",
162            "presence-channel",
163            "user_id",
164            Some(&serde_json::json!({"name": "Test User"}))
165        );
166        assert!(result.is_ok());
167    }
168
169}