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:
thakaresandCopilot committed 2026-08-16 16:26:24 +05:30
commit 2ac6c81dfe
140 files changed
+31342

No files matched your search

@@ -0,0 +1,111 @@
//! Single Administrator account, password, API tokens, and session management view model.
use chrono::NaiveDateTime;
use serde::{Deserialize, Serialize};
/// Active session view row.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SessionRowView {
pub id: String,
pub ip_address: String,
pub user_agent: String,
pub created_at: NaiveDateTime,
pub expires_at: NaiveDateTime,
pub is_current: bool,
}
/// Active API token view row.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ApiTokenRowView {
pub id: String,
pub name: String,
pub created_at: NaiveDateTime,
pub expires_at: Option<NaiveDateTime>,
pub last_used_at: Option<NaiveDateTime>,
pub is_revoked: bool,
}
/// Administrator management view state.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AdministratorState {
// Card 1: Account
pub username: String,
pub last_login_at: Option<NaiveDateTime>,
pub last_login_ip: Option<String>,
pub totp_enabled: bool,
pub active_session_count: usize,
// Card 2: Password change
pub current_password: String,
pub new_password: String,
pub confirm_password: String,
// Card 3: API Tokens
pub new_token_name: String,
pub new_token_expires_days: Option<u32>,
pub created_token_plaintext: Option<String>,
pub tokens: Vec<ApiTokenRowView>,
// Card 4: Sessions
pub sessions: Vec<SessionRowView>,
// Feedback
pub success_message: Option<String>,
pub error_message: Option<String>,
}
impl Default for AdministratorState {
fn default() -> Self {
Self {
username: "admin".to_string(),
last_login_at: None,
last_login_ip: None,
totp_enabled: false,
active_session_count: 1,
current_password: String::new(),
new_password: String::new(),
confirm_password: String::new(),
new_token_name: String::new(),
new_token_expires_days: Some(30),
created_token_plaintext: None,
tokens: Vec::new(),
sessions: Vec::new(),
success_message: None,
error_message: None,
}
}
}
impl AdministratorState {
pub fn validate_password_change(&self) -> Result<(), &'static str> {
if self.current_password.is_empty() {
return Err("Current password is required");
}
if self.new_password.len() < 8 {
return Err("New password must be at least 8 characters long");
}
if self.new_password != self.confirm_password {
return Err("New password and confirmation do not match");
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_admin_password_validation() {
let mut state = AdministratorState::default();
assert!(state.validate_password_change().is_err());
state.current_password = "OldPassword123!".to_string();
state.new_password = "NewPassword123!".to_string();
state.confirm_password = "MismatchPassword!".to_string();
assert!(state.validate_password_change().is_err());
state.confirm_password = "NewPassword123!".to_string();
assert!(state.validate_password_change().is_ok());
}
}