From 40accb96974d46afc3bea2159611b3531620037d Mon Sep 17 00:00:00 2001 From: Stan Grams Date: Sat, 1 Aug 2026 16:39:52 +0200 Subject: [PATCH] refactor: extract typed authentication API --- .../assets/web/generated/app.js | 89 ++++++++++++------- .../frontend/src/api/auth.ts | 68 ++++++++++++++ .../trx-frontend-http/frontend/src/app.js | 41 ++------- 3 files changed, 133 insertions(+), 65 deletions(-) create mode 100644 src/trx-client/trx-frontend/trx-frontend-http/frontend/src/api/auth.ts diff --git a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/generated/app.js b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/generated/app.js index 09a3e166..017fa03b 100644 --- a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/generated/app.js +++ b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/generated/app.js @@ -137,48 +137,73 @@ bridge.decoderRegistry = decoderRegistry; bridge.onDecoderRegistryReady = onDecoderRegistryReady; + // src/api/auth.ts + function decodeAuthSession(value) { + if (typeof value !== "object" || value === null) { + throw new TypeError("The authentication response is malformed"); + } + const session = value; + if (typeof session.authenticated !== "boolean") { + throw new TypeError("The authentication response has no authenticated flag"); + } + if (session.role !== void 0 && session.role !== "rx" && session.role !== "control") { + throw new TypeError("The authentication response has an invalid role"); + } + if (session.auth_disabled !== void 0 && typeof session.auth_disabled !== "boolean") { + throw new TypeError("The authentication response has an invalid auth_disabled flag"); + } + const decoded = { authenticated: session.authenticated }; + if (session.role !== void 0) decoded.role = session.role; + if (session.auth_disabled !== void 0) decoded.auth_disabled = session.auth_disabled; + return decoded; + } + var authDisabledSession = { + authenticated: true, + role: "control", + auth_disabled: true + }; + async function fetchAuthSession() { + try { + const response = await fetch("/auth/session"); + if (response.status === 404) return authDisabledSession; + if (!response.ok) return { authenticated: false }; + return decodeAuthSession(await response.json()); + } catch (error) { + console.error("Auth check failed:", error); + return { authenticated: false }; + } + } + async function login(passphrase) { + const response = await fetch("/auth/login", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ passphrase }) + }); + if (response.status === 404) return authDisabledSession; + if (!response.ok) { + const message = await response.text(); + throw new Error(message || "Login failed"); + } + return decodeAuthSession(await response.json()); + } + async function logout() { + const response = await fetch("/auth/logout", { method: "POST" }); + if (response.status !== 404 && !response.ok) throw new Error("Logout failed"); + } + // src/app.js void loadDecoderRegistry(refreshOperatorLayoutCapabilities); var authRole = null; var authEnabled = true; async function checkAuthStatus() { - try { - const resp = await fetch("/auth/session"); - if (resp.status === 404) { - return { authenticated: true, role: "control", auth_disabled: true }; - } - if (!resp.ok) return { authenticated: false }; - const data = await resp.json(); - return data; - } catch (e) { - console.error("Auth check failed:", e); - return { authenticated: false }; - } + return fetchAuthSession(); } async function authLogin(passphrase) { - try { - const resp = await fetch("/auth/login", { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ passphrase }) - }); - if (resp.status === 404) { - return { authenticated: true, role: "control", auth_disabled: true }; - } - if (!resp.ok) { - const text = await resp.text(); - throw new Error(text || "Login failed"); - } - const data = await resp.json(); - return data; - } catch (e) { - throw e; - } + return login(passphrase); } async function authLogout() { try { - const resp = await fetch("/auth/logout", { method: "POST" }); - if (resp.status !== 404 && !resp.ok) throw new Error("Logout failed"); + await logout(); authRole = null; disconnect(); setDecodeHistoryOverlayVisible(false); diff --git a/src/trx-client/trx-frontend/trx-frontend-http/frontend/src/api/auth.ts b/src/trx-client/trx-frontend/trx-frontend-http/frontend/src/api/auth.ts new file mode 100644 index 00000000..d7d9ece5 --- /dev/null +++ b/src/trx-client/trx-frontend/trx-frontend-http/frontend/src/api/auth.ts @@ -0,0 +1,68 @@ +// SPDX-FileCopyrightText: 2026 Stan Grams +// +// SPDX-License-Identifier: GPL-2.0-or-later + +export type AuthRole = "rx" | "control"; + +export interface AuthSession { + authenticated: boolean; + role?: AuthRole; + auth_disabled?: boolean; +} + +function decodeAuthSession(value: unknown): AuthSession { + if (typeof value !== "object" || value === null) { + throw new TypeError("The authentication response is malformed"); + } + const session = value as Record; + if (typeof session.authenticated !== "boolean") { + throw new TypeError("The authentication response has no authenticated flag"); + } + if (session.role !== undefined && session.role !== "rx" && session.role !== "control") { + throw new TypeError("The authentication response has an invalid role"); + } + if (session.auth_disabled !== undefined && typeof session.auth_disabled !== "boolean") { + throw new TypeError("The authentication response has an invalid auth_disabled flag"); + } + const decoded: AuthSession = { authenticated: session.authenticated }; + if (session.role !== undefined) decoded.role = session.role; + if (session.auth_disabled !== undefined) decoded.auth_disabled = session.auth_disabled; + return decoded; +} + +const authDisabledSession: AuthSession = { + authenticated: true, + role: "control", + auth_disabled: true, +}; + +export async function fetchAuthSession(): Promise { + try { + const response = await fetch("/auth/session"); + if (response.status === 404) return authDisabledSession; + if (!response.ok) return { authenticated: false }; + return decodeAuthSession(await response.json()); + } catch (error: unknown) { + console.error("Auth check failed:", error); + return { authenticated: false }; + } +} + +export async function login(passphrase: string): Promise { + const response = await fetch("/auth/login", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ passphrase }), + }); + if (response.status === 404) return authDisabledSession; + if (!response.ok) { + const message = await response.text(); + throw new Error(message || "Login failed"); + } + return decodeAuthSession(await response.json()); +} + +export async function logout(): Promise { + const response = await fetch("/auth/logout", { method: "POST" }); + if (response.status !== 404 && !response.ok) throw new Error("Logout failed"); +} diff --git a/src/trx-client/trx-frontend/trx-frontend-http/frontend/src/app.js b/src/trx-client/trx-frontend/trx-frontend-http/frontend/src/app.js index 7e11ca16..f317909b 100644 --- a/src/trx-client/trx-frontend/trx-frontend-http/frontend/src/app.js +++ b/src/trx-client/trx-frontend/trx-frontend-http/frontend/src/app.js @@ -15,6 +15,11 @@ import { decoderRegistry, loadDecoderRegistry, } from "./core/decoder-registry.js"; +import { + fetchAuthSession, + login, + logout, +} from "./api/auth.js"; // --- Decoder registry (fetched from /decoders on load) --- void loadDecoderRegistry(refreshOperatorLayoutCapabilities); @@ -24,46 +29,16 @@ let authRole = null; // null (not authenticated), "rx" (read-only), or "control let authEnabled = true; async function checkAuthStatus() { - try { - const resp = await fetch("/auth/session"); - if (resp.status === 404) { - // Auth API not exposed -> treat as auth-disabled mode. - return { authenticated: true, role: "control", auth_disabled: true }; - } - if (!resp.ok) return { authenticated: false }; - const data = await resp.json(); - return data; - } catch (e) { - console.error("Auth check failed:", e); - return { authenticated: false }; - } + return fetchAuthSession(); } async function authLogin(passphrase) { - try { - const resp = await fetch("/auth/login", { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ passphrase }), - }); - if (resp.status === 404) { - return { authenticated: true, role: "control", auth_disabled: true }; - } - if (!resp.ok) { - const text = await resp.text(); - throw new Error(text || "Login failed"); - } - const data = await resp.json(); - return data; - } catch (e) { - throw e; - } + return login(passphrase); } async function authLogout() { try { - const resp = await fetch("/auth/logout", { method: "POST" }); - if (resp.status !== 404 && !resp.ok) throw new Error("Logout failed"); + await logout(); authRole = null; // Disconnect and show auth gate without page reload disconnect();