/** * Security regression: dismissAlert / acknowledgeAlert must filter by * portId so an alert UUID from port A can't be mutated by a session * scoped to port B. */ import { describe, it, expect, beforeAll } from 'vitest'; import { eq } from 'drizzle-orm'; import { db } from '@/lib/db'; import { alerts } from '@/lib/db/schema/insights'; import { user } from '@/lib/db/schema/users'; import { dismissAlert, acknowledgeAlert } from '@/lib/services/alerts.service'; import { makePort } from '../helpers/factories'; let TEST_USER_ID = ''; beforeAll(async () => { // dismissedBy / acknowledgedBy reference user.id; pull any seeded user. const [u] = await db.select({ id: user.id }).from(user).limit(1); if (!u) throw new Error('No user available; run pnpm db:seed first'); TEST_USER_ID = u.id; }); async function makeAlert(portId: string) { const [row] = await db .insert(alerts) .values({ portId, ruleId: 'document_overdue', severity: 'medium', title: 'Test alert', link: '/test', fingerprint: `fp-${Math.random().toString(36).slice(2)}`, metadata: {}, }) .returning({ id: alerts.id }); if (!row) throw new Error('failed to insert alert'); return row.id; } describe('alerts service — tenant isolation', () => { it('dismissAlert is a no-op when called with the wrong portId', async () => { const portA = await makePort(); const portB = await makePort(); const alertId = await makeAlert(portA.id); // Attacker session scoped to port B tries to dismiss port A's alert. await dismissAlert(alertId, portB.id, TEST_USER_ID); const after = await db.query.alerts.findFirst({ where: eq(alerts.id, alertId) }); expect(after?.dismissedAt).toBeNull(); expect(after?.dismissedBy).toBeNull(); }); it('dismissAlert succeeds when portId matches', async () => { const port = await makePort(); const alertId = await makeAlert(port.id); await dismissAlert(alertId, port.id, TEST_USER_ID); const after = await db.query.alerts.findFirst({ where: eq(alerts.id, alertId) }); expect(after?.dismissedAt).not.toBeNull(); expect(after?.dismissedBy).toBe(TEST_USER_ID); }); it('acknowledgeAlert is a no-op when called with the wrong portId', async () => { const portA = await makePort(); const portB = await makePort(); const alertId = await makeAlert(portA.id); await acknowledgeAlert(alertId, portB.id, TEST_USER_ID); const after = await db.query.alerts.findFirst({ where: eq(alerts.id, alertId) }); expect(after?.acknowledgedAt).toBeNull(); expect(after?.acknowledgedBy).toBeNull(); }); it('acknowledgeAlert succeeds when portId matches', async () => { const port = await makePort(); const alertId = await makeAlert(port.id); await acknowledgeAlert(alertId, port.id, TEST_USER_ID); const after = await db.query.alerts.findFirst({ where: eq(alerts.id, alertId) }); expect(after?.acknowledgedAt).not.toBeNull(); expect(after?.acknowledgedBy).toBe(TEST_USER_ID); }); });