1use std::collections::HashMap;
8use std::sync::atomic::{AtomicBool, Ordering};
9use std::sync::{Arc, RwLock};
10
11use crate::crypto::os_random;
12use crate::crypto::sha256::sha256;
13use crate::storage::encryption::argon2id::{derive_key, Argon2Params};
14use crate::storage::engine::pager::Pager;
15
16use super::column_policy_gate::{ColumnAccessRequest, ColumnPolicyGate, ColumnPolicyOutcome};
17use super::policies::{self as iam_policies, EvalContext, Policy, ResourceRef, SimulationOutcome};
18use super::privileges::{
19 check_grant, Action, AuthzContext, AuthzError, Grant, GrantPrincipal, GrantsView,
20 PermissionCache, Resource, UserAttributes,
21};
22use super::vault::{KeyPair, Vault, VaultState};
23use super::{now_ms, ApiKey, AuthConfig, AuthError, Role, Session, User, UserId};
24
25#[derive(Debug, Clone, PartialEq, Eq, Hash)]
31pub enum PrincipalRef {
32 User(UserId),
33 Group(String),
34}
35
36pub const PUBLIC_IAM_GROUP: &str = "__public__";
39
40#[derive(Debug, Clone, Default)]
43pub struct SimCtx {
44 pub current_tenant: Option<String>,
45 pub peer_ip: Option<std::net::IpAddr>,
46 pub mfa_present: bool,
47 pub now_ms: Option<u128>,
48}
49
50#[derive(Debug)]
59pub struct BootstrapResult {
60 pub user: User,
61 pub api_key: ApiKey,
62 pub certificate: Option<String>,
64}
65
66pub struct AuthStore {
74 users: RwLock<HashMap<UserId, User>>,
77 sessions: RwLock<HashMap<String, Session>>,
78 api_key_index: RwLock<HashMap<String, (UserId, Role)>>,
80 bootstrapped: AtomicBool,
82 config: AuthConfig,
83 vault: RwLock<Option<Vault>>,
85 pager: Option<Arc<Pager>>,
87 keypair: RwLock<Option<KeyPair>>,
90 vault_kv: RwLock<HashMap<String, String>>,
93 grants: RwLock<HashMap<UserId, Vec<Grant>>>,
98 public_grants: RwLock<Vec<Grant>>,
100 user_attributes: RwLock<HashMap<UserId, UserAttributes>>,
104 session_count_by_user: RwLock<HashMap<UserId, u32>>,
108 permission_cache: RwLock<HashMap<UserId, PermissionCache>>,
112 policies: RwLock<HashMap<String, Arc<Policy>>>,
116 user_attachments: RwLock<HashMap<UserId, Vec<String>>>,
119 group_attachments: RwLock<HashMap<String, Vec<String>>>,
123 iam_effective_cache: RwLock<HashMap<UserId, Vec<Arc<Policy>>>>,
126 iam_authorization_enabled: AtomicBool,
130 visible_collections_cache: super::scope_cache::AuthCache,
140}
141
142fn auth_argon2_params() -> Argon2Params {
145 Argon2Params {
146 m_cost: 4 * 1024, t_cost: 3,
148 p: 1,
149 tag_len: 32,
150 }
151}
152
153impl AuthStore {
154 pub fn new(config: AuthConfig) -> Self {
159 Self {
160 users: RwLock::new(HashMap::new()),
161 sessions: RwLock::new(HashMap::new()),
162 api_key_index: RwLock::new(HashMap::new()),
163 bootstrapped: AtomicBool::new(false),
164 config,
165 vault: RwLock::new(None),
166 pager: None,
167 keypair: RwLock::new(None),
168 vault_kv: RwLock::new(HashMap::new()),
169 grants: RwLock::new(HashMap::new()),
170 public_grants: RwLock::new(Vec::new()),
171 user_attributes: RwLock::new(HashMap::new()),
172 session_count_by_user: RwLock::new(HashMap::new()),
173 permission_cache: RwLock::new(HashMap::new()),
174 policies: RwLock::new(HashMap::new()),
175 user_attachments: RwLock::new(HashMap::new()),
176 group_attachments: RwLock::new(HashMap::new()),
177 iam_effective_cache: RwLock::new(HashMap::new()),
178 iam_authorization_enabled: AtomicBool::new(false),
179 visible_collections_cache: super::scope_cache::AuthCache::new(
180 super::scope_cache::DEFAULT_TTL,
181 ),
182 }
183 }
184
185 pub fn with_vault(
192 config: AuthConfig,
193 pager: Arc<Pager>,
194 passphrase: Option<&str>,
195 ) -> Result<Self, AuthError> {
196 let vault = Vault::open(&pager, passphrase)?;
197 let mut store = Self::new(config);
198
199 if let Some(state) = vault.load(&pager)? {
201 store.restore_from_vault(state);
202 }
203
204 *store.vault.write().unwrap_or_else(|e| e.into_inner()) = Some(vault);
205 store.pager = Some(pager);
206 Ok(store)
207 }
208
209 pub fn config(&self) -> &AuthConfig {
210 &self.config
211 }
212
213 pub fn is_enabled(&self) -> bool {
214 self.config.enabled
215 }
216
217 pub fn needs_bootstrap(&self) -> bool {
219 !self.bootstrapped.load(Ordering::Acquire)
220 && self.users.read().map(|u| u.is_empty()).unwrap_or(true)
221 }
222
223 fn get_user_cloned(&self, id: &UserId) -> Option<User> {
225 self.users.read().ok().and_then(|m| m.get(id).cloned())
226 }
227
228 pub fn is_bootstrapped(&self) -> bool {
230 self.bootstrapped.load(Ordering::Acquire)
231 }
232
233 pub fn bootstrap(&self, username: &str, password: &str) -> Result<BootstrapResult, AuthError> {
244 if self
246 .bootstrapped
247 .compare_exchange(false, true, Ordering::AcqRel, Ordering::Acquire)
248 .is_err()
249 {
250 return Err(AuthError::Forbidden(
251 "bootstrap already completed — sealed permanently".to_string(),
252 ));
253 }
254
255 {
257 let users = self.users.read().map_err(lock_err)?;
258 if !users.is_empty() {
259 return Err(AuthError::Forbidden(
260 "bootstrap already completed — users exist".to_string(),
261 ));
262 }
263 }
264
265 let user = self.create_user(username, password, Role::Admin)?;
266 let key = self.create_api_key(username, "bootstrap", Role::Admin)?;
267
268 let certificate = if let Some(ref pager) = self.pager {
270 let kp = KeyPair::generate();
271 let cert_hex = kp.certificate_hex();
272
273 let new_vault = Vault::with_certificate_bytes(pager, &kp.certificate)
275 .map_err(|e| AuthError::Internal(format!("vault re-seal failed: {e}")))?;
276
277 if let Ok(mut kp_guard) = self.keypair.write() {
279 *kp_guard = Some(kp);
280 }
281
282 if let Ok(mut vault_guard) = self.vault.write() {
284 *vault_guard = Some(new_vault);
285 }
286 self.ensure_vault_secret_key();
288 self.persist_to_vault();
289
290 Some(cert_hex)
291 } else {
292 None
293 };
294
295 Ok(BootstrapResult {
296 user,
297 api_key: key,
298 certificate,
299 })
300 }
301
302 pub fn bootstrap_from_env(&self) -> Option<BootstrapResult> {
310 if !self.needs_bootstrap() {
311 return None;
312 }
313
314 let username = std::env::var("REDDB_USERNAME").ok()?;
315 let password = std::env::var("REDDB_PASSWORD").ok()?;
316
317 if username.is_empty() || password.is_empty() {
318 return None;
319 }
320
321 match self.bootstrap(&username, &password) {
322 Ok(result) => {
323 tracing::info!(
329 username = %reddb_wire::audit_safe_log_field(&username),
330 "bootstrapped admin user from REDDB_USERNAME/REDDB_PASSWORD"
331 );
332 if let Some(ref cert) = result.certificate {
333 eprintln!("[reddb] CERTIFICATE: {}", cert);
337 tracing::warn!(
338 "vault certificate issued — save it: ONLY way to unseal after restart"
339 );
340 }
341 Some(result)
342 }
343 Err(e) => {
344 tracing::error!(err = %e, "env bootstrap failed");
345 None
346 }
347 }
348 }
349
350 fn persist_to_vault_result(&self) -> Result<(), AuthError> {
356 let vault_guard = self.vault.read().unwrap_or_else(|e| e.into_inner());
357 if let (Some(ref vault), Some(ref pager)) = (&*vault_guard, &self.pager) {
358 let state = self.snapshot();
359 vault.save(pager, &state)?;
360 }
361 Ok(())
362 }
363
364 fn persist_to_vault(&self) {
370 if let Err(e) = self.persist_to_vault_result() {
371 tracing::error!(err = %e, "vault persist failed");
372 crate::telemetry::operator_event::OperatorEvent::SecretRotationFailed {
378 secret_ref: "auth_vault".to_string(),
379 error: e.to_string(),
380 }
381 .emit_global();
382 }
383 }
384
385 pub fn is_vault_backed(&self) -> bool {
387 self.pager.is_some()
388 && self
389 .vault
390 .read()
391 .map(|guard| guard.is_some())
392 .unwrap_or(false)
393 }
394
395 pub fn vault_kv_get(&self, key: &str) -> Option<String> {
401 self.vault_kv
402 .read()
403 .ok()
404 .and_then(|kv| kv.get(key).cloned())
405 }
406
407 pub fn vault_kv_snapshot(&self) -> HashMap<String, String> {
409 self.vault_kv
410 .read()
411 .map(|kv| kv.clone())
412 .unwrap_or_default()
413 }
414
415 pub fn vault_kv_export_encrypted(&self) -> Result<Option<String>, AuthError> {
418 if !self.is_vault_backed() {
419 return Err(AuthError::Forbidden(
420 "vault KV export requires an enabled, unsealed vault".to_string(),
421 ));
422 }
423 let kv = self.vault_kv_snapshot();
424 if kv.is_empty() {
425 return Ok(None);
426 }
427
428 let vault_guard = self.vault.read().map_err(lock_err)?;
429 let vault = vault_guard.as_ref().ok_or_else(|| {
430 AuthError::Forbidden("vault KV export requires an enabled, unsealed vault".to_string())
431 })?;
432 let state = VaultState {
433 users: Vec::new(),
434 api_keys: Vec::new(),
435 bootstrapped: false,
436 master_secret: None,
437 kv,
438 };
439 Ok(Some(vault.seal_logical_export(&state)?))
440 }
441
442 pub fn vault_kv_try_import(
445 &self,
446 entries: HashMap<String, String>,
447 ) -> Result<usize, AuthError> {
448 if !self.is_vault_backed() {
449 return Err(AuthError::Forbidden(
450 "vault KV import requires an enabled, unsealed vault".to_string(),
451 ));
452 }
453 if entries.is_empty() {
454 return Ok(0);
455 }
456
457 let mut previous = HashMap::new();
458 {
459 let mut kv = self.vault_kv.write().map_err(lock_err)?;
460 for (key, value) in &entries {
461 previous.insert(key.clone(), kv.insert(key.clone(), value.clone()));
462 }
463 }
464
465 if let Err(err) = self.persist_to_vault_result() {
466 if let Ok(mut kv) = self.vault_kv.write() {
467 for (key, old) in previous {
468 match old {
469 Some(value) => {
470 kv.insert(key, value);
471 }
472 None => {
473 kv.remove(&key);
474 }
475 }
476 }
477 }
478 return Err(err);
479 }
480
481 Ok(entries.len())
482 }
483
484 pub fn vault_kv_try_import_placeholders(&self, keys: &[String]) -> Result<usize, AuthError> {
487 let entries = keys
488 .iter()
489 .map(|key| (key.clone(), "false".to_string()))
490 .collect();
491 self.vault_kv_try_import(entries)
492 }
493
494 pub fn vault_kv_set(&self, key: String, value: String) {
496 if let Ok(mut kv) = self.vault_kv.write() {
497 kv.insert(key, value);
498 }
499 self.persist_to_vault();
500 }
501
502 pub fn vault_kv_try_set(&self, key: String, value: String) -> Result<(), AuthError> {
505 if !self.is_vault_backed() {
506 return Err(AuthError::Forbidden(
507 "SET SECRET requires an enabled, unsealed vault".to_string(),
508 ));
509 }
510
511 let previous = {
512 let mut kv = self.vault_kv.write().map_err(lock_err)?;
513 kv.insert(key.clone(), value)
514 };
515
516 if let Err(err) = self.persist_to_vault_result() {
517 if let Ok(mut kv) = self.vault_kv.write() {
518 match previous {
519 Some(value) => {
520 kv.insert(key, value);
521 }
522 None => {
523 kv.remove(&key);
524 }
525 }
526 }
527 return Err(err);
528 }
529
530 Ok(())
531 }
532
533 pub fn vault_kv_delete(&self, key: &str) -> bool {
535 let existed = self
536 .vault_kv
537 .write()
538 .map(|mut kv| kv.remove(key).is_some())
539 .unwrap_or(false);
540 if existed {
541 self.persist_to_vault();
542 }
543 existed
544 }
545
546 pub fn vault_kv_try_delete(&self, key: &str) -> Result<bool, AuthError> {
549 if !self.is_vault_backed() {
550 return Err(AuthError::Forbidden(
551 "DELETE SECRET requires an enabled, unsealed vault".to_string(),
552 ));
553 }
554
555 let removed = {
556 let mut kv = self.vault_kv.write().map_err(lock_err)?;
557 kv.remove(key)
558 };
559
560 if removed.is_none() {
561 return Ok(false);
562 }
563
564 if let Err(err) = self.persist_to_vault_result() {
565 if let Ok(mut kv) = self.vault_kv.write() {
566 if let Some(value) = removed {
567 kv.insert(key.to_string(), value);
568 }
569 }
570 return Err(err);
571 }
572
573 Ok(true)
574 }
575
576 pub fn vault_kv_keys(&self) -> Vec<String> {
578 self.vault_kv
579 .read()
580 .map(|kv| kv.keys().cloned().collect())
581 .unwrap_or_default()
582 }
583
584 pub fn vault_secret_key(&self) -> Option<[u8; 32]> {
587 let hex_str = self.vault_kv_get("red.secret.aes_key")?;
588 let bytes = hex::decode(hex_str).ok()?;
589 if bytes.len() == 32 {
590 let mut key = [0u8; 32];
591 key.copy_from_slice(&bytes);
592 Some(key)
593 } else {
594 None
595 }
596 }
597
598 pub fn ensure_vault_secret_key(&self) {
600 if self.vault_kv_get("red.secret.aes_key").is_none() {
601 let key = random_bytes(32);
602 self.vault_kv_set("red.secret.aes_key".to_string(), hex::encode(key));
603 }
604 }
605
606 fn snapshot(&self) -> VaultState {
608 let users_guard = self.users.read().unwrap_or_else(|e| e.into_inner());
609 let users: Vec<User> = users_guard.values().cloned().collect();
610
611 let mut api_keys = Vec::new();
614 for user in &users {
615 let owner = UserId::from_parts(user.tenant_id.as_deref(), &user.username);
616 for key in &user.api_keys {
617 api_keys.push((owner.clone(), key.clone()));
618 }
619 }
620
621 let master_secret = self
623 .keypair
624 .read()
625 .ok()
626 .and_then(|guard| guard.as_ref().map(|kp| kp.master_secret.clone()));
627
628 let kv = self.vault_kv.read().map(|m| m.clone()).unwrap_or_default();
629
630 VaultState {
631 users,
632 api_keys,
633 bootstrapped: self.bootstrapped.load(Ordering::Acquire),
634 master_secret,
635 kv,
636 }
637 }
638
639 fn restore_from_vault(&mut self, state: VaultState) {
641 if state.bootstrapped {
643 self.bootstrapped.store(true, Ordering::Release);
644 }
645
646 if let Some(secret) = state.master_secret {
648 let kp = KeyPair::from_master_secret(secret);
649 if let Ok(mut guard) = self.keypair.write() {
650 *guard = Some(kp);
651 }
652 }
653
654 if let Ok(mut kv) = self.vault_kv.write() {
656 *kv = state.kv;
657 }
658
659 let mut users = self.users.write().unwrap_or_else(|e| e.into_inner());
661 let mut idx = self
662 .api_key_index
663 .write()
664 .unwrap_or_else(|e| e.into_inner());
665
666 for user in state.users {
667 let id = UserId::from_parts(user.tenant_id.as_deref(), &user.username);
668 for key in &user.api_keys {
670 idx.insert(key.key.clone(), (id.clone(), key.role));
671 }
672 users.insert(id, user);
673 }
674 drop(idx);
675 drop(users);
676
677 self.rehydrate_acl();
678 self.rehydrate_iam();
679 }
680
681 pub fn create_user(
689 &self,
690 username: &str,
691 password: &str,
692 role: Role,
693 ) -> Result<User, AuthError> {
694 self.create_user_in_tenant(None, username, password, role)
695 }
696
697 pub fn create_user_in_tenant(
702 &self,
703 tenant_id: Option<&str>,
704 username: &str,
705 password: &str,
706 role: Role,
707 ) -> Result<User, AuthError> {
708 let id = UserId::from_parts(tenant_id, username);
709 let mut users = self.users.write().map_err(lock_err)?;
710 if users.contains_key(&id) {
711 return Err(AuthError::UserExists(id.to_string()));
712 }
713
714 let now = now_ms();
715 let user = User {
716 username: username.to_string(),
717 tenant_id: tenant_id.map(|s| s.to_string()),
718 password_hash: hash_password(password),
719 scram_verifier: Some(make_scram_verifier(password)),
720 role,
721 api_keys: Vec::new(),
722 created_at: now,
723 updated_at: now,
724 enabled: true,
725 };
726 users.insert(id, user.clone());
727 drop(users); self.persist_to_vault();
729 Ok(user)
730 }
731
732 pub fn lookup_scram_verifier(&self, id: &UserId) -> Option<crate::auth::scram::ScramVerifier> {
738 let users = self.users.read().ok()?;
739 users.get(id).and_then(|u| u.scram_verifier.clone())
740 }
741
742 pub fn lookup_scram_verifier_global(
747 &self,
748 username: &str,
749 ) -> Option<crate::auth::scram::ScramVerifier> {
750 self.lookup_scram_verifier(&UserId::platform(username))
751 }
752
753 pub fn list_users(&self) -> Vec<User> {
755 let users = match self.users.read() {
756 Ok(g) => g,
757 Err(_) => return Vec::new(),
758 };
759 users
760 .values()
761 .map(|u| User {
762 password_hash: String::new(), ..u.clone()
764 })
765 .collect()
766 }
767
768 pub fn list_users_scoped(&self, tenant_filter: Option<Option<&str>>) -> Vec<User> {
775 let users = match self.users.read() {
776 Ok(g) => g,
777 Err(_) => return Vec::new(),
778 };
779 users
780 .values()
781 .filter(|u| match tenant_filter {
782 None => true,
783 Some(t) => u.tenant_id.as_deref() == t,
784 })
785 .map(|u| User {
786 password_hash: String::new(), ..u.clone()
788 })
789 .collect()
790 }
791
792 pub fn get_user(&self, tenant_id: Option<&str>, username: &str) -> Option<User> {
795 let id = UserId::from_parts(tenant_id, username);
796 self.get_user_cloned(&id).map(|u| User {
797 password_hash: String::new(),
798 ..u
799 })
800 }
801
802 pub fn delete_user(&self, username: &str) -> Result<(), AuthError> {
807 self.delete_user_in_tenant(None, username)
808 }
809
810 pub fn delete_user_in_tenant(
813 &self,
814 tenant_id: Option<&str>,
815 username: &str,
816 ) -> Result<(), AuthError> {
817 let id = UserId::from_parts(tenant_id, username);
818 let mut users = self.users.write().map_err(lock_err)?;
819 let user = users
820 .remove(&id)
821 .ok_or_else(|| AuthError::UserNotFound(id.to_string()))?;
822
823 if let Ok(mut idx) = self.api_key_index.write() {
825 for api_key in &user.api_keys {
826 idx.remove(&api_key.key);
827 }
828 }
829
830 if let Ok(mut sessions) = self.sessions.write() {
833 sessions
834 .retain(|_, s| !(s.username == username && s.tenant_id.as_deref() == tenant_id));
835 }
836
837 self.persist_to_vault();
838 Ok(())
839 }
840
841 pub fn change_password(
845 &self,
846 username: &str,
847 old_password: &str,
848 new_password: &str,
849 ) -> Result<(), AuthError> {
850 self.change_password_in_tenant(None, username, old_password, new_password)
851 }
852
853 pub fn change_password_in_tenant(
854 &self,
855 tenant_id: Option<&str>,
856 username: &str,
857 old_password: &str,
858 new_password: &str,
859 ) -> Result<(), AuthError> {
860 let id = UserId::from_parts(tenant_id, username);
861 let mut users = self.users.write().map_err(lock_err)?;
862 let user = users
863 .get_mut(&id)
864 .ok_or_else(|| AuthError::UserNotFound(id.to_string()))?;
865
866 if !verify_password(old_password, &user.password_hash) {
867 return Err(AuthError::InvalidCredentials);
868 }
869
870 user.password_hash = hash_password(new_password);
871 user.scram_verifier = Some(make_scram_verifier(new_password));
872 user.updated_at = now_ms();
873 drop(users); self.persist_to_vault();
875 Ok(())
876 }
877
878 pub fn change_role(&self, username: &str, new_role: Role) -> Result<(), AuthError> {
881 self.change_role_in_tenant(None, username, new_role)
882 }
883
884 pub fn change_role_in_tenant(
885 &self,
886 tenant_id: Option<&str>,
887 username: &str,
888 new_role: Role,
889 ) -> Result<(), AuthError> {
890 let id = UserId::from_parts(tenant_id, username);
891 let mut users = self.users.write().map_err(lock_err)?;
892 let user = users
893 .get_mut(&id)
894 .ok_or_else(|| AuthError::UserNotFound(id.to_string()))?;
895
896 let prior_role = user.role;
897 user.role = new_role;
898 user.updated_at = now_ms();
899
900 if new_role == Role::Admin && prior_role != Role::Admin {
905 crate::telemetry::operator_event::OperatorEvent::AdminCapabilityGranted {
906 granted_to: id.to_string(),
907 capability: "Role::Admin".to_string(),
908 granted_by: "auth_store::change_role".to_string(),
909 }
910 .emit_global();
911 }
912
913 for key in &mut user.api_keys {
915 if key.role > new_role {
916 key.role = new_role;
917 }
918 }
919
920 if let Ok(mut idx) = self.api_key_index.write() {
922 for key in &user.api_keys {
923 if let Some(entry) = idx.get_mut(&key.key) {
924 entry.1 = key.role;
925 }
926 }
927 }
928
929 self.persist_to_vault();
930 Ok(())
931 }
932
933 pub fn authenticate(&self, username: &str, password: &str) -> Result<Session, AuthError> {
945 self.authenticate_in_tenant(None, username, password)
946 }
947
948 pub fn authenticate_in_tenant(
952 &self,
953 tenant_id: Option<&str>,
954 username: &str,
955 password: &str,
956 ) -> Result<Session, AuthError> {
957 let id = UserId::from_parts(tenant_id, username);
958 let users = self.users.read().map_err(lock_err)?;
959 let user = users.get(&id).ok_or(AuthError::InvalidCredentials)?;
960
961 if !user.enabled {
962 return Err(AuthError::InvalidCredentials);
963 }
964
965 if !verify_password(password, &user.password_hash) {
966 return Err(AuthError::InvalidCredentials);
967 }
968
969 let token = match self.keypair.read().ok().and_then(|g| {
971 g.as_ref().map(|kp| {
972 let token_id = random_hex(16);
973 let sig = kp.sign(format!("session:{}", token_id).as_bytes());
974 format!("rs_{}{}", token_id, hex::encode(&sig[..16]))
976 })
977 }) {
978 Some(signed_token) => signed_token,
979 None => generate_session_token(),
980 };
981
982 let now = now_ms();
983 let session = Session {
984 token,
985 username: username.to_string(),
986 tenant_id: user.tenant_id.clone(),
987 role: user.role,
988 created_at: now,
989 expires_at: now + (self.config.session_ttl_secs as u128 * 1000),
990 };
991
992 drop(users); let mut sessions = self.sessions.write().map_err(lock_err)?;
995 sessions.insert(session.token.clone(), session.clone());
996 Ok(session)
997 }
998
999 pub fn validate_token(&self, token: &str) -> Option<(String, Role)> {
1011 self.validate_token_full(token)
1012 .map(|(id, role)| (id.username, role))
1013 }
1014
1015 pub fn validate_token_full(&self, token: &str) -> Option<(UserId, Role)> {
1018 if token.starts_with("rs_") {
1020 if let Ok(sessions) = self.sessions.read() {
1021 if let Some(session) = sessions.get(token) {
1022 let now = now_ms();
1023 if now < session.expires_at {
1024 return Some((
1025 UserId::from_parts(session.tenant_id.as_deref(), &session.username),
1026 session.role,
1027 ));
1028 }
1029 }
1030 }
1031 return None;
1032 }
1033
1034 if token.starts_with("rk_") {
1036 if let Ok(idx) = self.api_key_index.read() {
1037 return idx.get(token).cloned();
1038 }
1039 return None;
1040 }
1041
1042 None
1043 }
1044
1045 pub fn create_api_key(
1053 &self,
1054 username: &str,
1055 name: &str,
1056 role: Role,
1057 ) -> Result<ApiKey, AuthError> {
1058 self.create_api_key_in_tenant(None, username, name, role)
1059 }
1060
1061 pub fn create_api_key_in_tenant(
1062 &self,
1063 tenant_id: Option<&str>,
1064 username: &str,
1065 name: &str,
1066 role: Role,
1067 ) -> Result<ApiKey, AuthError> {
1068 let id = UserId::from_parts(tenant_id, username);
1069 let mut users = self.users.write().map_err(lock_err)?;
1070 let user = users
1071 .get_mut(&id)
1072 .ok_or_else(|| AuthError::UserNotFound(id.to_string()))?;
1073
1074 if role > user.role {
1076 return Err(AuthError::RoleExceeded {
1077 requested: role,
1078 ceiling: user.role,
1079 });
1080 }
1081
1082 let api_key = ApiKey {
1083 key: generate_api_key(),
1084 name: name.to_string(),
1085 role,
1086 created_at: now_ms(),
1087 };
1088
1089 user.api_keys.push(api_key.clone());
1090 user.updated_at = now_ms();
1091
1092 if let Ok(mut idx) = self.api_key_index.write() {
1094 idx.insert(api_key.key.clone(), (id.clone(), api_key.role));
1095 }
1096
1097 drop(users); self.persist_to_vault();
1099 Ok(api_key)
1100 }
1101
1102 pub fn revoke_api_key(&self, key: &str) -> Result<(), AuthError> {
1104 let mut users = self.users.write().map_err(lock_err)?;
1105
1106 let owner_id: UserId = {
1110 if let Ok(idx) = self.api_key_index.read() {
1111 if let Some((id, _)) = idx.get(key) {
1112 id.clone()
1113 } else {
1114 return Err(AuthError::KeyNotFound(key.to_string()));
1115 }
1116 } else {
1117 let owner = users
1118 .iter()
1119 .find(|(_, u)| u.api_keys.iter().any(|k| k.key == key));
1120 match owner {
1121 Some((id, _)) => id.clone(),
1122 None => return Err(AuthError::KeyNotFound(key.to_string())),
1123 }
1124 }
1125 };
1126
1127 let user = users
1128 .get_mut(&owner_id)
1129 .ok_or_else(|| AuthError::KeyNotFound(key.to_string()))?;
1130 user.api_keys.retain(|k| k.key != key);
1131 user.updated_at = now_ms();
1132
1133 if let Ok(mut idx) = self.api_key_index.write() {
1135 idx.remove(key);
1136 }
1137
1138 self.persist_to_vault();
1139 Ok(())
1140 }
1141
1142 pub fn revoke_session(&self, token: &str) {
1148 if let Ok(mut sessions) = self.sessions.write() {
1149 sessions.remove(token);
1150 }
1151 }
1152
1153 pub fn purge_expired_sessions(&self) -> usize {
1155 let now = now_ms();
1156 if let Ok(mut sessions) = self.sessions.write() {
1157 let before = sessions.len();
1158 sessions.retain(|_, s| s.expires_at > now);
1159 return before - sessions.len();
1160 }
1161 0
1162 }
1163
1164 pub fn grant(
1184 &self,
1185 granter: &UserId,
1186 granter_role: Role,
1187 principal: GrantPrincipal,
1188 resource: Resource,
1189 actions: Vec<Action>,
1190 with_grant_option: bool,
1191 tenant: Option<String>,
1192 ) -> Result<(), AuthError> {
1193 if granter_role != Role::Admin {
1194 return Err(AuthError::Forbidden(format!(
1195 "GRANT requires Admin role; granter `{}` has `{:?}`",
1196 granter, granter_role
1197 )));
1198 }
1199
1200 if granter.tenant.is_some() && granter.tenant != tenant {
1204 return Err(AuthError::Forbidden(format!(
1205 "cross-tenant GRANT denied: granter tenant `{:?}` != grant tenant `{:?}`",
1206 granter.tenant, tenant
1207 )));
1208 }
1209
1210 let mut actions_set = std::collections::BTreeSet::new();
1211 for a in actions {
1212 actions_set.insert(a);
1213 }
1214 let g = Grant {
1215 principal: principal.clone(),
1216 resource,
1217 actions: actions_set,
1218 with_grant_option,
1219 granted_by: granter.to_string(),
1220 granted_at: now_ms(),
1221 tenant,
1222 columns: None,
1223 };
1224
1225 match &principal {
1226 GrantPrincipal::User(uid) => {
1227 self.grants
1228 .write()
1229 .unwrap_or_else(|e| e.into_inner())
1230 .entry(uid.clone())
1231 .or_default()
1232 .push(g.clone());
1233 self.invalidate_permission_cache(Some(uid));
1234 }
1235 GrantPrincipal::Public => {
1236 self.public_grants
1237 .write()
1238 .unwrap_or_else(|e| e.into_inner())
1239 .push(g.clone());
1240 self.invalidate_permission_cache(None);
1241 }
1242 GrantPrincipal::Group(_) => {
1243 return Err(AuthError::Forbidden(
1244 "GROUP principals are not yet supported; use a USER or PUBLIC".to_string(),
1245 ));
1246 }
1247 }
1248
1249 self.invalidate_visible_collections_for_tenant(g.tenant.as_deref());
1254
1255 self.persist_acl_to_kv();
1256 Ok(())
1257 }
1258
1259 pub fn revoke(
1262 &self,
1263 granter_role: Role,
1264 principal: &GrantPrincipal,
1265 resource: &Resource,
1266 actions: &[Action],
1267 ) -> Result<usize, AuthError> {
1268 if granter_role != Role::Admin {
1269 return Err(AuthError::Forbidden(format!(
1270 "REVOKE requires Admin role; granter has `{:?}`",
1271 granter_role
1272 )));
1273 }
1274
1275 let removed = match principal {
1276 GrantPrincipal::User(uid) => {
1277 let mut g = self.grants.write().unwrap_or_else(|e| e.into_inner());
1278 let before = g.get(uid).map(|v| v.len()).unwrap_or(0);
1279 if let Some(list) = g.get_mut(uid) {
1280 list.retain(|gr| {
1281 !(gr.resource == *resource
1282 && (actions.iter().any(|a| gr.actions.contains(a))
1283 || (gr.actions.contains(&Action::All) && !actions.is_empty())))
1284 });
1285 }
1286 let after = g.get(uid).map(|v| v.len()).unwrap_or(0);
1287 drop(g);
1288 self.invalidate_permission_cache(Some(uid));
1289 before - after
1290 }
1291 GrantPrincipal::Public => {
1292 let mut p = self
1293 .public_grants
1294 .write()
1295 .unwrap_or_else(|e| e.into_inner());
1296 let before = p.len();
1297 p.retain(|gr| {
1298 !(gr.resource == *resource
1299 && (actions.iter().any(|a| gr.actions.contains(a))
1300 || (gr.actions.contains(&Action::All) && !actions.is_empty())))
1301 });
1302 let after = p.len();
1303 drop(p);
1304 self.invalidate_permission_cache(None);
1305 before - after
1306 }
1307 GrantPrincipal::Group(_) => 0,
1308 };
1309
1310 if removed > 0 {
1311 match principal {
1317 GrantPrincipal::User(uid) => {
1318 self.invalidate_visible_collections_for_tenant(uid.tenant.as_deref());
1319 }
1320 GrantPrincipal::Public | GrantPrincipal::Group(_) => {
1321 self.invalidate_visible_collections_cache();
1322 }
1323 }
1324 self.persist_acl_to_kv();
1325 }
1326 Ok(removed)
1327 }
1328
1329 pub fn visible_collections_for_scope(
1343 &self,
1344 tenant: Option<&str>,
1345 role: Role,
1346 principal: &str,
1347 all_collections: &[String],
1348 ) -> std::collections::HashSet<String> {
1349 let key = super::scope_cache::ScopeKey::new(tenant, principal, role);
1350 if let Some(hit) = self.visible_collections_cache.get(&key) {
1351 return hit;
1352 }
1353 let ctx = AuthzContext {
1356 principal,
1357 effective_role: role,
1358 tenant,
1359 };
1360 let mut visible = std::collections::HashSet::new();
1361 for collection in all_collections {
1362 let resource = Resource::table_from_name(collection);
1363 if self.check_grant(&ctx, Action::Select, &resource).is_ok() {
1364 visible.insert(collection.clone());
1365 }
1366 }
1367 self.visible_collections_cache.insert(key, visible.clone());
1368 visible
1369 }
1370
1371 pub fn auth_cache_stats(&self) -> super::scope_cache::AuthCacheStats {
1375 self.visible_collections_cache.stats()
1376 }
1377
1378 pub fn invalidate_visible_collections_cache(&self) {
1382 self.visible_collections_cache.invalidate_all();
1383 }
1384
1385 pub fn invalidate_visible_collections_for_tenant(&self, tenant: Option<&str>) {
1388 self.visible_collections_cache.invalidate_tenant(tenant);
1389 }
1390
1391 pub fn effective_grants(&self, uid: &UserId) -> Vec<Grant> {
1394 let mut out = Vec::new();
1395 if let Ok(g) = self.grants.read() {
1396 if let Some(list) = g.get(uid) {
1397 out.extend(list.iter().cloned());
1398 }
1399 }
1400 if let Ok(p) = self.public_grants.read() {
1401 out.extend(p.iter().cloned());
1402 }
1403 out
1404 }
1405
1406 pub fn check_grant(
1409 &self,
1410 ctx: &AuthzContext<'_>,
1411 action: Action,
1412 resource: &Resource,
1413 ) -> Result<(), AuthzError> {
1414 if ctx.effective_role == Role::Admin {
1415 return Ok(());
1416 }
1417
1418 let uid = UserId::from_parts(ctx.tenant, ctx.principal);
1419
1420 if let Ok(cache) = self.permission_cache.read() {
1422 if let Some(pc) = cache.get(&uid) {
1423 if pc.allows(resource, action) {
1424 return Ok(());
1425 }
1426 }
1427 }
1428
1429 let user_grants = self
1431 .grants
1432 .read()
1433 .ok()
1434 .and_then(|g| g.get(&uid).cloned())
1435 .unwrap_or_default();
1436 let any_user_grants = self
1437 .grants
1438 .read()
1439 .ok()
1440 .map(|g| g.values().any(|list| !list.is_empty()))
1441 .unwrap_or(false);
1442 let public_grants = self
1443 .public_grants
1444 .read()
1445 .ok()
1446 .map(|p| p.clone())
1447 .unwrap_or_default();
1448 if user_grants.is_empty() && public_grants.is_empty() && any_user_grants {
1449 return Err(AuthzError::PermissionDenied {
1450 action,
1451 resource: resource.clone(),
1452 principal: ctx.principal.to_string(),
1453 });
1454 }
1455 let view = GrantsView {
1456 user_grants: &user_grants,
1457 public_grants: &public_grants,
1458 };
1459 let result = check_grant(ctx, action, resource, &view);
1460
1461 if result.is_ok() {
1462 let pc = PermissionCache::build(&user_grants, &public_grants);
1463 if let Ok(mut cache) = self.permission_cache.write() {
1464 cache.insert(uid, pc);
1465 }
1466 }
1467 result
1468 }
1469
1470 pub fn set_user_attributes(
1476 &self,
1477 uid: &UserId,
1478 attrs: UserAttributes,
1479 ) -> Result<(), AuthError> {
1480 let users = self.users.read().map_err(lock_err)?;
1481 if !users.contains_key(uid) {
1482 return Err(AuthError::UserNotFound(uid.to_string()));
1483 }
1484 drop(users);
1485
1486 self.user_attributes
1487 .write()
1488 .unwrap_or_else(|e| e.into_inner())
1489 .insert(uid.clone(), attrs);
1490 self.invalidate_iam_cache(Some(uid));
1491 self.persist_acl_to_kv();
1492 Ok(())
1493 }
1494
1495 pub fn user_attributes(&self, uid: &UserId) -> UserAttributes {
1498 self.user_attributes
1499 .read()
1500 .ok()
1501 .and_then(|m| m.get(uid).cloned())
1502 .unwrap_or_default()
1503 }
1504
1505 pub fn add_user_to_group(&self, uid: &UserId, group: &str) -> Result<(), AuthError> {
1506 if group.trim().is_empty() {
1507 return Err(AuthError::Forbidden("group name cannot be empty".into()));
1508 }
1509 let mut attrs = self.user_attributes(uid);
1510 if !attrs.groups.iter().any(|g| g == group) {
1511 attrs.groups.push(group.to_string());
1512 attrs.groups.sort();
1513 }
1514 self.set_user_attributes(uid, attrs)
1515 }
1516
1517 pub fn remove_user_from_group(&self, uid: &UserId, group: &str) -> Result<(), AuthError> {
1518 let mut attrs = self.user_attributes(uid);
1519 attrs.groups.retain(|g| g != group);
1520 self.set_user_attributes(uid, attrs)
1521 }
1522
1523 pub fn set_user_enabled(&self, uid: &UserId, enabled: bool) -> Result<(), AuthError> {
1525 let mut users = self.users.write().map_err(lock_err)?;
1526 let user = users
1527 .get_mut(uid)
1528 .ok_or_else(|| AuthError::UserNotFound(uid.to_string()))?;
1529 user.enabled = enabled;
1530 user.updated_at = now_ms();
1531 drop(users);
1532 self.persist_to_vault();
1533 Ok(())
1534 }
1535
1536 pub fn authenticate_with_attrs(
1546 &self,
1547 tenant_id: Option<&str>,
1548 username: &str,
1549 password: &str,
1550 ) -> Result<Session, AuthError> {
1551 let uid = UserId::from_parts(tenant_id, username);
1552 let attrs = self.user_attributes(&uid);
1553
1554 if let Some(deadline) = attrs.valid_until {
1555 if now_ms() >= deadline {
1556 return Err(AuthError::Forbidden(format!(
1557 "account `{}` expired (VALID UNTIL exceeded)",
1558 uid
1559 )));
1560 }
1561 }
1562
1563 if let Some(limit) = attrs.connection_limit {
1564 let current = self
1565 .session_count_by_user
1566 .read()
1567 .ok()
1568 .and_then(|m| m.get(&uid).copied())
1569 .unwrap_or(0);
1570 if current >= limit {
1571 return Err(AuthError::Forbidden(format!(
1572 "account `{}` exceeded CONNECTION LIMIT ({})",
1573 uid, limit
1574 )));
1575 }
1576 }
1577
1578 let session = self.authenticate_in_tenant(tenant_id, username, password)?;
1579
1580 if let Ok(mut counts) = self.session_count_by_user.write() {
1581 *counts.entry(uid).or_insert(0) += 1;
1582 }
1583 Ok(session)
1584 }
1585
1586 pub fn decrement_session_count(&self, uid: &UserId) {
1589 if let Ok(mut counts) = self.session_count_by_user.write() {
1590 if let Some(c) = counts.get_mut(uid) {
1591 *c = c.saturating_sub(1);
1592 }
1593 }
1594 }
1595
1596 pub fn rehydrate_acl(&self) {
1603 let kv_snapshot: Vec<(String, String)> = self
1604 .vault_kv
1605 .read()
1606 .map(|kv| {
1607 kv.iter()
1608 .filter(|(k, _)| {
1609 k.starts_with("red.acl.grants.")
1610 || k.starts_with("red.acl.attrs.")
1611 || k == &"red.acl.public_grants"
1612 })
1613 .map(|(k, v)| (k.clone(), v.clone()))
1614 .collect()
1615 })
1616 .unwrap_or_default();
1617
1618 for (k, v) in kv_snapshot {
1619 if k == "red.acl.public_grants" {
1620 if let Some(parsed) = decode_grants_blob(&v) {
1621 *self
1622 .public_grants
1623 .write()
1624 .unwrap_or_else(|e| e.into_inner()) = parsed;
1625 }
1626 } else if let Some(suffix) = k.strip_prefix("red.acl.grants.") {
1627 if let Some(uid) = decode_uid(suffix) {
1628 if let Some(mut parsed) = decode_grants_blob(&v) {
1629 for g in parsed.iter_mut() {
1632 g.principal = GrantPrincipal::User(uid.clone());
1633 }
1634 self.grants
1635 .write()
1636 .unwrap_or_else(|e| e.into_inner())
1637 .insert(uid, parsed);
1638 }
1639 }
1640 } else if let Some(suffix) = k.strip_prefix("red.acl.attrs.") {
1641 if let Some(uid) = decode_uid(suffix) {
1642 if let Some(parsed) = decode_attrs_blob(&v) {
1643 self.user_attributes
1644 .write()
1645 .unwrap_or_else(|e| e.into_inner())
1646 .insert(uid, parsed);
1647 }
1648 }
1649 }
1650 }
1651
1652 self.permission_cache
1653 .write()
1654 .unwrap_or_else(|e| e.into_inner())
1655 .clear();
1656 }
1657
1658 fn persist_acl_to_kv(&self) {
1660 let public = self
1661 .public_grants
1662 .read()
1663 .ok()
1664 .map(|p| encode_grants_blob(&p))
1665 .unwrap_or_default();
1666 self.vault_kv_set("red.acl.public_grants".to_string(), public);
1667
1668 let snapshot: Vec<(UserId, Vec<Grant>)> = self
1669 .grants
1670 .read()
1671 .ok()
1672 .map(|g| g.iter().map(|(k, v)| (k.clone(), v.clone())).collect())
1673 .unwrap_or_default();
1674 for (uid, list) in snapshot {
1675 let key = format!("red.acl.grants.{}", encode_uid(&uid));
1676 let val = encode_grants_blob(&list);
1677 self.vault_kv_set(key, val);
1678 }
1679
1680 let attrs_snapshot: Vec<(UserId, UserAttributes)> = self
1681 .user_attributes
1682 .read()
1683 .ok()
1684 .map(|m| m.iter().map(|(k, v)| (k.clone(), v.clone())).collect())
1685 .unwrap_or_default();
1686 for (uid, attrs) in attrs_snapshot {
1687 let key = format!("red.acl.attrs.{}", encode_uid(&uid));
1688 let val = encode_attrs_blob(&attrs);
1689 self.vault_kv_set(key, val);
1690 }
1691 }
1692
1693 fn invalidate_permission_cache(&self, uid: Option<&UserId>) {
1694 if let Ok(mut cache) = self.permission_cache.write() {
1695 match uid {
1696 Some(u) => {
1697 cache.remove(u);
1698 }
1699 None => cache.clear(),
1700 }
1701 }
1702 }
1703
1704 pub fn put_policy(&self, p: Policy) -> Result<(), AuthError> {
1717 if p.id.starts_with("_grant_") || p.id.starts_with("_default_") {
1718 return Err(AuthError::Forbidden(format!(
1719 "policy id `{}` is reserved",
1720 p.id
1721 )));
1722 }
1723 self.put_policy_internal(p)
1724 }
1725
1726 pub fn put_policy_internal(&self, p: Policy) -> Result<(), AuthError> {
1731 p.validate()
1732 .map_err(|e| AuthError::Forbidden(format!("invalid policy `{}`: {e}", p.id)))?;
1733 let id = p.id.clone();
1734 self.policies
1735 .write()
1736 .unwrap_or_else(|e| e.into_inner())
1737 .insert(id, Arc::new(p));
1738 self.iam_authorization_enabled
1739 .store(true, Ordering::Release);
1740 self.iam_effective_cache
1741 .write()
1742 .unwrap_or_else(|e| e.into_inner())
1743 .clear();
1744 self.invalidate_visible_collections_cache();
1748 self.persist_iam_to_kv();
1749 Ok(())
1750 }
1751
1752 pub fn iam_authorization_enabled(&self) -> bool {
1757 self.iam_authorization_enabled.load(Ordering::Acquire)
1758 }
1759
1760 pub fn delete_policy(&self, id: &str) -> Result<(), AuthError> {
1762 let removed = self
1763 .policies
1764 .write()
1765 .unwrap_or_else(|e| e.into_inner())
1766 .remove(id)
1767 .is_some();
1768 if !removed {
1769 return Err(AuthError::Forbidden(format!("policy `{id}` not found")));
1770 }
1771 if let Ok(mut ua) = self.user_attachments.write() {
1773 for ids in ua.values_mut() {
1774 ids.retain(|p| p != id);
1775 }
1776 ua.retain(|_, v| !v.is_empty());
1777 }
1778 if let Ok(mut ga) = self.group_attachments.write() {
1779 for ids in ga.values_mut() {
1780 ids.retain(|p| p != id);
1781 }
1782 ga.retain(|_, v| !v.is_empty());
1783 }
1784 self.iam_effective_cache
1785 .write()
1786 .unwrap_or_else(|e| e.into_inner())
1787 .clear();
1788 self.invalidate_visible_collections_cache();
1791 self.persist_iam_to_kv();
1792 Ok(())
1793 }
1794
1795 pub fn list_policies(&self) -> Vec<Arc<Policy>> {
1797 let map = match self.policies.read() {
1798 Ok(g) => g,
1799 Err(_) => return Vec::new(),
1800 };
1801 let mut out: Vec<Arc<Policy>> = map.values().cloned().collect();
1802 out.sort_by(|a, b| a.id.cmp(&b.id));
1803 out
1804 }
1805
1806 pub fn get_policy(&self, id: &str) -> Option<Arc<Policy>> {
1808 self.policies.read().ok().and_then(|m| m.get(id).cloned())
1809 }
1810
1811 pub fn group_policies(&self, group: &str) -> Vec<Arc<Policy>> {
1813 let policies = self.policies.read();
1814 let attachments = self.group_attachments.read();
1815 let mut out = Vec::new();
1816 if let (Ok(p_map), Ok(ga_map)) = (policies, attachments) {
1817 if let Some(ids) = ga_map.get(group) {
1818 for id in ids {
1819 if let Some(p) = p_map.get(id) {
1820 out.push(p.clone());
1821 }
1822 }
1823 }
1824 }
1825 out.sort_by(|a, b| a.id.cmp(&b.id));
1826 out
1827 }
1828
1829 pub fn delete_synthetic_grant_policies(
1833 &self,
1834 principal: &GrantPrincipal,
1835 resource: &Resource,
1836 actions: &[Action],
1837 ) -> usize {
1838 let attached = match principal {
1839 GrantPrincipal::User(uid) => self
1840 .user_attachments
1841 .read()
1842 .ok()
1843 .and_then(|m| m.get(uid).cloned())
1844 .unwrap_or_default(),
1845 GrantPrincipal::Group(group) => self
1846 .group_attachments
1847 .read()
1848 .ok()
1849 .and_then(|m| m.get(group).cloned())
1850 .unwrap_or_default(),
1851 GrantPrincipal::Public => self
1852 .group_attachments
1853 .read()
1854 .ok()
1855 .and_then(|m| m.get(PUBLIC_IAM_GROUP).cloned())
1856 .unwrap_or_default(),
1857 };
1858 if attached.is_empty() {
1859 return 0;
1860 }
1861
1862 let mut delete_ids = Vec::new();
1863 if let Ok(policies) = self.policies.read() {
1864 for id in attached {
1865 let Some(policy) = policies.get(&id) else {
1866 continue;
1867 };
1868 if !policy.id.starts_with("_grant_") {
1869 continue;
1870 }
1871 if synthetic_grant_matches(policy, resource, actions) {
1872 delete_ids.push(policy.id.clone());
1873 }
1874 }
1875 }
1876
1877 let mut deleted = 0usize;
1878 for id in delete_ids {
1879 if self.delete_policy(&id).is_ok() {
1880 deleted += 1;
1881 }
1882 }
1883 deleted
1884 }
1885
1886 pub fn attach_policy(&self, principal: PrincipalRef, policy_id: &str) -> Result<(), AuthError> {
1889 if !self
1890 .policies
1891 .read()
1892 .map(|m| m.contains_key(policy_id))
1893 .unwrap_or(false)
1894 {
1895 return Err(AuthError::Forbidden(format!(
1896 "policy `{policy_id}` not found"
1897 )));
1898 }
1899 match &principal {
1900 PrincipalRef::User(uid) => {
1901 let mut ua = self
1902 .user_attachments
1903 .write()
1904 .unwrap_or_else(|e| e.into_inner());
1905 let list = ua.entry(uid.clone()).or_default();
1906 if !list.iter().any(|p| p == policy_id) {
1907 list.push(policy_id.to_string());
1908 }
1909 drop(ua);
1910 self.invalidate_iam_cache(Some(uid));
1911 }
1912 PrincipalRef::Group(g) => {
1913 let mut ga = self
1914 .group_attachments
1915 .write()
1916 .unwrap_or_else(|e| e.into_inner());
1917 let list = ga.entry(g.clone()).or_default();
1918 if !list.iter().any(|p| p == policy_id) {
1919 list.push(policy_id.to_string());
1920 }
1921 drop(ga);
1922 self.invalidate_iam_cache(None);
1923 }
1924 }
1925 self.persist_iam_to_kv();
1926 Ok(())
1927 }
1928
1929 pub fn detach_policy(&self, principal: PrincipalRef, policy_id: &str) -> Result<(), AuthError> {
1931 match &principal {
1932 PrincipalRef::User(uid) => {
1933 if let Ok(mut ua) = self.user_attachments.write() {
1934 if let Some(list) = ua.get_mut(uid) {
1935 list.retain(|p| p != policy_id);
1936 if list.is_empty() {
1937 ua.remove(uid);
1938 }
1939 }
1940 }
1941 self.invalidate_iam_cache(Some(uid));
1942 }
1943 PrincipalRef::Group(g) => {
1944 if let Ok(mut ga) = self.group_attachments.write() {
1945 if let Some(list) = ga.get_mut(g) {
1946 list.retain(|p| p != policy_id);
1947 if list.is_empty() {
1948 ga.remove(g);
1949 }
1950 }
1951 }
1952 self.invalidate_iam_cache(None);
1953 }
1954 }
1955 self.persist_iam_to_kv();
1956 Ok(())
1957 }
1958
1959 pub fn effective_policies(&self, user: &UserId) -> Vec<Arc<Policy>> {
1963 if let Ok(cache) = self.iam_effective_cache.read() {
1964 if let Some(hit) = cache.get(user) {
1965 return hit.clone();
1966 }
1967 }
1968 let policies = self.policies.read();
1969 let user_attachments = self.user_attachments.read();
1970 let group_attachments = self.group_attachments.read();
1971 let mut groups = self
1972 .user_attributes
1973 .read()
1974 .ok()
1975 .and_then(|m| m.get(user).map(|attrs| attrs.groups.clone()))
1976 .unwrap_or_default();
1977 groups.insert(0, PUBLIC_IAM_GROUP.to_string());
1978 let mut out: Vec<Arc<Policy>> = Vec::new();
1979 if let (Ok(p_map), Ok(ua_map), Ok(ga_map)) = (policies, user_attachments, group_attachments)
1980 {
1981 for group in groups {
1982 if let Some(ids) = ga_map.get(&group) {
1983 for id in ids {
1984 if let Some(p) = p_map.get(id) {
1985 out.push(p.clone());
1986 }
1987 }
1988 }
1989 }
1990 if let Some(ids) = ua_map.get(user) {
1991 for id in ids {
1992 if let Some(p) = p_map.get(id) {
1993 out.push(p.clone());
1994 }
1995 }
1996 }
1997 }
1998 if let Ok(mut cache) = self.iam_effective_cache.write() {
1999 cache.insert(user.clone(), out.clone());
2000 }
2001 out
2002 }
2003
2004 pub fn simulate(
2007 &self,
2008 principal: &UserId,
2009 action: &str,
2010 resource: &ResourceRef,
2011 ctx_extras: SimCtx,
2012 ) -> SimulationOutcome {
2013 let user_role = self
2014 .users
2015 .read()
2016 .ok()
2017 .and_then(|u| u.get(principal).map(|u| u.role));
2018 let principal_is_admin_role = user_role == Some(Role::Admin);
2019 let now = ctx_extras.now_ms.unwrap_or_else(now_ms);
2020 let ctx = EvalContext {
2021 principal_tenant: principal.tenant.clone(),
2022 current_tenant: ctx_extras.current_tenant,
2023 peer_ip: ctx_extras.peer_ip,
2024 mfa_present: ctx_extras.mfa_present,
2025 now_ms: now,
2026 principal_is_admin_role,
2027 };
2028 let pols = self.effective_policies(principal);
2029 let refs: Vec<&Policy> = pols.iter().map(|p| p.as_ref()).collect();
2030 iam_policies::simulate(&refs, action, resource, &ctx)
2031 }
2032
2033 pub fn check_policy_authz(
2036 &self,
2037 principal: &UserId,
2038 action: &str,
2039 resource: &ResourceRef,
2040 ctx: &EvalContext,
2041 ) -> bool {
2042 let pols = self.effective_policies(principal);
2043 let refs: Vec<&Policy> = pols.iter().map(|p| p.as_ref()).collect();
2044 let decision = iam_policies::evaluate(&refs, action, resource, ctx);
2045 matches!(
2046 decision,
2047 iam_policies::Decision::Allow { .. } | iam_policies::Decision::AdminBypass
2048 )
2049 }
2050
2051 pub fn check_column_projection_authz(
2055 &self,
2056 principal: &UserId,
2057 request: &ColumnAccessRequest,
2058 ctx: &EvalContext,
2059 ) -> ColumnPolicyOutcome {
2060 let pols = self.effective_policies(principal);
2061 let refs: Vec<&Policy> = pols.iter().map(|p| p.as_ref()).collect();
2062 ColumnPolicyGate::new(&refs).evaluate(request, ctx)
2063 }
2064
2065 fn invalidate_iam_cache(&self, uid: Option<&UserId>) {
2066 if let Ok(mut cache) = self.iam_effective_cache.write() {
2067 match uid {
2068 Some(u) => {
2069 cache.remove(u);
2070 }
2071 None => cache.clear(),
2072 }
2073 }
2074 }
2075
2076 pub fn invalidate_all_iam_cache(&self) {
2080 self.invalidate_iam_cache(None);
2081 }
2082
2083 pub fn rehydrate_iam(&self) {
2091 let mut enabled = self
2092 .vault_kv_get("red.iam.enabled")
2093 .map(|v| v == "true")
2094 .unwrap_or(false);
2095 if let Some(blob) = self.vault_kv_get("red.iam.policies") {
2097 if let Ok(val) = crate::serde_json::from_str::<crate::serde_json::Value>(&blob) {
2098 if let Some(obj) = val.as_object() {
2099 let mut map = HashMap::new();
2100 for (id, body) in obj.iter() {
2101 let s = body.to_string_compact();
2102 if let Ok(p) = Policy::from_json_str(&s) {
2103 map.insert(id.clone(), Arc::new(p));
2104 }
2105 }
2106 if !map.is_empty() {
2107 enabled = true;
2108 }
2109 *self.policies.write().unwrap_or_else(|e| e.into_inner()) = map;
2110 }
2111 }
2112 }
2113 if let Some(blob) = self.vault_kv_get("red.iam.attachments.users") {
2115 if let Ok(val) = crate::serde_json::from_str::<crate::serde_json::Value>(&blob) {
2116 if let Some(obj) = val.as_object() {
2117 let mut map: HashMap<UserId, Vec<String>> = HashMap::new();
2118 for (encoded_uid, ids_v) in obj.iter() {
2119 let Some(uid) = decode_uid(encoded_uid) else {
2120 continue;
2121 };
2122 if let Some(arr) = ids_v.as_array() {
2123 let ids: Vec<String> = arr
2124 .iter()
2125 .filter_map(|v| v.as_str().map(|s| s.to_string()))
2126 .collect();
2127 map.insert(uid, ids);
2128 }
2129 }
2130 *self
2131 .user_attachments
2132 .write()
2133 .unwrap_or_else(|e| e.into_inner()) = map;
2134 }
2135 }
2136 }
2137 if let Some(blob) = self.vault_kv_get("red.iam.attachments.groups") {
2139 if let Ok(val) = crate::serde_json::from_str::<crate::serde_json::Value>(&blob) {
2140 if let Some(obj) = val.as_object() {
2141 let mut map: HashMap<String, Vec<String>> = HashMap::new();
2142 for (g, ids_v) in obj.iter() {
2143 if let Some(arr) = ids_v.as_array() {
2144 let ids: Vec<String> = arr
2145 .iter()
2146 .filter_map(|v| v.as_str().map(|s| s.to_string()))
2147 .collect();
2148 map.insert(g.clone(), ids);
2149 }
2150 }
2151 *self
2152 .group_attachments
2153 .write()
2154 .unwrap_or_else(|e| e.into_inner()) = map;
2155 }
2156 }
2157 }
2158 self.iam_authorization_enabled
2159 .store(enabled, Ordering::Release);
2160 self.invalidate_iam_cache(None);
2161 }
2162
2163 fn persist_iam_to_kv(&self) {
2166 let enabled = if self.iam_authorization_enabled() {
2167 "true"
2168 } else {
2169 "false"
2170 };
2171 self.vault_kv_set("red.iam.enabled".to_string(), enabled.to_string());
2172
2173 let policies_obj = {
2175 let map = self.policies.read().unwrap_or_else(|e| e.into_inner());
2176 let mut obj = crate::serde_json::Map::new();
2177 for (id, p) in map.iter() {
2178 let s = p.to_json_string();
2179 if let Ok(v) = crate::serde_json::from_str::<crate::serde_json::Value>(&s) {
2180 obj.insert(id.clone(), v);
2181 }
2182 }
2183 crate::serde_json::Value::Object(obj).to_string_compact()
2184 };
2185 self.vault_kv_set("red.iam.policies".to_string(), policies_obj);
2186
2187 let users_obj = {
2189 let map = self
2190 .user_attachments
2191 .read()
2192 .unwrap_or_else(|e| e.into_inner());
2193 let mut obj = crate::serde_json::Map::new();
2194 for (uid, ids) in map.iter() {
2195 let arr = crate::serde_json::Value::Array(
2196 ids.iter()
2197 .map(|s| crate::serde_json::Value::String(s.clone()))
2198 .collect(),
2199 );
2200 obj.insert(encode_uid(uid), arr);
2201 }
2202 crate::serde_json::Value::Object(obj).to_string_compact()
2203 };
2204 self.vault_kv_set("red.iam.attachments.users".to_string(), users_obj);
2205
2206 let groups_obj = {
2208 let map = self
2209 .group_attachments
2210 .read()
2211 .unwrap_or_else(|e| e.into_inner());
2212 let mut obj = crate::serde_json::Map::new();
2213 for (g, ids) in map.iter() {
2214 let arr = crate::serde_json::Value::Array(
2215 ids.iter()
2216 .map(|s| crate::serde_json::Value::String(s.clone()))
2217 .collect(),
2218 );
2219 obj.insert(g.clone(), arr);
2220 }
2221 crate::serde_json::Value::Object(obj).to_string_compact()
2222 };
2223 self.vault_kv_set("red.iam.attachments.groups".to_string(), groups_obj);
2224 }
2225}
2226
2227fn synthetic_grant_matches(policy: &Policy, resource: &Resource, actions: &[Action]) -> bool {
2228 policy.statements.iter().any(|st| {
2229 st.effect == crate::auth::policies::Effect::Allow
2230 && st.condition.is_none()
2231 && grant_actions_overlap(&st.actions, actions)
2232 && grant_resource_matches(&st.resources, resource)
2233 })
2234}
2235
2236fn grant_actions_overlap(
2237 patterns: &[crate::auth::policies::ActionPattern],
2238 actions: &[Action],
2239) -> bool {
2240 if actions.contains(&Action::All) {
2241 return true;
2242 }
2243 patterns.iter().any(|pat| match pat {
2244 crate::auth::policies::ActionPattern::Wildcard => true,
2245 crate::auth::policies::ActionPattern::Exact(s) => {
2246 actions.iter().any(|a| s.eq_ignore_ascii_case(a.as_str()))
2247 }
2248 crate::auth::policies::ActionPattern::Prefix(_) => false,
2249 })
2250}
2251
2252fn grant_resource_matches(
2253 patterns: &[crate::auth::policies::ResourcePattern],
2254 resource: &Resource,
2255) -> bool {
2256 let expected = grant_resource_pattern(resource);
2257 patterns.iter().any(|pat| pat == &expected)
2258}
2259
2260fn grant_resource_pattern(resource: &Resource) -> crate::auth::policies::ResourcePattern {
2261 use crate::auth::policies::ResourcePattern;
2262
2263 match resource {
2264 Resource::Database => ResourcePattern::Glob("table:*".to_string()),
2265 Resource::Schema(s) => ResourcePattern::Glob(format!("table:{s}.*")),
2266 Resource::Table { schema, table } => ResourcePattern::Exact {
2267 kind: "table".to_string(),
2268 name: match schema {
2269 Some(s) => format!("{s}.{table}"),
2270 None => table.clone(),
2271 },
2272 },
2273 Resource::Function { schema, name } => ResourcePattern::Exact {
2274 kind: "function".to_string(),
2275 name: match schema {
2276 Some(s) => format!("{s}.{name}"),
2277 None => name.clone(),
2278 },
2279 },
2280 }
2281}
2282
2283fn encode_uid(uid: &UserId) -> String {
2299 match &uid.tenant {
2300 Some(t) => format!("{}/{}", t, uid.username),
2301 None => format!("*/{}", uid.username),
2302 }
2303}
2304
2305fn decode_uid(s: &str) -> Option<UserId> {
2306 let (tenant, username) = s.split_once('/')?;
2307 Some(if tenant == "*" {
2308 UserId::platform(username)
2309 } else {
2310 UserId::scoped(tenant, username)
2311 })
2312}
2313
2314fn encode_resource(r: &Resource) -> String {
2315 match r {
2316 Resource::Database => "db".into(),
2317 Resource::Schema(s) => format!("schema:{}", s),
2318 Resource::Table { schema, table } => {
2319 format!("table:{}:{}", schema.as_deref().unwrap_or("*"), table)
2320 }
2321 Resource::Function { schema, name } => {
2322 format!("func:{}:{}", schema.as_deref().unwrap_or("*"), name)
2323 }
2324 }
2325}
2326
2327fn decode_resource(s: &str) -> Option<Resource> {
2328 if s == "db" {
2329 return Some(Resource::Database);
2330 }
2331 if let Some(rest) = s.strip_prefix("schema:") {
2332 return Some(Resource::Schema(rest.to_string()));
2333 }
2334 if let Some(rest) = s.strip_prefix("table:") {
2335 let (schema, table) = rest.split_once(':')?;
2336 return Some(Resource::Table {
2337 schema: if schema == "*" {
2338 None
2339 } else {
2340 Some(schema.to_string())
2341 },
2342 table: table.to_string(),
2343 });
2344 }
2345 if let Some(rest) = s.strip_prefix("func:") {
2346 let (schema, name) = rest.split_once(':')?;
2347 return Some(Resource::Function {
2348 schema: if schema == "*" {
2349 None
2350 } else {
2351 Some(schema.to_string())
2352 },
2353 name: name.to_string(),
2354 });
2355 }
2356 None
2357}
2358
2359fn encode_grants_blob(grants: &[Grant]) -> String {
2360 let mut out = String::new();
2361 for g in grants {
2362 let actions: Vec<&str> = g.actions.iter().map(|a| a.as_str()).collect();
2363 out.push_str(&format!(
2364 "GRANT|{}|{}|{}|{}|{}|{}\n",
2365 encode_resource(&g.resource),
2366 actions.join(","),
2367 g.with_grant_option,
2368 g.tenant.as_deref().unwrap_or("*"),
2369 g.granted_by,
2370 g.granted_at,
2371 ));
2372 }
2373 out
2374}
2375
2376fn decode_grants_blob(s: &str) -> Option<Vec<Grant>> {
2377 let mut out = Vec::new();
2378 for line in s.lines() {
2379 if line.is_empty() {
2380 continue;
2381 }
2382 let parts: Vec<&str> = line.split('|').collect();
2383 if parts.len() != 7 || parts[0] != "GRANT" {
2384 return None;
2385 }
2386 let resource = decode_resource(parts[1])?;
2387 let mut actions = std::collections::BTreeSet::new();
2388 for token in parts[2].split(',') {
2389 if let Some(a) = Action::from_keyword(token) {
2390 actions.insert(a);
2391 }
2392 }
2393 let with_grant_option = parts[3] == "true";
2394 let tenant = if parts[4] == "*" {
2395 None
2396 } else {
2397 Some(parts[4].to_string())
2398 };
2399 let granted_by = parts[5].to_string();
2400 let granted_at: u128 = parts[6].parse().unwrap_or(0);
2401 out.push(Grant {
2402 principal: GrantPrincipal::Public,
2405 resource,
2406 actions,
2407 with_grant_option,
2408 granted_by,
2409 granted_at,
2410 tenant,
2411 columns: None,
2412 });
2413 }
2414 Some(out)
2415}
2416
2417fn encode_attrs_blob(a: &UserAttributes) -> String {
2418 let valid = a
2419 .valid_until
2420 .map(|t| t.to_string())
2421 .unwrap_or_else(|| "*".into());
2422 let limit = a
2423 .connection_limit
2424 .map(|l| l.to_string())
2425 .unwrap_or_else(|| "*".into());
2426 let path = a.search_path.clone().unwrap_or_else(|| "*".into());
2427 let groups = if a.groups.is_empty() {
2428 "*".to_string()
2429 } else {
2430 a.groups.join(",")
2431 };
2432 format!("ATTR|{}|{}|{}|{}\n", valid, limit, path, groups)
2433}
2434
2435fn decode_attrs_blob(s: &str) -> Option<UserAttributes> {
2436 let line = s.lines().next()?;
2437 let parts: Vec<&str> = line.split('|').collect();
2438 if !(parts.len() == 4 || parts.len() == 5) || parts[0] != "ATTR" {
2439 return None;
2440 }
2441 let groups = if parts.get(4).copied().unwrap_or("*") == "*" {
2442 Vec::new()
2443 } else {
2444 parts[4]
2445 .split(',')
2446 .filter(|g| !g.is_empty())
2447 .map(|g| g.to_string())
2448 .collect()
2449 };
2450 Some(UserAttributes {
2451 valid_until: if parts[1] == "*" {
2452 None
2453 } else {
2454 parts[1].parse().ok()
2455 },
2456 connection_limit: if parts[2] == "*" {
2457 None
2458 } else {
2459 parts[2].parse().ok()
2460 },
2461 search_path: if parts[3] == "*" {
2462 None
2463 } else {
2464 Some(parts[3].to_string())
2465 },
2466 groups,
2467 })
2468}
2469
2470fn make_scram_verifier(password: &str) -> crate::auth::scram::ScramVerifier {
2480 let salt = random_bytes(16);
2481 crate::auth::scram::ScramVerifier::from_password(
2482 password,
2483 salt,
2484 crate::auth::scram::DEFAULT_ITER,
2485 )
2486}
2487
2488pub(crate) fn hash_password(password: &str) -> String {
2492 let salt = random_bytes(16);
2493 let params = auth_argon2_params();
2494 let hash = derive_key(password.as_bytes(), &salt, ¶ms);
2495 format!("argon2id${}${}", hex::encode(&salt), hex::encode(&hash))
2496}
2497
2498pub(crate) fn verify_password(password: &str, stored_hash: &str) -> bool {
2500 let parts: Vec<&str> = stored_hash.splitn(3, '$').collect();
2501 if parts.len() != 3 || parts[0] != "argon2id" {
2502 return false;
2503 }
2504
2505 let salt = match hex::decode(parts[1]) {
2506 Ok(s) => s,
2507 Err(_) => return false,
2508 };
2509
2510 let expected_hash = match hex::decode(parts[2]) {
2511 Ok(h) => h,
2512 Err(_) => return false,
2513 };
2514
2515 let params = auth_argon2_params();
2516 let computed = derive_key(password.as_bytes(), &salt, ¶ms);
2517 constant_time_eq(&computed, &expected_hash)
2518}
2519
2520fn constant_time_eq(a: &[u8], b: &[u8]) -> bool {
2522 if a.len() != b.len() {
2523 return false;
2524 }
2525 let mut diff: u8 = 0;
2526 for (x, y) in a.iter().zip(b.iter()) {
2527 diff |= x ^ y;
2528 }
2529 diff == 0
2530}
2531
2532fn generate_session_token() -> String {
2537 format!("rs_{}", hex::encode(random_bytes(32)))
2538}
2539
2540fn generate_api_key() -> String {
2541 format!("rk_{}", hex::encode(random_bytes(32)))
2542}
2543
2544fn random_hex(n: usize) -> String {
2546 hex::encode(random_bytes(n))
2547}
2548
2549pub(crate) fn random_bytes(n: usize) -> Vec<u8> {
2552 let mut buf = vec![0u8; n.max(32)];
2553 if os_random::fill_bytes(&mut buf).is_err() {
2554 let seed = now_ms().to_le_bytes();
2556 for (i, byte) in buf.iter_mut().enumerate() {
2557 *byte = seed[i % seed.len()] ^ (i as u8);
2558 }
2559 }
2560 let digest = sha256(&buf);
2562 if n <= 32 {
2563 digest[..n].to_vec()
2564 } else {
2565 let mut out = Vec::with_capacity(n);
2567 let mut prev = digest;
2568 while out.len() < n {
2569 out.extend_from_slice(&prev[..std::cmp::min(32, n - out.len())]);
2570 prev = sha256(&prev);
2571 }
2572 out
2573 }
2574}
2575
2576fn lock_err<T>(_: T) -> AuthError {
2581 AuthError::Internal("lock poisoned".to_string())
2582}
2583
2584#[cfg(test)]
2589mod tests {
2590 use super::*;
2591
2592 fn test_config() -> AuthConfig {
2593 AuthConfig {
2594 enabled: true,
2595 session_ttl_secs: 60,
2596 require_auth: true,
2597 auto_encrypt_storage: false,
2598 vault_enabled: false,
2599 cert: Default::default(),
2600 oauth: Default::default(),
2601 }
2602 }
2603
2604 #[test]
2605 fn test_create_and_list_users() {
2606 let store = AuthStore::new(test_config());
2607 store.create_user("alice", "pass1", Role::Admin).unwrap();
2608 store.create_user("bob", "pass2", Role::Read).unwrap();
2609
2610 let users = store.list_users();
2611 assert_eq!(users.len(), 2);
2612 for u in &users {
2614 assert!(u.password_hash.is_empty());
2615 }
2616 }
2617
2618 #[test]
2619 fn test_create_duplicate_user() {
2620 let store = AuthStore::new(test_config());
2621 store.create_user("alice", "pass", Role::Admin).unwrap();
2622 let err = store.create_user("alice", "pass2", Role::Read).unwrap_err();
2623 assert!(matches!(err, AuthError::UserExists(_)));
2624 }
2625
2626 #[test]
2627 fn test_authenticate_and_validate() {
2628 let store = AuthStore::new(test_config());
2629 store.create_user("alice", "secret", Role::Write).unwrap();
2630
2631 let session = store.authenticate("alice", "secret").unwrap();
2632 assert!(session.token.starts_with("rs_"));
2633
2634 let (username, role) = store.validate_token(&session.token).unwrap();
2635 assert_eq!(username, "alice");
2636 assert_eq!(role, Role::Write);
2637 }
2638
2639 #[test]
2640 fn test_authenticate_wrong_password() {
2641 let store = AuthStore::new(test_config());
2642 store.create_user("alice", "secret", Role::Read).unwrap();
2643
2644 let err = store.authenticate("alice", "wrong").unwrap_err();
2645 assert!(matches!(err, AuthError::InvalidCredentials));
2646 }
2647
2648 #[test]
2649 fn test_api_key_lifecycle() {
2650 let store = AuthStore::new(test_config());
2651 store.create_user("alice", "pass", Role::Admin).unwrap();
2652
2653 let key = store
2654 .create_api_key("alice", "ci-token", Role::Write)
2655 .unwrap();
2656 assert!(key.key.starts_with("rk_"));
2657
2658 let (username, role) = store.validate_token(&key.key).unwrap();
2659 assert_eq!(username, "alice");
2660 assert_eq!(role, Role::Write);
2661
2662 store.revoke_api_key(&key.key).unwrap();
2663 assert!(store.validate_token(&key.key).is_none());
2664 }
2665
2666 #[test]
2667 fn test_api_key_role_exceeded() {
2668 let store = AuthStore::new(test_config());
2669 store.create_user("bob", "pass", Role::Read).unwrap();
2670
2671 let err = store
2672 .create_api_key("bob", "escalate", Role::Admin)
2673 .unwrap_err();
2674 assert!(matches!(err, AuthError::RoleExceeded { .. }));
2675 }
2676
2677 #[test]
2678 fn test_change_password() {
2679 let store = AuthStore::new(test_config());
2680 store.create_user("alice", "old", Role::Write).unwrap();
2681
2682 store.change_password("alice", "old", "new").unwrap();
2683
2684 assert!(store.authenticate("alice", "old").is_err());
2686 assert!(store.authenticate("alice", "new").is_ok());
2688 }
2689
2690 #[test]
2691 fn test_change_role() {
2692 let store = AuthStore::new(test_config());
2693 store.create_user("alice", "pass", Role::Admin).unwrap();
2694 store.create_api_key("alice", "key1", Role::Admin).unwrap();
2695
2696 store.change_role("alice", Role::Read).unwrap();
2697
2698 let users = store.list_users();
2700 let alice = users.iter().find(|u| u.username == "alice").unwrap();
2701 assert_eq!(alice.role, Role::Read);
2702
2703 assert_eq!(alice.api_keys[0].role, Role::Read);
2705 }
2706
2707 #[test]
2708 fn test_delete_user() {
2709 let store = AuthStore::new(test_config());
2710 store.create_user("alice", "pass", Role::Admin).unwrap();
2711 let key = store.create_api_key("alice", "key1", Role::Read).unwrap();
2712 let session = store.authenticate("alice", "pass").unwrap();
2713
2714 store.delete_user("alice").unwrap();
2715
2716 assert!(store.validate_token(&key.key).is_none());
2717 assert!(store.validate_token(&session.token).is_none());
2718 assert!(store.list_users().is_empty());
2719 }
2720
2721 #[test]
2722 fn test_revoke_session() {
2723 let store = AuthStore::new(test_config());
2724 store.create_user("alice", "pass", Role::Read).unwrap();
2725 let session = store.authenticate("alice", "pass").unwrap();
2726
2727 store.revoke_session(&session.token);
2728 assert!(store.validate_token(&session.token).is_none());
2729 }
2730
2731 #[test]
2732 fn test_password_hash_format() {
2733 let hash = hash_password("test");
2734 assert!(hash.starts_with("argon2id$"));
2735 let parts: Vec<&str> = hash.splitn(3, '$').collect();
2736 assert_eq!(parts.len(), 3);
2737 assert_eq!(parts[1].len(), 32);
2739 assert_eq!(parts[2].len(), 64);
2741 }
2742
2743 #[test]
2744 fn test_constant_time_eq() {
2745 assert!(constant_time_eq(b"hello", b"hello"));
2746 assert!(!constant_time_eq(b"hello", b"world"));
2747 assert!(!constant_time_eq(b"short", b"longer"));
2748 }
2749
2750 #[test]
2751 fn test_bootstrap_seals_permanently() {
2752 let store = AuthStore::new(test_config());
2753
2754 assert!(store.needs_bootstrap());
2755 assert!(!store.is_bootstrapped());
2756
2757 let result = store.bootstrap("admin", "secret");
2759 assert!(result.is_ok());
2760 let br = result.unwrap();
2761 assert_eq!(br.user.username, "admin");
2762 assert_eq!(br.user.role, Role::Admin);
2763 assert!(br.api_key.key.starts_with("rk_"));
2764 assert!(br.certificate.is_none());
2766
2767 assert!(!store.needs_bootstrap());
2769 assert!(store.is_bootstrapped());
2770
2771 let result = store.bootstrap("admin2", "secret2");
2773 assert!(result.is_err());
2774 let err = result.unwrap_err();
2775 assert!(err.to_string().contains("sealed permanently"));
2776
2777 assert_eq!(store.list_users().len(), 1);
2779 assert_eq!(store.list_users()[0].username, "admin");
2780 }
2781
2782 #[test]
2783 fn test_bootstrap_after_manual_user_creation() {
2784 let store = AuthStore::new(test_config());
2785
2786 store.create_user("existing", "pass", Role::Read).unwrap();
2788
2789 assert!(!store.needs_bootstrap()); }
2793
2794 #[test]
2799 fn test_same_username_two_tenants_distinct() {
2800 let store = AuthStore::new(test_config());
2801 store
2802 .create_user_in_tenant(Some("acme"), "alice", "pw-acme", Role::Write)
2803 .unwrap();
2804 store
2805 .create_user_in_tenant(Some("globex"), "alice", "pw-globex", Role::Read)
2806 .unwrap();
2807
2808 let users = store.list_users();
2810 assert_eq!(users.len(), 2);
2811
2812 assert!(store
2814 .authenticate_in_tenant(Some("acme"), "alice", "pw-acme")
2815 .is_ok());
2816 assert!(store
2817 .authenticate_in_tenant(Some("globex"), "alice", "pw-globex")
2818 .is_ok());
2819
2820 assert!(store
2822 .authenticate_in_tenant(Some("acme"), "alice", "pw-globex")
2823 .is_err());
2824 assert!(store
2825 .authenticate_in_tenant(Some("globex"), "alice", "pw-acme")
2826 .is_err());
2827 }
2828
2829 #[test]
2830 fn test_session_carries_tenant() {
2831 let store = AuthStore::new(test_config());
2832 store
2833 .create_user_in_tenant(Some("acme"), "alice", "pw", Role::Admin)
2834 .unwrap();
2835 let session = store
2836 .authenticate_in_tenant(Some("acme"), "alice", "pw")
2837 .unwrap();
2838 assert_eq!(session.tenant_id.as_deref(), Some("acme"));
2839
2840 let (id, role) = store.validate_token_full(&session.token).unwrap();
2841 assert_eq!(id.tenant.as_deref(), Some("acme"));
2842 assert_eq!(id.username, "alice");
2843 assert_eq!(role, Role::Admin);
2844 }
2845
2846 #[test]
2847 fn test_platform_user_has_no_tenant() {
2848 let store = AuthStore::new(test_config());
2849 store.create_user("admin", "pw", Role::Admin).unwrap();
2850 let session = store.authenticate("admin", "pw").unwrap();
2851 assert!(session.tenant_id.is_none());
2852
2853 let (id, _) = store.validate_token_full(&session.token).unwrap();
2854 assert!(id.tenant.is_none());
2855 }
2856
2857 #[test]
2858 fn test_lookup_scram_verifier_global_resolves_platform() {
2859 let store = AuthStore::new(test_config());
2860 store.create_user("admin", "pw", Role::Admin).unwrap();
2861 store
2862 .create_user_in_tenant(Some("acme"), "admin", "pw", Role::Admin)
2863 .unwrap();
2864
2865 let v = store.lookup_scram_verifier_global("admin");
2867 assert!(v.is_some());
2868
2869 let v_acme = store.lookup_scram_verifier(&UserId::scoped("acme", "admin"));
2871 assert!(v_acme.is_some());
2872
2873 assert_ne!(v.unwrap().salt, v_acme.unwrap().salt);
2875 }
2876
2877 #[test]
2878 fn test_delete_in_tenant_does_not_touch_other_tenant() {
2879 let store = AuthStore::new(test_config());
2880 store
2881 .create_user_in_tenant(Some("acme"), "alice", "pw", Role::Admin)
2882 .unwrap();
2883 store
2884 .create_user_in_tenant(Some("globex"), "alice", "pw", Role::Admin)
2885 .unwrap();
2886
2887 store.delete_user_in_tenant(Some("acme"), "alice").unwrap();
2888
2889 assert!(store
2891 .authenticate_in_tenant(Some("globex"), "alice", "pw")
2892 .is_ok());
2893 assert!(store
2895 .authenticate_in_tenant(Some("acme"), "alice", "pw")
2896 .is_err());
2897 }
2898
2899 #[test]
2900 fn test_user_id_display() {
2901 assert_eq!(UserId::platform("admin").to_string(), "admin");
2902 assert_eq!(UserId::scoped("acme", "alice").to_string(), "acme/alice");
2903 }
2904}