feat: add operator landing and login
GeoIntel release gates / Compile, test, contracts and builds (push) Canceled after 0s
GeoIntel release gates / Python and npm vulnerability policy (push) Canceled after 0s
GeoIntel release gates / GIS image, SBOM and container scan (push) Canceled after 0s

This commit is contained in:
Codex
2026-07-22 20:10:21 +02:00
parent 36d137e224
commit 115f9850a7
27 changed files with 1448 additions and 8 deletions
+20
View File
@@ -0,0 +1,20 @@
import { apiGet, apiPost } from './client'
export interface AuthSession {
authentication_required: boolean
authenticated: boolean
username: string | null
expires_at: string | null
}
export function getAuthSession(): Promise<AuthSession> {
return apiGet<AuthSession>('/api/v1/auth/session')
}
export function login(username: string, password: string): Promise<AuthSession> {
return apiPost<AuthSession>('/api/v1/auth/login', { username, password })
}
export function logout(): Promise<AuthSession> {
return apiPost<AuthSession>('/api/v1/auth/logout')
}
+8 -1
View File
@@ -23,19 +23,23 @@ async function parseResponse<T>(response: Response): Promise<T> {
const code = typeof payload?.error === "string" ? payload.error : legacyError?.code ?? "REQUEST_ERROR";
const message = payload?.message ?? legacyError?.message ?? `Request failed (${response.status})`;
const details = payload?.details ?? legacyError?.details;
if (response.status === 401 && code === "AUTHENTICATION_REQUIRED") {
window.dispatchEvent(new CustomEvent("geointel:session-expired"));
}
throw new ApiHttpError(message, code, details);
}
return payload.data as T;
}
export async function apiGet<T>(path: string): Promise<T> {
const response = await fetch(apiUrl(path));
const response = await fetch(apiUrl(path), { credentials: "same-origin" });
return parseResponse<T>(response);
}
export async function apiPost<T>(path: string, body?: object): Promise<T> {
const response = await fetch(apiUrl(path), {
method: "POST",
credentials: "same-origin",
headers: { "Content-Type": "application/json" },
body: body ? JSON.stringify(body) : undefined,
});
@@ -45,6 +49,7 @@ export async function apiPost<T>(path: string, body?: object): Promise<T> {
export async function apiPatch<T>(path: string, body?: object): Promise<T> {
const response = await fetch(apiUrl(path), {
method: "PATCH",
credentials: "same-origin",
headers: { "Content-Type": "application/json" },
body: body ? JSON.stringify(body) : undefined,
});
@@ -54,6 +59,7 @@ export async function apiPatch<T>(path: string, body?: object): Promise<T> {
export async function apiDelete<T>(path: string): Promise<T> {
const response = await fetch(apiUrl(path), {
method: "DELETE",
credentials: "same-origin",
});
return parseResponse<T>(response);
}
@@ -61,6 +67,7 @@ export async function apiDelete<T>(path: string): Promise<T> {
export async function apiMultipart<T>(path: string, form: FormData): Promise<T> {
const response = await fetch(apiUrl(path), {
method: "POST",
credentials: "same-origin",
body: form,
});
return parseResponse<T>(response);