import { afterEach, describe, expect, it, vi } from 'vitest'; import { NextRequest } from 'next/server'; import { createAccessToken } from '@/lib/server/access-token'; import { requireOpsAccess } from '@/lib/server/ops-access'; afterEach(() => { vi.unstubAllEnvs(); }); describe('requireOpsAccess', () => { it('rejects course management on a learner deployment', async () => { vi.stubEnv('NODE_ENV', 'production'); vi.stubEnv('OPENMAIC_DEPLOYMENT_ROLE', 'learner'); const response = await requireOpsAccess(new NextRequest('http://localhost/api/courses')); expect(response?.status).toBe(403); await expect(response?.json()).resolves.toMatchObject({ errorCode: 'FORBIDDEN' }); }); it('fails closed when a production ops deployment has no Works identity origin', async () => { vi.stubEnv('NODE_ENV', 'production'); vi.stubEnv('OPENMAIC_DEPLOYMENT_ROLE', 'ops'); vi.stubEnv('ACCESS_CODE', ''); const response = await requireOpsAccess(new NextRequest('http://localhost/api/courses')); expect(response?.status).toBe(503); }); it('accepts a valid access-code session only for development compatibility', async () => { vi.stubEnv('NODE_ENV', 'development'); vi.stubEnv('OPENMAIC_DEPLOYMENT_ROLE', 'ops'); vi.stubEnv('ACCESS_CODE', 'test-access-code'); const token = createAccessToken('test-access-code'); const request = new NextRequest('http://localhost/api/courses', { headers: { cookie: `openmaic_access=${token}` }, }); await expect(requireOpsAccess(request)).resolves.toBeNull(); }); it('accepts a same-origin mutation with a development ops session', async () => { vi.stubEnv('NODE_ENV', 'development'); vi.stubEnv('OPENMAIC_DEPLOYMENT_ROLE', 'ops'); vi.stubEnv('ACCESS_CODE', 'test-access-code'); const token = createAccessToken('test-access-code'); const request = new NextRequest('https://ops.example/api/courses', { method: 'POST', headers: { cookie: `openmaic_access=${token}`, origin: 'https://ops.example', }, }); await expect(requireOpsAccess(request)).resolves.toBeNull(); }); it('rejects missing or cross-origin mutation origins', async () => { vi.stubEnv('NODE_ENV', 'development'); vi.stubEnv('OPENMAIC_DEPLOYMENT_ROLE', 'ops'); vi.stubEnv('ACCESS_CODE', 'test-access-code'); const token = createAccessToken('test-access-code'); for (const origin of [undefined, 'https://attacker.example']) { const response = await requireOpsAccess( new NextRequest('https://ops.example/api/courses', { method: 'POST', headers: { cookie: `openmaic_access=${token}`, ...(origin ? { origin } : {}), }, }), ); expect(response?.status).toBe(403); await expect(response?.json()).resolves.toMatchObject({ errorCode: 'FORBIDDEN' }); } }); it('uses a configured public origin and never trusts forged forwarded hosts', async () => { vi.stubEnv('NODE_ENV', 'development'); vi.stubEnv('OPENMAIC_DEPLOYMENT_ROLE', 'ops'); vi.stubEnv('ACCESS_CODE', 'test-access-code'); vi.stubEnv('ACCESS_CODE_TRUST_PROXY_HEADERS', 'true'); vi.stubEnv('OPS_PUBLIC_ORIGIN', 'https://ops.example'); const token = createAccessToken('test-access-code'); const accepted = new NextRequest('http://internal:3000/api/courses', { method: 'POST', headers: { cookie: `openmaic_access=${token}`, origin: 'https://ops.example', 'x-forwarded-host': 'attacker.example', 'x-forwarded-proto': 'https', }, }); await expect(requireOpsAccess(accepted)).resolves.toBeNull(); const rejected = new NextRequest('http://internal:3000/api/courses', { method: 'POST', headers: { cookie: `openmaic_access=${token}`, origin: 'https://attacker.example', 'x-forwarded-host': 'attacker.example', 'x-forwarded-proto': 'https', }, }); const response = await requireOpsAccess(rejected); expect(response?.status).toBe(403); await expect(response?.json()).resolves.toMatchObject({ errorCode: 'FORBIDDEN' }); }); it('fails closed when the configured public origin is malformed', async () => { vi.stubEnv('NODE_ENV', 'development'); vi.stubEnv('OPENMAIC_DEPLOYMENT_ROLE', 'ops'); vi.stubEnv('ACCESS_CODE', 'test-access-code'); vi.stubEnv('OPS_PUBLIC_ORIGIN', 'https://ops.example/path'); const token = createAccessToken('test-access-code'); const response = await requireOpsAccess( new NextRequest('https://ops.example/api/courses', { method: 'POST', headers: { cookie: `openmaic_access=${token}`, origin: 'https://ops.example', }, }), ); expect(response?.status).toBe(403); }); it('keeps development role=all aligned with the ops session contract', async () => { vi.stubEnv('NODE_ENV', 'development'); vi.stubEnv('OPENMAIC_DEPLOYMENT_ROLE', 'all'); vi.stubEnv('ACCESS_CODE', 'test-access-code'); const token = createAccessToken('test-access-code'); const request = new NextRequest('https://ops.example/api/coursewares', { method: 'POST', headers: { cookie: `openmaic_access=${token}`, origin: 'https://ops.example', }, }); await expect(requireOpsAccess(request)).resolves.toBeNull(); }); });