import { describe, it, before, after } from "node:test"; import assert from "node:assert/strict"; import { createTestDb } from "../../helpers/sqlite.js"; import { createTestServer, type TestServer } from "../../helpers/koa.js"; import { hashPassword } from "../../../src/util/hash.js"; import type { SqliteInterface } from "../../../src/db/sqlite/SqliteInterface.js"; const EMAIL = "auth-http@test.example"; const PASSWORD = "auth-http-password"; function getCookie(res: Response, name: string): string | undefined { const raw = res.headers.getSetCookie?.() ?? []; for (const entry of raw) { const [pair] = entry.split(";"); const [key, value] = pair.split("="); if (key === name) return value; } return undefined; } describe("api backend: auth over HTTP", async () => { let db: SqliteInterface; let closeDb: () => void; let server: TestServer; let uid: string; before(async () => { ({ db, close: closeDb } = await createTestDb()); server = await createTestServer(db); const pw = await hashPassword(PASSWORD); const user = await db.createUser({ email: EMAIL, name: "Auth HTTP User", password: pw, flags: {}, }); uid = user.uid; }); after(async () => { await server.close(); closeDb(); }); it("POST /auth/login sets a session cookie", async () => { const res = await fetch(`${server.url}/auth/login`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ email: EMAIL, password: PASSWORD }), }); assert.equal(res.status, 200); const cookie = getCookie(res, "abode_session"); assert.ok(cookie, "session cookie set"); }); it("session cookie authenticates GET /auth/self", async () => { const login = await fetch(`${server.url}/auth/login`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ email: EMAIL, password: PASSWORD }), }); const cookie = getCookie(login, "abode_session"); const self = await fetch(`${server.url}/auth/self`, { headers: { Cookie: `abode_session=${cookie}` }, }); assert.equal(self.status, 200); const body = (await self.json()) as { uid: string }; assert.equal(body.uid, uid); }); it("GET /auth/self without credentials returns 401", async () => { const res = await fetch(`${server.url}/auth/self`); assert.equal(res.status, 401); }); it("POST /auth/logout clears the cookie and invalidates the session server-side", async () => { const login = await fetch(`${server.url}/auth/login`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ email: EMAIL, password: PASSWORD }), }); const cookie = getCookie(login, "abode_session"); const logout = await fetch(`${server.url}/auth/logout`, { method: "POST", headers: { Cookie: `abode_session=${cookie}` }, }); assert.equal(logout.status, 204); assert.equal(getCookie(logout, "abode_session"), ""); const self = await fetch(`${server.url}/auth/self`, { headers: { Cookie: `abode_session=${cookie}` }, }); assert.equal(self.status, 401, "session was invalidated server-side, not just the cookie cleared"); }); it("POST /auth/clear-sessions invalidates outstanding session cookies", async () => { const login = await fetch(`${server.url}/auth/login`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ email: EMAIL, password: PASSWORD }), }); const cookie = getCookie(login, "abode_session"); const clear = await fetch(`${server.url}/auth/clear-sessions`, { method: "POST", headers: { Cookie: `abode_session=${cookie}` }, }); assert.equal(clear.status, 204); const self = await fetch(`${server.url}/auth/self`, { headers: { Cookie: `abode_session=${cookie}` }, }); assert.equal(self.status, 401); }); it("Bearer apikey token authenticates protected routes", async () => { const [, token] = await db.createApikey({ uid, name: "HTTP Test Key", permissions: { all: true }, }); const res = await fetch(`${server.url}/users`, { headers: { Authorization: `Bearer ${token}` }, }); assert.equal(res.status, 200); }); it("invalid Bearer apikey token returns 401 invalid_apikey", async () => { const res = await fetch(`${server.url}/users`, { headers: { Authorization: `Bearer at_${"0".repeat(32)}` }, }); assert.equal(res.status, 401); const body = (await res.json()) as { error: string }; assert.equal(body.error, "invalid_apikey"); }); });