e322ed1167
Auth login fort : mot de passe scrypt + TOTP 2FA (otplib) + JWT HS256 (jose), login tout-ou-rien sans indice. ChatService factorise le tour d'agent pour toutes les surfaces (Telegram refactoré). 60 tests, 0 vuln. Palier de risque : reversible (logique d'auth ; surface API câblée en v0.20). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
74 lines
2.2 KiB
TypeScript
74 lines
2.2 KiB
TypeScript
import { describe, it, expect } from "vitest";
|
|
import { generateSecret, generateSync } from "otplib";
|
|
import {
|
|
hashPassword,
|
|
verifyPassword,
|
|
verifyTotp,
|
|
issueJwt,
|
|
verifyJwt,
|
|
login,
|
|
type AuthConfig,
|
|
} from "../src/api/auth.js";
|
|
|
|
describe("password (scrypt)", () => {
|
|
it("vérifie un mot de passe correct et rejette un faux", () => {
|
|
const h = hashPassword("s3cr3t!");
|
|
expect(verifyPassword("s3cr3t!", h)).toBe(true);
|
|
expect(verifyPassword("wrong", h)).toBe(false);
|
|
});
|
|
|
|
it("rejette un hash malformé", () => {
|
|
expect(verifyPassword("x", "pasunhash")).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe("TOTP", () => {
|
|
it("valide un code généré pour le secret", () => {
|
|
const secret = generateSecret();
|
|
const code = generateSync({ secret: secret });
|
|
expect(verifyTotp(code, secret)).toBe(true);
|
|
expect(verifyTotp("000000", secret)).toBe(false);
|
|
});
|
|
});
|
|
|
|
const cfg = (): AuthConfig => ({
|
|
adminUser: "kantin",
|
|
adminPasswordHash: hashPassword("pw"),
|
|
totpSecret: generateSecret(),
|
|
jwtSecret: "a-very-long-jwt-secret-value-32+chars",
|
|
});
|
|
|
|
describe("JWT", () => {
|
|
it("émet et vérifie un JWT", async () => {
|
|
const c = cfg();
|
|
const token = await issueJwt(c);
|
|
expect(await verifyJwt(token, c.jwtSecret)).toBe("kantin");
|
|
});
|
|
|
|
it("rejette un JWT avec mauvais secret", async () => {
|
|
const c = cfg();
|
|
const token = await issueJwt(c);
|
|
expect(await verifyJwt(token, "autre-secret-completement-different")).toBeNull();
|
|
});
|
|
});
|
|
|
|
describe("login (mdp + TOTP, tout-ou-rien)", () => {
|
|
it("réussit avec les bons facteurs", async () => {
|
|
const c = cfg();
|
|
const token = await login(c, {
|
|
user: "kantin",
|
|
password: "pw",
|
|
totp: generateSync({ secret: c.totpSecret }),
|
|
});
|
|
expect(token).not.toBeNull();
|
|
});
|
|
|
|
it("échoue si un seul facteur est faux", async () => {
|
|
const c = cfg();
|
|
const good = generateSync({ secret: c.totpSecret });
|
|
expect(await login(c, { user: "kantin", password: "bad", totp: good })).toBeNull();
|
|
expect(await login(c, { user: "kantin", password: "pw", totp: "000000" })).toBeNull();
|
|
expect(await login(c, { user: "intrus", password: "pw", totp: good })).toBeNull();
|
|
});
|
|
});
|