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 };
});