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,184 @@
|
||||
//! Integration tests for Client Profiles REST API endpoints and resolver.
|
||||
|
||||
use axum::body::Body;
|
||||
use axum::http::{Request, StatusCode};
|
||||
use ipnet::IpNet;
|
||||
use nx9_wg_api::state::AppState;
|
||||
use nx9_wg_core::crypto::generate_keypair;
|
||||
use nx9_wg_core::types::client_profile::{ClientProfile, ConnectionType, ResolvedClientProfile};
|
||||
use nx9_wg_core::types::wireguard::{Interface, Peer, PeerProfile, PeerState, PeerType};
|
||||
use nx9_wg_db::Store;
|
||||
use std::str::FromStr;
|
||||
use tower::ServiceExt;
|
||||
use uuid::Uuid;
|
||||
|
||||
async fn setup_test_app() -> (axum::Router, AppState, String, Interface, Peer) {
|
||||
let store = Store::connect_in_memory().await.unwrap();
|
||||
store.migrate().await.unwrap();
|
||||
|
||||
let now = chrono::Utc::now().naive_utc();
|
||||
let hash = nx9_wg_core::crypto::hash_password("adminpassword123").unwrap();
|
||||
store.create_admin("admin", &hash).await.unwrap();
|
||||
|
||||
// Create session token
|
||||
let session = nx9_wg_core::types::auth::Session {
|
||||
id: "test-session-id-12345".to_string(),
|
||||
admin_id: 1,
|
||||
created_at: now,
|
||||
expires_at: now + chrono::Duration::hours(24),
|
||||
last_seen_at: Some(now),
|
||||
ip_address: Some("127.0.0.1".to_string()),
|
||||
user_agent: Some("test-agent".to_string()),
|
||||
};
|
||||
store.create_session(&session).await.unwrap();
|
||||
|
||||
let (srv_priv, srv_pub) = generate_keypair();
|
||||
let (peer_priv, peer_pub) = generate_keypair();
|
||||
|
||||
let interface = Interface {
|
||||
id: Uuid::new_v4(),
|
||||
name: "wg0".to_string(),
|
||||
private_key: srv_priv,
|
||||
public_key: srv_pub,
|
||||
listen_port: 51820,
|
||||
address_v4: IpNet::from_str("10.0.0.1/24").unwrap(),
|
||||
address_v6: None,
|
||||
mtu: Some(1420),
|
||||
dns: Some("1.1.1.1".to_string()),
|
||||
enabled: true,
|
||||
pre_up: None,
|
||||
post_up: None,
|
||||
pre_down: None,
|
||||
post_down: None,
|
||||
created_at: now,
|
||||
updated_at: now,
|
||||
};
|
||||
store.create_interface(&interface).await.unwrap();
|
||||
|
||||
let peer = Peer {
|
||||
id: Uuid::new_v4(),
|
||||
interface_id: interface.id,
|
||||
name: "test-mobile-peer".to_string(),
|
||||
peer_type: PeerType::RoadWarrior,
|
||||
state: PeerState::Active,
|
||||
public_key: peer_pub,
|
||||
private_key: Some(peer_priv),
|
||||
preshared_key: None,
|
||||
endpoint: None,
|
||||
allowed_ips: "10.0.0.2/32".to_string(),
|
||||
server_allowed_ips: None,
|
||||
address_v4: Some(IpNet::from_str("10.0.0.2/32").unwrap()),
|
||||
address_v6: None,
|
||||
dns: None,
|
||||
mtu: None,
|
||||
persistent_keepalive: None,
|
||||
profile: PeerProfile::FullTunnel,
|
||||
expires_at: None,
|
||||
last_handshake_at: None,
|
||||
created_at: now,
|
||||
updated_at: now,
|
||||
};
|
||||
store.create_peer(&peer).await.unwrap();
|
||||
|
||||
let state = AppState::new(store);
|
||||
let app = nx9_wg_api::routes::build_api_router(state.clone());
|
||||
|
||||
(app, state, session.id, interface, peer)
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_client_profiles_endpoints() {
|
||||
let (app, _state, session_id, _iface, peer) = setup_test_app().await;
|
||||
|
||||
// 1. List client profiles
|
||||
let req = Request::builder()
|
||||
.uri("/api/v1/client-profiles")
|
||||
.header("Cookie", format!("nx9_session={session_id}"))
|
||||
.body(Body::empty())
|
||||
.unwrap();
|
||||
let res = app.clone().oneshot(req).await.unwrap();
|
||||
assert_eq!(res.status(), StatusCode::OK);
|
||||
let body = axum::body::to_bytes(res.into_body(), usize::MAX)
|
||||
.await
|
||||
.unwrap();
|
||||
let profiles: Vec<ClientProfile> = serde_json::from_slice(&body).unwrap();
|
||||
assert!(profiles.len() >= 10);
|
||||
|
||||
// 2. List distinct providers
|
||||
let req = Request::builder()
|
||||
.uri("/api/v1/client-profiles/providers")
|
||||
.header("Cookie", format!("nx9_session={session_id}"))
|
||||
.body(Body::empty())
|
||||
.unwrap();
|
||||
let res = app.clone().oneshot(req).await.unwrap();
|
||||
assert_eq!(res.status(), StatusCode::OK);
|
||||
let body = axum::body::to_bytes(res.into_body(), usize::MAX)
|
||||
.await
|
||||
.unwrap();
|
||||
let providers: Vec<String> = serde_json::from_slice(&body).unwrap();
|
||||
assert!(providers.contains(&"tmobile".to_string()));
|
||||
assert!(providers.contains(&"starlink".to_string()));
|
||||
|
||||
// 3. List device categories
|
||||
let req = Request::builder()
|
||||
.uri("/api/v1/client-profiles/devices")
|
||||
.header("Cookie", format!("nx9_session={session_id}"))
|
||||
.body(Body::empty())
|
||||
.unwrap();
|
||||
let res = app.clone().oneshot(req).await.unwrap();
|
||||
assert_eq!(res.status(), StatusCode::OK);
|
||||
|
||||
// 4. Resolve client profile via POST
|
||||
let resolve_body = serde_json::json!({
|
||||
"connection": "mobile",
|
||||
"device": "android",
|
||||
"nat": "cgnat"
|
||||
});
|
||||
let req = Request::builder()
|
||||
.method("POST")
|
||||
.uri("/api/v1/client-profiles/resolve")
|
||||
.header("Cookie", format!("nx9_session={session_id}"))
|
||||
.header("Content-Type", "application/json")
|
||||
.body(Body::from(serde_json::to_vec(&resolve_body).unwrap()))
|
||||
.unwrap();
|
||||
let res = app.clone().oneshot(req).await.unwrap();
|
||||
assert_eq!(res.status(), StatusCode::OK);
|
||||
let body = axum::body::to_bytes(res.into_body(), usize::MAX)
|
||||
.await
|
||||
.unwrap();
|
||||
let resolved: ResolvedClientProfile = serde_json::from_slice(&body).unwrap();
|
||||
assert_eq!(resolved.mtu, 1280);
|
||||
assert_eq!(resolved.connection_type, ConnectionType::Mobile);
|
||||
|
||||
// 5. Download peer .conf with mobile profile parameters
|
||||
let req = Request::builder()
|
||||
.uri(format!(
|
||||
"/api/v1/peers/{}/config?connection=mobile&device=android",
|
||||
peer.id
|
||||
))
|
||||
.header("Cookie", format!("nx9_session={session_id}"))
|
||||
.body(Body::empty())
|
||||
.unwrap();
|
||||
let res = app.clone().oneshot(req).await.unwrap();
|
||||
assert_eq!(res.status(), StatusCode::OK);
|
||||
let body = axum::body::to_bytes(res.into_body(), usize::MAX)
|
||||
.await
|
||||
.unwrap();
|
||||
let conf_str = String::from_utf8(body.to_vec()).unwrap();
|
||||
assert!(conf_str.contains("MTU = 1280"));
|
||||
assert!(conf_str.contains("PersistentKeepalive = 25"));
|
||||
|
||||
// 6. Get QR code with CGNAT profile parameters
|
||||
let req = Request::builder()
|
||||
.uri(format!("/api/v1/peers/{}/qr?nat=cgnat", peer.id))
|
||||
.header("Cookie", format!("nx9_session={session_id}"))
|
||||
.body(Body::empty())
|
||||
.unwrap();
|
||||
let res = app.clone().oneshot(req).await.unwrap();
|
||||
assert_eq!(res.status(), StatusCode::OK);
|
||||
let body = axum::body::to_bytes(res.into_body(), usize::MAX)
|
||||
.await
|
||||
.unwrap();
|
||||
let qr_json: serde_json::Value = serde_json::from_slice(&body).unwrap();
|
||||
assert!(qr_json["svg"].as_str().unwrap().contains("<svg"));
|
||||
}
|
||||
Reference in new issue
Block a user