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, ¶ms)?;
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(); 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}