use super::{RpcHandler, DEV_DEFAULT_PASSWORD}; use anyhow::Result; impl RpcHandler { pub(super) async fn handle_auth_login( &self, params: Option, ) -> Result { 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 { tracing::info!("[onboarding] login via dev default password"); return Ok(serde_json::Value::Null); } tracing::warn!("[onboarding] login attempt before setup complete"); return Err(anyhow::anyhow!( "User not set up. Please complete setup first." )); } let valid = self.auth_manager.verify_password(password).await?; if !valid { tracing::warn!("[onboarding] login failed — wrong password"); return Err(anyhow::anyhow!("Password Incorrect")); } tracing::info!("[onboarding] login successful"); // 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), } }); Ok(serde_json::Value::Null) } pub(super) async fn handle_auth_logout(&self) -> Result { tracing::info!("[onboarding] logout"); Ok(serde_json::Value::Null) } pub(super) async fn handle_auth_change_password( &self, params: Option, session_token: &Option, ) -> Result { 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); self.auth_manager .change_password(current_password, new_password, also_change_ssh) .await?; // 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; } Ok(serde_json::json!({ "success": true, "session_rotated": true })) } pub(super) async fn handle_auth_is_setup(&self) -> Result { 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, ) -> Result { // Prevent re-setup if already set up let is_setup = self.auth_manager.is_setup().await?; if is_setup { tracing::warn!("[onboarding] setup rejected — already set up"); return Err(anyhow::anyhow!( "Already set up. Use auth.changePassword to change." )); } 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 { tracing::warn!("[onboarding] setup rejected — password too short"); return Err(anyhow::anyhow!("Password must be at least 8 characters")); } self.auth_manager.setup_user(password).await?; tracing::info!("[onboarding] user setup complete"); Ok(serde_json::json!(true)) } pub(super) async fn handle_auth_onboarding_complete(&self) -> Result { self.auth_manager.complete_onboarding().await?; tracing::info!("[onboarding] onboarding marked complete"); // 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), } }); Ok(serde_json::json!(true)) } pub(super) async fn handle_auth_is_onboarding_complete(&self) -> Result { let complete = self.auth_manager.is_onboarding_complete().await?; tracing::debug!("[onboarding] isOnboardingComplete={}", complete); Ok(serde_json::json!(complete)) } pub(super) async fn handle_auth_reset_onboarding( &self, params: Option, ) -> Result { 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 { tracing::warn!("[onboarding] reset rejected — wrong password"); return Err(anyhow::anyhow!("Password Incorrect")); } self.auth_manager.reset_onboarding().await?; tracing::info!("[onboarding] onboarding reset"); Ok(serde_json::json!(true)) } }