mirror of
https://github.com/Nezumi-2711/uptime-monitoring.git
synced 2026-09-22 13:48:31 +00:00
123 lines
4.0 KiB
TypeScript
123 lines
4.0 KiB
TypeScript
import { applyD1Migrations, env, SELF, type D1Migration } from "cloudflare:test";
|
|
import { beforeAll, beforeEach, describe, expect, it } from "vitest";
|
|
import { hashPassword } from "../src/worker/lib/password";
|
|
|
|
const ADMIN_PASSWORD = "correct-horse-battery-staple";
|
|
|
|
async function seedAdmin() {
|
|
await env.DB.batch([
|
|
env.DB.prepare("DELETE FROM login_attempts"),
|
|
env.DB.prepare("DELETE FROM sessions"),
|
|
env.DB.prepare("DELETE FROM admin_credentials"),
|
|
]);
|
|
|
|
const now = Date.now();
|
|
await env.DB.prepare(
|
|
"INSERT INTO admin_credentials (id, password_hash, created_at, updated_at) VALUES (1, ?, ?, ?)",
|
|
)
|
|
.bind(
|
|
await hashPassword(ADMIN_PASSWORD),
|
|
now,
|
|
now,
|
|
)
|
|
.run();
|
|
}
|
|
|
|
function login(password = ADMIN_PASSWORD, ipAddress = "198.51.100.10") {
|
|
return SELF.fetch("https://example.com/api/auth/login", {
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
"CF-Connecting-IP": ipAddress,
|
|
Origin: "https://example.com",
|
|
},
|
|
body: JSON.stringify({ password }),
|
|
});
|
|
}
|
|
|
|
function cookieFrom(response: Response) {
|
|
return response.headers.get("Set-Cookie")?.split(";", 1)[0] ?? "";
|
|
}
|
|
|
|
describe("authentication", () => {
|
|
beforeAll(async () => {
|
|
const testEnv = env as Env & { TEST_MIGRATIONS: D1Migration[] };
|
|
await applyD1Migrations(testEnv.DB, testEnv.TEST_MIGRATIONS);
|
|
});
|
|
beforeEach(seedAdmin);
|
|
|
|
it("logs in with the admin password and creates an HttpOnly session", async () => {
|
|
const response = await login();
|
|
const body = await response.json<{ authenticated: boolean }>();
|
|
|
|
expect(response.status).toBe(200);
|
|
expect(response.headers.get("Set-Cookie")).toContain("upwatch_session=");
|
|
expect(response.headers.get("Set-Cookie")).toContain("HttpOnly");
|
|
expect(response.headers.get("Set-Cookie")).toContain("SameSite=Lax");
|
|
expect(body).toEqual({ authenticated: true });
|
|
|
|
const session = await env.DB.prepare("SELECT id FROM sessions").first();
|
|
expect(session).not.toBeNull();
|
|
});
|
|
|
|
it("rejects an incorrect password without setting a cookie", async () => {
|
|
const response = await login("incorrect-password");
|
|
|
|
expect(response.status).toBe(401);
|
|
expect(response.headers.get("Set-Cookie")).toBeNull();
|
|
expect(await response.json()).toEqual({
|
|
message: "Password is incorrect",
|
|
});
|
|
});
|
|
|
|
it("returns authentication state from the session endpoint", async () => {
|
|
const anonymousResponse = await SELF.fetch("https://example.com/api/auth/me");
|
|
expect(anonymousResponse.status).toBe(200);
|
|
expect(await anonymousResponse.json()).toEqual({ authenticated: false });
|
|
|
|
const loginResponse = await login();
|
|
const authenticatedResponse = await SELF.fetch("https://example.com/api/auth/me", {
|
|
headers: { Cookie: cookieFrom(loginResponse) },
|
|
});
|
|
|
|
expect(authenticatedResponse.status).toBe(200);
|
|
expect(await authenticatedResponse.json()).toEqual({ authenticated: true });
|
|
});
|
|
|
|
it("revokes the persisted session on logout", async () => {
|
|
const loginResponse = await login();
|
|
const cookie = cookieFrom(loginResponse);
|
|
const logoutResponse = await SELF.fetch("https://example.com/api/auth/logout", {
|
|
method: "POST",
|
|
headers: {
|
|
Cookie: cookie,
|
|
Origin: "https://example.com",
|
|
},
|
|
});
|
|
|
|
expect(logoutResponse.status).toBe(200);
|
|
expect(await logoutResponse.json()).toEqual({ ok: true });
|
|
const sessionCount = await env.DB.prepare("SELECT COUNT(*) AS count FROM sessions")
|
|
.first<{ count: number }>();
|
|
expect(sessionCount?.count).toBe(0);
|
|
|
|
const sessionResponse = await SELF.fetch("https://example.com/api/auth/me", {
|
|
headers: { Cookie: cookie },
|
|
});
|
|
expect(await sessionResponse.json()).toEqual({ authenticated: false });
|
|
});
|
|
|
|
it("rate limits repeated failed login attempts by IP", async () => {
|
|
const responses: Response[] = [];
|
|
for (let attempt = 0; attempt < 11; attempt += 1) {
|
|
responses.push(await login("incorrect-password", "203.0.113.42"));
|
|
}
|
|
|
|
expect(responses.slice(0, 10).every((response) => response.status === 401)).toBe(true);
|
|
expect(responses[10].status).toBe(429);
|
|
expect(await responses[10].json()).toEqual({
|
|
message: "Too many login attempts. Try again later",
|
|
});
|
|
});
|
|
});
|