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

+123
View File
@@ -0,0 +1,123 @@
//! Cryptographic utilities.
use crate::error::{Nx9Error, Result};
use crate::types::wireguard::{WireGuardPresharedKey, WireGuardPrivateKey, WireGuardPublicKey};
/// Hash a password with Argon2id. Returns the PHC-formatted hash string.
pub fn hash_password(password: &str) -> Result<String> {
use argon2::Argon2;
use argon2::password_hash::rand_core::OsRng;
use argon2::password_hash::{PasswordHasher, SaltString};
let salt = SaltString::generate(&mut OsRng);
let argon2 = Argon2::default();
argon2
.hash_password(password.as_bytes(), &salt)
.map(|h| h.to_string())
.map_err(|e| Nx9Error::Crypto(format!("password hashing failed: {}", e)))
}
/// Verify a password against an Argon2id PHC hash string.
pub fn verify_password(password: &str, hash: &str) -> Result<bool> {
use argon2::Argon2;
use argon2::password_hash::{PasswordHash, PasswordVerifier};
let parsed_hash = PasswordHash::new(hash)
.map_err(|e| Nx9Error::Crypto(format!("invalid password hash: {}", e)))?;
Ok(Argon2::default()
.verify_password(password.as_bytes(), &parsed_hash)
.is_ok())
}
/// Generate a WireGuard key pair (x25519).
pub fn generate_keypair() -> (WireGuardPrivateKey, WireGuardPublicKey) {
use base64::Engine;
use base64::engine::general_purpose::STANDARD;
use rand::rngs::OsRng;
use x25519_dalek::{PublicKey, StaticSecret};
let secret = StaticSecret::random_from_rng(OsRng);
let public = PublicKey::from(&secret);
let priv_b64 = STANDARD.encode(secret.to_bytes());
let pub_b64 = STANDARD.encode(public.as_bytes());
(
WireGuardPrivateKey::new(priv_b64),
WireGuardPublicKey::new(pub_b64),
)
}
/// Generate a WireGuard preshared key (32 random bytes, base64).
pub fn generate_preshared_key() -> WireGuardPresharedKey {
use base64::Engine;
use base64::engine::general_purpose::STANDARD;
use rand::RngCore;
let mut key = [0u8; 32];
rand::rngs::OsRng.fill_bytes(&mut key);
WireGuardPresharedKey::new(STANDARD.encode(key))
}
/// Generate a session ID (UUID v4).
pub fn generate_session_id() -> String {
uuid::Uuid::new_v4().to_string()
}
/// Generate an API token. Returns (plaintext_token, sha256_hex_hash).
pub fn generate_api_token() -> (String, String) {
use base64::Engine;
use base64::engine::general_purpose::URL_SAFE_NO_PAD;
use rand::RngCore;
use sha2::{Digest, Sha256};
let mut token_bytes = [0u8; 32];
rand::rngs::OsRng.fill_bytes(&mut token_bytes);
let plaintext = format!("nx9_{}", URL_SAFE_NO_PAD.encode(token_bytes));
let hash_bytes = Sha256::digest(plaintext.as_bytes());
let hash = hash_bytes
.iter()
.map(|b| format!("{:02x}", b))
.collect::<String>();
(plaintext, hash)
}
/// Generate a cryptographically secure random password.
pub fn generate_secure_password(length: usize) -> String {
use rand::Rng;
const CHARSET: &[u8] =
b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*-_=+";
let mut rng = rand::rngs::OsRng;
(0..length)
.map(|_| {
let idx = rng.gen_range(0..CHARSET.len());
CHARSET[idx] as char
})
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_password_hashing() {
let password = "my_secure_password";
let hash = hash_password(password).unwrap();
assert!(verify_password(password, &hash).unwrap());
assert!(!verify_password("wrong_password", &hash).unwrap());
}
#[test]
fn test_generate_keypair() {
let (priv_key, pub_key) = generate_keypair();
assert!(!priv_key.as_str().is_empty());
assert!(!pub_key.as_str().is_empty());
}
#[test]
fn test_generate_api_token() {
let (token, hash) = generate_api_token();
assert!(token.starts_with("nx9_"));
assert_eq!(hash.len(), 64);
}
#[test]
fn test_generate_secure_password() {
let pw = generate_secure_password(16);
assert_eq!(pw.len(), 16);
}
}