lightcone_sdk/websocket/
auth.rs1use std::time::{Duration, SystemTime, UNIX_EPOCH};
15
16use ed25519_dalek::{Signer, SigningKey};
17use reqwest::Client;
18use serde::{Deserialize, Serialize};
19
20use crate::websocket::error::{WebSocketError, WsResult};
21
22pub const AUTH_API_URL: &str = "https://tapi.lightcone.xyz/api";
24
25const AUTH_TIMEOUT: Duration = Duration::from_secs(10);
27
28#[derive(Debug, Clone)]
30pub struct AuthCredentials {
31 pub auth_token: String,
33 pub user_pubkey: String,
35 pub user_id: String,
37 pub expires_at: i64,
39}
40
41#[derive(Debug, Serialize)]
43struct LoginRequest {
44 pubkey_bytes: Vec<u8>,
46 message: String,
48 signature_bs58: String,
50}
51
52#[derive(Debug, Deserialize)]
54struct LoginResponse {
55 token: String,
57 user_id: String,
59 expires_at: i64,
61}
62
63pub fn generate_signin_message() -> WsResult<String> {
78 let timestamp_ms = SystemTime::now()
79 .duration_since(UNIX_EPOCH)
80 .map_err(|_| WebSocketError::Protocol("System time before UNIX epoch".to_string()))?
81 .as_millis();
82
83 Ok(format!("Sign in to Lightcone\n\nTimestamp: {}", timestamp_ms))
84}
85
86pub fn generate_signin_message_with_timestamp(timestamp_ms: u128) -> String {
96 format!("Sign in to Lightcone\n\nTimestamp: {}", timestamp_ms)
97}
98
99pub async fn authenticate(signing_key: &SigningKey) -> WsResult<AuthCredentials> {
120 let message = generate_signin_message()?;
122
123 let signature = signing_key.sign(message.as_bytes());
125 let signature_b58 = bs58::encode(signature.to_bytes()).into_string();
126
127 let public_key = signing_key.verifying_key();
129 let public_key_b58 = bs58::encode(public_key.to_bytes()).into_string();
130
131 let request = LoginRequest {
133 pubkey_bytes: public_key.to_bytes().to_vec(),
134 message,
135 signature_bs58: signature_b58,
136 };
137
138 let client = Client::builder()
140 .timeout(AUTH_TIMEOUT)
141 .build()
142 .map_err(|e| WebSocketError::HttpError(e.to_string()))?;
143
144 let url = format!("{}/auth/login_or_register_with_message", AUTH_API_URL);
146 let response = client
147 .post(&url)
148 .json(&request)
149 .send()
150 .await
151 .map_err(|e| WebSocketError::HttpError(e.to_string()))?;
152
153 if !response.status().is_success() {
155 return Err(WebSocketError::AuthenticationFailed(format!(
156 "HTTP error: {}",
157 response.status()
158 )));
159 }
160
161 let login_response: LoginResponse = response.json().await.map_err(|e| {
163 WebSocketError::AuthenticationFailed(format!("Failed to parse response: {}", e))
164 })?;
165
166 Ok(AuthCredentials {
167 auth_token: login_response.token,
168 user_pubkey: public_key_b58,
169 user_id: login_response.user_id,
170 expires_at: login_response.expires_at,
171 })
172}
173
174#[cfg(test)]
175mod tests {
176 use super::*;
177
178 #[test]
179 fn test_generate_signin_message() {
180 let message = generate_signin_message().unwrap();
181 assert!(message.starts_with("Sign in to Lightcone\n\nTimestamp: "));
182 }
183
184 #[test]
185 fn test_generate_signin_message_with_timestamp() {
186 let timestamp = 1234567890123u128;
187 let message = generate_signin_message_with_timestamp(timestamp);
188 assert_eq!(message, "Sign in to Lightcone\n\nTimestamp: 1234567890123");
189 }
190}