Files
opsx-vpn/client/src-tauri/src/localstore.rs
T

90 lines
2.8 KiB
Rust

// 本机会话持久化:令牌只存本机数据目录(0600),不进入 WebView 或日志。
use std::path::{Path, PathBuf};
use serde::{Deserialize, Serialize};
use crate::api::Line;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Session {
pub base_url: String,
pub token: String,
pub username: String,
pub display_name: String,
pub device_name: String,
pub lines: Vec<Line>,
}
fn session_path(dir: &Path) -> PathBuf {
dir.join("session.json")
}
pub fn save(dir: &Path, session: &Session) -> Result<(), String> {
std::fs::create_dir_all(dir).map_err(|err| format!("无法创建数据目录:{err}"))?;
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
std::fs::set_permissions(dir, std::fs::Permissions::from_mode(0o700))
.map_err(|err| format!("无法限制数据目录权限:{err}"))?;
}
let raw = serde_json::to_string_pretty(session).map_err(|err| err.to_string())?;
write_private(&session_path(dir), raw.as_bytes())
}
pub fn load(dir: &Path) -> Option<Session> {
let raw = std::fs::read_to_string(session_path(dir)).ok()?;
serde_json::from_str(&raw).ok()
}
pub fn clear(dir: &Path) {
let _ = std::fs::remove_file(session_path(dir));
}
#[cfg(unix)]
fn write_private(path: &Path, bytes: &[u8]) -> Result<(), String> {
use std::io::Write;
use std::os::unix::fs::OpenOptionsExt;
let mut file = std::fs::OpenOptions::new()
.write(true)
.create(true)
.truncate(true)
.mode(0o600)
.open(path)
.map_err(|err| format!("无法写入会话文件:{err}"))?;
use std::os::unix::fs::PermissionsExt;
file.set_permissions(std::fs::Permissions::from_mode(0o600))
.map_err(|err| format!("无法限制会话文件权限:{err}"))?;
file.write_all(bytes)
.map_err(|err| format!("写入会话文件失败:{err}"))
}
#[cfg(not(unix))]
fn write_private(path: &Path, bytes: &[u8]) -> Result<(), String> {
std::fs::write(path, bytes).map_err(|err| format!("无法写入会话文件:{err}"))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn session_roundtrip_and_clear() {
let dir = std::env::temp_dir().join(format!("opsx-session-test-{}", std::process::id()));
let session = Session {
base_url: "https://vpn.example.com".to_string(),
token: "token-abc".to_string(),
username: "alice".to_string(),
display_name: "爱丽丝".to_string(),
device_name: "alice-mac".to_string(),
lines: vec![],
};
save(&dir, &session).unwrap();
let loaded = load(&dir).unwrap();
assert_eq!(loaded.token, "token-abc");
assert_eq!(loaded.username, "alice");
clear(&dir);
assert!(load(&dir).is_none());
let _ = std::fs::remove_dir_all(&dir);
}
}