224 lines
6.8 KiB
Rust

#![allow(dead_code)]
use crate::error::{AppError, AppResult};
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AppConfig {
pub backend_base_url: String,
pub mdm_server_url: String,
pub supabase_url: String,
pub supabase_anon_key: String,
/// Stable device identity known to the backend (UserDevice.deviceId).
/// Filled after first successful device registration.
#[serde(skip_serializing_if = "Option::is_none")]
pub device_id: Option<String>,
/// Hardware-bound identity (UserDevice.hardwareId).
#[serde(skip_serializing_if = "Option::is_none")]
pub hardware_id: Option<String>,
}
impl Default for AppConfig {
fn default() -> Self {
Self {
backend_base_url: "https://staging.rebreak.org".to_string(),
mdm_server_url: "https://mdm.rebreak.org".to_string(),
supabase_url: String::new(),
supabase_anon_key: String::new(),
device_id: None,
hardware_id: None,
}
}
}
const KEYRING_SERVICE: &str = "org.rebreak.magic";
const KEYRING_ACCOUNT: &str = "magic-session";
const SESSION_FILE: &str = "session.json";
const DESKTOP_PROTECTION_FILE: &str = "desktop-protection.json";
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MagicSession {
pub access_token: String,
pub session_id: String,
pub user_id: String,
#[serde(with = "chrono::serde::ts_seconds")]
pub created_at: chrono::DateTime<chrono::Utc>,
pub label: Option<String>,
}
impl MagicSession {
pub fn new(access_token: String, session_id: String, label: Option<String>) -> Self {
Self {
access_token,
session_id,
user_id: String::new(),
created_at: chrono::Utc::now(),
label,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BinderConfig {
#[serde(rename = "mdmServer")]
pub mdm_server: String,
#[serde(rename = "mdmUser")]
pub mdm_user: String,
#[serde(rename = "mdmApiKey")]
pub mdm_api_key: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DesktopProtectionState {
pub active: bool,
pub platform: String,
#[serde(with = "chrono::serde::ts_seconds")]
pub activated_at: chrono::DateTime<chrono::Utc>,
}
impl AppConfig {
pub fn config_dir() -> AppResult<PathBuf> {
let dir = dirs::config_dir()
.ok_or_else(|| AppError::new("Could not find config directory"))?
.join("org.rebreak.magic");
std::fs::create_dir_all(&dir)?;
Ok(dir)
}
pub fn config_path() -> AppResult<PathBuf> {
Ok(Self::config_dir()?.join("config.json"))
}
pub fn binder_config_path() -> AppResult<PathBuf> {
Ok(dirs::config_dir()
.ok_or_else(|| AppError::new("Could not find config directory"))?
.join("rebreak-binder")
.join("config.json"))
}
pub fn load_binder_config() -> AppResult<BinderConfig> {
let path = Self::binder_config_path()?;
if !path.exists() {
return Err(AppError::new(
"rebreak-binder config nicht gefunden. Bitte README → 'Config (lokal)'.".to_string(),
));
}
let json = std::fs::read_to_string(&path)?;
serde_json::from_str(&json)
.map_err(|e| AppError::new(format!("rebreak-binder config kaputt: {}", e)))
}
pub fn load() -> Self {
match Self::config_path() {
Ok(path) if path.exists() => {
std::fs::read_to_string(&path)
.ok()
.and_then(|s| serde_json::from_str(&s).ok())
.unwrap_or_default()
}
_ => Self::default(),
}
}
pub fn save(&self) -> AppResult<()> {
let path = Self::config_path()?;
let contents = serde_json::to_string_pretty(self)?;
std::fs::write(path, contents)?;
Ok(())
}
fn session_path() -> AppResult<PathBuf> {
Ok(Self::config_dir()?.join(SESSION_FILE))
}
pub fn save_magic_session(session: &MagicSession) -> AppResult<()> {
let path = Self::session_path()?;
let json = serde_json::to_string_pretty(session)
.map_err(|e| AppError::new(format!("Failed to serialize session: {}", e)))?;
std::fs::write(&path, json)?;
Ok(())
}
pub fn load_magic_session() -> AppResult<Option<MagicSession>> {
let path = match Self::session_path() {
Ok(p) => p,
Err(_) => return Ok(None),
};
if !path.exists() {
return Ok(None);
}
let json = std::fs::read_to_string(&path)?;
if json.trim().is_empty() {
return Ok(None);
}
let session = serde_json::from_str(&json)
.map_err(|e| AppError::new(format!("Failed to parse session: {}", e)))?;
Ok(Some(session))
}
pub fn clear_magic_session() -> AppResult<()> {
let path = match Self::session_path() {
Ok(p) => p,
Err(_) => return Ok(()),
};
if path.exists() {
std::fs::remove_file(&path)?;
}
Ok(())
}
fn desktop_protection_path() -> AppResult<PathBuf> {
Ok(Self::config_dir()?.join(DESKTOP_PROTECTION_FILE))
}
pub fn save_desktop_protection(state: &DesktopProtectionState) -> AppResult<()> {
let path = Self::desktop_protection_path()?;
let json = serde_json::to_string_pretty(state)
.map_err(|e| AppError::new(format!("Failed to serialize desktop protection state: {}", e)))?;
std::fs::write(&path, json)?;
Ok(())
}
pub fn load_desktop_protection() -> AppResult<Option<DesktopProtectionState>> {
let path = match Self::desktop_protection_path() {
Ok(p) => p,
Err(_) => return Ok(None),
};
if !path.exists() {
return Ok(None);
}
let json = std::fs::read_to_string(&path)?;
if json.trim().is_empty() {
return Ok(None);
}
let state = serde_json::from_str(&json)
.map_err(|e| AppError::new(format!("Failed to parse desktop protection state: {}", e)))?;
Ok(Some(state))
}
pub fn clear_desktop_protection() -> AppResult<()> {
let path = match Self::desktop_protection_path() {
Ok(p) => p,
Err(_) => return Ok(()),
};
if path.exists() {
std::fs::remove_file(&path)?;
}
Ok(())
}
pub fn set_device_id(&mut self, id: String) -> AppResult<()> {
self.device_id = Some(id);
self.save()
}
pub fn set_hardware_id(&mut self, id: String) -> AppResult<()> {
self.hardware_id = Some(id);
self.save()
}
}