mirror of
https://github.com/moku-project/Moku.git
synced 2026-06-13 09:19:56 -05:00
Chore: Embed AuthURL into Images
This commit is contained in:
+17
-37
@@ -1,15 +1,8 @@
|
|||||||
import { store, updateSettings } from "../store/state.svelte";
|
import { store, updateSettings } from "../store/state.svelte";
|
||||||
|
|
||||||
// Only NONE and BASIC_AUTH are supported. SIMPLE_LOGIN and UI_LOGIN are
|
|
||||||
// recognised as values the server may report, but this client will not
|
|
||||||
// attempt to authenticate with them — it will show an unsupported-mode
|
|
||||||
// warning instead.
|
|
||||||
export type AuthMode = "NONE" | "BASIC_AUTH" | "SIMPLE_LOGIN" | "UI_LOGIN";
|
export type AuthMode = "NONE" | "BASIC_AUTH" | "SIMPLE_LOGIN" | "UI_LOGIN";
|
||||||
|
|
||||||
export const authSession = {
|
export const authSession = {
|
||||||
// These stubs exist so callers that imported authSession don't break.
|
|
||||||
// Basic-auth credentials are never stored client-side; they are sent
|
|
||||||
// per-request via the Authorization header.
|
|
||||||
clearTokens() {},
|
clearTokens() {},
|
||||||
hasSession(): boolean { return true; },
|
hasSession(): boolean { return true; },
|
||||||
};
|
};
|
||||||
@@ -32,8 +25,8 @@ function buildRequestInit(init: RequestInit, extraHeaders: Record<string, string
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function fetchAuthenticated(
|
export async function fetchAuthenticated(
|
||||||
url: string,
|
url: string,
|
||||||
init: RequestInit,
|
init: RequestInit,
|
||||||
signal?: AbortSignal,
|
signal?: AbortSignal,
|
||||||
): Promise<Response> {
|
): Promise<Response> {
|
||||||
const mode = store.settings.serverAuthMode ?? "NONE";
|
const mode = store.settings.serverAuthMode ?? "NONE";
|
||||||
@@ -46,26 +39,21 @@ export async function fetchAuthenticated(
|
|||||||
return fetch(url, buildRequestInit({ ...init, signal }, headers));
|
return fetch(url, buildRequestInit({ ...init, signal }, headers));
|
||||||
}
|
}
|
||||||
|
|
||||||
// SIMPLE_LOGIN, UI_LOGIN, and any future unknown modes: send the request
|
|
||||||
// unauthenticated. The probe/login gate in App.svelte will have already
|
|
||||||
// shown an unsupported-mode warning so the user knows requests may fail.
|
|
||||||
return fetch(url, { ...init, signal });
|
return fetch(url, { ...init, signal });
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function loginBasic(user: string, pass: string): Promise<void> {
|
export async function loginBasic(user: string, pass: string): Promise<void> {
|
||||||
const res = await fetch(`${getServerBase()}/api/graphql`, {
|
const res = await fetch(`${getServerBase()}/api/graphql`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: { "Content-Type": "application/json", ...basicHeader(user, pass) },
|
headers: { "Content-Type": "application/json", ...basicHeader(user, pass) },
|
||||||
body: JSON.stringify({ query: "{ __typename }" }),
|
body: JSON.stringify({ query: "{ __typename }" }),
|
||||||
signal: AbortSignal.timeout(5000),
|
signal: AbortSignal.timeout(5000),
|
||||||
});
|
});
|
||||||
if (!res.ok) throw new Error(`Authentication failed (${res.status})`);
|
if (!res.ok) throw new Error(`Authentication failed (${res.status})`);
|
||||||
// Persist credentials through the store so fetchAuthenticated picks them up.
|
|
||||||
updateSettings({ serverAuthMode: "BASIC_AUTH", serverAuthUser: user, serverAuthPass: pass });
|
updateSettings({ serverAuthMode: "BASIC_AUTH", serverAuthUser: user, serverAuthPass: pass });
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function logout(): Promise<void> {
|
export async function logout(): Promise<void> {
|
||||||
// Basic auth has no server-side session to invalidate.
|
|
||||||
updateSettings({ serverAuthPass: "" });
|
updateSettings({ serverAuthPass: "" });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,48 +63,40 @@ export async function probeServer(): Promise<"ok" | "auth_required" | "unsupport
|
|||||||
const s = store.settings;
|
const s = store.settings;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let headers: Record<string, string> = { "Content-Type": "application/json" };
|
const headers: Record<string, string> = { "Content-Type": "application/json" };
|
||||||
|
|
||||||
if (mode === "BASIC_AUTH") {
|
if (mode === "BASIC_AUTH") {
|
||||||
const user = s.serverAuthUser?.trim() ?? "";
|
const user = s.serverAuthUser?.trim() ?? "";
|
||||||
const pass = s.serverAuthPass?.trim() ?? "";
|
const pass = s.serverAuthPass?.trim() ?? "";
|
||||||
// If we have credentials, try them — a 200 means we're good.
|
|
||||||
// If we don't have credentials yet, fall through to the unauthenticated
|
|
||||||
// probe so we still get the WWW-Authenticate header back.
|
|
||||||
if (user && pass) Object.assign(headers, basicHeader(user, pass));
|
if (user && pass) Object.assign(headers, basicHeader(user, pass));
|
||||||
}
|
}
|
||||||
|
|
||||||
const res = await fetch(`${base}/api/graphql`, {
|
const res = await fetch(`${base}/api/graphql`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
credentials: "include",
|
credentials: "include",
|
||||||
headers,
|
headers,
|
||||||
body: JSON.stringify({ query: "{ __typename }" }),
|
body: JSON.stringify({ query: "{ __typename }" }),
|
||||||
signal: AbortSignal.timeout(2000),
|
signal: AbortSignal.timeout(2000),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (res.ok) return "ok";
|
if (res.ok) {
|
||||||
|
if (mode === "SIMPLE_LOGIN" || mode === "UI_LOGIN") {
|
||||||
|
updateSettings({ serverAuthMode: "NONE" });
|
||||||
|
}
|
||||||
|
return "ok";
|
||||||
|
}
|
||||||
|
|
||||||
if (res.status === 401) {
|
if (res.status === 401) {
|
||||||
// Sniff the WWW-Authenticate header to auto-detect the server's scheme.
|
const wwwAuth = res.headers.get("WWW-Authenticate") ?? "";
|
||||||
const wwwAuth = (res.headers.get("WWW-Authenticate") ?? "").toLowerCase();
|
|
||||||
|
|
||||||
if (/basic/i.test(wwwAuth)) {
|
if (/basic/i.test(wwwAuth)) {
|
||||||
// Server wants Basic Auth — update the stored mode so the login gate
|
if (mode !== "BASIC_AUTH") updateSettings({ serverAuthMode: "BASIC_AUTH" });
|
||||||
// shows the right UI and fetchAuthenticated uses the right scheme.
|
|
||||||
if (mode !== "BASIC_AUTH") {
|
|
||||||
updateSettings({ serverAuthMode: "BASIC_AUTH" });
|
|
||||||
}
|
|
||||||
return "auth_required";
|
return "auth_required";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Any other 401 (Bearer, Digest, cookie-based, etc.) is unsupported.
|
|
||||||
// Try to figure out what it is for a better warning label.
|
|
||||||
if (/bearer/i.test(wwwAuth)) {
|
if (/bearer/i.test(wwwAuth)) {
|
||||||
// Likely SIMPLE_LOGIN or UI_LOGIN — store it so the warning names it.
|
|
||||||
if (mode !== "UI_LOGIN") updateSettings({ serverAuthMode: "UI_LOGIN" });
|
if (mode !== "UI_LOGIN") updateSettings({ serverAuthMode: "UI_LOGIN" });
|
||||||
} else if (mode === "NONE") {
|
} else if (mode === "NONE") {
|
||||||
// Unknown scheme and we had no mode stored — store a sentinel so the
|
|
||||||
// warning fires instead of an infinite auth_required loop.
|
|
||||||
updateSettings({ serverAuthMode: "SIMPLE_LOGIN" });
|
updateSettings({ serverAuthMode: "SIMPLE_LOGIN" });
|
||||||
}
|
}
|
||||||
return "unsupported_mode";
|
return "unsupported_mode";
|
||||||
|
|||||||
+17
-2
@@ -13,11 +13,26 @@ function gqlUrl(): string { return `${getServerUrl()}/api/graphql`; }
|
|||||||
export function thumbUrl(path: string): string {
|
export function thumbUrl(path: string): string {
|
||||||
if (!path) return "";
|
if (!path) return "";
|
||||||
if (path.startsWith("http")) return path;
|
if (path.startsWith("http")) return path;
|
||||||
return `${getServerUrl()}${path}`;
|
|
||||||
|
const base = getServerUrl();
|
||||||
|
const mode = store.settings.serverAuthMode;
|
||||||
|
|
||||||
|
if (mode === "BASIC_AUTH") {
|
||||||
|
const user = store.settings.serverAuthUser?.trim() ?? "";
|
||||||
|
const pass = store.settings.serverAuthPass?.trim() ?? "";
|
||||||
|
if (user && pass) {
|
||||||
|
const url = new URL(`${base}${path}`);
|
||||||
|
url.username = user;
|
||||||
|
url.password = pass;
|
||||||
|
return url.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${base}${path}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface GQLResponse<T> {
|
interface GQLResponse<T> {
|
||||||
data: T;
|
data: T;
|
||||||
errors?: { message: string }[];
|
errors?: { message: string }[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user