1use argon2::{
9 password_hash::{rand_core::OsRng, PasswordHash, PasswordHasher, PasswordVerifier, SaltString},
10 Argon2,
11};
12use parking_lot::RwLock;
13use rand::Rng;
14use serde::{Deserialize, Serialize};
15use std::collections::{HashMap, HashSet};
16use std::path::PathBuf;
17use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
18
19#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
25#[serde(rename_all = "lowercase")]
26pub enum AuthProvider {
27 Local,
28 Ldap,
29 OAuth2,
30 Oidc,
31 Saml,
32}
33
34#[derive(Debug, Clone, Serialize, Deserialize)]
36pub struct LdapConfig {
37 pub server_url: String,
38 pub bind_dn: String,
39 pub bind_password: String,
40 pub base_dn: String,
41 pub user_filter: String,
42 pub group_filter: String,
43 pub use_tls: bool,
44 pub group_attribute: String,
45 pub admin_groups: Vec<String>,
46 pub operator_groups: Vec<String>,
47}
48
49impl Default for LdapConfig {
50 fn default() -> Self {
51 Self {
52 server_url: "ldap://localhost:389".to_string(),
53 bind_dn: "cn=admin,dc=aegisdb,dc=io".to_string(),
54 bind_password: String::new(),
55 base_dn: "dc=aegisdb,dc=io".to_string(),
56 user_filter: "(uid={username})".to_string(),
57 group_filter: "(member={dn})".to_string(),
58 use_tls: true,
59 group_attribute: "memberOf".to_string(),
60 admin_groups: vec!["cn=admins,ou=groups,dc=aegisdb,dc=io".to_string()],
61 operator_groups: vec!["cn=operators,ou=groups,dc=aegisdb,dc=io".to_string()],
62 }
63 }
64}
65
66#[derive(Debug, Clone, Serialize, Deserialize)]
68pub struct OAuth2Config {
69 pub provider_name: String,
70 pub client_id: String,
71 pub client_secret: String,
72 pub authorization_url: String,
73 pub token_url: String,
74 pub userinfo_url: String,
75 pub redirect_uri: String,
76 pub scopes: Vec<String>,
77 pub role_claim: String,
78 pub admin_roles: Vec<String>,
79 pub operator_roles: Vec<String>,
80}
81
82impl Default for OAuth2Config {
83 fn default() -> Self {
84 Self {
85 provider_name: "default".to_string(),
86 client_id: String::new(),
87 client_secret: String::new(),
88 authorization_url: "https://auth.example.com/authorize".to_string(),
89 token_url: "https://auth.example.com/token".to_string(),
90 userinfo_url: "https://auth.example.com/userinfo".to_string(),
91 redirect_uri: "http://localhost:8080/callback".to_string(),
92 scopes: vec![
93 "openid".to_string(),
94 "profile".to_string(),
95 "email".to_string(),
96 ],
97 role_claim: "roles".to_string(),
98 admin_roles: vec!["admin".to_string()],
99 operator_roles: vec!["operator".to_string()],
100 }
101 }
102}
103
104#[derive(Debug, Clone)]
106pub struct LdapAuthResult {
107 pub success: bool,
108 pub user_dn: Option<String>,
109 pub email: Option<String>,
110 pub display_name: Option<String>,
111 pub groups: Vec<String>,
112 pub error: Option<String>,
113}
114
115#[derive(Debug, Clone, Serialize, Deserialize)]
117pub struct OAuth2TokenResponse {
118 pub access_token: String,
119 pub token_type: String,
120 pub expires_in: Option<u64>,
121 pub refresh_token: Option<String>,
122 pub id_token: Option<String>,
123}
124
125#[derive(Debug, Clone, Serialize, Deserialize)]
127pub struct OAuth2UserInfo {
128 pub sub: String,
129 pub email: Option<String>,
130 pub name: Option<String>,
131 pub preferred_username: Option<String>,
132 pub roles: Option<Vec<String>>,
133}
134
135#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
141#[serde(rename_all = "lowercase")]
142pub enum UserRole {
143 Admin,
144 Operator,
145 Viewer,
146}
147
148impl std::fmt::Display for UserRole {
149 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
150 match self {
151 UserRole::Admin => write!(f, "admin"),
152 UserRole::Operator => write!(f, "operator"),
153 UserRole::Viewer => write!(f, "viewer"),
154 }
155 }
156}
157
158#[derive(Debug, Clone, Serialize, Deserialize)]
160pub struct User {
161 pub id: String,
162 pub username: String,
163 pub email: String,
164 pub password_hash: String,
165 pub role: UserRole,
166 pub mfa_enabled: bool,
167 pub mfa_secret: Option<String>,
168 pub created_at: u64,
169 pub last_login: Option<u64>,
170}
171
172#[derive(Debug, Clone, Serialize, Deserialize)]
174pub struct UserInfo {
175 pub id: String,
176 pub username: String,
177 pub email: String,
178 pub role: UserRole,
179 pub mfa_enabled: bool,
180 pub created_at: String,
181}
182
183impl From<&User> for UserInfo {
184 fn from(user: &User) -> Self {
185 Self {
186 id: user.id.clone(),
187 username: user.username.clone(),
188 email: user.email.clone(),
189 role: user.role,
190 mfa_enabled: user.mfa_enabled,
191 created_at: format_timestamp(user.created_at),
192 }
193 }
194}
195
196#[derive(Debug, Clone)]
202pub struct Session {
203 pub token: String,
204 pub user_id: String,
205 pub created_at: Instant,
206 pub expires_at: Instant,
207 pub mfa_verified: bool,
208}
209
210impl Session {
211 pub fn is_expired(&self) -> bool {
212 Instant::now() > self.expires_at
213 }
214}
215
216#[derive(Debug, Clone)]
218pub struct PendingMfaSession {
219 pub temp_token: String,
220 pub user_id: String,
221 pub created_at: Instant,
222 pub expires_at: Instant,
223}
224
225impl PendingMfaSession {
226 pub fn is_expired(&self) -> bool {
227 Instant::now() > self.expires_at
228 }
229}
230
231#[derive(Debug, Clone, Deserialize)]
237pub struct LoginRequest {
238 pub username: String,
239 pub password: String,
240}
241
242#[derive(Debug, Clone, Deserialize)]
244pub struct MfaVerifyRequest {
245 pub code: String,
246 pub token: String,
247}
248
249#[derive(Debug, Clone, Serialize, Deserialize)]
251pub struct MfaSetupData {
252 pub secret: String,
253 pub qr_code: String,
254 pub backup_codes: Vec<String>,
255}
256
257#[derive(Debug, Clone, Serialize)]
259pub struct AuthResponse {
260 pub token: Option<String>,
261 pub user: Option<UserInfo>,
262 pub requires_mfa: Option<bool>,
263 pub requires_mfa_setup: Option<bool>,
264 pub mfa_setup_data: Option<MfaSetupData>,
265 pub error: Option<String>,
266}
267
268impl AuthResponse {
269 pub fn success(token: String, user: UserInfo) -> Self {
270 Self {
271 token: Some(token),
272 user: Some(user),
273 requires_mfa: None,
274 requires_mfa_setup: None,
275 mfa_setup_data: None,
276 error: None,
277 }
278 }
279
280 pub fn requires_mfa(temp_token: String) -> Self {
281 Self {
282 token: Some(temp_token),
283 user: None,
284 requires_mfa: Some(true),
285 requires_mfa_setup: None,
286 mfa_setup_data: None,
287 error: None,
288 }
289 }
290
291 pub fn error(message: &str) -> Self {
292 Self {
293 token: None,
294 user: None,
295 requires_mfa: None,
296 requires_mfa_setup: None,
297 mfa_setup_data: None,
298 error: Some(message.to_string()),
299 }
300 }
301}
302
303pub struct AuthService {
309 users: RwLock<HashMap<String, User>>,
310 sessions: RwLock<HashMap<String, Session>>,
311 pending_mfa: RwLock<HashMap<String, PendingMfaSession>>,
312 session_duration: Duration,
313 mfa_timeout: Duration,
314 data_dir: Option<PathBuf>,
315}
316
317impl AuthService {
318 pub fn new() -> Self {
322 Self::with_data_dir(None)
323 }
324
325 pub fn with_data_dir(data_dir: Option<PathBuf>) -> Self {
328 Self::with_data_dir_and_secrets(data_dir, None)
329 }
330
331 pub fn with_data_dir_and_secrets(
335 data_dir: Option<PathBuf>,
336 secrets: Option<&dyn crate::secrets::SecretsProvider>,
337 ) -> Self {
338 let mut users = HashMap::new();
339
340 if let Some(ref dir) = data_dir {
342 let users_path = dir.join("users.json");
343 if users_path.exists() {
344 match std::fs::read_to_string(&users_path) {
345 Ok(data) => match serde_json::from_str::<HashMap<String, User>>(&data) {
346 Ok(loaded) => {
347 tracing::info!(
348 "Loaded {} users from {}",
349 loaded.len(),
350 users_path.display()
351 );
352 users = loaded;
353 }
354 Err(e) => {
355 tracing::error!(
356 "Failed to parse users file {}: {}",
357 users_path.display(),
358 e
359 );
360 }
361 },
362 Err(e) => {
363 tracing::error!(
364 "Failed to read users file {}: {}",
365 users_path.display(),
366 e
367 );
368 }
369 }
370 }
371 }
372
373 let admin_username = secrets
376 .and_then(|s| s.get(crate::secrets::keys::ADMIN_USERNAME))
377 .or_else(|| std::env::var("AEGIS_ADMIN_USERNAME").ok());
378 let admin_password = secrets
379 .and_then(|s| s.get(crate::secrets::keys::ADMIN_PASSWORD))
380 .or_else(|| std::env::var("AEGIS_ADMIN_PASSWORD").ok());
381 let admin_email = secrets
382 .and_then(|s| s.get(crate::secrets::keys::ADMIN_EMAIL))
383 .or_else(|| std::env::var("AEGIS_ADMIN_EMAIL").ok());
384
385 if let (Some(username), Some(password)) = (admin_username, admin_password) {
387 if !users.contains_key(&username) {
388 if password.len() >= 12 {
389 let email =
390 admin_email.unwrap_or_else(|| format!("{}@localhost", username));
391
392 let user_count = users.len() + 1;
393 let admin = User {
394 id: format!("user-{:03}", user_count),
395 username: username.clone(),
396 email,
397 password_hash: hash_password(&password),
398 role: UserRole::Admin,
399 mfa_enabled: false,
400 mfa_secret: None,
401 created_at: now_timestamp(),
402 last_login: None,
403 };
404 tracing::info!("Created initial admin user '{}' from secrets provider", username);
405 users.insert(admin.username.clone(), admin);
406 } else {
407 tracing::warn!(
408 "AEGIS_ADMIN_PASSWORD must be at least 12 characters. Initial admin user not created."
409 );
410 }
411 }
412 } else if users.is_empty() {
413 tracing::info!(
414 "No initial admin configured. Store credentials in the vault or set AEGIS_ADMIN_USERNAME and AEGIS_ADMIN_PASSWORD."
415 );
416 }
417
418 let service = Self {
419 users: RwLock::new(users),
420 sessions: RwLock::new(HashMap::new()),
421 pending_mfa: RwLock::new(HashMap::new()),
422 session_duration: Duration::from_secs(24 * 60 * 60), mfa_timeout: Duration::from_secs(5 * 60), data_dir,
425 };
426
427 service.flush_users_to_disk();
429
430 service
431 }
432
433 fn flush_users_to_disk(&self) {
435 let Some(ref dir) = self.data_dir else { return };
436 let users_path = dir.join("users.json");
437 let users = self.users.read();
438 match serde_json::to_string_pretty(&*users) {
439 Ok(json) => {
440 if let Err(e) = std::fs::write(&users_path, json) {
441 tracing::error!("Failed to write users to {}: {}", users_path.display(), e);
442 }
443 }
444 Err(e) => {
445 tracing::error!("Failed to serialize users: {}", e);
446 }
447 }
448 }
449
450 pub fn login(&self, username: &str, password: &str) -> AuthResponse {
452 let users = self.users.read();
453
454 let user = match users.get(username) {
455 Some(u) => u,
456 None => return AuthResponse::error("Invalid credentials"),
457 };
458
459 if !verify_password(password, &user.password_hash) {
460 return AuthResponse::error("Invalid credentials");
461 }
462
463 drop(users);
465 {
466 let mut users = self.users.write();
467 if let Some(u) = users.get_mut(username) {
468 u.last_login = Some(now_timestamp());
469 }
470 }
471 let users = self.users.read();
472 let user = users
473 .get(username)
474 .expect("user should exist after successful credential check");
475
476 if user.mfa_enabled {
477 let temp_token = generate_token();
479 let pending = PendingMfaSession {
480 temp_token: temp_token.clone(),
481 user_id: user.id.clone(),
482 created_at: Instant::now(),
483 expires_at: Instant::now() + self.mfa_timeout,
484 };
485 self.pending_mfa.write().insert(temp_token.clone(), pending);
486
487 AuthResponse::requires_mfa(temp_token)
488 } else {
489 let token = generate_token();
491 let session = Session {
492 token: token.clone(),
493 user_id: user.id.clone(),
494 created_at: Instant::now(),
495 expires_at: Instant::now() + self.session_duration,
496 mfa_verified: true,
497 };
498 self.sessions.write().insert(token.clone(), session);
499
500 AuthResponse::success(token, UserInfo::from(user))
501 }
502 }
503
504 pub fn verify_mfa(&self, code: &str, temp_token: &str) -> AuthResponse {
506 let pending = {
508 let pending_sessions = self.pending_mfa.read();
509 match pending_sessions.get(temp_token) {
510 Some(p) if !p.is_expired() => p.clone(),
511 Some(_) => return AuthResponse::error("MFA session expired"),
512 None => return AuthResponse::error("Invalid MFA session"),
513 }
514 };
515
516 let users = self.users.read();
518 let user = match users.values().find(|u| u.id == pending.user_id) {
519 Some(u) => u,
520 None => return AuthResponse::error("User not found"),
521 };
522
523 let secret = match &user.mfa_secret {
525 Some(s) => s,
526 None => return AuthResponse::error("MFA not configured"),
527 };
528
529 if !verify_totp(code, secret) {
530 return AuthResponse::error("Invalid MFA code");
531 }
532
533 self.pending_mfa.write().remove(temp_token);
535
536 let token = generate_token();
538 let session = Session {
539 token: token.clone(),
540 user_id: user.id.clone(),
541 created_at: Instant::now(),
542 expires_at: Instant::now() + self.session_duration,
543 mfa_verified: true,
544 };
545 self.sessions.write().insert(token.clone(), session);
546
547 AuthResponse::success(token, UserInfo::from(user))
548 }
549
550 pub fn validate_session(&self, token: &str) -> Option<UserInfo> {
552 let sessions = self.sessions.read();
553 let session = sessions.get(token)?;
554
555 if session.is_expired() {
556 return None;
557 }
558
559 let users = self.users.read();
560 let user = users.values().find(|u| u.id == session.user_id)?;
561
562 Some(UserInfo::from(user))
563 }
564
565 pub fn logout(&self, token: &str) -> bool {
567 self.sessions.write().remove(token).is_some()
568 }
569
570 pub fn get_user(&self, user_id: &str) -> Option<UserInfo> {
572 let users = self.users.read();
573 users.values().find(|u| u.id == user_id).map(UserInfo::from)
574 }
575
576 pub fn list_users(&self) -> Vec<UserInfo> {
578 let users = self.users.read();
579 users.values().map(UserInfo::from).collect()
580 }
581
582 pub fn create_user(
584 &self,
585 username: &str,
586 email: &str,
587 password: &str,
588 role: &str,
589 ) -> Result<UserInfo, String> {
590 let mut users = self.users.write();
591
592 if users.contains_key(username) {
593 return Err(format!("User '{}' already exists", username));
594 }
595
596 if password.len() < 8 {
598 return Err("Password must be at least 8 characters".to_string());
599 }
600
601 if !username.chars().all(|c| c.is_alphanumeric() || c == '_') {
603 return Err(
604 "Username must contain only alphanumeric characters and underscores".to_string(),
605 );
606 }
607
608 if !email.contains('@') || !email.contains('.') {
610 return Err("Invalid email format".to_string());
611 }
612
613 let user_role = match role.to_lowercase().as_str() {
614 "admin" => UserRole::Admin,
615 "operator" => UserRole::Operator,
616 "viewer" | _ => UserRole::Viewer,
617 };
618
619 let id = format!("user-{:03}", users.len() + 1);
620 let user = User {
621 id: id.clone(),
622 username: username.to_string(),
623 email: email.to_string(),
624 password_hash: hash_password(password),
625 role: user_role,
626 mfa_enabled: false,
627 mfa_secret: None,
628 created_at: now_timestamp(),
629 last_login: None,
630 };
631
632 let user_info = UserInfo::from(&user);
633 users.insert(username.to_string(), user);
634
635 tracing::info!("Created user '{}' with role '{}'", username, role);
637 drop(users);
638 self.flush_users_to_disk();
639
640 Ok(user_info)
641 }
642
643 pub fn update_user(
645 &self,
646 username: &str,
647 email: Option<String>,
648 role: Option<String>,
649 password: Option<String>,
650 ) -> Result<UserInfo, String> {
651 let mut users = self.users.write();
652
653 let user = users
654 .get_mut(username)
655 .ok_or_else(|| format!("User '{}' not found", username))?;
656
657 if let Some(new_email) = email {
658 user.email = new_email;
659 }
660
661 if let Some(new_role) = role {
662 user.role = match new_role.to_lowercase().as_str() {
663 "admin" => UserRole::Admin,
664 "operator" => UserRole::Operator,
665 "viewer" | _ => UserRole::Viewer,
666 };
667 }
668
669 let password_changed = if let Some(new_password) = password {
670 user.password_hash = hash_password(&new_password);
671 true
672 } else {
673 false
674 };
675
676 let info = UserInfo::from(user as &User);
677 drop(users);
678
679 if password_changed {
681 let user_id = info.id.clone();
682 let mut sessions = self.sessions.write();
683 let before = sessions.len();
684 sessions.retain(|_, s| s.user_id != user_id);
685 let revoked = before - sessions.len();
686 if revoked > 0 {
687 tracing::info!(
688 "Revoked {} session(s) for user '{}' due to password change",
689 revoked,
690 username
691 );
692 }
693 }
694
695 self.flush_users_to_disk();
696
697 Ok(info)
698 }
699
700 pub fn delete_user(&self, username: &str) -> Result<(), String> {
702 let mut users = self.users.write();
703
704 if !users.contains_key(username) {
705 return Err(format!("User '{}' not found", username));
706 }
707
708 if username == "admin" {
710 return Err("Cannot delete the admin user".to_string());
711 }
712
713 users.remove(username);
714 drop(users);
715 self.flush_users_to_disk();
716 Ok(())
717 }
718
719 pub fn enable_mfa(&self, username: &str) -> Result<String, String> {
722 let mut users = self.users.write();
723
724 let user = users
725 .get_mut(username)
726 .ok_or_else(|| format!("User '{}' not found", username))?;
727
728 if user.mfa_enabled {
729 return Err("MFA is already enabled for this user".to_string());
730 }
731
732 let secret = generate_mfa_secret();
734 user.mfa_secret = Some(secret.clone());
735 user.mfa_enabled = true;
736
737 tracing::info!("Enabled MFA for user '{}'", username);
738 drop(users);
739 self.flush_users_to_disk();
740
741 Ok(secret)
742 }
743
744 pub fn disable_mfa(&self, username: &str) -> Result<(), String> {
746 let mut users = self.users.write();
747
748 let user = users
749 .get_mut(username)
750 .ok_or_else(|| format!("User '{}' not found", username))?;
751
752 if !user.mfa_enabled {
753 return Err("MFA is not enabled for this user".to_string());
754 }
755
756 user.mfa_secret = None;
757 user.mfa_enabled = false;
758
759 tracing::info!("Disabled MFA for user '{}'", username);
760 drop(users);
761 self.flush_users_to_disk();
762
763 Ok(())
764 }
765
766 pub fn cleanup_expired(&self) {
768 let mut sessions = self.sessions.write();
769 sessions.retain(|_, s| !s.is_expired());
770
771 let mut pending = self.pending_mfa.write();
772 pending.retain(|_, p| !p.is_expired());
773 }
774}
775
776impl Default for AuthService {
777 fn default() -> Self {
778 Self::new()
779 }
780}
781
782fn generate_token() -> String {
788 let mut rng = rand::thread_rng();
789 let bytes: [u8; 32] = rng.gen();
790 hex::encode(&bytes)
791}
792
793mod hex {
795 const HEX_CHARS: &[u8; 16] = b"0123456789abcdef";
796
797 pub fn encode(bytes: &[u8]) -> String {
798 let mut result = String::with_capacity(bytes.len() * 2);
799 for byte in bytes {
800 result.push(HEX_CHARS[(byte >> 4) as usize] as char);
801 result.push(HEX_CHARS[(byte & 0x0f) as usize] as char);
802 }
803 result
804 }
805}
806
807fn hash_password(password: &str) -> String {
809 let salt = SaltString::generate(&mut OsRng);
810 let argon2 = Argon2::default();
811
812 argon2
813 .hash_password(password.as_bytes(), &salt)
814 .expect("Failed to hash password")
815 .to_string()
816}
817
818fn verify_password(password: &str, hash: &str) -> bool {
820 let parsed_hash = match PasswordHash::new(hash) {
821 Ok(h) => h,
822 Err(_) => return false,
823 };
824
825 Argon2::default()
826 .verify_password(password.as_bytes(), &parsed_hash)
827 .is_ok()
828}
829
830fn generate_mfa_secret() -> String {
832 let mut rng = rand::thread_rng();
833 let bytes: [u8; 20] = rng.gen(); const BASE32_ALPHABET: &[u8; 32] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
837 let mut result = String::with_capacity(32);
838
839 for chunk in bytes.chunks(5) {
840 let mut buffer = [0u8; 5];
841 buffer[..chunk.len()].copy_from_slice(chunk);
842
843 result.push(BASE32_ALPHABET[(buffer[0] >> 3) as usize] as char);
844 result.push(BASE32_ALPHABET[((buffer[0] & 0x07) << 2 | buffer[1] >> 6) as usize] as char);
845 result.push(BASE32_ALPHABET[((buffer[1] & 0x3E) >> 1) as usize] as char);
846 result.push(BASE32_ALPHABET[((buffer[1] & 0x01) << 4 | buffer[2] >> 4) as usize] as char);
847 result.push(BASE32_ALPHABET[((buffer[2] & 0x0F) << 1 | buffer[3] >> 7) as usize] as char);
848 result.push(BASE32_ALPHABET[((buffer[3] & 0x7C) >> 2) as usize] as char);
849 result.push(BASE32_ALPHABET[((buffer[3] & 0x03) << 3 | buffer[4] >> 5) as usize] as char);
850 result.push(BASE32_ALPHABET[(buffer[4] & 0x1F) as usize] as char);
851 }
852
853 result
854}
855
856fn verify_totp(code: &str, secret: &str) -> bool {
858 use data_encoding::BASE32_NOPAD;
859 use hmac::{Hmac, Mac};
860 use sha1::Sha1;
861
862 let secret_bytes = match BASE32_NOPAD.decode(secret.to_uppercase().as_bytes()) {
864 Ok(bytes) => bytes,
865 Err(_) => {
866 let padded = format!(
868 "{}{}",
869 secret.to_uppercase(),
870 &"========"[..((8 - secret.len() % 8) % 8)]
871 );
872 match data_encoding::BASE32.decode(padded.as_bytes()) {
873 Ok(bytes) => bytes,
874 Err(_) => return false,
875 }
876 }
877 };
878
879 let timestamp = now_timestamp() / 1000; let time_step = timestamp / 30;
882
883 for offset in [-1i64, 0, 1] {
885 let counter = (time_step as i64 + offset) as u64;
886 let counter_bytes = counter.to_be_bytes();
887
888 let mut mac = match Hmac::<Sha1>::new_from_slice(&secret_bytes) {
890 Ok(m) => m,
891 Err(_) => return false,
892 };
893 mac.update(&counter_bytes);
894 let result = mac.finalize().into_bytes();
895
896 let offset_idx = (result[19] & 0x0f) as usize;
898 let binary_code = ((result[offset_idx] & 0x7f) as u32) << 24
899 | (result[offset_idx + 1] as u32) << 16
900 | (result[offset_idx + 2] as u32) << 8
901 | (result[offset_idx + 3] as u32);
902
903 let otp = binary_code % 1_000_000;
905 let expected = format!("{:06}", otp);
906
907 if code == expected {
908 return true;
909 }
910 }
911
912 false
913}
914
915fn now_timestamp() -> u64 {
917 SystemTime::now()
918 .duration_since(UNIX_EPOCH)
919 .unwrap_or_default()
920 .as_millis() as u64
921}
922
923fn format_timestamp(timestamp_ms: u64) -> String {
925 let secs = timestamp_ms / 1000;
926 let datetime = UNIX_EPOCH + Duration::from_secs(secs);
927
928 let duration = datetime.duration_since(UNIX_EPOCH).unwrap_or_default();
930 let total_secs = duration.as_secs();
931
932 let days_since_epoch = total_secs / 86400;
933 let secs_today = total_secs % 86400;
934
935 let hours = secs_today / 3600;
936 let minutes = (secs_today % 3600) / 60;
937 let seconds = secs_today % 60;
938
939 let mut year = 1970;
941 let mut remaining_days = days_since_epoch;
942
943 loop {
944 let days_in_year = if is_leap_year(year) { 366 } else { 365 };
945 if remaining_days < days_in_year {
946 break;
947 }
948 remaining_days -= days_in_year;
949 year += 1;
950 }
951
952 let days_in_months: [u64; 12] = if is_leap_year(year) {
953 [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
954 } else {
955 [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
956 };
957
958 let mut month = 1;
959 for &days in &days_in_months {
960 if remaining_days < days {
961 break;
962 }
963 remaining_days -= days;
964 month += 1;
965 }
966 let day = remaining_days + 1;
967
968 format!(
969 "{:04}-{:02}-{:02}T{:02}:{:02}:{:02}Z",
970 year, month, day, hours, minutes, seconds
971 )
972}
973
974fn is_leap_year(year: u64) -> bool {
975 (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)
976}
977
978#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
984#[serde(rename_all = "snake_case")]
985pub enum Permission {
986 DatabaseCreate,
988 DatabaseDrop,
989 DatabaseList,
990
991 TableCreate,
993 TableDrop,
994 TableAlter,
995 TableList,
996
997 DataSelect,
999 DataInsert,
1000 DataUpdate,
1001 DataDelete,
1002
1003 UserCreate,
1005 UserDelete,
1006 UserModify,
1007 RoleCreate,
1008 RoleDelete,
1009 RoleAssign,
1010
1011 ConfigView,
1013 ConfigModify,
1014 MetricsView,
1015 LogsView,
1016 BackupCreate,
1017 BackupRestore,
1018
1019 NodeAdd,
1021 NodeRemove,
1022 ClusterManage,
1023}
1024
1025#[derive(Debug, Clone, Serialize, Deserialize)]
1027pub struct Role {
1028 pub name: String,
1029 pub description: String,
1030 pub permissions: HashSet<Permission>,
1031 pub created_at: u64,
1032 pub created_by: String,
1033}
1034
1035impl Role {
1036 pub fn new(name: &str, description: &str, permissions: Vec<Permission>) -> Self {
1038 Self {
1039 name: name.to_string(),
1040 description: description.to_string(),
1041 permissions: permissions.into_iter().collect(),
1042 created_at: now_timestamp(),
1043 created_by: "system".to_string(),
1044 }
1045 }
1046
1047 pub fn has_permission(&self, permission: Permission) -> bool {
1049 self.permissions.contains(&permission)
1050 }
1051}
1052
1053pub struct RbacManager {
1055 roles: RwLock<HashMap<String, Role>>,
1056 user_roles: RwLock<HashMap<String, HashSet<String>>>,
1057 row_policies: RwLock<Vec<RowLevelPolicy>>,
1058 data_dir: Option<PathBuf>,
1059}
1060
1061#[derive(serde::Serialize, serde::Deserialize)]
1062struct RbacSnapshot {
1063 roles: HashMap<String, Role>,
1064 user_roles: HashMap<String, HashSet<String>>,
1065 row_policies: Vec<RowLevelPolicy>,
1066}
1067
1068impl RbacManager {
1069 pub fn new() -> Self {
1071 Self::with_data_dir(None)
1072 }
1073
1074 pub fn with_data_dir(data_dir: Option<PathBuf>) -> Self {
1076 if let Some(ref dir) = data_dir {
1078 let path = dir.join("rbac.json");
1079 if path.exists() {
1080 match std::fs::read_to_string(&path) {
1081 Ok(contents) => match serde_json::from_str::<RbacSnapshot>(&contents) {
1082 Ok(snapshot) => {
1083 tracing::info!("Loaded RBAC state from {}", path.display());
1084 return Self {
1085 roles: RwLock::new(snapshot.roles),
1086 user_roles: RwLock::new(snapshot.user_roles),
1087 row_policies: RwLock::new(snapshot.row_policies),
1088 data_dir,
1089 };
1090 }
1091 Err(e) => {
1092 tracing::error!(
1093 "Failed to parse RBAC snapshot from {}: {}",
1094 path.display(),
1095 e
1096 );
1097 }
1098 },
1099 Err(e) => {
1100 tracing::error!(
1101 "Failed to read RBAC snapshot from {}: {}",
1102 path.display(),
1103 e
1104 );
1105 }
1106 }
1107 }
1108 }
1109
1110 let mut roles = HashMap::new();
1112
1113 let admin_permissions = vec![
1115 Permission::DatabaseCreate,
1116 Permission::DatabaseDrop,
1117 Permission::DatabaseList,
1118 Permission::TableCreate,
1119 Permission::TableDrop,
1120 Permission::TableAlter,
1121 Permission::TableList,
1122 Permission::DataSelect,
1123 Permission::DataInsert,
1124 Permission::DataUpdate,
1125 Permission::DataDelete,
1126 Permission::UserCreate,
1127 Permission::UserDelete,
1128 Permission::UserModify,
1129 Permission::RoleCreate,
1130 Permission::RoleDelete,
1131 Permission::RoleAssign,
1132 Permission::ConfigView,
1133 Permission::ConfigModify,
1134 Permission::MetricsView,
1135 Permission::LogsView,
1136 Permission::BackupCreate,
1137 Permission::BackupRestore,
1138 Permission::NodeAdd,
1139 Permission::NodeRemove,
1140 Permission::ClusterManage,
1141 ];
1142 roles.insert(
1143 "admin".to_string(),
1144 Role::new("admin", "Full system administrator", admin_permissions),
1145 );
1146
1147 let operator_permissions = vec![
1149 Permission::DatabaseList,
1150 Permission::TableCreate,
1151 Permission::TableAlter,
1152 Permission::TableList,
1153 Permission::DataSelect,
1154 Permission::DataInsert,
1155 Permission::DataUpdate,
1156 Permission::DataDelete,
1157 Permission::ConfigView,
1158 Permission::MetricsView,
1159 Permission::LogsView,
1160 Permission::BackupCreate,
1161 ];
1162 roles.insert(
1163 "operator".to_string(),
1164 Role::new("operator", "Database operator", operator_permissions),
1165 );
1166
1167 let viewer_permissions = vec![
1169 Permission::DatabaseList,
1170 Permission::TableList,
1171 Permission::DataSelect,
1172 Permission::MetricsView,
1173 ];
1174 roles.insert(
1175 "viewer".to_string(),
1176 Role::new("viewer", "Read-only viewer", viewer_permissions),
1177 );
1178
1179 let analyst_permissions = vec![
1181 Permission::DatabaseList,
1182 Permission::TableList,
1183 Permission::DataSelect,
1184 Permission::MetricsView,
1185 Permission::LogsView,
1186 ];
1187 roles.insert(
1188 "analyst".to_string(),
1189 Role::new(
1190 "analyst",
1191 "Data analyst with read access",
1192 analyst_permissions,
1193 ),
1194 );
1195
1196 let user_roles: HashMap<String, HashSet<String>> = HashMap::new();
1198
1199 Self {
1200 roles: RwLock::new(roles),
1201 user_roles: RwLock::new(user_roles),
1202 row_policies: RwLock::new(Vec::new()),
1203 data_dir,
1204 }
1205 }
1206
1207 fn flush_to_disk(&self) {
1209 let Some(ref dir) = self.data_dir else {
1210 return;
1211 };
1212 let snapshot = RbacSnapshot {
1213 roles: self.roles.read().clone(),
1214 user_roles: self.user_roles.read().clone(),
1215 row_policies: self.row_policies.read().clone(),
1216 };
1217 let path = dir.join("rbac.json");
1218 match serde_json::to_string_pretty(&snapshot) {
1219 Ok(json) => {
1220 if let Err(e) = std::fs::write(&path, json) {
1221 tracing::error!("Failed to write RBAC snapshot to {}: {}", path.display(), e);
1222 }
1223 }
1224 Err(e) => {
1225 tracing::error!("Failed to serialize RBAC snapshot: {}", e);
1226 }
1227 }
1228 }
1229
1230 pub fn create_role(
1232 &self,
1233 name: &str,
1234 description: &str,
1235 permissions: Vec<Permission>,
1236 created_by: &str,
1237 ) -> Result<(), String> {
1238 let mut roles = self.roles.write();
1239 if roles.contains_key(name) {
1240 return Err(format!("Role '{}' already exists", name));
1241 }
1242
1243 let mut role = Role::new(name, description, permissions);
1244 role.created_by = created_by.to_string();
1245 roles.insert(name.to_string(), role);
1246 drop(roles);
1247 self.flush_to_disk();
1248 Ok(())
1249 }
1250
1251 pub fn delete_role(&self, name: &str) -> Result<(), String> {
1253 let mut roles = self.roles.write();
1254 if !roles.contains_key(name) {
1255 return Err(format!("Role '{}' not found", name));
1256 }
1257 if name == "admin" || name == "operator" || name == "viewer" {
1258 return Err("Cannot delete built-in roles".to_string());
1259 }
1260 roles.remove(name);
1261 drop(roles);
1262 self.flush_to_disk();
1263 Ok(())
1264 }
1265
1266 pub fn list_roles(&self) -> Vec<Role> {
1268 self.roles.read().values().cloned().collect()
1269 }
1270
1271 pub fn get_role(&self, name: &str) -> Option<Role> {
1273 self.roles.read().get(name).cloned()
1274 }
1275
1276 pub fn assign_role(&self, user_id: &str, role_name: &str) -> Result<(), String> {
1278 if !self.roles.read().contains_key(role_name) {
1279 return Err(format!("Role '{}' not found", role_name));
1280 }
1281
1282 let mut user_roles = self.user_roles.write();
1283 user_roles
1284 .entry(user_id.to_string())
1285 .or_default()
1286 .insert(role_name.to_string());
1287 drop(user_roles);
1288 self.flush_to_disk();
1289 Ok(())
1290 }
1291
1292 pub fn revoke_role(&self, user_id: &str, role_name: &str) -> Result<(), String> {
1294 let mut user_roles = self.user_roles.write();
1295 if let Some(roles) = user_roles.get_mut(user_id) {
1296 roles.remove(role_name);
1297 drop(user_roles);
1298 self.flush_to_disk();
1299 Ok(())
1300 } else {
1301 Err(format!("User '{}' has no roles assigned", user_id))
1302 }
1303 }
1304
1305 pub fn get_user_roles(&self, user_id: &str) -> Vec<String> {
1307 self.user_roles
1308 .read()
1309 .get(user_id)
1310 .map(|r| r.iter().cloned().collect())
1311 .unwrap_or_default()
1312 }
1313
1314 pub fn check_permission(&self, user_id: &str, permission: Permission) -> bool {
1316 let user_roles = self.user_roles.read();
1317 let roles = self.roles.read();
1318
1319 if let Some(user_role_names) = user_roles.get(user_id) {
1320 for role_name in user_role_names {
1321 if let Some(role) = roles.get(role_name) {
1322 if role.has_permission(permission) {
1323 return true;
1324 }
1325 }
1326 }
1327 }
1328 false
1329 }
1330
1331 pub fn get_user_permissions(&self, user_id: &str) -> HashSet<Permission> {
1333 let mut permissions = HashSet::new();
1334 let user_roles = self.user_roles.read();
1335 let roles = self.roles.read();
1336
1337 if let Some(user_role_names) = user_roles.get(user_id) {
1338 for role_name in user_role_names {
1339 if let Some(role) = roles.get(role_name) {
1340 permissions.extend(role.permissions.iter().cloned());
1341 }
1342 }
1343 }
1344 permissions
1345 }
1346
1347 pub fn add_row_policy(&self, policy: RowLevelPolicy) {
1349 self.row_policies.write().push(policy);
1350 self.flush_to_disk();
1351 }
1352
1353 pub fn get_row_policies(&self, table: &str, user_id: &str) -> Vec<RowLevelPolicy> {
1355 self.row_policies
1356 .read()
1357 .iter()
1358 .filter(|p| {
1359 p.table == table
1360 && (p.applies_to.is_empty() || p.applies_to.contains(&user_id.to_string()))
1361 })
1362 .cloned()
1363 .collect()
1364 }
1365}
1366
1367impl Default for RbacManager {
1368 fn default() -> Self {
1369 Self::new()
1370 }
1371}
1372
1373#[derive(Debug, Clone, Serialize, Deserialize)]
1379pub struct RowLevelPolicy {
1380 pub name: String,
1381 pub table: String,
1382 pub operation: RowPolicyOperation,
1383 pub condition: String,
1384 pub applies_to: Vec<String>,
1385 pub enabled: bool,
1386}
1387
1388#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1390#[serde(rename_all = "lowercase")]
1391pub enum RowPolicyOperation {
1392 Select,
1393 Insert,
1394 Update,
1395 Delete,
1396 All,
1397}
1398
1399#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1405#[serde(rename_all = "snake_case")]
1406pub enum AuditEventType {
1407 LoginSuccess,
1409 LoginFailure,
1410 Logout,
1411 MfaVerified,
1412 MfaFailed,
1413 SessionExpired,
1414
1415 PermissionGranted,
1417 PermissionDenied,
1418 RoleAssigned,
1419 RoleRevoked,
1420
1421 DataRead,
1423 DataWrite,
1424 DataDelete,
1425 SchemaChange,
1426
1427 UserCreated,
1429 UserDeleted,
1430 UserModified,
1431 ConfigChanged,
1432 BackupCreated,
1433 BackupRestored,
1434
1435 ServiceStarted,
1437 ServiceStopped,
1438 NodeJoined,
1439 NodeLeft,
1440}
1441
1442#[derive(Debug, Clone, Serialize, Deserialize)]
1444pub struct AuditEntry {
1445 pub id: String,
1446 pub timestamp: u64,
1447 pub event_type: AuditEventType,
1448 pub user_id: Option<String>,
1449 pub username: Option<String>,
1450 pub ip_address: Option<String>,
1451 pub resource: Option<String>,
1452 pub action: String,
1453 pub result: AuditResult,
1454 pub details: HashMap<String, String>,
1455}
1456
1457#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1459#[serde(rename_all = "lowercase")]
1460pub enum AuditResult {
1461 Success,
1462 Failure,
1463 Denied,
1464}
1465
1466pub struct AuditLogger {
1468 entries: RwLock<Vec<AuditEntry>>,
1469 max_entries: usize,
1470 entry_counter: RwLock<u64>,
1471}
1472
1473impl AuditLogger {
1474 pub fn new(max_entries: usize) -> Self {
1476 Self {
1477 entries: RwLock::new(Vec::with_capacity(max_entries)),
1478 max_entries,
1479 entry_counter: RwLock::new(0),
1480 }
1481 }
1482
1483 pub fn log(
1485 &self,
1486 event_type: AuditEventType,
1487 user_id: Option<&str>,
1488 username: Option<&str>,
1489 ip_address: Option<&str>,
1490 resource: Option<&str>,
1491 action: &str,
1492 result: AuditResult,
1493 details: HashMap<String, String>,
1494 ) {
1495 let mut counter = self.entry_counter.write();
1496 *counter += 1;
1497 let id = format!("audit-{:012}", *counter);
1498
1499 let entry = AuditEntry {
1500 id,
1501 timestamp: now_timestamp(),
1502 event_type,
1503 user_id: user_id.map(String::from),
1504 username: username.map(String::from),
1505 ip_address: ip_address.map(String::from),
1506 resource: resource.map(String::from),
1507 action: action.to_string(),
1508 result,
1509 details,
1510 };
1511
1512 let mut entries = self.entries.write();
1513 if entries.len() >= self.max_entries {
1514 entries.remove(0);
1515 }
1516 entries.push(entry);
1517 }
1518
1519 pub fn log_login_success(&self, user_id: &str, username: &str, ip: Option<&str>) {
1521 self.log(
1522 AuditEventType::LoginSuccess,
1523 Some(user_id),
1524 Some(username),
1525 ip,
1526 None,
1527 "User logged in",
1528 AuditResult::Success,
1529 HashMap::new(),
1530 );
1531 }
1532
1533 pub fn log_login_failure(&self, username: &str, ip: Option<&str>, reason: &str) {
1535 let mut details = HashMap::new();
1536 details.insert("reason".to_string(), reason.to_string());
1537 self.log(
1538 AuditEventType::LoginFailure,
1539 None,
1540 Some(username),
1541 ip,
1542 None,
1543 "Login attempt failed",
1544 AuditResult::Failure,
1545 details,
1546 );
1547 }
1548
1549 pub fn log_permission_denied(
1551 &self,
1552 user_id: &str,
1553 username: &str,
1554 resource: &str,
1555 permission: &str,
1556 ) {
1557 let mut details = HashMap::new();
1558 details.insert("permission".to_string(), permission.to_string());
1559 self.log(
1560 AuditEventType::PermissionDenied,
1561 Some(user_id),
1562 Some(username),
1563 None,
1564 Some(resource),
1565 "Permission denied",
1566 AuditResult::Denied,
1567 details,
1568 );
1569 }
1570
1571 pub fn log_data_access(
1573 &self,
1574 user_id: &str,
1575 username: &str,
1576 table: &str,
1577 operation: &str,
1578 rows_affected: u64,
1579 ) {
1580 let mut details = HashMap::new();
1581 details.insert("rows_affected".to_string(), rows_affected.to_string());
1582 let event_type = match operation {
1583 "SELECT" => AuditEventType::DataRead,
1584 "INSERT" | "UPDATE" => AuditEventType::DataWrite,
1585 "DELETE" => AuditEventType::DataDelete,
1586 _ => AuditEventType::DataRead,
1587 };
1588 self.log(
1589 event_type,
1590 Some(user_id),
1591 Some(username),
1592 None,
1593 Some(table),
1594 &format!("{} on {}", operation, table),
1595 AuditResult::Success,
1596 details,
1597 );
1598 }
1599
1600 pub fn log_schema_change(&self, user_id: &str, username: &str, object: &str, action: &str) {
1602 let mut details = HashMap::new();
1603 details.insert("action".to_string(), action.to_string());
1604 self.log(
1605 AuditEventType::SchemaChange,
1606 Some(user_id),
1607 Some(username),
1608 None,
1609 Some(object),
1610 &format!("Schema change: {} on {}", action, object),
1611 AuditResult::Success,
1612 details,
1613 );
1614 }
1615
1616 pub fn get_entries(&self, limit: usize, offset: usize) -> Vec<AuditEntry> {
1618 let entries = self.entries.read();
1619 let start = entries.len().saturating_sub(limit + offset);
1620 let end = entries.len().saturating_sub(offset);
1621 entries[start..end].iter().rev().cloned().collect()
1622 }
1623
1624 pub fn get_entries_by_type(&self, event_type: AuditEventType, limit: usize) -> Vec<AuditEntry> {
1626 let entries = self.entries.read();
1627 entries
1628 .iter()
1629 .rev()
1630 .filter(|e| e.event_type == event_type)
1631 .take(limit)
1632 .cloned()
1633 .collect()
1634 }
1635
1636 pub fn get_entries_for_user(&self, user_id: &str, limit: usize) -> Vec<AuditEntry> {
1638 let entries = self.entries.read();
1639 entries
1640 .iter()
1641 .rev()
1642 .filter(|e| e.user_id.as_deref() == Some(user_id))
1643 .take(limit)
1644 .cloned()
1645 .collect()
1646 }
1647
1648 pub fn get_failed_logins(&self, since: u64) -> Vec<AuditEntry> {
1650 let entries = self.entries.read();
1651 entries
1652 .iter()
1653 .filter(|e| e.event_type == AuditEventType::LoginFailure && e.timestamp >= since)
1654 .cloned()
1655 .collect()
1656 }
1657
1658 pub fn count(&self) -> usize {
1660 self.entries.read().len()
1661 }
1662
1663 pub fn export(&self, start_time: u64, end_time: u64) -> Vec<AuditEntry> {
1665 let entries = self.entries.read();
1666 entries
1667 .iter()
1668 .filter(|e| e.timestamp >= start_time && e.timestamp <= end_time)
1669 .cloned()
1670 .collect()
1671 }
1672}
1673
1674impl Default for AuditLogger {
1675 fn default() -> Self {
1676 Self::new(100_000) }
1678}
1679
1680pub struct LdapAuthenticator {
1686 config: LdapConfig,
1687}
1688
1689impl LdapAuthenticator {
1690 pub fn new(config: LdapConfig) -> Self {
1692 Self { config }
1693 }
1694
1695 pub fn authenticate(&self, username: &str, password: &str) -> LdapAuthResult {
1697 if self.config.server_url.is_empty() {
1699 return LdapAuthResult {
1700 success: false,
1701 user_dn: None,
1702 email: None,
1703 display_name: None,
1704 groups: vec![],
1705 error: Some("LDAP server URL not configured".to_string()),
1706 };
1707 }
1708
1709 if password.is_empty() {
1710 return LdapAuthResult {
1711 success: false,
1712 user_dn: None,
1713 email: None,
1714 display_name: None,
1715 groups: vec![],
1716 error: Some("Password is required".to_string()),
1717 };
1718 }
1719
1720 let runtime = match tokio::runtime::Handle::try_current() {
1722 Ok(handle) => handle,
1723 Err(_) => {
1724 return LdapAuthResult {
1725 success: false,
1726 user_dn: None,
1727 email: None,
1728 display_name: None,
1729 groups: vec![],
1730 error: Some("No async runtime available".to_string()),
1731 };
1732 }
1733 };
1734
1735 let config = self.config.clone();
1736 let username = username.to_string();
1737 let password = password.to_string();
1738
1739 let result = runtime
1741 .block_on(async move { Self::authenticate_async(&config, &username, &password).await });
1742
1743 result
1744 }
1745
1746 async fn authenticate_async(
1748 config: &LdapConfig,
1749 username: &str,
1750 password: &str,
1751 ) -> LdapAuthResult {
1752 use ldap3::{LdapConnAsync, Scope, SearchEntry};
1753
1754 let (conn, mut ldap) = match LdapConnAsync::new(&config.server_url).await {
1756 Ok(c) => c,
1757 Err(e) => {
1758 return LdapAuthResult {
1759 success: false,
1760 user_dn: None,
1761 email: None,
1762 display_name: None,
1763 groups: vec![],
1764 error: Some(format!("Failed to connect to LDAP server: {}", e)),
1765 };
1766 }
1767 };
1768
1769 ldap3::drive!(conn);
1771
1772 if let Err(e) = ldap
1774 .simple_bind(&config.bind_dn, &config.bind_password)
1775 .await
1776 {
1777 let _ = ldap.unbind().await;
1778 return LdapAuthResult {
1779 success: false,
1780 user_dn: None,
1781 email: None,
1782 display_name: None,
1783 groups: vec![],
1784 error: Some(format!("Service account bind failed: {}", e)),
1785 };
1786 }
1787
1788 let user_filter = config.user_filter.replace("{username}", username);
1790 let search_result = match ldap
1791 .search(
1792 &config.base_dn,
1793 Scope::Subtree,
1794 &user_filter,
1795 vec!["dn", "mail", "displayName", "cn", &config.group_attribute],
1796 )
1797 .await
1798 {
1799 Ok(result) => result,
1800 Err(e) => {
1801 let _ = ldap.unbind().await;
1802 return LdapAuthResult {
1803 success: false,
1804 user_dn: None,
1805 email: None,
1806 display_name: None,
1807 groups: vec![],
1808 error: Some(format!("User search failed: {}", e)),
1809 };
1810 }
1811 };
1812
1813 let (entries, _result) = search_result.success().unwrap_or((
1814 vec![],
1815 ldap3::LdapResult {
1816 rc: 0,
1817 matched: String::new(),
1818 text: String::new(),
1819 refs: vec![],
1820 ctrls: vec![],
1821 },
1822 ));
1823
1824 if entries.is_empty() {
1825 let _ = ldap.unbind().await;
1826 return LdapAuthResult {
1827 success: false,
1828 user_dn: None,
1829 email: None,
1830 display_name: None,
1831 groups: vec![],
1832 error: Some("User not found".to_string()),
1833 };
1834 }
1835
1836 let entry = SearchEntry::construct(entries[0].clone());
1838 let user_dn = entry.dn.clone();
1839 let email = entry.attrs.get("mail").and_then(|v| v.first()).cloned();
1840 let display_name = entry
1841 .attrs
1842 .get("displayName")
1843 .or_else(|| entry.attrs.get("cn"))
1844 .and_then(|v| v.first())
1845 .cloned();
1846 let groups: Vec<String> = entry
1847 .attrs
1848 .get(&config.group_attribute)
1849 .cloned()
1850 .unwrap_or_default();
1851
1852 if let Err(e) = ldap.simple_bind(&user_dn, password).await {
1854 let _ = ldap.unbind().await;
1855 return LdapAuthResult {
1856 success: false,
1857 user_dn: None,
1858 email: None,
1859 display_name: None,
1860 groups: vec![],
1861 error: Some(format!("Authentication failed: {}", e)),
1862 };
1863 }
1864
1865 let bind_result = ldap.simple_bind(&user_dn, password).await;
1867 let _ = ldap.unbind().await;
1868
1869 match bind_result {
1870 Ok(result) => {
1871 if result.rc == 0 {
1872 LdapAuthResult {
1873 success: true,
1874 user_dn: Some(user_dn),
1875 email,
1876 display_name,
1877 groups,
1878 error: None,
1879 }
1880 } else {
1881 LdapAuthResult {
1882 success: false,
1883 user_dn: None,
1884 email: None,
1885 display_name: None,
1886 groups: vec![],
1887 error: Some("Invalid credentials".to_string()),
1888 }
1889 }
1890 }
1891 Err(e) => LdapAuthResult {
1892 success: false,
1893 user_dn: None,
1894 email: None,
1895 display_name: None,
1896 groups: vec![],
1897 error: Some(format!("Bind failed: {}", e)),
1898 },
1899 }
1900 }
1901
1902 pub fn determine_role(&self, groups: &[String]) -> UserRole {
1904 for group in groups {
1905 if self.config.admin_groups.contains(group) {
1906 return UserRole::Admin;
1907 }
1908 }
1909 for group in groups {
1910 if self.config.operator_groups.contains(group) {
1911 return UserRole::Operator;
1912 }
1913 }
1914 UserRole::Viewer
1915 }
1916}
1917
1918pub struct OAuth2Authenticator {
1924 config: OAuth2Config,
1925 pending_states: RwLock<HashMap<String, OAuth2State>>,
1926 http_client: reqwest::Client,
1927}
1928
1929#[derive(Debug, Clone)]
1931#[allow(dead_code)]
1932struct OAuth2State {
1933 created_at: Instant,
1934 redirect_uri: String,
1935}
1936
1937impl OAuth2Authenticator {
1938 pub fn new(config: OAuth2Config) -> Self {
1940 let http_client = reqwest::Client::builder()
1941 .timeout(Duration::from_secs(30))
1942 .build()
1943 .unwrap_or_else(|_| reqwest::Client::new());
1944
1945 Self {
1946 config,
1947 pending_states: RwLock::new(HashMap::new()),
1948 http_client,
1949 }
1950 }
1951
1952 pub fn get_authorization_url(&self) -> (String, String) {
1954 let state = generate_token();
1955
1956 self.pending_states.write().insert(
1958 state.clone(),
1959 OAuth2State {
1960 created_at: Instant::now(),
1961 redirect_uri: self.config.redirect_uri.clone(),
1962 },
1963 );
1964
1965 let scopes = self.config.scopes.join(" ");
1966 let url = format!(
1967 "{}?client_id={}&redirect_uri={}&response_type=code&scope={}&state={}",
1968 self.config.authorization_url,
1969 self.config.client_id,
1970 urlencoding_encode(&self.config.redirect_uri),
1971 urlencoding_encode(&scopes),
1972 state
1973 );
1974
1975 (url, state)
1976 }
1977
1978 pub fn verify_state(&self, state: &str) -> bool {
1980 let mut states = self.pending_states.write();
1981 if let Some(stored) = states.remove(state) {
1982 stored.created_at.elapsed() < Duration::from_secs(600) } else {
1984 false
1985 }
1986 }
1987
1988 pub fn exchange_code(&self, code: &str) -> Result<OAuth2TokenResponse, String> {
1990 let runtime = tokio::runtime::Handle::try_current()
1991 .map_err(|_| "No async runtime available".to_string())?;
1992
1993 let client = self.http_client.clone();
1994 let token_url = self.config.token_url.clone();
1995 let client_id = self.config.client_id.clone();
1996 let client_secret = self.config.client_secret.clone();
1997 let redirect_uri = self.config.redirect_uri.clone();
1998 let code = code.to_string();
1999
2000 runtime.block_on(async move {
2001 Self::exchange_code_async(
2002 &client,
2003 &token_url,
2004 &client_id,
2005 &client_secret,
2006 &redirect_uri,
2007 &code,
2008 )
2009 .await
2010 })
2011 }
2012
2013 async fn exchange_code_async(
2015 client: &reqwest::Client,
2016 token_url: &str,
2017 client_id: &str,
2018 client_secret: &str,
2019 redirect_uri: &str,
2020 code: &str,
2021 ) -> Result<OAuth2TokenResponse, String> {
2022 let params = [
2023 ("grant_type", "authorization_code"),
2024 ("code", code),
2025 ("client_id", client_id),
2026 ("client_secret", client_secret),
2027 ("redirect_uri", redirect_uri),
2028 ];
2029
2030 let response = client
2031 .post(token_url)
2032 .form(¶ms)
2033 .send()
2034 .await
2035 .map_err(|e| format!("Token request failed: {}", e))?;
2036
2037 if !response.status().is_success() {
2038 let status = response.status();
2039 let body = response.text().await.unwrap_or_default();
2040 return Err(format!("Token endpoint returned {}: {}", status, body));
2041 }
2042
2043 response
2044 .json::<OAuth2TokenResponse>()
2045 .await
2046 .map_err(|e| format!("Failed to parse token response: {}", e))
2047 }
2048
2049 pub fn get_user_info(&self, access_token: &str) -> Result<OAuth2UserInfo, String> {
2051 let runtime = tokio::runtime::Handle::try_current()
2052 .map_err(|_| "No async runtime available".to_string())?;
2053
2054 let client = self.http_client.clone();
2055 let userinfo_url = self.config.userinfo_url.clone();
2056 let access_token = access_token.to_string();
2057
2058 runtime.block_on(async move {
2059 Self::get_user_info_async(&client, &userinfo_url, &access_token).await
2060 })
2061 }
2062
2063 async fn get_user_info_async(
2065 client: &reqwest::Client,
2066 userinfo_url: &str,
2067 access_token: &str,
2068 ) -> Result<OAuth2UserInfo, String> {
2069 let response = client
2070 .get(userinfo_url)
2071 .bearer_auth(access_token)
2072 .send()
2073 .await
2074 .map_err(|e| format!("Userinfo request failed: {}", e))?;
2075
2076 if !response.status().is_success() {
2077 let status = response.status();
2078 let body = response.text().await.unwrap_or_default();
2079 return Err(format!("Userinfo endpoint returned {}: {}", status, body));
2080 }
2081
2082 response
2083 .json::<OAuth2UserInfo>()
2084 .await
2085 .map_err(|e| format!("Failed to parse userinfo response: {}", e))
2086 }
2087
2088 pub fn determine_role(&self, roles: &[String]) -> UserRole {
2090 for role in roles {
2091 if self.config.admin_roles.contains(role) {
2092 return UserRole::Admin;
2093 }
2094 }
2095 for role in roles {
2096 if self.config.operator_roles.contains(role) {
2097 return UserRole::Operator;
2098 }
2099 }
2100 UserRole::Viewer
2101 }
2102}
2103
2104fn urlencoding_encode(s: &str) -> String {
2106 s.replace(' ', "%20")
2107 .replace('&', "%26")
2108 .replace('=', "%3D")
2109 .replace('?', "%3F")
2110 .replace('/', "%2F")
2111}
2112
2113#[cfg(test)]
2118mod tests {
2119 use super::*;
2120
2121 fn auth_with_test_user() -> AuthService {
2123 let auth = AuthService::new();
2124 auth.create_user("testuser", "test@example.com", "TestPassword123!", "viewer")
2125 .expect("failed to create test user");
2126 auth
2127 }
2128
2129 fn auth_with_admin_user() -> (AuthService, String) {
2131 let auth = AuthService::new();
2132 auth.create_user(
2133 "testadmin",
2134 "admin@example.com",
2135 "AdminPassword123!",
2136 "admin",
2137 )
2138 .expect("failed to create admin user");
2139
2140 let secret = generate_mfa_secret();
2142 {
2143 let mut users = auth.users.write();
2144 if let Some(user) = users.get_mut("testadmin") {
2145 user.mfa_enabled = true;
2146 user.mfa_secret = Some(secret.clone());
2147 }
2148 }
2149
2150 (auth, secret)
2151 }
2152
2153 #[test]
2154 fn test_login_success() {
2155 let auth = auth_with_test_user();
2156 let response = auth.login("testuser", "TestPassword123!");
2157 assert!(response.token.is_some());
2158 assert!(response.user.is_some());
2159 }
2160
2161 #[test]
2162 fn test_login_invalid_password() {
2163 let auth = auth_with_test_user();
2164 let response = auth.login("testuser", "wrong");
2165 assert!(response.error.is_some());
2166 }
2167
2168 #[test]
2169 fn test_login_mfa_required() {
2170 let (auth, _secret) = auth_with_admin_user();
2171 let response = auth.login("testadmin", "AdminPassword123!");
2172 assert!(response.requires_mfa == Some(true));
2173 assert!(response.token.is_some());
2174 }
2175
2176 #[test]
2177 fn test_mfa_verification() {
2178 let (auth, secret) = auth_with_admin_user();
2179 let login_response = auth.login("testadmin", "AdminPassword123!");
2180 let temp_token = login_response
2181 .token
2182 .expect("login should return temp token for MFA");
2183
2184 let totp_code = generate_test_totp(&secret);
2186
2187 let mfa_response = auth.verify_mfa(&totp_code, &temp_token);
2188 assert!(
2189 mfa_response.token.is_some(),
2190 "MFA verification failed: {:?}",
2191 mfa_response.error
2192 );
2193 assert!(mfa_response.user.is_some());
2194 }
2195
2196 fn generate_test_totp(secret: &str) -> String {
2198 use data_encoding::BASE32_NOPAD;
2199 use hmac::{Hmac, Mac};
2200 use sha1::Sha1;
2201
2202 let secret_bytes = BASE32_NOPAD
2203 .decode(secret.to_uppercase().as_bytes())
2204 .unwrap_or_else(|_| {
2205 let padded = format!(
2206 "{}{}",
2207 secret.to_uppercase(),
2208 &"========"[..((8 - secret.len() % 8) % 8)]
2209 );
2210 data_encoding::BASE32
2211 .decode(padded.as_bytes())
2212 .expect("padded BASE32 decode should succeed")
2213 });
2214
2215 let timestamp = std::time::SystemTime::now()
2216 .duration_since(std::time::UNIX_EPOCH)
2217 .expect("system time should be after UNIX_EPOCH")
2218 .as_secs();
2219 let time_step = timestamp / 30;
2220 let counter_bytes = time_step.to_be_bytes();
2221
2222 let mut mac =
2223 Hmac::<Sha1>::new_from_slice(&secret_bytes).expect("HMAC key should be valid");
2224 mac.update(&counter_bytes);
2225 let result = mac.finalize().into_bytes();
2226
2227 let offset_idx = (result[19] & 0x0f) as usize;
2228 let binary_code = ((result[offset_idx] & 0x7f) as u32) << 24
2229 | (result[offset_idx + 1] as u32) << 16
2230 | (result[offset_idx + 2] as u32) << 8
2231 | (result[offset_idx + 3] as u32);
2232
2233 let otp = binary_code % 1_000_000;
2234 format!("{:06}", otp)
2235 }
2236
2237 #[test]
2238 fn test_session_validation() {
2239 let auth = auth_with_test_user();
2240 let response = auth.login("testuser", "TestPassword123!");
2241 let token = response.token.expect("login should return token");
2242
2243 let user = auth.validate_session(&token);
2244 assert!(user.is_some());
2245 assert_eq!(user.expect("user should be present").username, "testuser");
2246 }
2247
2248 #[test]
2249 fn test_logout() {
2250 let auth = auth_with_test_user();
2251 let response = auth.login("testuser", "TestPassword123!");
2252 let token = response.token.expect("login should return token");
2253
2254 assert!(auth.logout(&token));
2255 assert!(auth.validate_session(&token).is_none());
2256 }
2257
2258 #[test]
2259 fn test_password_validation() {
2260 let auth = AuthService::new();
2261
2262 let result = auth.create_user("user1", "test@example.com", "short", "viewer");
2264 assert!(result.is_err());
2265 assert!(result.unwrap_err().contains("8 characters"));
2266 }
2267
2268 #[test]
2269 fn test_username_validation() {
2270 let auth = AuthService::new();
2271
2272 let result = auth.create_user(
2274 "user@name",
2275 "test@example.com",
2276 "ValidPassword123",
2277 "viewer",
2278 );
2279 assert!(result.is_err());
2280 assert!(result.unwrap_err().contains("alphanumeric"));
2281 }
2282
2283 #[test]
2284 fn test_email_validation() {
2285 let auth = AuthService::new();
2286
2287 let result = auth.create_user("username", "invalid-email", "ValidPassword123", "viewer");
2289 assert!(result.is_err());
2290 assert!(result.unwrap_err().contains("email"));
2291 }
2292
2293 #[test]
2295 fn test_rbac_default_roles() {
2296 let rbac = RbacManager::new();
2297 let roles = rbac.list_roles();
2298 assert!(roles.iter().any(|r| r.name == "admin"));
2299 assert!(roles.iter().any(|r| r.name == "operator"));
2300 assert!(roles.iter().any(|r| r.name == "viewer"));
2301 }
2302
2303 #[test]
2304 fn test_rbac_check_permission() {
2305 let rbac = RbacManager::new();
2306
2307 rbac.assign_role("test-user-1", "admin")
2309 .expect("failed to assign admin role");
2310 rbac.assign_role("test-user-2", "viewer")
2311 .expect("failed to assign viewer role");
2312
2313 assert!(rbac.check_permission("test-user-1", Permission::DatabaseCreate));
2315 assert!(rbac.check_permission("test-user-1", Permission::ClusterManage));
2316
2317 assert!(rbac.check_permission("test-user-2", Permission::DataSelect));
2319 assert!(!rbac.check_permission("test-user-2", Permission::DataInsert));
2320 }
2321
2322 #[test]
2323 fn test_rbac_create_role() {
2324 let rbac = RbacManager::new();
2325 let result = rbac.create_role(
2326 "custom_role",
2327 "Custom role for testing",
2328 vec![Permission::DataSelect, Permission::DataInsert],
2329 "admin",
2330 );
2331 assert!(result.is_ok());
2332
2333 let role = rbac.get_role("custom_role");
2334 assert!(role.is_some());
2335 assert!(role
2336 .expect("role should exist")
2337 .has_permission(Permission::DataSelect));
2338 }
2339
2340 #[test]
2341 fn test_rbac_assign_role() {
2342 let rbac = RbacManager::new();
2343 let result = rbac.assign_role("new-user", "analyst");
2344 assert!(result.is_ok());
2345
2346 let roles = rbac.get_user_roles("new-user");
2347 assert!(roles.contains(&"analyst".to_string()));
2348 }
2349
2350 #[test]
2351 fn test_rbac_cannot_delete_builtin() {
2352 let rbac = RbacManager::new();
2353 let result = rbac.delete_role("admin");
2354 assert!(result.is_err());
2355 }
2356
2357 #[test]
2359 fn test_audit_log_entry() {
2360 let audit = AuditLogger::new(1000);
2361 audit.log_login_success("user-001", "testuser", Some("192.168.1.1"));
2362
2363 let entries = audit.get_entries(10, 0);
2364 assert_eq!(entries.len(), 1);
2365 assert_eq!(entries[0].event_type, AuditEventType::LoginSuccess);
2366 }
2367
2368 #[test]
2369 fn test_audit_get_by_type() {
2370 let audit = AuditLogger::new(1000);
2371 audit.log_login_success("user-001", "admin", None);
2372 audit.log_login_failure("baduser", None, "Invalid password");
2373 audit.log_login_success("user-002", "demo", None);
2374
2375 let failures = audit.get_entries_by_type(AuditEventType::LoginFailure, 10);
2376 assert_eq!(failures.len(), 1);
2377
2378 let successes = audit.get_entries_by_type(AuditEventType::LoginSuccess, 10);
2379 assert_eq!(successes.len(), 2);
2380 }
2381
2382 #[test]
2383 fn test_audit_get_for_user() {
2384 let audit = AuditLogger::new(1000);
2385 audit.log_login_success("user-001", "admin", None);
2386 audit.log_data_access("user-001", "admin", "users", "SELECT", 10);
2387 audit.log_login_success("user-002", "demo", None);
2388
2389 let user1_entries = audit.get_entries_for_user("user-001", 10);
2390 assert_eq!(user1_entries.len(), 2);
2391 }
2392
2393 #[test]
2394 fn test_audit_max_entries() {
2395 let audit = AuditLogger::new(5);
2396 for i in 0..10 {
2397 audit.log_login_success(&format!("user-{}", i), "test", None);
2398 }
2399
2400 assert_eq!(audit.count(), 5);
2401 }
2402
2403 #[test]
2405 fn test_password_hashing_unique() {
2406 let hash1 = hash_password("testpassword");
2407 let hash2 = hash_password("testpassword");
2408 assert_ne!(hash1, hash2);
2410 }
2411
2412 #[test]
2413 fn test_password_verification() {
2414 let password = "SecurePassword123!";
2415 let hash = hash_password(password);
2416 assert!(verify_password(password, &hash));
2417 assert!(!verify_password("wrongpassword", &hash));
2418 }
2419
2420 #[test]
2421 fn test_token_generation_unique() {
2422 let token1 = generate_token();
2423 let token2 = generate_token();
2424 assert_ne!(token1, token2);
2425 assert_eq!(token1.len(), 64); }
2427
2428 #[test]
2429 fn test_mfa_secret_generation() {
2430 let secret1 = generate_mfa_secret();
2431 let secret2 = generate_mfa_secret();
2432 assert_ne!(secret1, secret2);
2433 assert_eq!(secret1.len(), 32); }
2435
2436 #[test]
2438 fn test_ldap_authenticator_config_validation() {
2439 let mut config = LdapConfig::default();
2441 config.server_url = String::new();
2442 let ldap = LdapAuthenticator::new(config);
2443
2444 let result = ldap.authenticate("testuser", "password");
2445 assert!(!result.success);
2446 assert!(result.error.is_some());
2447 assert!(result
2448 .error
2449 .as_ref()
2450 .expect("error should be present")
2451 .contains("not configured"));
2452 }
2453
2454 #[test]
2455 fn test_ldap_authenticator_empty_password() {
2456 let config = LdapConfig::default();
2457 let ldap = LdapAuthenticator::new(config);
2458
2459 let result = ldap.authenticate("testuser", "");
2460 assert!(!result.success);
2461 assert!(result.error.is_some());
2462 assert!(result
2463 .error
2464 .as_ref()
2465 .expect("error should be present")
2466 .contains("Password is required"));
2467 }
2468
2469 #[test]
2470 fn test_ldap_role_mapping() {
2471 let config = LdapConfig::default();
2472 let ldap = LdapAuthenticator::new(config.clone());
2473
2474 let admin_role = ldap.determine_role(&config.admin_groups);
2475 assert_eq!(admin_role, UserRole::Admin);
2476
2477 let viewer_role = ldap.determine_role(&[]);
2478 assert_eq!(viewer_role, UserRole::Viewer);
2479 }
2480
2481 #[test]
2483 fn test_oauth2_authorization_url() {
2484 let config = OAuth2Config::default();
2485 let oauth = OAuth2Authenticator::new(config);
2486
2487 let (url, state) = oauth.get_authorization_url();
2488 assert!(url.contains("client_id="));
2489 assert!(url.contains(&state));
2490 }
2491
2492 #[test]
2493 fn test_oauth2_state_verification() {
2494 let config = OAuth2Config::default();
2495 let oauth = OAuth2Authenticator::new(config);
2496
2497 let (_, state) = oauth.get_authorization_url();
2498 assert!(oauth.verify_state(&state));
2499 assert!(!oauth.verify_state(&state)); }
2501
2502 #[test]
2503 fn test_oauth2_role_mapping() {
2504 let config = OAuth2Config::default();
2505 let oauth = OAuth2Authenticator::new(config.clone());
2506
2507 let admin_role = oauth.determine_role(&config.admin_roles);
2508 assert_eq!(admin_role, UserRole::Admin);
2509 }
2510}