feat: optimize admin password management and add reset

This commit is contained in:
Kuingsmile
2025-07-17 17:32:57 +08:00
parent c9ccf6d1ce
commit e0d3250823
12 changed files with 411 additions and 60 deletions

View File

@@ -1,16 +1,103 @@
use std::env;
use std::path::PathBuf;
use std::process::Command;
use once_cell::sync::Lazy;
use regex::Regex;
use tauri::State;
use crate::object::structs::AppState;
static ADMIN_PWD_REGEX: Lazy<Regex> = Lazy::new(|| {
Regex::new(r"Successfully created the admin user and the initial password is: (\w+)")
.expect("Invalid regex pattern")
});
fn generate_random_password() -> String {
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
use std::time::{SystemTime, UNIX_EPOCH};
let mut hasher = DefaultHasher::new();
if let Ok(duration) = SystemTime::now().duration_since(UNIX_EPOCH) {
duration.as_nanos().hash(&mut hasher);
}
std::process::id().hash(&mut hasher);
let dummy = [1, 2, 3];
(dummy.as_ptr() as usize).hash(&mut hasher);
let hash = hasher.finish();
let chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
let mut password = String::new();
let mut current_hash = hash;
for _ in 0..16 {
let index = (current_hash % chars.len() as u64) as usize;
password.push(chars.chars().nth(index).unwrap());
current_hash = current_hash.wrapping_mul(1103515245).wrapping_add(12345);
}
password
}
async fn execute_openlist_admin_set(
password: &str,
state: &State<'_, AppState>,
) -> Result<(), String> {
let exe_path =
env::current_exe().map_err(|e| format!("Failed to determine executable path: {e}"))?;
let app_dir = exe_path
.parent()
.ok_or("Executable has no parent directory")?;
let possible_names = ["openlist", "openlist.exe"];
let mut openlist_exe = None;
for name in &possible_names {
let exe_path = app_dir.join(name);
if exe_path.exists() {
openlist_exe = Some(exe_path);
break;
}
}
let openlist_exe = openlist_exe.ok_or_else(|| {
format!(
"OpenList executable not found. Searched for: {:?} in {}",
possible_names,
app_dir.display()
)
})?;
log::info!(
"Setting new admin password using: {}",
openlist_exe.display()
);
let mut cmd = Command::new(&openlist_exe);
cmd.args(["admin", "set", password]);
if let Some(settings) = state.get_settings()
&& !settings.openlist.data_dir.is_empty()
{
cmd.arg("--data");
cmd.arg(&settings.openlist.data_dir);
log::info!("Using data directory: {}", settings.openlist.data_dir);
}
log::info!("Executing command: {cmd:?}");
let output = cmd
.output()
.map_err(|e| format!("Failed to execute openlist command: {e}"))?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
let stdout = String::from_utf8_lossy(&output.stdout);
log::error!("OpenList admin set command failed. stdout: {stdout}, stderr: {stderr}");
return Err(format!("OpenList admin set command failed: {stderr}"));
}
let stdout = String::from_utf8_lossy(&output.stdout);
log::info!("Successfully set admin password. Output: {stdout}");
Ok(())
}
fn resolve_log_paths(source: Option<&str>, data_dir: Option<&str>) -> Result<Vec<PathBuf>, String> {
let exe_path =
@@ -45,20 +132,78 @@ fn resolve_log_paths(source: Option<&str>, data_dir: Option<&str>) -> Result<Vec
#[tauri::command]
pub async fn get_admin_password(state: State<'_, AppState>) -> Result<String, String> {
let data_dir = state
.get_settings()
.map(|s| s.openlist.data_dir)
.filter(|d| !d.is_empty());
if let Some(settings) = state.get_settings()
&& let Some(ref stored_password) = settings.app.admin_password
&& !stored_password.is_empty()
{
log::info!("Found admin password in local settings");
return Ok(stored_password.clone());
}
let paths = resolve_log_paths(Some("openlist_core"), data_dir.as_deref())?;
let content =
std::fs::read_to_string(&paths[0]).map_err(|e| format!("Failed to read log file: {e}"))?;
let new_password = generate_random_password();
ADMIN_PWD_REGEX
.captures_iter(&content)
.filter_map(|cap| cap.get(1).map(|m| m.as_str().to_string()))
.last()
.ok_or_else(|| "No admin password found in logs".into())
if let Err(e) = execute_openlist_admin_set(&new_password, &state).await {
return Err(format!("Failed to set new admin password: {e}"));
}
log::info!("Successfully generated and set new admin password");
if let Some(mut settings) = state.get_settings() {
settings.app.admin_password = Some(new_password.clone());
state.update_settings(settings.clone());
if let Err(e) = settings.save() {
log::warn!("Failed to save new admin password to settings: {e}");
}
}
Ok(new_password)
}
#[tauri::command]
pub async fn reset_admin_password(state: State<'_, AppState>) -> Result<String, String> {
log::info!("Forcing admin password reset");
let new_password = generate_random_password();
if let Err(e) = execute_openlist_admin_set(&new_password, &state).await {
return Err(format!("Failed to set new admin password: {e}"));
}
log::info!("Successfully generated and set new admin password via force reset");
if let Some(mut settings) = state.get_settings() {
settings.app.admin_password = Some(new_password.clone());
state.update_settings(settings.clone());
if let Err(e) = settings.save() {
log::warn!("Failed to save new admin password to settings: {e}");
}
}
Ok(new_password)
}
#[tauri::command]
pub async fn set_admin_password(
password: String,
state: State<'_, AppState>,
) -> Result<String, String> {
log::info!("Setting custom admin password");
if let Err(e) = execute_openlist_admin_set(&password, &state).await {
return Err(format!("Failed to set admin password: {e}"));
}
log::info!("Successfully set custom admin password");
if let Some(mut settings) = state.get_settings() {
settings.app.admin_password = Some(password.clone());
state.update_settings(settings.clone());
if let Err(e) = settings.save() {
log::warn!("Failed to save admin password to settings: {e}");
}
}
Ok(password)
}
#[tauri::command]

View File

@@ -7,6 +7,7 @@ pub struct AppConfig {
pub gh_proxy: Option<String>,
pub gh_proxy_api: Option<bool>,
pub open_links_in_browser: Option<bool>,
pub admin_password: Option<String>,
}
impl AppConfig {
@@ -17,6 +18,7 @@ impl AppConfig {
gh_proxy: None,
gh_proxy_api: Some(false),
open_links_in_browser: Some(false),
admin_password: None,
}
}
}

View File

@@ -17,7 +17,9 @@ use cmd::firewall::{add_firewall_rule, check_firewall_rule, remove_firewall_rule
use cmd::http_api::{
delete_process, get_process_list, restart_process, start_process, stop_process, update_process,
};
use cmd::logs::{clear_logs, get_admin_password, get_logs};
use cmd::logs::{
clear_logs, get_admin_password, get_logs, reset_admin_password, set_admin_password,
};
use cmd::openlist_core::{create_openlist_core_process, get_openlist_core_status};
use cmd::os_operate::{
get_available_versions, list_files, open_file, open_folder, open_url, open_url_in_browser,
@@ -148,6 +150,8 @@ pub fn run() {
get_logs,
clear_logs,
get_admin_password,
reset_admin_password,
set_admin_password,
get_binary_version,
select_directory,
get_available_versions,