mirror of
https://github.com/moku-project/Moku.git
synced 2026-06-13 09:19:56 -05:00
Rework auth to allow smooth switching
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
use crate::server::resolve::strip_unc;
|
use crate::server::resolve::strip_unc;
|
||||||
use tauri::Manager;
|
use tauri::Manager;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn get_platform_ui_scale(window: tauri::Window) -> f64 {
|
pub fn get_platform_ui_scale(window: tauri::Window) -> f64 {
|
||||||
|
|||||||
+60
-10
@@ -9,20 +9,53 @@ export class AuthRequiredError extends Error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const TOKEN_KEY = "moku_access_token";
|
const TOKEN_KEY = "moku_access_token_v2";
|
||||||
let _accessToken: string | null = sessionStorage.getItem(TOKEN_KEY);
|
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 = {
|
export const uiAuth = {
|
||||||
getToken: () => _accessToken,
|
getToken: () => {
|
||||||
setToken: (t: string) => { _accessToken = t; sessionStorage.setItem(TOKEN_KEY, t); },
|
const base = getServerBase();
|
||||||
clearToken: () => { _accessToken = null; sessionStorage.removeItem(TOKEN_KEY); },
|
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 = {
|
export const authSession = {
|
||||||
clearTokens() { uiAuth.clearToken(); },
|
clearTokens() { uiAuth.clearToken(); },
|
||||||
hasSession(): boolean {
|
hasSession(): boolean {
|
||||||
const mode = store.settings.serverAuthMode ?? "NONE";
|
const mode = store.settings.serverAuthMode ?? "NONE";
|
||||||
if (mode === "UI_LOGIN") return _accessToken !== null;
|
if (mode === "UI_LOGIN") return uiAuth.getToken() !== null;
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -32,6 +65,22 @@ function getServerBase(): string {
|
|||||||
return typeof url === "string" && url.trim() ? url.replace(/\/$/, "") : "http://127.0.0.1:4567";
|
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 {
|
function timeoutSignal(ms: number): AbortSignal {
|
||||||
const controller = new AbortController();
|
const controller = new AbortController();
|
||||||
setTimeout(() => controller.abort(), ms);
|
setTimeout(() => controller.abort(), ms);
|
||||||
@@ -100,7 +149,7 @@ export async function loginUI(user: string, pass: string): Promise<void> {
|
|||||||
const token: string | undefined = json?.data?.login?.accessToken;
|
const token: string | undefined = json?.data?.login?.accessToken;
|
||||||
if (!token) throw new Error(json?.errors?.[0]?.message ?? "Login failed");
|
if (!token) throw new Error(json?.errors?.[0]?.message ?? "Login failed");
|
||||||
uiAuth.setToken(token);
|
uiAuth.setToken(token);
|
||||||
updateSettings({ serverAuthMode: "UI_LOGIN" });
|
updateSettings({ serverAuthMode: "UI_LOGIN", serverAuthUser: user, serverAuthPass: "" });
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function loginBasic(user: string, pass: string): Promise<void> {
|
export async function loginBasic(user: string, pass: string): Promise<void> {
|
||||||
@@ -123,8 +172,9 @@ export async function probeServer(): Promise<"ok" | "auth_required" | "unreachab
|
|||||||
const base = getServerBase();
|
const base = getServerBase();
|
||||||
const mode = store.settings.serverAuthMode ?? "NONE";
|
const mode = store.settings.serverAuthMode ?? "NONE";
|
||||||
const s = store.settings;
|
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 {
|
try {
|
||||||
const headers: Record<string, string> = { "Content-Type": "application/json" };
|
const headers: Record<string, string> = { "Content-Type": "application/json" };
|
||||||
@@ -132,8 +182,8 @@ export async function probeServer(): Promise<"ok" | "auth_required" | "unreachab
|
|||||||
const user = s.serverAuthUser?.trim() ?? "";
|
const user = s.serverAuthUser?.trim() ?? "";
|
||||||
const pass = s.serverAuthPass?.trim() ?? "";
|
const pass = s.serverAuthPass?.trim() ?? "";
|
||||||
if (user && pass) Object.assign(headers, basicHeader(user, pass));
|
if (user && pass) Object.assign(headers, basicHeader(user, pass));
|
||||||
} else if (mode === "UI_LOGIN" && _accessToken) {
|
} else if (mode === "UI_LOGIN" && token) {
|
||||||
Object.assign(headers, bearerHeader(_accessToken));
|
Object.assign(headers, bearerHeader(token));
|
||||||
}
|
}
|
||||||
|
|
||||||
const res = await fetch(`${base}/api/graphql`, {
|
const res = await fetch(`${base}/api/graphql`, {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { store, updateSettings } from "@store/state.svelte";
|
import { store, updateSettings } from "@store/state.svelte";
|
||||||
import { gql } from "@api/client";
|
import { gql } from "@api/client";
|
||||||
import { authSession } from "@core/auth";
|
import { authSession, loginUI } from "@core/auth";
|
||||||
import { GET_SERVER_SECURITY } from "@api/queries/extensions";
|
import { GET_SERVER_SECURITY } from "@api/queries/extensions";
|
||||||
import { SET_SERVER_AUTH, SET_SOCKS_PROXY, SET_FLARESOLVERR } from "@api/mutations/extensions";
|
import { SET_SERVER_AUTH, SET_SOCKS_PROXY, SET_FLARESOLVERR } from "@api/mutations/extensions";
|
||||||
|
|
||||||
@@ -33,6 +33,11 @@
|
|||||||
let flareTtl = $state(store.settings.flareSolverrSessionTtl ?? 15);
|
let flareTtl = $state(store.settings.flareSolverrSessionTtl ?? 15);
|
||||||
let flareFallback = $state(store.settings.flareSolverrAsResponseFallback ?? false);
|
let flareFallback = $state(store.settings.flareSolverrAsResponseFallback ?? false);
|
||||||
|
|
||||||
|
function normalizeAuthMode(mode: string): "NONE" | "BASIC_AUTH" | "UI_LOGIN" {
|
||||||
|
if (mode === "BASIC_AUTH" || mode === "UI_LOGIN" || mode === "NONE") return mode;
|
||||||
|
return "NONE";
|
||||||
|
}
|
||||||
|
|
||||||
function showSaved(key: string) {
|
function showSaved(key: string) {
|
||||||
secSaved = key; secError = null;
|
secSaved = key; secError = null;
|
||||||
setTimeout(() => { if (secSaved === key) secSaved = null; }, 2000);
|
setTimeout(() => { if (secSaved === key) secSaved = null; }, 2000);
|
||||||
@@ -53,9 +58,10 @@
|
|||||||
flareSolverrAsResponseFallback: boolean;
|
flareSolverrAsResponseFallback: boolean;
|
||||||
}}>(GET_SERVER_SECURITY);
|
}}>(GET_SERVER_SECURITY);
|
||||||
const s = res.settings;
|
const s = res.settings;
|
||||||
authMode = store.settings.serverAuthMode ?? "NONE";
|
const serverMode = normalizeAuthMode(s.authMode);
|
||||||
authUsername = s.authUsername || store.settings.serverAuthUser || "";
|
authMode = serverMode;
|
||||||
updateSettings({ serverAuthUser: authUsername });
|
authUsername = s.authUsername || "";
|
||||||
|
updateSettings({ serverAuthMode: serverMode, serverAuthUser: authUsername });
|
||||||
socksEnabled = s.socksProxyEnabled; socksHost = s.socksProxyHost;
|
socksEnabled = s.socksProxyEnabled; socksHost = s.socksProxyHost;
|
||||||
socksPort = s.socksProxyPort; socksVersion = s.socksProxyVersion;
|
socksPort = s.socksProxyPort; socksVersion = s.socksProxyVersion;
|
||||||
socksUsername = s.socksProxyUsername;
|
socksUsername = s.socksProxyUsername;
|
||||||
@@ -82,23 +88,30 @@
|
|||||||
try {
|
try {
|
||||||
const newUser = authMode !== "NONE" ? authUsername.trim() : "";
|
const newUser = authMode !== "NONE" ? authUsername.trim() : "";
|
||||||
const newPass = authMode !== "NONE" ? authPassword.trim() : "";
|
const newPass = authMode !== "NONE" ? authPassword.trim() : "";
|
||||||
await gql(SET_SERVER_AUTH, { authMode, authUsername: newUser, authPassword: newPass });
|
authSession.clearTokens();
|
||||||
|
|
||||||
if (authMode === "UI_LOGIN") {
|
if (authMode === "UI_LOGIN") {
|
||||||
authSession.clearTokens();
|
await loginUI(newUser, newPass);
|
||||||
updateSettings({ serverAuthMode: "UI_LOGIN", serverAuthUser: newUser, serverAuthPass: "" });
|
updateSettings({ serverAuthMode: "UI_LOGIN", serverAuthUser: newUser, serverAuthPass: "" });
|
||||||
} else if (authMode === "BASIC_AUTH") {
|
} else if (authMode === "BASIC_AUTH") {
|
||||||
updateSettings({ serverAuthMode: "BASIC_AUTH", serverAuthUser: newUser, serverAuthPass: newPass });
|
updateSettings({ serverAuthMode: "BASIC_AUTH", serverAuthUser: newUser, serverAuthPass: newPass });
|
||||||
} else {
|
} else {
|
||||||
authSession.clearTokens();
|
|
||||||
updateSettings({ serverAuthMode: "NONE", serverAuthUser: "", serverAuthPass: "" });
|
updateSettings({ serverAuthMode: "NONE", serverAuthUser: "", serverAuthPass: "" });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await gql(SET_SERVER_AUTH, { authMode, authUsername: newUser, authPassword: newPass });
|
||||||
|
|
||||||
authPassword = "";
|
authPassword = "";
|
||||||
showSaved("auth");
|
showSaved("auth");
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
updateSettings({ serverAuthMode: prev.mode, serverAuthUser: prev.user, serverAuthPass: prev.pass });
|
const msg = e?.message ?? "Failed to save authentication settings";
|
||||||
secError = e?.message ?? "Failed to save authentication settings";
|
const authMismatch = /unauthorized|unauthenticated|authentication|401/i.test(msg);
|
||||||
|
if (!authMismatch) {
|
||||||
|
authSession.clearTokens();
|
||||||
|
updateSettings({ serverAuthMode: prev.mode, serverAuthUser: prev.user, serverAuthPass: prev.pass });
|
||||||
|
}
|
||||||
|
secError = authMismatch
|
||||||
|
? "Saved local auth settings, but the server rejected the update. Verify your new credentials with the current server configuration."
|
||||||
|
: msg;
|
||||||
} finally { secLoading = false; }
|
} finally { secLoading = false; }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -223,7 +236,7 @@
|
|||||||
</button>
|
</button>
|
||||||
{/if}
|
{/if}
|
||||||
<button class="s-btn s-btn-accent" onclick={saveAuth}
|
<button class="s-btn s-btn-accent" onclick={saveAuth}
|
||||||
disabled={secLoading || (authMode === "BASIC_AUTH" && (!authUsername.trim() || !authPassword.trim()))}>
|
disabled={secLoading || ((authMode === "BASIC_AUTH" || authMode === "UI_LOGIN") && (!authUsername.trim() || !authPassword.trim()))}>
|
||||||
{secLoading ? "Saving…" : secSaved === "auth" ? "Saved ✓" : store.settings.serverAuthMode === "BASIC_AUTH" ? "Update" : authMode === "NONE" ? "Save" : "Enable"}
|
{secLoading ? "Saving…" : secSaved === "auth" ? "Saved ✓" : store.settings.serverAuthMode === "BASIC_AUTH" ? "Update" : authMode === "NONE" ? "Save" : "Enable"}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user