2026-03-04 05:23:42 +00:00
|
|
|
use super::{RpcHandler, DEV_DEFAULT_PASSWORD};
|
|
|
|
|
use anyhow::Result;
|
|
|
|
|
|
|
|
|
|
impl RpcHandler {
|
|
|
|
|
pub(super) async fn handle_auth_login(
|
|
|
|
|
&self,
|
|
|
|
|
params: Option<serde_json::Value>,
|
|
|
|
|
) -> Result<serde_json::Value> {
|
|
|
|
|
let params = params.ok_or_else(|| anyhow::anyhow!("Missing params"))?;
|
|
|
|
|
let password = params
|
|
|
|
|
.get("password")
|
|
|
|
|
.and_then(|v| v.as_str())
|
|
|
|
|
.ok_or_else(|| anyhow::anyhow!("Missing password"))?;
|
|
|
|
|
|
|
|
|
|
let is_setup = self.auth_manager.is_setup().await?;
|
|
|
|
|
if !is_setup {
|
|
|
|
|
// Dev mode: allow default password so UI can log in without running setup
|
|
|
|
|
if self.config.dev_mode && password == DEV_DEFAULT_PASSWORD {
|
2026-03-28 11:31:48 +00:00
|
|
|
tracing::info!("[onboarding] login via dev default password");
|
2026-03-04 05:23:42 +00:00
|
|
|
return Ok(serde_json::Value::Null);
|
|
|
|
|
}
|
2026-03-28 11:31:48 +00:00
|
|
|
tracing::warn!("[onboarding] login attempt before setup complete");
|
2026-03-04 05:23:42 +00:00
|
|
|
return Err(anyhow::anyhow!(
|
|
|
|
|
"User not set up. Please complete setup first."
|
|
|
|
|
));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let valid = self.auth_manager.verify_password(password).await?;
|
|
|
|
|
if !valid {
|
2026-03-28 11:31:48 +00:00
|
|
|
tracing::warn!("[onboarding] login failed — wrong password");
|
2026-03-04 05:23:42 +00:00
|
|
|
return Err(anyhow::anyhow!("Password Incorrect"));
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-28 11:31:48 +00:00
|
|
|
tracing::info!("[onboarding] login successful");
|
2026-04-09 20:42:09 +02:00
|
|
|
|
|
|
|
|
// Ensure NostrVPN config exists — covers the case where onboardingComplete
|
|
|
|
|
// was never called (e.g., user took the "already set up" shortcut).
|
|
|
|
|
let data_dir = self.config.data_dir.clone();
|
|
|
|
|
tokio::spawn(async move {
|
|
|
|
|
// Quick check: if config.toml already exists, skip
|
|
|
|
|
let config_path = data_dir.join("nostr-vpn/.config/nvpn/config.toml");
|
|
|
|
|
if config_path.exists() {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
// Identity must exist for VPN config
|
|
|
|
|
if !data_dir.join("identity/nostr_pubkey").exists() {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
match crate::vpn::configure_nostr_vpn(&data_dir).await {
|
|
|
|
|
Ok(()) => tracing::info!("[login] NostrVPN auto-configured on first login"),
|
|
|
|
|
Err(e) => tracing::debug!("[login] NostrVPN auto-config skipped: {}", e),
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
2026-03-04 05:23:42 +00:00
|
|
|
Ok(serde_json::Value::Null)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub(super) async fn handle_auth_logout(&self) -> Result<serde_json::Value> {
|
2026-03-28 11:31:48 +00:00
|
|
|
tracing::info!("[onboarding] logout");
|
2026-03-04 05:23:42 +00:00
|
|
|
Ok(serde_json::Value::Null)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub(super) async fn handle_auth_change_password(
|
|
|
|
|
&self,
|
|
|
|
|
params: Option<serde_json::Value>,
|
2026-03-12 00:19:30 +00:00
|
|
|
session_token: &Option<String>,
|
2026-03-04 05:23:42 +00:00
|
|
|
) -> Result<serde_json::Value> {
|
|
|
|
|
let params = params.ok_or_else(|| anyhow::anyhow!("Missing params"))?;
|
|
|
|
|
let current_password = params
|
|
|
|
|
.get("currentPassword")
|
|
|
|
|
.and_then(|v| v.as_str())
|
|
|
|
|
.ok_or_else(|| anyhow::anyhow!("Missing currentPassword"))?;
|
|
|
|
|
let new_password = params
|
|
|
|
|
.get("newPassword")
|
|
|
|
|
.and_then(|v| v.as_str())
|
|
|
|
|
.ok_or_else(|| anyhow::anyhow!("Missing newPassword"))?;
|
|
|
|
|
let also_change_ssh = params
|
|
|
|
|
.get("alsoChangeSsh")
|
|
|
|
|
.and_then(|v| v.as_bool())
|
|
|
|
|
.unwrap_or(true);
|
|
|
|
|
|
2026-06-11 00:24:32 -04:00
|
|
|
let outcome = self
|
|
|
|
|
.auth_manager
|
2026-03-04 05:23:42 +00:00
|
|
|
.change_password(current_password, new_password, also_change_ssh)
|
|
|
|
|
.await?;
|
|
|
|
|
|
2026-03-12 00:19:30 +00:00
|
|
|
// Session rotation: invalidate all other sessions, rotate the caller's session
|
|
|
|
|
if let Some(token) = session_token {
|
|
|
|
|
self.session_store.invalidate_all_except(token).await;
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-11 00:24:32 -04:00
|
|
|
Ok(serde_json::json!({
|
|
|
|
|
"success": true,
|
|
|
|
|
"session_rotated": true,
|
|
|
|
|
"ssh_updated": outcome.ssh_updated,
|
|
|
|
|
"ssh_error": outcome.ssh_error,
|
|
|
|
|
}))
|
2026-03-04 05:23:42 +00:00
|
|
|
}
|
|
|
|
|
|
2026-03-26 09:12:16 +00:00
|
|
|
pub(super) async fn handle_auth_is_setup(&self) -> Result<serde_json::Value> {
|
|
|
|
|
let is_setup = self.auth_manager.is_setup().await?;
|
|
|
|
|
Ok(serde_json::json!(is_setup))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub(super) async fn handle_auth_setup(
|
|
|
|
|
&self,
|
|
|
|
|
params: Option<serde_json::Value>,
|
|
|
|
|
) -> Result<serde_json::Value> {
|
|
|
|
|
// Prevent re-setup if already set up
|
|
|
|
|
let is_setup = self.auth_manager.is_setup().await?;
|
|
|
|
|
if is_setup {
|
2026-03-28 11:31:48 +00:00
|
|
|
tracing::warn!("[onboarding] setup rejected — already set up");
|
chore(ci): rustfmt + clippy clean-up to unblock the Rust CI job
The .github/workflows/ci.yml Rust job runs cargo fmt --check, clippy
with -D warnings, and tests. All three were failing. This commit:
- Applies rustfmt across the tree (the bulk of the diff — untouched
since the last toolchain bump, so a wide sweep was unavoidable).
- Fixes the correctness-level clippy errors:
container/bitcoin_simulator.rs wildcard-in-or-pattern
container/manifest.rs from_str rename to parse (reserved name)
container/podman_client.rs .get(0) -> .first()
container/runtime.rs manual += collapse
archipelago/src/constants.rs doc-comment → module-doc
api/rpc/package/install.rs stray /// comment above a non-item
container/docker_packages.rs redundant field init
streaming/advertisement.rs missing Metric import in tests
tests/orchestration_tests.rs `vec!` in non-Vec contexts
mesh/listener/dispatch.rs unused store_plain_message import
api/rpc/tor/mod.rs and mesh/steganography.rs: push-after-new → vec!
- Quiets wide legacy surfaces with crate-level allows in main.rs for
stylistic lints (too_many_arguments, type_complexity, doc indent,
enum variant prefix, wildcard-in-or, assertions-on-constants,
drop_non_drop, unused_io_amount, ptr_arg) — these fired in dozens
of places with no correctness payoff and have been churning every
toolchain bump.
- Tags intentional-dead-code helpers: wallet/ and streaming/ modules
are WIP, mesh::send_chunked_payload and DM_V1_MARKER are kept for
rollback compatibility, vpn::get_nostr_vpn_status is surface-area
for a not-yet-landed RPC.
cargo fmt --check, cargo clippy --all-targets --all-features
-- -D warnings, and cargo test --all-features now all pass locally.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 17:23:46 -04:00
|
|
|
return Err(anyhow::anyhow!(
|
|
|
|
|
"Already set up. Use auth.changePassword to change."
|
|
|
|
|
));
|
2026-03-26 09:12:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let params = params.ok_or_else(|| anyhow::anyhow!("Missing params"))?;
|
|
|
|
|
let password = params
|
|
|
|
|
.get("password")
|
|
|
|
|
.and_then(|v| v.as_str())
|
|
|
|
|
.ok_or_else(|| anyhow::anyhow!("Missing password"))?;
|
|
|
|
|
|
|
|
|
|
if password.len() < 8 {
|
2026-03-28 11:31:48 +00:00
|
|
|
tracing::warn!("[onboarding] setup rejected — password too short");
|
2026-03-26 09:12:16 +00:00
|
|
|
return Err(anyhow::anyhow!("Password must be at least 8 characters"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
self.auth_manager.setup_user(password).await?;
|
2026-03-28 11:31:48 +00:00
|
|
|
tracing::info!("[onboarding] user setup complete");
|
2026-03-26 09:12:16 +00:00
|
|
|
Ok(serde_json::json!(true))
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-04 05:23:42 +00:00
|
|
|
pub(super) async fn handle_auth_onboarding_complete(&self) -> Result<serde_json::Value> {
|
|
|
|
|
self.auth_manager.complete_onboarding().await?;
|
2026-03-28 11:31:48 +00:00
|
|
|
tracing::info!("[onboarding] onboarding marked complete");
|
2026-04-07 14:49:34 +01:00
|
|
|
|
|
|
|
|
// Auto-configure NostrVPN with the node's Nostr identity
|
|
|
|
|
let data_dir = self.config.data_dir.clone();
|
|
|
|
|
tokio::spawn(async move {
|
|
|
|
|
match crate::vpn::configure_nostr_vpn(&data_dir).await {
|
|
|
|
|
Ok(()) => tracing::info!("[onboarding] NostrVPN configured and started"),
|
|
|
|
|
Err(e) => tracing::warn!("[onboarding] NostrVPN setup (non-fatal): {}", e),
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
2026-03-04 05:23:42 +00:00
|
|
|
Ok(serde_json::json!(true))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub(super) async fn handle_auth_is_onboarding_complete(&self) -> Result<serde_json::Value> {
|
|
|
|
|
let complete = self.auth_manager.is_onboarding_complete().await?;
|
2026-03-28 11:31:48 +00:00
|
|
|
tracing::debug!("[onboarding] isOnboardingComplete={}", complete);
|
2026-03-04 05:23:42 +00:00
|
|
|
Ok(serde_json::json!(complete))
|
|
|
|
|
}
|
|
|
|
|
|
security+feat: v1.3.0 — pentest remediation, container reliability, UI overhaul
Security (33 pentest findings addressed):
- CRITICAL: backend binds 127.0.0.1, path traversal in tor.rs/dwn fixed
- HIGH: federation requires signatures, XSS login redirect, RBAC viewer restricted
- HIGH: tar slip prevention, S3 SSRF validation, backup ID validation
- MEDIUM: remember-me random secret, TOTP session rotation, password re-auth
- LOW: CSP unsafe-inline removed, CORS dev-only, onion/webhook validation
Container reliability:
- Memory limits on all 37 containers (OOM prevention)
- Exited vs stopped state distinction with health-aware status badges
- Crash recovery coordination (no more restart cascade)
- User-stopped tracking survives reboots
- Tiered boot recovery (databases → core → services → apps)
UI:
- Wallet TransactionsModal, health-aware app status badges
- Restart button on containers, exited/crashed red state
- Mesh view overhaul, glass button updates, BaseModal/ToggleSwitch
- Apps sticky header removed, dev faucet, mutable mock wallet
Infrastructure:
- LND REST port 8080 exposed over Tor (LND Connect fix)
- Nginx cookie_session fix, deploy script Tor config updated
- Dev environment: podman auto-start, boot mode simulation
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 12:44:31 +00:00
|
|
|
pub(super) async fn handle_auth_reset_onboarding(
|
|
|
|
|
&self,
|
|
|
|
|
params: Option<serde_json::Value>,
|
|
|
|
|
) -> Result<serde_json::Value> {
|
|
|
|
|
let params = params.ok_or_else(|| anyhow::anyhow!("Missing params"))?;
|
|
|
|
|
let password = params
|
|
|
|
|
.get("password")
|
|
|
|
|
.and_then(|v| v.as_str())
|
|
|
|
|
.ok_or_else(|| anyhow::anyhow!("Missing password — re-authentication required"))?;
|
|
|
|
|
|
|
|
|
|
let valid = self.auth_manager.verify_password(password).await?;
|
|
|
|
|
if !valid {
|
2026-03-28 11:31:48 +00:00
|
|
|
tracing::warn!("[onboarding] reset rejected — wrong password");
|
security+feat: v1.3.0 — pentest remediation, container reliability, UI overhaul
Security (33 pentest findings addressed):
- CRITICAL: backend binds 127.0.0.1, path traversal in tor.rs/dwn fixed
- HIGH: federation requires signatures, XSS login redirect, RBAC viewer restricted
- HIGH: tar slip prevention, S3 SSRF validation, backup ID validation
- MEDIUM: remember-me random secret, TOTP session rotation, password re-auth
- LOW: CSP unsafe-inline removed, CORS dev-only, onion/webhook validation
Container reliability:
- Memory limits on all 37 containers (OOM prevention)
- Exited vs stopped state distinction with health-aware status badges
- Crash recovery coordination (no more restart cascade)
- User-stopped tracking survives reboots
- Tiered boot recovery (databases → core → services → apps)
UI:
- Wallet TransactionsModal, health-aware app status badges
- Restart button on containers, exited/crashed red state
- Mesh view overhaul, glass button updates, BaseModal/ToggleSwitch
- Apps sticky header removed, dev faucet, mutable mock wallet
Infrastructure:
- LND REST port 8080 exposed over Tor (LND Connect fix)
- Nginx cookie_session fix, deploy script Tor config updated
- Dev environment: podman auto-start, boot mode simulation
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 12:44:31 +00:00
|
|
|
return Err(anyhow::anyhow!("Password Incorrect"));
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-04 05:23:42 +00:00
|
|
|
self.auth_manager.reset_onboarding().await?;
|
2026-03-28 11:31:48 +00:00
|
|
|
tracing::info!("[onboarding] onboarding reset");
|
2026-03-04 05:23:42 +00:00
|
|
|
Ok(serde_json::json!(true))
|
|
|
|
|
}
|
|
|
|
|
}
|