AirLibrary/Authentication/
mod.rs1use std::{collections::HashMap, sync::Arc};
8
9use chrono::{DateTime, Utc};
10use serde::{Deserialize, Serialize};
11use tokio::sync::{Mutex, RwLock};
12use base64::{Engine as _, engine::general_purpose::URL_SAFE};
13use ring::{aead, rand::SecureRandom};
14
15use crate::{AirError, ApplicationState::ApplicationState, Configuration::ConfigurationManager, Result, Utility};
16
17pub struct AuthenticationService {
19 AppState:Arc<ApplicationState>,
21
22 Sessions:Arc<RwLock<HashMap<String, AuthSession>>>,
24
25 Credentials:Arc<Mutex<CredentialsStore>>,
27
28 CryptoKeys:Arc<Mutex<CryptoKeys>>,
30 AeadAlgo:&'static aead::Algorithm,
32}
33
34#[derive(Debug, Clone, Serialize, Deserialize)]
36pub struct AuthSession {
37 pub SessionId:String,
38 pub UserId:String,
39 pub Provider:String,
40 pub Token:String,
41 pub CreatedAt:DateTime<Utc>,
42 pub ExpiresAt:DateTime<Utc>,
43 pub IsValid:bool,
44}
45
46#[derive(Debug, Serialize, Deserialize)]
48struct CredentialsStore {
49 Credentials:HashMap<String, UserCredentials>,
50 FilePath:String,
51}
52
53#[derive(Debug, Clone, Serialize, Deserialize)]
55pub struct UserCredentials {
56 pub UserId:String,
57 pub Provider:String,
58 pub EncryptedPassword:String,
59 pub LastUsed:DateTime<Utc>,
60 pub IsValid:bool,
61}
62
63#[derive(Debug)]
65struct CryptoKeys {
66 SigningKey:ring::signature::Ed25519KeyPair,
67 EncryptionKey:[u8; 32],
68}
69
70impl AuthenticationService {
71 pub async fn new(AppState:Arc<ApplicationState>) -> Result<Self> {
73 let config = &AppState.Configuration.Authentication;
74
75 let CredentialsPath = ConfigurationManager::ExpandPath(&config.CredentialsPath)?;
77
78 let CredentialsStore = Self::LoadCredentialsStore(&CredentialsPath).await?;
80
81 let CryptoKeys = Self::GenerateCryptoKeys()?;
83 let AeadAlgo = &aead::AES_256_GCM;
84
85 let Service = Self {
86 AppState,
87 Sessions:Arc::new(RwLock::new(HashMap::new())),
88 Credentials:Arc::new(Mutex::new(CredentialsStore)),
89 CryptoKeys:Arc::new(Mutex::new(CryptoKeys)),
90 AeadAlgo,
91 };
92
93 Service
95 .AppState
96 .UpdateServiceStatus("authentication", crate::ApplicationState::ServiceStatus::Running)
97 .await
98 .map_err(|e| AirError::Authentication(e.to_string()))?;
99
100 Ok(Service)
101 }
102
103 pub async fn AuthenticateUser(&self, Username:String, Password:String, Provider:String) -> Result<String> {
105 if Username.is_empty() || Password.is_empty() || Provider.is_empty() {
107 return Err(AirError::Authentication("Invalid authentication parameters".to_string()));
108 }
109
110 let _UserCredentials = self.ValidateCredentials(&Username, &Password, &Provider).await?;
112
113 let Token = self.GenerateSessionToken(&Username, &Provider).await?;
115
116 let SessionId = Utility::GenerateRequestId();
118 let Session = AuthSession {
119 SessionId,
120 UserId:Username.clone(),
121 Provider:Provider.clone(),
122 Token:Token.clone(),
123 CreatedAt:chrono::Utc::now(),
124 ExpiresAt:chrono::Utc::now()
125 + chrono::Duration::hours(self.AppState.Configuration.Authentication.TokenExpirationHours as i64),
126 IsValid:true,
127 };
128
129 {
131 let mut Sessions = self.Sessions.write().await;
132 Sessions.insert(Session.SessionId.clone(), Session);
133 }
134
135 self.UpdateCredentialsUsage(&Username, &Provider).await?;
137
138 Ok(Token)
139 }
140
141 async fn ValidateCredentials(&self, Username:&str, Password:&str, Provider:&str) -> Result<UserCredentials> {
143 let CredentialsStore = self.Credentials.lock().await;
144
145 let Key = format!("{}:{}", Provider, Username);
146
147 if let Some(UserCredentials) = CredentialsStore.Credentials.get(&Key) {
148 if !UserCredentials.IsValid {
149 return Err(AirError::Authentication("Credentials are invalid".to_string()));
150 }
151
152 let DecryptedPassword = self.DecryptPassword(&UserCredentials.EncryptedPassword).await?;
155
156 if DecryptedPassword == Password {
157 Ok(UserCredentials.clone())
158 } else {
159 Err(AirError::Authentication("Invalid password".to_string()))
160 }
161 } else {
162 Err(AirError::Authentication("User not found".to_string()))
163 }
164 }
165
166 async fn GenerateSessionToken(&self, Username:&str, Provider:&str) -> Result<String> {
168 let CryptoKeys = self.CryptoKeys.lock().await;
169
170 let Payload = format!("{}:{}:{}", Username, Provider, Utility::CurrentTimestamp());
171
172 let Signature = CryptoKeys.SigningKey.sign(Payload.as_bytes());
174
175 let Token = URL_SAFE.encode(format!("{}:{}", Payload, URL_SAFE.encode(Signature.as_ref())));
177
178 Ok(Token)
179 }
180
181 async fn UpdateCredentialsUsage(&self, Username:&str, Provider:&str) -> Result<()> {
183 let mut CredentialsStore = self.Credentials.lock().await;
184
185 let Key = format!("{}:{}", Provider, Username);
186
187 if let Some(UserCredentials) = CredentialsStore.Credentials.get_mut(&Key) {
188 UserCredentials.LastUsed = Utc::now();
189 }
190
191 self.SaveCredentialsStore(&CredentialsStore).await?;
193
194 Ok(())
195 }
196
197 #[allow(dead_code)]
199 async fn EncryptPassword(&self, Password:&str) -> Result<String> {
200 let CryptoKeys = self.CryptoKeys.lock().await;
201
202 let UnboundKey = aead::UnboundKey::new(&aead::AES_256_GCM, &CryptoKeys.EncryptionKey)
204 .map_err(|e| AirError::Authentication(format!("Failed to create AEAD key: {:?}", e)))?;
205
206 let LessSafe = aead::LessSafeKey::new(UnboundKey);
207 let mut NonceBytes = [0u8; 12];
208 ring::rand::SystemRandom::new()
209 .fill(&mut NonceBytes)
210 .map_err(|e| AirError::Authentication(format!("Failed to generate nonce: {:?}", e)))?;
211
212 let Nonce = aead::Nonce::assume_unique_for_key(NonceBytes);
213
214 let mut InOut = Password.as_bytes().to_vec();
215 InOut.extend_from_slice(&[0u8; 16]); LessSafe
219 .seal_in_place_append_tag(Nonce, aead::Aad::empty(), &mut InOut)
220 .map_err(|e| AirError::Authentication(format!("Encryption failed: {:?}", e)))?;
221
222 let mut Out = Vec::with_capacity(NonceBytes.len() + InOut.len());
224 Out.extend_from_slice(&NonceBytes);
225 Out.extend_from_slice(&InOut);
226
227 Ok(URL_SAFE.encode(&Out))
228 }
229
230 async fn DecryptPassword(&self, EncryptedPassword:&str) -> Result<String> {
232 let CryptoKeys = self.CryptoKeys.lock().await;
233
234 let Data = URL_SAFE
235 .decode(EncryptedPassword)
236 .map_err(|e| AirError::Authentication(format!("Failed to decode password: {}", e)))?;
237
238 if Data.len() < 12 + aead::AES_256_GCM.tag_len() {
239 return Err(AirError::Authentication("Encrypted data too short".to_string()));
240 }
241
242 let (NonceBytes, CipherBytes) = Data.split_at(12);
243
244 let mut NonceArr = [0u8; 12];
245 NonceArr.copy_from_slice(&NonceBytes[0..12]);
246
247 let UnboundKey = aead::UnboundKey::new(&aead::AES_256_GCM, &CryptoKeys.EncryptionKey)
248 .map_err(|e| AirError::Authentication(format!("Failed to create AEAD key: {:?}", e)))?;
249
250 let LessSafe = aead::LessSafeKey::new(UnboundKey);
251 let Nonce = aead::Nonce::assume_unique_for_key(NonceArr);
252
253 let mut CipherVec = CipherBytes.to_vec();
254 let Plain = LessSafe
255 .open_in_place(Nonce, aead::Aad::empty(), &mut CipherVec)
256 .map_err(|e| AirError::Authentication(format!("Decryption failed: {:?}", e)))?;
257
258 String::from_utf8(Plain.to_vec())
259 .map_err(|e| AirError::Authentication(format!("Failed to decode password string: {}", e)))
260 }
261
262 async fn LoadCredentialsStore(FilePath:&std::path::Path) -> Result<CredentialsStore> {
264 if FilePath.exists() {
265 let Content = tokio::fs::read_to_string(FilePath)
266 .await
267 .map_err(|e| AirError::Authentication(format!("Failed to read credentials file: {}", e)))?;
268
269 let Credentials:HashMap<String, UserCredentials> = serde_json::from_str(&Content)
270 .map_err(|e| AirError::Authentication(format!("Failed to parse credentials file: {}", e)))?;
271
272 Ok(CredentialsStore { Credentials, FilePath:FilePath.to_string_lossy().to_string() })
273 } else {
274 Ok(CredentialsStore { Credentials:HashMap::new(), FilePath:FilePath.to_string_lossy().to_string() })
276 }
277 }
278
279 async fn SaveCredentialsStore(&self, Store:&CredentialsStore) -> Result<()> {
281 let Content = serde_json::to_string_pretty(&Store.Credentials)
282 .map_err(|e| AirError::Authentication(format!("Failed to serialize credentials: {}", e)))?;
283
284 if let Some(Parent) = std::path::Path::new(&Store.FilePath).parent() {
286 tokio::fs::create_dir_all(Parent)
287 .await
288 .map_err(|e| AirError::Authentication(format!("Failed to create credentials directory: {}", e)))?;
289
290 tokio::fs::write(&Store.FilePath, Content)
291 .await
292 .map_err(|e| AirError::Authentication(format!("Failed to write credentials file: {}", e)))?;
293
294 Ok(())
295 } else {
296 Err(AirError::Authentication("Invalid file path - no parent directory".to_string()))
297 }
298 }
299
300 fn GenerateCryptoKeys() -> Result<CryptoKeys> {
302 let Rng = ring::rand::SystemRandom::new();
304 let Pkcs8Bytes = ring::signature::Ed25519KeyPair::generate_pkcs8(&Rng)
305 .map_err(|e| AirError::Authentication(format!("Failed to generate signing key: {}", e)))?;
306
307 let SigningKey = ring::signature::Ed25519KeyPair::from_pkcs8(Pkcs8Bytes.as_ref())
308 .map_err(|e| AirError::Authentication(format!("Failed to load signing key: {}", e)))?;
309
310 let mut EncryptionKey = [0u8; 32];
312 ring::rand::SystemRandom::new()
313 .fill(&mut EncryptionKey)
314 .map_err(|e| AirError::Authentication(format!("Failed to generate encryption key: {}", e)))
315 .map_err(|e| AirError::Authentication(format!("Failed to generate encryption key: {}", e)))?;
316
317 Ok(CryptoKeys { SigningKey, EncryptionKey })
318 }
319
320 pub async fn StartBackgroundTasks(&self) -> Result<tokio::task::JoinHandle<()>> {
322 let Service = self.clone();
323
324 let Handle = tokio::spawn(async move {
325 Service.BackgroundTask().await;
326 });
327
328 Ok(Handle)
329 }
330
331 async fn BackgroundTask(&self) {
333 let mut Interval = tokio::time::interval(tokio::time::Duration::from_secs(300)); loop {
336 Interval.tick().await;
337
338 self.CleanupExpiredSessions().await;
340
341 if let Err(E) = self.SaveCredentialsPeriodically().await {
343 log::error!("[Authentication] Failed to save credentials: {}", E);
344 }
345 }
346 }
347
348 async fn CleanupExpiredSessions(&self) {
350 let Now = Utc::now();
351 let mut Sessions = self.Sessions.write().await;
352
353 Sessions.retain(|_, Session| Session.ExpiresAt > Now && Session.IsValid);
354
355 log::debug!("[Authentication] Cleaned up expired sessions");
356 }
357
358 async fn SaveCredentialsPeriodically(&self) -> Result<()> {
360 let CredentialsStore = self.Credentials.lock().await;
361 self.SaveCredentialsStore(&CredentialsStore).await
362 }
363
364 pub async fn StopBackgroundTasks(&self) {
366 log::info!("[Authentication] Stopping background tasks");
368 }
369}
370
371impl Clone for AuthenticationService {
372 fn clone(&self) -> Self {
373 Self {
374 AppState:self.AppState.clone(),
375 Sessions:self.Sessions.clone(),
376 Credentials:self.Credentials.clone(),
377 CryptoKeys:self.CryptoKeys.clone(),
378 AeadAlgo:self.AeadAlgo,
379 }
380 }
381}