feat(auth): setup/login/logout, argon2id, sessions, login rate limit
This commit is contained in:
55
server/test/auth.test.ts
Normal file
55
server/test/auth.test.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
import { describe, expect, test } from "bun:test";
|
||||
import { mkdtempSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { createApp } from "../src/app";
|
||||
import { openDb } from "../src/db";
|
||||
import { loadOrCreateKey } from "../src/lib/crypto";
|
||||
|
||||
function makeApp() {
|
||||
const dir = mkdtempSync(join(tmpdir(), "helios-"));
|
||||
return createApp({ db: openDb(dir), key: loadOrCreateKey(dir) });
|
||||
}
|
||||
const json = (body: unknown) => ({
|
||||
method: "POST",
|
||||
headers: { "content-type": "application/json" },
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
|
||||
describe("auth", () => {
|
||||
test("fresh instance needs setup; setup then login yields a session", async () => {
|
||||
const app = makeApp();
|
||||
let me = await (await app.request("/api/me")).json();
|
||||
expect(me).toEqual({ needsSetup: true, authenticated: false });
|
||||
|
||||
expect((await app.request("/api/setup", json({ password: "hunter2hunter2" }))).status).toBe(204);
|
||||
expect((await app.request("/api/setup", json({ password: "again-not-allowed" }))).status).toBe(400);
|
||||
|
||||
const login = await app.request("/api/login", json({ password: "hunter2hunter2" }));
|
||||
expect(login.status).toBe(204);
|
||||
const cookie = login.headers.get("set-cookie")!;
|
||||
expect(cookie).toContain("helios_session=");
|
||||
expect(cookie).toContain("HttpOnly");
|
||||
|
||||
me = await (await app.request("/api/me", { headers: { cookie } })).json();
|
||||
expect(me.authenticated).toBe(true);
|
||||
});
|
||||
|
||||
test("wrong password 401; protected route 401 without cookie", async () => {
|
||||
const app = makeApp();
|
||||
await app.request("/api/setup", json({ password: "hunter2hunter2" }));
|
||||
expect((await app.request("/api/login", json({ password: "wrong-wrong-1" }))).status).toBe(401);
|
||||
expect((await app.request("/api/settings")).status).toBe(401);
|
||||
});
|
||||
|
||||
test("11th failed login from one IP is rate-limited", async () => {
|
||||
const app = makeApp();
|
||||
await app.request("/api/setup", json({ password: "hunter2hunter2" }));
|
||||
const hdrs = { "content-type": "application/json", "x-forwarded-for": "10.9.8.7" };
|
||||
let last = 0;
|
||||
for (let i = 0; i < 11; i++) {
|
||||
last = (await app.request("/api/login", { method: "POST", headers: hdrs, body: JSON.stringify({ password: "wrong-wrong-1" }) })).status;
|
||||
}
|
||||
expect(last).toBe(429);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user