import { identityApp, type IdentityApp, ownerPassword, } from './helpers/identity-app'; import { hashToken } from '../src/identity/tokens'; describe('authentication with migrated PostgreSQL engine', () => { let ctx: IdentityApp; beforeAll(async () => { ctx = await identityApp(); }, 60000); afterAll(async () => { await ctx?.close(); }); beforeEach(async () => { await ctx.clearLimits(); }); it('stores only hashed session tokens and never exposes password hashes', async () => { const session = await ctx.db.session.findUniqueOrThrow({ where: { tokenHash: hashToken(ctx.token) }, }); expect(session.tokenHash).not.toBe(ctx.token); const me = await ctx .api() .get('/api/v1/auth/me') .auth(ctx.token, { type: 'bearer' }) .expect(200); expect(me.body.organizationId).toBe(ctx.owner.organizationId); expect(me.text).not.toContain('password'); expect(me.headers['cache-control']).toBe('no-store'); }); it('rejects missing, malformed, unknown and expired sessions', async () => { await ctx.api().get('/api/v1/users').expect(401); await ctx .api() .get('/api/v1/users') .set('Authorization', 'Bearer bad') .expect(401); await ctx .api() .get('/api/v1/users') .auth('a'.repeat(43), { type: 'bearer' }) .expect(401); const login = await ctx.login(); await ctx.db.session.update({ where: { tokenHash: hashToken(login.body.accessToken) }, data: { expiresAt: new Date(0) }, }); await ctx .api() .get('/api/v1/auth/me') .auth(login.body.accessToken, { type: 'bearer' }) .expect(401); }); it('returns the same credential error for unknown email and wrong password', async () => { const missing = await ctx.login('missing@example.com'); const wrong = await ctx.login('owner@example.com', 'wrong password'); expect(missing.status).toBe(401); expect(wrong.body.code).toBe(missing.body.code); expect(wrong.body.message).toBe(missing.body.message); }); it('validates payloads without echoing secrets and rejects mass assignment', async () => { const response = await ctx .api() .post('/api/v1/auth/login') .send({ organizationId: ctx.owner.organizationId, email: 'owner@example.com', password: 'SECRET', isOwner: true, }) .expect(400); expect(response.text).not.toContain('SECRET'); }); it('revokes a session on logout', async () => { const login = await ctx.login(); const bearer = login.body.accessToken; await ctx .api() .post('/api/v1/auth/logout') .auth(bearer, { type: 'bearer' }) .expect(204); await ctx .api() .get('/api/v1/auth/me') .auth(bearer, { type: 'bearer' }) .expect(401); }); it('limits repeated attempts per account in durable storage', async () => { for (let i = 0; i < 10; i++) expect((await ctx.login('unknown@example.com')).status).toBe(401); expect((await ctx.login('unknown@example.com')).status).toBe(429); }, 15000); it('enforces IP throttling even for malformed input', async () => { for (let i = 0; i < 30; i++) await ctx.api().post('/api/v1/auth/login').send({}).expect(400); await ctx.api().post('/api/v1/auth/login').send({}).expect(429); }); it('recovery consumes a token once and revokes existing sessions', async () => { await ctx .api() .post('/api/v1/auth/recovery/request') .send({ organizationId: ctx.owner.organizationId, email: 'owner@example.com', }) .expect(202); const recoveryToken = ctx.mailer.send.mock.calls.at(-1)![1] as string; const newPassword = 'a replacement secure passphrase'; await ctx .api() .post('/api/v1/auth/recovery/reset') .send({ token: recoveryToken, password: newPassword }) .expect(204); await ctx .api() .post('/api/v1/auth/recovery/reset') .send({ token: recoveryToken, password: newPassword }) .expect(400); await ctx .api() .get('/api/v1/auth/me') .auth(ctx.token, { type: 'bearer' }) .expect(401); expect((await ctx.login('owner@example.com', ownerPassword)).status).toBe( 401, ); const login = await ctx.login('owner@example.com', newPassword); expect(login.status).toBe(200); ctx.token = login.body.accessToken; }); it('revokes all sessions and records security events without secrets', async () => { const login = await ctx.login( 'owner@example.com', 'a replacement secure passphrase', ); await ctx .api() .post('/api/v1/auth/logout-all') .auth(ctx.token, { type: 'bearer' }) .expect(204); await ctx .api() .get('/api/v1/auth/me') .auth(login.body.accessToken, { type: 'bearer' }) .expect(401); const events = await ctx.db.auditEvent.findMany(); expect(events.map((event) => event.action)).toEqual( expect.arrayContaining([ 'auth.login', 'auth.login_failed', 'auth.password_reset', 'auth.logout_all', ]), ); expect(JSON.stringify(events)).not.toContain(ctx.token); expect(JSON.stringify(events)).not.toContain(ownerPassword); }); });