import assert from 'node:assert/strict'; import test from 'node:test'; import type { TestContext } from 'node:test'; import type { Transaction } from 'sequelize'; import db from '../../src/db/models/index.ts'; import AccessPolicy from '../../src/services/access-policy.ts'; import AccessPolicyAuditService from '../../src/services/access-policy-audit.ts'; import type { CurrentUser, PermissionRecord, ProjectCreatePayload, ProjectModelRecord, ProductionPresentationVisibility, RoleModelRecord, UserModelRecord, UserProductionPresentationAccessPayload, UserRecord, } from '../../src/types/index.ts'; const suffix = `${Date.now()}-${process.pid}`; void test.after(async () => { await db.sequelize.close(); }); async function authenticateWithTimeout(timeoutMs = 1500): Promise { let timeoutId: NodeJS.Timeout | undefined; const timeout = new Promise((_, reject) => { timeoutId = setTimeout( () => reject(new Error(`Database unavailable after ${timeoutMs}ms`)), timeoutMs, ); }); try { await Promise.race([db.sequelize.authenticate(), timeout]); } finally { if (timeoutId !== undefined) { clearTimeout(timeoutId); } } } async function withTransaction( t: TestContext, callback: (transaction: Transaction) => Promise, ): Promise { try { await authenticateWithTimeout(); } catch (error) { const message = error instanceof Error ? error.message : 'unknown error'; t.skip(`Database unavailable: ${message}`); return; } const transaction = await db.sequelize.transaction(); try { await callback(transaction); } finally { await transaction.rollback(); } } async function createRole( name: string, transaction: Transaction, ): Promise { return db.roles.create({ name }, { transaction }); } function createPermission( name: string, transaction: Transaction, ): Promise { return db.permissions.create({ name }, { transaction }); } interface CreateUserOptions { email: string; role: RoleModelRecord; } async function createUser( { email, role }: CreateUserOptions, transaction: Transaction, ): Promise { const user = await db.users.create( { email, password: 'not-used-in-test', emailVerified: true, }, { transaction }, ); await user.setApp_role(role, { transaction }); return user; } interface CreateProjectOptions { slug: string; visibility: ProductionPresentationVisibility; } async function createProject( { slug, visibility }: CreateProjectOptions, transaction: Transaction, ): Promise { const data: ProjectCreatePayload = { id: undefined, name: `Test ${slug}`, slug, description: undefined, logo_url: undefined, favicon_url: undefined, og_image_url: undefined, design_width: undefined, design_height: undefined, production_presentation_visibility: visibility, importHash: null, createdById: null, updatedById: null, }; return db.projects.create(data, { transaction }); } function requireUserModelRecord( user: UserModelRecord | null, ): UserModelRecord { if (!user) { throw new Error('Expected test user to exist.'); } return user; } function toCurrentUser(user: UserRecord): CurrentUser { const currentUser: CurrentUser = { id: user.id }; if (typeof user.email === 'string') { currentUser.email = user.email; } if (user.app_role !== undefined) { currentUser.app_role = user.app_role; } if (user.custom_permissions !== undefined) { currentUser.custom_permissions = user.custom_permissions; } if (user.app_role_permissions !== undefined) { currentUser.app_role_permissions = user.app_role_permissions; } return currentUser; } function createProductionAccessPayload( userId: string, projectId: string, ): UserProductionPresentationAccessPayload { const now = new Date(); return { userId, projectId, createdAt: now, updatedAt: now, createdById: null, updatedById: null, }; } void test('guest can view public production presentation only', async (t) => { await withTransaction(t, async (transaction) => { const publicProject = await createProject( { slug: `test-public-runtime-access-${suffix}`, visibility: 'public', }, transaction, ); const privateProject = await createProject( { slug: `test-private-runtime-access-${suffix}`, visibility: 'private', }, transaction, ); assert.equal( await AccessPolicy.canViewProductionPresentation( null, publicProject.slug, { transaction }, ), true, ); assert.equal( await AccessPolicy.canViewProductionPresentation( null, privateProject.slug, { transaction }, ), false, ); }); }); void test('public user can view granted private production presentation', async (t) => { await withTransaction(t, async (transaction) => { const publicRole = await createRole('Public', transaction); const publicUser = await createUser( { email: `public-granted-${suffix}@example.test`, role: publicRole, }, transaction, ); const privateProject = await createProject( { slug: `test-private-granted-runtime-access-${suffix}`, visibility: 'private', }, transaction, ); await db.production_presentation_access.create( createProductionAccessPayload(publicUser.id, privateProject.id), { transaction }, ); const authUser = await db.users.findOne({ where: { id: publicUser.id }, include: [ { association: 'app_role', include: [{ association: 'permissions' }] }, { association: 'custom_permissions' }, ], transaction, }); const authUserRecord = requireUserModelRecord(authUser); assert.equal( await AccessPolicy.canViewProductionPresentation( toCurrentUser(authUserRecord.get({ plain: true })), privateProject.slug, { transaction }, ), true, ); }); }); void test('internal user with permission can use admin api and view private presentation', async (t) => { await withTransaction(t, async (transaction) => { const role = await createRole('Content Reviewer', transaction); const permission = await createPermission( `TEST_READ_PROJECTS_${suffix}`, transaction, ); await role.setPermissions([permission], { transaction }); const internalUser = await createUser( { email: `internal-access-${suffix}@example.test`, role, }, transaction, ); const privateProject = await createProject( { slug: `test-private-internal-runtime-access-${suffix}`, visibility: 'private', }, transaction, ); const authUser = await db.users.findOne({ where: { id: internalUser.id }, include: [ { association: 'app_role', include: [{ association: 'permissions' }] }, { association: 'custom_permissions' }, ], transaction, }); const plainUser = toCurrentUser( requireUserModelRecord(authUser).get({ plain: true }), ); assert.equal(AccessPolicy.canUseAdminApi(plainUser), true); assert.equal( await AccessPolicy.canViewProductionPresentation( plainUser, privateProject.slug, { transaction }, ), true, ); }); }); void test('audit finds and cleanup removes stale Public grants', async (t) => { await withTransaction(t, async (transaction) => { const publicRole = await createRole('Public', transaction); const internalRole = await createRole('Tour Designer', transaction); const permission = await createPermission( `TEST_READ_USERS_${suffix}`, transaction, ); await publicRole.setPermissions([permission], { transaction }); const publicUser = await createUser( { email: `public-stale-${suffix}@example.test`, role: publicRole, }, transaction, ); await publicUser.setCustom_permissions([permission], { transaction }); const internalUser = await createUser( { email: `internal-stale-grant-${suffix}@example.test`, role: internalRole, }, transaction, ); const privateProject = await createProject( { slug: `test-private-stale-grant-${suffix}`, visibility: 'private', }, transaction, ); await db.production_presentation_access.create( createProductionAccessPayload(internalUser.id, privateProject.id), { transaction }, ); const report = await AccessPolicyAuditService.findViolations({ transaction, }); assert.ok(report.publicRolePermissions.length >= 1); assert.ok(report.publicUsersWithCustomPermissions.length >= 1); assert.ok(report.productionPresentationAccessForNonPublicUsers.length >= 1); const cleanup = await AccessPolicyAuditService.cleanupViolations({ transaction, }); assert.ok(cleanup.removedPublicRolePermissions >= 1); assert.ok(cleanup.clearedPublicUserCustomPermissions >= 1); assert.ok(cleanup.removedNonPublicProductionPresentationGrants >= 1); const after = await AccessPolicyAuditService.findViolations({ transaction, }); assert.equal(AccessPolicyAuditService.hasViolations(after), false); }); });