You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
43 lines
2.3 KiB
TypeScript
43 lines
2.3 KiB
TypeScript
// apps/hq/test/auth.test.ts
|
|
import { describe, it, expect } from 'vitest'
|
|
import { openDb } from '../src/db'
|
|
import { createStaff, login, verifySession } from '../src/auth'
|
|
|
|
describe('hq auth', () => {
|
|
it('logs in with correct password, rejects wrong one', async () => {
|
|
const db = openDb(':memory:')
|
|
await createStaff(db, { email: 'admin@sims.com', displayName: 'Owner', role: 'owner', password: 'let-me-in-9' })
|
|
expect(await login(db, 'admin@sims.com', 'wrong')).toMatchObject({ ok: false, reason: 'invalid' })
|
|
const ok = await login(db, 'admin@sims.com', 'let-me-in-9')
|
|
expect(ok.ok).toBe(true)
|
|
const staff = await verifySession(db, ok.ok ? ok.token : '')
|
|
expect(staff).toMatchObject({ role: 'owner' })
|
|
})
|
|
it('rejects passwords under 8 chars at creation', async () => {
|
|
const db = openDb(':memory:')
|
|
await expect(
|
|
createStaff(db, { email: 'a@b.c', displayName: 'X', role: 'staff', password: 'short' }),
|
|
).rejects.toThrow(/8/)
|
|
})
|
|
it('locks the account after repeated failures, then a wrong-but-formerly-valid attempt is barred (D24)', async () => {
|
|
const db = openDb(':memory:')
|
|
await createStaff(db, { email: 'lock@t.in', displayName: 'L', role: 'staff', password: 'correct-horse-9' })
|
|
for (let i = 0; i < 8; i++) expect((await login(db, 'lock@t.in', 'nope')).ok).toBe(false)
|
|
// Now locked — even the CORRECT password is refused with reason 'locked'.
|
|
expect(await login(db, 'lock@t.in', 'correct-horse-9')).toMatchObject({ ok: false, reason: 'locked' })
|
|
})
|
|
it('a successful login clears the failure counter (D24)', async () => {
|
|
const db = openDb(':memory:')
|
|
await createStaff(db, { email: 'reset@t.in', displayName: 'R', role: 'staff', password: 'correct-horse-9' })
|
|
for (let i = 0; i < 3; i++) await login(db, 'reset@t.in', 'nope')
|
|
expect((await login(db, 'reset@t.in', 'correct-horse-9')).ok).toBe(true)
|
|
const row = await db.get<{ failed_count: number; locked_until: string | null }>(
|
|
`SELECT failed_count, locked_until FROM staff_user WHERE email='reset@t.in'`)
|
|
expect(row).toMatchObject({ failed_count: 0, locked_until: null })
|
|
})
|
|
it('an unknown email still returns invalid (constant-time, no enumeration) (D24)', async () => {
|
|
const db = openDb(':memory:')
|
|
expect(await login(db, 'ghost@nowhere.in', 'whatever')).toMatchObject({ ok: false, reason: 'invalid' })
|
|
})
|
|
})
|