diff --git a/frontend/index.html b/frontend/index.html index 1a8138c..b1114b8 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -453,16 +453,33 @@ let searchTimer = null; let pendingDeleteSlug = null; + function authHeaders() { + const token = localStorage.getItem('token'); + return token ? { 'Authorization': 'Bearer ' + token } : {}; + } + + function apiFetch(url, options = {}) { + return fetch(url, { + ...options, + headers: { ...authHeaders(), ...(options.headers || {}) }, + }); + } + // ── Auth ────────────────────────────────────────────────────────────────── async function checkAuth() { - const res = await fetch('/api/auth/me'); + if (!localStorage.getItem('token')) { + document.getElementById('login-view').style.display = 'flex'; + return; + } + const res = await apiFetch('/api/auth/me'); if (res.ok) { const data = await res.json(); setUser(data.username); showPage('customers'); loadCustomers(); } else { + localStorage.removeItem('token'); document.getElementById('login-view').style.display = 'flex'; } } @@ -487,6 +504,7 @@ }); const data = await res.json(); if (!res.ok) throw new Error(data.message ?? 'Login failed'); + localStorage.setItem('token', data.token); setUser(data.username); document.getElementById('login-view').style.display = 'none'; showPage('customers'); @@ -498,7 +516,7 @@ } async function logout() { - await fetch('/api/auth/logout', { method: 'POST' }); + localStorage.removeItem('token'); document.getElementById('app-view').style.display = 'none'; document.getElementById('login-view').style.display = 'flex'; document.getElementById('login-password').value = ''; @@ -525,7 +543,7 @@ const s = tableState; const params = new URLSearchParams({ page: s.page, limit: s.limit, sort: s.sort, order: s.order }); if (s.q) params.set('q', s.q); - const res = await fetch('/api/customers?' + params); + const res = await apiFetch('/api/customers?' + params); if (!res.ok) return; const { data, pagination } = await res.json(); tableState.total = pagination.total; @@ -642,7 +660,7 @@ btn.disabled = true; btn.textContent = 'Deleting…'; try { - const res = await fetch(`/api/customers/${slug}`, { method: 'DELETE' }); + const res = await apiFetch(`/api/customers/${slug}`, { method: 'DELETE' }); if (!res.ok) { const d = await res.json().catch(() => ({})); throw new Error(d.message ?? 'Delete failed'); @@ -659,7 +677,7 @@ async function removeRecord(slug) { if (!confirm(`Remove "${slug}" from the manager database only?\n\nThis will NOT delete the ArgoCD app or DO database — use this only for failed partial deployments.`)) return; - const res = await fetch(`/api/customers/${slug}/record`, { method: 'DELETE' }); + const res = await apiFetch(`/api/customers/${slug}/record`, { method: 'DELETE' }); if (res.ok) { loadCustomers(); } else { @@ -689,7 +707,7 @@ btn.textContent = 'Provisioning…'; status.className = 'status'; try { - const res = await fetch('/api/customers', { + const res = await apiFetch('/api/customers', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ slug, name, modules, startDate, expirationDate }), @@ -724,7 +742,7 @@ return; } try { - const res = await fetch('/api/auth/password', { + const res = await apiFetch('/api/auth/password', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ password: pw }), diff --git a/src/index.ts b/src/index.ts index 9bd2e25..b604985 100644 --- a/src/index.ts +++ b/src/index.ts @@ -17,16 +17,13 @@ import { customerRoutes } from "./routes/customers"; const app = Fastify({ logger: true }); await app.register(cookiePlugin); -await app.register(jwtPlugin, { - secret: config.jwtSecret, - cookie: { cookieName: "token", signed: false }, -}); +await app.register(jwtPlugin, { secret: config.jwtSecret }); app.decorate("authenticate", async function (req: any, reply: any) { try { - await req.jwtVerify({ onlyCookie: true }); + await req.jwtVerify(); } catch { - reply.status(401).send({ message: "Unauthorized" }); + return reply.status(401).send({ message: "Unauthorized" }); } }); diff --git a/src/routes/auth.ts b/src/routes/auth.ts index 70b2fbe..c5020b3 100644 --- a/src/routes/auth.ts +++ b/src/routes/auth.ts @@ -47,20 +47,10 @@ export async function authRoutes(app: FastifyInstance) { } const token = app.jwt.sign({ sub: user.id, username: user.username }, { expiresIn: "7d" }); - - reply.setCookie("token", token, { - httpOnly: true, - secure: true, - sameSite: "lax", - path: "/", - maxAge: 60 * 60 * 24 * 7, - }); - - return { username: user.username }; + return { username: user.username, token }; }); - app.post("/auth/logout", async (_req, reply) => { - reply.clearCookie("token", { path: "/" }); + app.post("/auth/logout", async () => { return { ok: true }; });