cli: avoid data-dir initialization for version; create db parent dirs; redact generated passwords in CLI output
- Prevent 'nx9-wg version' from creating data directories by avoiding database initialization. - Create parent directories when an explicit --database path is provided. - Redact printed generated administrator passwords; announce file path or redact instead. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
commit
2ac6c81dfe
140 files changed
+31342
No files matched your search
@@ -0,0 +1,80 @@
|
||||
//! Tests for Administrator repository operations and security invariants.
|
||||
|
||||
use nx9_wg_core::crypto::{hash_password, verify_password};
|
||||
use nx9_wg_db::Store;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_admin_single_identity_and_crud() {
|
||||
let store = Store::connect_in_memory().await.expect("connect");
|
||||
store.migrate().await.expect("migrate");
|
||||
|
||||
// Initially no admin exists
|
||||
assert!(!store.admin_exists().await.expect("admin_exists"));
|
||||
assert!(store.get_admin().await.expect("get_admin").is_none());
|
||||
|
||||
// Create single admin with Argon2id hash
|
||||
let password = "CorrectHorseBatteryStaple123!";
|
||||
let password_hash = hash_password(password).expect("hash password");
|
||||
let admin = store
|
||||
.create_admin("admin", &password_hash)
|
||||
.await
|
||||
.expect("create_admin");
|
||||
|
||||
assert_eq!(admin.id, 1);
|
||||
assert_eq!(admin.username, "admin");
|
||||
assert!(!admin.totp_enabled);
|
||||
assert!(admin.last_login_at.is_none());
|
||||
|
||||
// Verify admin_exists returns true
|
||||
assert!(store.admin_exists().await.expect("admin_exists"));
|
||||
|
||||
// Verify lookup by username
|
||||
let fetched = store
|
||||
.get_admin_by_username("admin")
|
||||
.await
|
||||
.expect("get_admin_by_username")
|
||||
.expect("admin found");
|
||||
assert_eq!(fetched.id, 1);
|
||||
assert!(verify_password(password, &fetched.password_hash).expect("verify password"));
|
||||
|
||||
// Reject second admin creation
|
||||
let second_res = store.create_admin("admin2", "hash2").await;
|
||||
assert!(second_res.is_err(), "second admin must be rejected");
|
||||
|
||||
// Test password change
|
||||
let new_password = "NewSuperSecurePassword456!";
|
||||
let new_hash = hash_password(new_password).expect("new hash");
|
||||
store
|
||||
.update_admin_password(&new_hash)
|
||||
.await
|
||||
.expect("update_admin_password");
|
||||
|
||||
let updated = store.get_admin().await.expect("get_admin").expect("admin");
|
||||
assert!(verify_password(new_password, &updated.password_hash).expect("verify new"));
|
||||
assert!(!verify_password(password, &updated.password_hash).expect("old password fails"));
|
||||
|
||||
// Test TOTP update
|
||||
store
|
||||
.update_admin_totp(Some("JBSWY3DPEHPK3PXP"), true)
|
||||
.await
|
||||
.expect("update_admin_totp");
|
||||
let totp_admin = store.get_admin().await.expect("get_admin").expect("admin");
|
||||
assert!(totp_admin.totp_enabled);
|
||||
assert_eq!(totp_admin.totp_secret.as_deref(), Some("JBSWY3DPEHPK3PXP"));
|
||||
|
||||
// Test recording login
|
||||
store
|
||||
.record_admin_login(Some("192.168.1.100"))
|
||||
.await
|
||||
.expect("record_admin_login");
|
||||
let login_admin = store.get_admin().await.expect("get_admin").expect("admin");
|
||||
assert!(login_admin.last_login_at.is_some());
|
||||
assert_eq!(login_admin.last_login_ip.as_deref(), Some("192.168.1.100"));
|
||||
|
||||
// Verify Debug formatting redacts password_hash and totp_secret
|
||||
let debug_str = format!("{:?}", login_admin);
|
||||
assert!(debug_str.contains("[REDACTED]"));
|
||||
assert!(!debug_str.contains(password));
|
||||
assert!(!debug_str.contains(new_password));
|
||||
assert!(!debug_str.contains("JBSWY3DPEHPK3PXP"));
|
||||
}
|
||||
Reference in new issue
Block a user