From 0bea9c22cb5bc084b7aee3a1376a3736a2b441db Mon Sep 17 00:00:00 2001 From: Zerebos Date: Fri, 15 May 2026 23:50:19 -0400 Subject: [PATCH 01/10] Rework auth to allow smooth switching --- src-tauri/src/commands/system.rs | 3 +- src/core/auth.ts | 70 ++++++++++++++++--- .../settings/sections/SecuritySettings.svelte | 35 +++++++--- 3 files changed, 86 insertions(+), 22 deletions(-) diff --git a/src-tauri/src/commands/system.rs b/src-tauri/src/commands/system.rs index e0d7659..b864824 100644 --- a/src-tauri/src/commands/system.rs +++ b/src-tauri/src/commands/system.rs @@ -1,6 +1,7 @@ #[cfg(target_os = "windows")] use crate::server::resolve::strip_unc; use tauri::Manager; +use std::path::PathBuf; #[tauri::command] pub fn get_platform_ui_scale(window: tauri::Window) -> f64 { @@ -97,4 +98,4 @@ pub fn reset_suwayomi_data(app: tauri::AppHandle) -> Result<(), String> { } } Ok(()) -} \ No newline at end of file +} diff --git a/src/core/auth.ts b/src/core/auth.ts index 0747ec6..4671003 100644 --- a/src/core/auth.ts +++ b/src/core/auth.ts @@ -9,20 +9,53 @@ export class AuthRequiredError extends Error { } } -const TOKEN_KEY = "moku_access_token"; -let _accessToken: string | null = sessionStorage.getItem(TOKEN_KEY); +const TOKEN_KEY = "moku_access_token_v2"; +const LEGACY_TOKEN_KEY = "moku_access_token"; + +interface StoredAccessToken { + base: string; + token: string; +} + +let _accessToken: string | null = null; +let _accessTokenBase: string | null = null; export const uiAuth = { - getToken: () => _accessToken, - setToken: (t: string) => { _accessToken = t; sessionStorage.setItem(TOKEN_KEY, t); }, - clearToken: () => { _accessToken = null; sessionStorage.removeItem(TOKEN_KEY); }, + getToken: () => { + const base = getServerBase(); + if (_accessToken && _accessTokenBase === base) return _accessToken; + const stored = readStoredToken(); + if (!stored) return null; + if (stored.base !== base) { + sessionStorage.removeItem(TOKEN_KEY); + _accessToken = null; + _accessTokenBase = null; + return null; + } + _accessToken = stored.token; + _accessTokenBase = stored.base; + return _accessToken; + }, + setToken: (t: string) => { + const base = getServerBase(); + _accessToken = t; + _accessTokenBase = base; + sessionStorage.setItem(TOKEN_KEY, JSON.stringify({ base, token: t })); + sessionStorage.removeItem(LEGACY_TOKEN_KEY); + }, + clearToken: () => { + _accessToken = null; + _accessTokenBase = null; + sessionStorage.removeItem(TOKEN_KEY); + sessionStorage.removeItem(LEGACY_TOKEN_KEY); + }, }; export const authSession = { clearTokens() { uiAuth.clearToken(); }, hasSession(): boolean { const mode = store.settings.serverAuthMode ?? "NONE"; - if (mode === "UI_LOGIN") return _accessToken !== null; + if (mode === "UI_LOGIN") return uiAuth.getToken() !== null; return true; }, }; @@ -32,6 +65,22 @@ function getServerBase(): string { return typeof url === "string" && url.trim() ? url.replace(/\/$/, "") : "http://127.0.0.1:4567"; } +function readStoredToken(): StoredAccessToken | null { + const raw = sessionStorage.getItem(TOKEN_KEY); + if (raw) { + try { + const parsed = JSON.parse(raw); + if (typeof parsed?.base === "string" && typeof parsed?.token === "string") + return { base: parsed.base, token: parsed.token }; + } catch {} + } + const legacy = sessionStorage.getItem(LEGACY_TOKEN_KEY); + if (legacy && legacy.trim()) { + return { base: getServerBase(), token: legacy.trim() }; + } + return null; +} + function timeoutSignal(ms: number): AbortSignal { const controller = new AbortController(); setTimeout(() => controller.abort(), ms); @@ -100,7 +149,7 @@ export async function loginUI(user: string, pass: string): Promise { const token: string | undefined = json?.data?.login?.accessToken; if (!token) throw new Error(json?.errors?.[0]?.message ?? "Login failed"); uiAuth.setToken(token); - updateSettings({ serverAuthMode: "UI_LOGIN" }); + updateSettings({ serverAuthMode: "UI_LOGIN", serverAuthUser: user, serverAuthPass: "" }); } export async function loginBasic(user: string, pass: string): Promise { @@ -123,8 +172,9 @@ export async function probeServer(): Promise<"ok" | "auth_required" | "unreachab const base = getServerBase(); const mode = store.settings.serverAuthMode ?? "NONE"; const s = store.settings; + const token = uiAuth.getToken(); - if (mode === "UI_LOGIN" && !_accessToken) return "auth_required"; + if (mode === "UI_LOGIN" && !token) return "auth_required"; try { const headers: Record = { "Content-Type": "application/json" }; @@ -132,8 +182,8 @@ export async function probeServer(): Promise<"ok" | "auth_required" | "unreachab const user = s.serverAuthUser?.trim() ?? ""; const pass = s.serverAuthPass?.trim() ?? ""; if (user && pass) Object.assign(headers, basicHeader(user, pass)); - } else if (mode === "UI_LOGIN" && _accessToken) { - Object.assign(headers, bearerHeader(_accessToken)); + } else if (mode === "UI_LOGIN" && token) { + Object.assign(headers, bearerHeader(token)); } const res = await fetch(`${base}/api/graphql`, { diff --git a/src/features/settings/sections/SecuritySettings.svelte b/src/features/settings/sections/SecuritySettings.svelte index aa98806..c171ce9 100644 --- a/src/features/settings/sections/SecuritySettings.svelte +++ b/src/features/settings/sections/SecuritySettings.svelte @@ -1,7 +1,7 @@