import { beforeEach, describe, expect, it } from "vitest"; import { eq } from "drizzle-orm"; import { db } from "@/db"; import { sessions, users } from "@/db/schema"; import { hashPassword, verifyPassword } from "@/lib/auth/password"; import { createSession, deleteSession, validateSessionToken, } from "@/lib/auth/session"; import { resetDb } from "../helpers/db"; beforeEach(resetDb); async function insertUser() { const [user] = await db .insert(users) .values({ username: "admin", passwordHash: await hashPassword("correct horse") }) .returning(); return user; } describe("password hashing", () => { it("verifies the original password and rejects others", async () => { const hash = await hashPassword("s3cret-passphrase"); expect(hash.startsWith("scrypt$")).toBe(true); expect(hash).not.toContain("s3cret-passphrase"); expect(await verifyPassword(hash, "s3cret-passphrase")).toBe(true); expect(await verifyPassword(hash, "wrong")).toBe(false); }); it("produces unique salts per hash", async () => { expect(await hashPassword("same")).not.toBe(await hashPassword("same")); }); it("rejects malformed stored hashes without throwing", async () => { expect(await verifyPassword("garbage", "x")).toBe(false); expect(await verifyPassword("scrypt$bad$data", "x")).toBe(false); }); }); describe("sessions", () => { it("round-trips a valid session token", async () => { const user = await insertUser(); const { token, expiresAt } = await createSession(user.id); expect(expiresAt.getTime()).toBeGreaterThan(Date.now()); const sessionUser = await validateSessionToken(token); expect(sessionUser).toEqual({ id: user.id, username: "admin", role: "author", permissions: { createTags: false, publishPosts: false, unpublishPosts: false, deletePosts: false, approveComments: false, }, }); // Only a hash of the token is stored. const rows = await db.select().from(sessions); expect(rows).toHaveLength(1); expect(rows[0].id).not.toBe(token); }); it("rejects unknown tokens", async () => { await insertUser(); expect(await validateSessionToken("not-a-real-token")).toBeNull(); expect(await validateSessionToken("")).toBeNull(); }); it("treats expired sessions as absent and deletes them", async () => { const user = await insertUser(); const { token } = await createSession(user.id); await db .update(sessions) .set({ expiresAt: new Date(Date.now() - 1000) }) .where(eq(sessions.userId, user.id)); expect(await validateSessionToken(token)).toBeNull(); expect(await db.select().from(sessions)).toHaveLength(0); }); it("invalidates a session on logout", async () => { const user = await insertUser(); const { token } = await createSession(user.id); await deleteSession(token); expect(await validateSessionToken(token)).toBeNull(); }); });