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

+215
View File
@@ -0,0 +1,215 @@
//! Administrator repository operations.
use crate::error::{DbError, Result};
use crate::models::{format_datetime, parse_datetime};
use chrono::Utc;
use nx9_wg_core::types::auth::Admin;
use sqlx::{Row, SqlitePool};
/// Retrieve the single administrator record, if initialized.
pub async fn get_admin(pool: &SqlitePool) -> Result<Option<Admin>> {
let row = sqlx::query(
r#"
SELECT id, username, password_hash, totp_secret, totp_enabled,
last_login_at, last_login_ip, created_at, updated_at
FROM admin
WHERE id = 1
"#,
)
.fetch_optional(pool)
.await
.map_err(DbError::Sqlx)?;
match row {
Some(r) => {
let id: i64 = r.try_get("id")?;
let username: String = r.try_get("username")?;
let password_hash: String = r.try_get("password_hash")?;
let totp_secret: Option<String> = r.try_get("totp_secret")?;
let totp_enabled_int: i64 = r.try_get("totp_enabled")?;
let last_login_at_str: Option<String> = r.try_get("last_login_at")?;
let last_login_ip: Option<String> = r.try_get("last_login_ip")?;
let created_at_str: String = r.try_get("created_at")?;
let updated_at_str: String = r.try_get("updated_at")?;
let last_login_at = match last_login_at_str {
Some(s) => Some(parse_datetime(&s)?),
None => None,
};
Ok(Some(Admin {
id,
username,
password_hash,
totp_secret,
totp_enabled: totp_enabled_int != 0,
last_login_at,
last_login_ip,
created_at: parse_datetime(&created_at_str)?,
updated_at: parse_datetime(&updated_at_str)?,
}))
}
None => Ok(None),
}
}
/// Retrieve the administrator record by username.
pub async fn get_admin_by_username(pool: &SqlitePool, username: &str) -> Result<Option<Admin>> {
let admin = get_admin(pool).await?;
match admin {
Some(a) if a.username == username => Ok(Some(a)),
_ => Ok(None),
}
}
/// Check whether the single administrator has already been initialized.
pub async fn admin_exists(pool: &SqlitePool) -> Result<bool> {
let row = sqlx::query("SELECT COUNT(*) as count FROM admin WHERE id = 1")
.fetch_one(pool)
.await
.map_err(DbError::Sqlx)?;
let count: i64 = row.try_get("count")?;
Ok(count > 0)
}
/// Create the single administrator record.
///
/// Fails if an administrator already exists.
pub async fn create_admin(pool: &SqlitePool, username: &str, password_hash: &str) -> Result<Admin> {
if admin_exists(pool).await? {
return Err(DbError::Conflict(
"Administrator has already been initialized".to_string(),
));
}
let now = Utc::now().naive_utc();
let now_str = format_datetime(&now);
sqlx::query(
r#"
INSERT INTO admin (id, username, password_hash, totp_secret, totp_enabled, created_at, updated_at)
VALUES (1, ?, ?, NULL, 0, ?, ?)
"#,
)
.bind(username)
.bind(password_hash)
.bind(&now_str)
.bind(&now_str)
.execute(pool)
.await
.map_err(|e| match &e {
sqlx::Error::Database(dbe) if dbe.is_unique_violation() => {
DbError::Conflict("Administrator already exists or username conflict".to_string())
}
_ => DbError::Sqlx(e),
})?;
Ok(Admin {
id: 1,
username: username.to_string(),
password_hash: password_hash.to_string(),
totp_secret: None,
totp_enabled: false,
last_login_at: None,
last_login_ip: None,
created_at: now,
updated_at: now,
})
}
/// Update the administrator's password hash.
pub async fn update_admin_password(pool: &SqlitePool, new_password_hash: &str) -> Result<()> {
let now = Utc::now().naive_utc();
let now_str = format_datetime(&now);
let result = sqlx::query(
r#"
UPDATE admin
SET password_hash = ?, updated_at = ?
WHERE id = 1
"#,
)
.bind(new_password_hash)
.bind(&now_str)
.execute(pool)
.await
.map_err(DbError::Sqlx)?;
if result.rows_affected() == 0 {
return Err(DbError::NotFound(
"Administrator record does not exist".to_string(),
));
}
Ok(())
}
/// Update administrator TOTP configuration.
pub async fn update_admin_totp(
pool: &SqlitePool,
totp_secret: Option<&str>,
totp_enabled: bool,
) -> Result<()> {
let now = Utc::now().naive_utc();
let now_str = format_datetime(&now);
let result = sqlx::query(
r#"
UPDATE admin
SET totp_secret = ?, totp_enabled = ?, updated_at = ?
WHERE id = 1
"#,
)
.bind(totp_secret)
.bind(if totp_enabled { 1 } else { 0 })
.bind(&now_str)
.execute(pool)
.await
.map_err(DbError::Sqlx)?;
if result.rows_affected() == 0 {
return Err(DbError::NotFound(
"Administrator record does not exist".to_string(),
));
}
Ok(())
}
/// Record a successful administrator login timestamp and IP address.
pub async fn record_admin_login(pool: &SqlitePool, ip_address: Option<&str>) -> Result<()> {
let now = Utc::now().naive_utc();
let now_str = format_datetime(&now);
let result = sqlx::query(
r#"
UPDATE admin
SET last_login_at = ?, last_login_ip = ?, updated_at = ?
WHERE id = 1
"#,
)
.bind(&now_str)
.bind(ip_address)
.bind(&now_str)
.execute(pool)
.await
.map_err(DbError::Sqlx)?;
if result.rows_affected() == 0 {
return Err(DbError::NotFound(
"Administrator record does not exist".to_string(),
));
}
Ok(())
}
/// Delete administrator record (if explicitly supported).
pub async fn delete_admin(pool: &SqlitePool) -> Result<()> {
sqlx::query("DELETE FROM admin WHERE id = 1")
.execute(pool)
.await
.map_err(DbError::Sqlx)?;
Ok(())
}