chahinebrini 4b4b9fc63b feat(magic-win): ReBreak Magic Windows-App (Tauri) + CI-Build
Tauri 2 DoH-Schutz für Windows: PowerShell-DoH-Takeover, Tamper-Service
(SYSTEM, windows-service), Browser-Policies (Chromium built-in-DNS + eigenes
DoH aus → OS-Resolver), 24h-Cooldown via bestehende magic/*-Endpoints.
GitHub-Actions baut den x64-NSIS-Installer auf windows-latest.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 13:39:52 +02:00

199 lines
6.4 KiB
Rust

//! ReBreak Magic für Windows — Tauri-Core.
//!
//! Alle Backend-Calls + Token-Handling laufen hier in Rust; die WebView
//! sieht nur den aggregierten UI-State (nie Tokens). Analog zur Mac-App
//! (rebreak-magic-mac), gleiche /api/magic/*-Endpoints.
mod api;
mod auth;
mod device;
mod setup;
use api::Api;
use protection_core::ProtectionState;
use serde::Serialize;
use tauri::Manager;
const DEFAULT_BACKEND_URL: &str = "https://staging.rebreak.org";
#[derive(Serialize, Clone, Default)]
#[serde(rename_all = "camelCase")]
struct UiState {
backend_url: String,
logged_in: bool,
nickname: Option<String>,
plan: Option<String>,
device_id: String,
hostname: String,
/// Magic-Binding existiert (DNS-Token provisioniert)
registered: bool,
/// Windows-DoH zeigt aktuell auf ReBreak (lokaler Check)
protection_applied: bool,
release_requested_at: Option<String>,
release_available_at: Option<String>,
}
/// Backend-URL: config.json im App-Config-Dir > Default (Staging).
/// Gleiche Semantik wie config.example.json der Mac-App.
fn backend_url(app: &tauri::AppHandle) -> String {
if let Ok(dir) = app.path().app_config_dir() {
if let Ok(raw) = std::fs::read_to_string(dir.join("config.json")) {
if let Ok(v) = serde_json::from_str::<serde_json::Value>(&raw) {
if let Some(url) = v.get("backendBaseUrl").and_then(|u| u.as_str()) {
return url.trim_end_matches('/').to_string();
}
}
}
}
DEFAULT_BACKEND_URL.into()
}
/// Aggregiert den kompletten UI-State (ein Roundtrip fürs Frontend).
async fn build_state(app: &tauri::AppHandle) -> UiState {
let mut state = UiState {
backend_url: backend_url(app),
device_id: device::device_id(),
hostname: device::hostname(),
..Default::default()
};
// Lokaler DoH-Check ist auth-unabhängig
let server_ip = protection_core::load_state()
.map(|s| s.server_ip)
.unwrap_or_else(setup::resolve_doh_ip);
state.protection_applied = protection_core::doh_is_applied(&server_ip);
let Some(token) = auth::load_session_token() else {
return state;
};
let api = Api::new(&state.backend_url);
match api.me(&token).await {
Ok(me) => {
state.logged_in = true;
state.nickname = me.nickname;
state.plan = me.plan;
}
Err(_) => {
// Token abgelaufen/revoked → wie ausgeloggt behandeln
return state;
}
}
if let Ok(devices) = api.devices(&token).await {
if let Some(mine) = devices.iter().find(|d| d.device_id == state.device_id) {
state.registered = true;
state.release_requested_at = mine.release_requested_at.clone();
state.release_available_at = mine.release_available_at.clone();
}
}
state
}
// ---------------------------------------------------------------------------
// Tauri-Commands — jedes gibt den frischen UiState zurück
// ---------------------------------------------------------------------------
#[tauri::command]
async fn get_state(app: tauri::AppHandle) -> Result<UiState, String> {
Ok(build_state(&app).await)
}
/// Pairing-Code einlösen + Device registrieren (ein Schritt fürs UI).
#[tauri::command]
async fn pair_and_register(app: tauri::AppHandle, code: String) -> Result<UiState, String> {
let code = code.trim().to_string();
if !code.chars().all(|c| c.is_ascii_digit()) || code.len() != 6 {
return Err("Der Code besteht aus 6 Ziffern.".into());
}
let base = backend_url(&app);
let api = Api::new(&base);
let hostname = device::hostname();
let pair = api.redeem_pair(&code, &hostname).await?;
auth::save_session_token(&pair.token)?;
let reg = api
.register(
&pair.token,
&device::device_id(),
&hostname,
"Windows-PC",
&device::os_version(),
)
.await?;
auth::save_dns_token(&reg.dns_token)?;
Ok(build_state(&app).await)
}
/// Schutz aktivieren: EIN UAC-Prompt → DoH + State + Tamper-Service.
#[tauri::command]
async fn activate_protection(app: tauri::AppHandle) -> Result<UiState, String> {
let dns_token = auth::load_dns_token()
.ok_or_else(|| "Kein DNS-Token — bitte zuerst koppeln.".to_string())?;
let server_ip = setup::resolve_doh_ip();
let state = ProtectionState {
doh_template: ProtectionState::doh_template_for(&dns_token),
dns_token,
server_ip,
backend_url: backend_url(&app),
device_id: device::device_id(),
};
let service_exe = setup::service_exe_path()?;
let script = setup::build_setup_script(&state, &service_exe.display().to_string());
setup::run_elevated(&script)?;
let ui = build_state(&app).await;
if !ui.protection_applied {
return Err("Setup lief durch, aber der DoH-Schutz ist nicht aktiv. Bitte erneut versuchen.".into());
}
Ok(ui)
}
/// 24h-Cooldown starten. Teardown macht NICHT die App — der SYSTEM-Service
/// pollt /api/magic/status und baut erst ab wenn das Backend revoked.
#[tauri::command]
async fn request_release(app: tauri::AppHandle) -> Result<UiState, String> {
let token = auth::load_session_token().ok_or("Nicht eingeloggt")?;
let api = Api::new(&backend_url(&app));
api.request_release(&token, &device::device_id()).await?;
Ok(build_state(&app).await)
}
#[tauri::command]
async fn cancel_release(app: tauri::AppHandle) -> Result<UiState, String> {
let token = auth::load_session_token().ok_or("Nicht eingeloggt")?;
let api = Api::new(&backend_url(&app));
api.cancel_release(&token, &device::device_id()).await?;
Ok(build_state(&app).await)
}
/// Logout entfernt nur die Session — Schutz + Service bleiben aktiv
/// (Deaktivierung geht ausschließlich über den 24h-Release-Flow).
#[tauri::command]
async fn logout(app: tauri::AppHandle) -> Result<UiState, String> {
auth::clear_all();
Ok(build_state(&app).await)
}
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
tauri::Builder::default()
.invoke_handler(tauri::generate_handler![
get_state,
pair_and_register,
activate_protection,
request_release,
cancel_release,
logout,
])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}