Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 15 additions & 3 deletions services/api/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -1463,7 +1463,11 @@
{
"bearerAuth": []
}
]
],
"x-permissions": {
"endpoint": "products",
"permission": "write"
}
}
},
"/1/products/:id": {
Expand Down Expand Up @@ -1562,7 +1566,11 @@
{
"bearerAuth": []
}
]
],
"x-permissions": {
"endpoint": "products",
"permission": "write"
}
},
"delete": {
"summary": "Delete product",
Expand All @@ -1581,7 +1589,11 @@
{
"bearerAuth": []
}
]
],
"x-permissions": {
"endpoint": "products",
"permission": "write"
}
}
},
"/1/products/search": {
Expand Down
7 changes: 4 additions & 3 deletions services/api/src/routes/products.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import Router from '@koa/router';
import { fetchByParam } from '../utils/middleware/params.js';
import { validateBody } from '../utils/middleware/validate.js';
import { authenticate } from '../utils/middleware/authenticate.js';
import { requirePermissions } from '../utils/middleware/permissions.js';
import { csvExport } from '../utils/csv.js';
import { Product } from '../models/index.js';

Expand All @@ -10,7 +11,7 @@ const router = new Router();
router
.use(authenticate())
.param('id', fetchByParam(Product))
.post('/', validateBody(Product.getCreateValidation()), async (ctx) => {
.post('/', requirePermissions('products.write'), validateBody(Product.getCreateValidation()), async (ctx) => {
const product = await Product.create(ctx.request.body);

ctx.body = {
Expand Down Expand Up @@ -44,7 +45,7 @@ router
};
},
)
.patch('/:id', validateBody(Product.getUpdateValidation()), async (ctx) => {
.patch('/:id', requirePermissions('products.write'), validateBody(Product.getUpdateValidation()), async (ctx) => {
const { product } = ctx.state;
product.assign(ctx.request.body);

Expand All @@ -54,7 +55,7 @@ router
data: product,
};
})
.delete('/:id', async (ctx) => {
.delete('/:id', requirePermissions('products.write'), async (ctx) => {
const { product } = ctx.state;
await product.delete();
ctx.status = 204;
Expand Down
38 changes: 34 additions & 4 deletions services/api/src/routes/products.test.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import mongoose from 'mongoose';

import { request, createUser } from '../utils/testing/index.js';
import { request, createUser, createAdmin } from '../utils/testing/index.js';
import { Product } from '../models/index.js';

describe('/1/products', () => {
describe('POST /', () => {
it('should be able to create product', async () => {
const user = await createUser();
const user = await createAdmin();
const response = await request(
'POST',
'/1/products',
Expand All @@ -20,6 +20,16 @@ describe('/1/products', () => {
expect(response).toHaveStatus(200);
expect(data.name).toBe('some other product');
});

it('should deny access to non-admins', async () => {
const user = await createUser();
const product = await Product.create({
name: 'test 1',
shop: new mongoose.Types.ObjectId(),
});
const response = await request('POST', '/1/products', { name: 'x', shop: product.shop }, { user });
expect(response).toHaveStatus(403);
});
});

describe('GET /:product', () => {
Expand Down Expand Up @@ -74,7 +84,7 @@ describe('/1/products', () => {

describe('PATCH /:product', () => {
it('admins should be able to update product', async () => {
const user = await createUser();
const user = await createAdmin();
const product = await Product.create({
name: 'test 1',
description: 'Some description',
Expand All @@ -86,11 +96,21 @@ describe('/1/products', () => {
const dbProduct = await Product.findById(product.id);
expect(dbProduct.name).toEqual('new name');
});

it('should deny access to non-admins', async () => {
const user = await createUser();
const product = await Product.create({
name: 'test 1',
shop: new mongoose.Types.ObjectId(),
});
const response = await request('PATCH', `/1/products/${product.id}`, { name: 'new name' }, { user });
expect(response).toHaveStatus(403);
});
});

describe('DELETE /:product', () => {
it('should be able to delete product', async () => {
const user = await createUser();
const user = await createAdmin();
const product = await Product.create({
name: 'test 1',
description: 'Some description',
Expand All @@ -101,5 +121,15 @@ describe('/1/products', () => {
const dbProduct = await Product.findByIdDeleted(product.id);
expect(dbProduct.deletedAt).toBeDefined();
});

it('should deny access to non-admins', async () => {
const user = await createUser();
const product = await Product.create({
name: 'test 1',
shop: new mongoose.Types.ObjectId(),
});
const response = await request('DELETE', `/1/products/${product.id}`, {}, { user });
expect(response).toHaveStatus(403);
});
});
});
Loading