Files

95 lines
3.5 KiB
TypeScript

import type { FastifyInstance } from "fastify";
import { afterEach, describe, expect, it } from "vitest";
import { buildApp } from "../src/app";
import { hashPassword } from "../src/auth/password";
import { CSRF_COOKIE, SESSION_COOKIE } from "../src/auth/session";
import type { Database } from "../src/db/client";
import { users } from "../src/db/schema";
import { makeConfig, openTestDb } from "./helpers";
// Tests : pas de worker pino-pretty, error-handler en mode production.
process.env.NODE_ENV = "production";
process.env.SEICHE_LOG_LEVEL = "silent";
let db: Database;
let app!: FastifyInstance;
afterEach(async () => {
await app.close();
});
async function seedUser(username: string, password: string): Promise<void> {
await db.insert(users).values({ username, passwordHash: await hashPassword(password) });
}
function setCookies(res: { headers: Record<string, unknown> }, name: string): string[] {
const raw = res.headers["set-cookie"];
const list = Array.isArray(raw) ? raw : raw === undefined ? [] : [String(raw)];
return list.filter((c): c is string => typeof c === "string" && c.startsWith(`${name}=`));
}
async function buildLoginApp(
overrides: Parameters<typeof makeConfig>[0],
): Promise<FastifyInstance> {
db = await openTestDb();
const instance = await buildApp({ config: makeConfig(overrides), db });
await seedUser("alice", "secret");
return instance;
}
function login(password: string, headers: Record<string, string> = {}) {
return app.inject({
method: "POST",
url: "/api/auth/login",
payload: { username: "alice", password },
headers,
});
}
describe("Proxy (SEICHE_TRUST_PROXY / SEICHE_COOKIE_SECURE)", () => {
describe("trustProxy", () => {
it("activé : X-Forwarded-For est parsé → chaque IP client est limitée séparément", async () => {
app = await buildLoginApp({ trustProxy: true });
const ips = [7, 8, 9, 10, 11, 12].map((n) => `203.0.113.${n}`);
for (const ip of ips) {
const res = await login("wrong", { "x-forwarded-for": ip });
expect(res.statusCode, `l'IP ${ip} a été bloquée à tort`).toBe(401);
}
});
it("désactivé (défaut) : X-Forwarded-For est ignoré → même IP socket, 6e tentative bloquée", async () => {
app = await buildLoginApp({ trustProxy: false });
const codes: number[] = [];
for (let i = 7; i <= 12; i++) {
const res = await login("wrong", { "x-forwarded-for": `203.0.113.${i}` });
codes.push(res.statusCode);
}
expect(codes).toEqual([401, 401, 401, 401, 401, 429]);
});
});
describe("cookieSecure", () => {
it("activé : les cookies de session et CSRF sont marqués Secure", async () => {
app = await buildLoginApp({ cookieSecure: true });
const res = await login("secret");
expect(res.statusCode).toBe(200);
for (const name of [SESSION_COOKIE, CSRF_COOKIE]) {
const cookies = setCookies(res, name);
expect(cookies, `cookie ${name} absent`).toHaveLength(1);
expect(cookies[0]?.toLowerCase()).toContain("secure");
}
});
it("désactivé (défaut) : pas de drapeau Secure", async () => {
app = await buildLoginApp({ cookieSecure: false });
const res = await login("secret");
expect(res.statusCode).toBe(200);
for (const name of [SESSION_COOKIE, CSRF_COOKIE]) {
const cookies = setCookies(res, name);
expect(cookies).toHaveLength(1);
expect(cookies[0]?.toLowerCase()).not.toContain("secure");
}
});
});
});