import { test } from '@japa/runner' import { DateTime } from 'luxon' import User from '#models/user' import Computer from '#models/computer' import ComputerState from '#models/computer_state' import Component from '#models/component' import ComponentType from '#models/component_type' async function createUser(email: string) { return User.create({ name: 'Test User', email, password: 'secret@123A', }) } async function createState(name = 'ON_GOING') { return ComputerState.firstOrCreate({ name }, { name }) } async function createType(name: string, unique = true) { return ComponentType.firstOrCreate({ name }, { name, unique }) } async function createComponent(userId: number, typeId: number, computerId: number | null = null) { return Component.create({ name: 'Test Component', price: 42.5, typeId, userId, computerId, purchaseDate: DateTime.now(), }) } async function createComputer(userId: number, stateId: number) { return Computer.create({ title: 'My PC', sellPrice: 100, gpuScore: 1, cpuScore: 1, globalScore: 1, stateId, userId, }) } test.group('Computers authorization (IDOR)', () => { test('owner can update their own computer', async ({ client, assert }) => { const owner = await createUser('owner-update@example.com') const state = await createState() const computer = await createComputer(owner.id, state.id) const response = await client .put(`/computers/${computer.id}`) .loginAs(owner) .withCsrfToken() .redirects(0) .json({ title: 'Updated title', sellPrice: 150, gpuScore: 2, cpuScore: 2, globalScore: 2, stateId: state.id, componentsId: [], pictures: [], }) response.assertStatus(302) await computer.refresh() assert.equal(computer.title, 'Updated title') }) test('a user cannot update another user computer', async ({ client }) => { const owner = await createUser('owner-a@example.com') const attacker = await createUser('attacker-a@example.com') const state = await createState() const computer = await createComputer(owner.id, state.id) const response = await client .put(`/computers/${computer.id}`) .loginAs(attacker) .withCsrfToken() .redirects(0) .json({ title: 'Hacked title', sellPrice: 1, gpuScore: 1, cpuScore: 1, globalScore: 1, stateId: state.id, componentsId: [], pictures: [], }) response.assertStatus(404) }) test('a user cannot delete another user computer', async ({ client, assert }) => { const owner = await createUser('owner-b@example.com') const attacker = await createUser('attacker-b@example.com') const state = await createState() const computer = await createComputer(owner.id, state.id) const response = await client .delete(`/computers/${computer.id}`) .loginAs(attacker) .withCsrfToken() .redirects(0) response.assertStatus(404) const stillExists = await Computer.find(computer.id) assert.isNotNull(stillExists) }) test('a user cannot attach another user component via componentsId', async ({ client, assert, }) => { const owner = await createUser('owner-c@example.com') const attacker = await createUser('attacker-c@example.com') const state = await createState() const type = await createType('Processor-IDOR', true) const victimComponent = await createComponent(owner.id, type.id) const attackerComputer = await createComputer(attacker.id, state.id) const response = await client .put(`/computers/${attackerComputer.id}`) .loginAs(attacker) .withCsrfToken() .redirects(0) .json({ title: attackerComputer.title, sellPrice: attackerComputer.sellPrice, gpuScore: 0, cpuScore: 0, globalScore: 0, stateId: state.id, componentsId: [victimComponent.id], pictures: [], }) // handleComponents scopes the lookup to the current user, so the // victim's component cannot be found/attached -> 404. response.assertStatus(404) await victimComponent.refresh() assert.isNull(victimComponent.computerId) }) test('cannot attach two components of the same unique type to a computer', async ({ client }) => { const owner = await createUser('owner-d@example.com') const state = await createState() const type = await createType('Motherboard-IDOR', true) const componentA = await createComponent(owner.id, type.id) const componentB = await createComponent(owner.id, type.id) const computer = await createComputer(owner.id, state.id) const response = await client .put(`/computers/${computer.id}`) .loginAs(owner) .withCsrfToken() .redirects(0) .json({ title: computer.title, sellPrice: computer.sellPrice, gpuScore: 0, cpuScore: 0, globalScore: 0, stateId: state.id, componentsId: [componentA.id, componentB.id], pictures: [], }) response.assertStatus(422) }) test('deleting a computer detaches its components instead of destroying them', async ({ client, assert, }) => { const owner = await createUser('owner-e@example.com') const state = await createState() const type = await createType('Fan-IDOR', false) const computer = await createComputer(owner.id, state.id) const component = await createComponent(owner.id, type.id, computer.id) const response = await client .delete(`/computers/${computer.id}`) .loginAs(owner) .withCsrfToken() .redirects(0) response.assertStatus(302) await component.refresh() assert.isNull(component.computerId) }) })