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,266 @@
|
||||
//! WireGuard Interface HTTP handlers.
|
||||
|
||||
use crate::error::{ApiError, ApiResult};
|
||||
use crate::routes::auth::GenericSuccess;
|
||||
use crate::state::{AppState, SystemEvent};
|
||||
use axum::Json;
|
||||
use axum::extract::{Path, State};
|
||||
use chrono::Utc;
|
||||
use nx9_wg_core::crypto::generate_keypair;
|
||||
use nx9_wg_core::types::wireguard::{Interface, WireGuardPrivateKey, WireGuardPublicKey};
|
||||
use nx9_wg_core::validation::{
|
||||
validate_cidr, validate_interface_name, validate_listen_port, validate_mtu,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct CreateInterfaceRequest {
|
||||
pub name: String,
|
||||
pub listen_port: Option<u16>,
|
||||
pub address_v4: String,
|
||||
pub address_v6: Option<String>,
|
||||
pub mtu: Option<u16>,
|
||||
pub dns: Option<String>,
|
||||
pub private_key: Option<String>,
|
||||
pub public_key: Option<String>,
|
||||
pub pre_up: Option<String>,
|
||||
pub post_up: Option<String>,
|
||||
pub pre_down: Option<String>,
|
||||
pub post_down: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct UpdateInterfaceRequest {
|
||||
pub name: Option<String>,
|
||||
pub listen_port: Option<u16>,
|
||||
pub address_v4: Option<String>,
|
||||
pub address_v6: Option<String>,
|
||||
pub mtu: Option<u16>,
|
||||
pub dns: Option<String>,
|
||||
pub pre_up: Option<String>,
|
||||
pub post_up: Option<String>,
|
||||
pub pre_down: Option<String>,
|
||||
pub post_down: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct InterfaceStatusResponse {
|
||||
pub interface: Interface,
|
||||
pub peer_count: usize,
|
||||
pub active_peer_count: usize,
|
||||
}
|
||||
|
||||
/// GET /api/v1/interfaces
|
||||
pub async fn list_interfaces_handler(
|
||||
State(state): State<AppState>,
|
||||
) -> ApiResult<Json<Vec<Interface>>> {
|
||||
let list = state.store.list_interfaces().await?;
|
||||
Ok(Json(list))
|
||||
}
|
||||
|
||||
/// POST /api/v1/interfaces
|
||||
pub async fn create_interface_handler(
|
||||
State(state): State<AppState>,
|
||||
Json(payload): Json<CreateInterfaceRequest>,
|
||||
) -> ApiResult<Json<Interface>> {
|
||||
validate_interface_name(&payload.name)?;
|
||||
let address_v4 = validate_cidr(&payload.address_v4)?;
|
||||
let address_v6 = match payload.address_v6.as_deref() {
|
||||
Some(s) if !s.trim().is_empty() => Some(validate_cidr(s)?),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
let listen_port = match payload.listen_port {
|
||||
Some(p) => validate_listen_port(p)?,
|
||||
None => 51820,
|
||||
};
|
||||
|
||||
if let Some(m) = payload.mtu {
|
||||
validate_mtu(m)?;
|
||||
}
|
||||
|
||||
let (priv_k, pub_k) = match (payload.private_key, payload.public_key) {
|
||||
(Some(priv_s), Some(pub_s)) => (
|
||||
WireGuardPrivateKey::new(priv_s),
|
||||
WireGuardPublicKey::new(pub_s),
|
||||
),
|
||||
_ => generate_keypair(),
|
||||
};
|
||||
|
||||
let now = Utc::now().naive_utc();
|
||||
let iface = Interface {
|
||||
id: Uuid::new_v4(),
|
||||
name: payload.name,
|
||||
private_key: priv_k,
|
||||
public_key: pub_k,
|
||||
listen_port,
|
||||
address_v4,
|
||||
address_v6,
|
||||
mtu: payload.mtu,
|
||||
dns: payload.dns,
|
||||
enabled: true,
|
||||
pre_up: payload.pre_up,
|
||||
post_up: payload.post_up,
|
||||
pre_down: payload.pre_down,
|
||||
post_down: payload.post_down,
|
||||
created_at: now,
|
||||
updated_at: now,
|
||||
};
|
||||
|
||||
state.store.create_interface(&iface).await?;
|
||||
|
||||
state.broadcast(SystemEvent::InterfaceChanged {
|
||||
id: iface.id.to_string(),
|
||||
action: "created".to_string(),
|
||||
});
|
||||
|
||||
Ok(Json(iface))
|
||||
}
|
||||
|
||||
/// GET /api/v1/interfaces/{id}
|
||||
pub async fn get_interface_handler(
|
||||
State(state): State<AppState>,
|
||||
Path(id): Path<Uuid>,
|
||||
) -> ApiResult<Json<Interface>> {
|
||||
let iface = state
|
||||
.store
|
||||
.get_interface(id)
|
||||
.await?
|
||||
.ok_or_else(|| ApiError::NotFound(format!("Interface '{id}' not found")))?;
|
||||
Ok(Json(iface))
|
||||
}
|
||||
|
||||
/// PUT /api/v1/interfaces/{id}
|
||||
pub async fn update_interface_handler(
|
||||
State(state): State<AppState>,
|
||||
Path(id): Path<Uuid>,
|
||||
Json(payload): Json<UpdateInterfaceRequest>,
|
||||
) -> ApiResult<Json<Interface>> {
|
||||
let mut iface = state
|
||||
.store
|
||||
.get_interface(id)
|
||||
.await?
|
||||
.ok_or_else(|| ApiError::NotFound(format!("Interface '{id}' not found")))?;
|
||||
|
||||
if let Some(ref name) = payload.name {
|
||||
validate_interface_name(name)?;
|
||||
iface.name = name.clone();
|
||||
}
|
||||
if let Some(port) = payload.listen_port {
|
||||
validate_listen_port(port)?;
|
||||
iface.listen_port = port;
|
||||
}
|
||||
if let Some(ref v4) = payload.address_v4 {
|
||||
iface.address_v4 = validate_cidr(v4)?;
|
||||
}
|
||||
if let Some(ref v6) = payload.address_v6 {
|
||||
iface.address_v6 = Some(validate_cidr(v6)?);
|
||||
}
|
||||
if let Some(m) = payload.mtu {
|
||||
validate_mtu(m)?;
|
||||
iface.mtu = Some(m);
|
||||
}
|
||||
if let Some(ref dns) = payload.dns {
|
||||
iface.dns = Some(dns.clone());
|
||||
}
|
||||
if payload.pre_up.is_some() {
|
||||
iface.pre_up = payload.pre_up;
|
||||
}
|
||||
if payload.post_up.is_some() {
|
||||
iface.post_up = payload.post_up;
|
||||
}
|
||||
if payload.pre_down.is_some() {
|
||||
iface.pre_down = payload.pre_down;
|
||||
}
|
||||
if payload.post_down.is_some() {
|
||||
iface.post_down = payload.post_down;
|
||||
}
|
||||
|
||||
state.store.update_interface(&iface).await?;
|
||||
|
||||
state.broadcast(SystemEvent::InterfaceChanged {
|
||||
id: iface.id.to_string(),
|
||||
action: "updated".to_string(),
|
||||
});
|
||||
|
||||
Ok(Json(iface))
|
||||
}
|
||||
|
||||
/// DELETE /api/v1/interfaces/{id}
|
||||
pub async fn delete_interface_handler(
|
||||
State(state): State<AppState>,
|
||||
Path(id): Path<Uuid>,
|
||||
) -> ApiResult<Json<GenericSuccess>> {
|
||||
state.store.delete_interface(id).await?;
|
||||
|
||||
state.broadcast(SystemEvent::InterfaceChanged {
|
||||
id: id.to_string(),
|
||||
action: "deleted".to_string(),
|
||||
});
|
||||
|
||||
Ok(Json(GenericSuccess {
|
||||
success: true,
|
||||
message: format!("Interface '{id}' and all associated peers deleted"),
|
||||
}))
|
||||
}
|
||||
|
||||
/// POST /api/v1/interfaces/{id}/enable
|
||||
pub async fn enable_interface_handler(
|
||||
State(state): State<AppState>,
|
||||
Path(id): Path<Uuid>,
|
||||
) -> ApiResult<Json<GenericSuccess>> {
|
||||
state.store.set_interface_enabled(id, true).await?;
|
||||
|
||||
state.broadcast(SystemEvent::InterfaceChanged {
|
||||
id: id.to_string(),
|
||||
action: "enabled".to_string(),
|
||||
});
|
||||
|
||||
Ok(Json(GenericSuccess {
|
||||
success: true,
|
||||
message: format!("Interface '{id}' enabled"),
|
||||
}))
|
||||
}
|
||||
|
||||
/// POST /api/v1/interfaces/{id}/disable
|
||||
pub async fn disable_interface_handler(
|
||||
State(state): State<AppState>,
|
||||
Path(id): Path<Uuid>,
|
||||
) -> ApiResult<Json<GenericSuccess>> {
|
||||
state.store.set_interface_enabled(id, false).await?;
|
||||
|
||||
state.broadcast(SystemEvent::InterfaceChanged {
|
||||
id: id.to_string(),
|
||||
action: "disabled".to_string(),
|
||||
});
|
||||
|
||||
Ok(Json(GenericSuccess {
|
||||
success: true,
|
||||
message: format!("Interface '{id}' disabled"),
|
||||
}))
|
||||
}
|
||||
|
||||
/// GET /api/v1/interfaces/{id}/status
|
||||
pub async fn interface_status_handler(
|
||||
State(state): State<AppState>,
|
||||
Path(id): Path<Uuid>,
|
||||
) -> ApiResult<Json<InterfaceStatusResponse>> {
|
||||
let iface = state
|
||||
.store
|
||||
.get_interface(id)
|
||||
.await?
|
||||
.ok_or_else(|| ApiError::NotFound(format!("Interface '{id}' not found")))?;
|
||||
|
||||
let peers = state.store.list_peers_for_interface(id).await?;
|
||||
let active_count = peers
|
||||
.iter()
|
||||
.filter(|p| p.state == nx9_wg_core::types::wireguard::PeerState::Active)
|
||||
.count();
|
||||
|
||||
Ok(Json(InterfaceStatusResponse {
|
||||
interface: iface,
|
||||
peer_count: peers.len(),
|
||||
active_peer_count: active_count,
|
||||
}))
|
||||
}
|
||||
Reference in new issue
Block a user