diff --git a/src/locales/da.json b/src/locales/da.json index a680484..1f04e66 100644 --- a/src/locales/da.json +++ b/src/locales/da.json @@ -116,8 +116,8 @@ "required": "Farve er påkrævet", "type": "Farve skal være en tekst" }, - "itemid": { - "type": "Item id skal være et tal" + "parentId": { + "type": "Parent id skal være et tal" } } } diff --git a/src/locales/en.json b/src/locales/en.json index 2514120..6d94b41 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -116,8 +116,8 @@ "required": "Color is required", "type": "Color must be a string" }, - "itemid": { - "type": "Item id must be a number" + "parentId": { + "type": "Parent id must be a number" } } } diff --git a/src/modules/item/folder/__test__/add.test.ts b/src/modules/item/folder/__test__/add.test.ts index ddeb9be..1e78862 100644 --- a/src/modules/item/folder/__test__/add.test.ts +++ b/src/modules/item/folder/__test__/add.test.ts @@ -1,22 +1,31 @@ import { User } from '@prisma/client'; import UserService from '../../../auth/user.service'; import AuthService from '../../../auth/auth.service'; +import FolderService from '../folder.service'; describe('POST /api/folder', () => { let userService: UserService; let authService: AuthService; + let folderService: FolderService; let user: User; + let otherUser: User; beforeAll(async () => { authService = new AuthService(); userService = new UserService(); + folderService = new FolderService(); user = await userService.createUser({ name: 'Joe Biden the 1st', email: 'joe@biden.com', password: '1234', }); + otherUser = await userService.createUser({ + name: 'Joe Biden the 2nd', + email: 'joe2@biden.com', + password: '4321', + }); }); it('should return status 200 and folder', async () => { @@ -72,6 +81,39 @@ describe('POST /api/folder', () => { }); }); + it('should return status 401, when parent id is provided, but no access to parent', async () => { + const { accessToken } = await authService.createTokens(user.id); + + const folder = await folderService.createFolder({ + name: 'Folder1', + ownerId: otherUser.id, + parentId: null, + color: '#78BC61', + }); + + const response = await global.fastify.inject({ + method: 'POST', + url: '/api/folder', + headers: { + authorization: 'Bearer ' + accessToken, + }, + payload: { + name: 'Folder Name', + color: '#78BC61', + parentId: folder.id, + }, + }); + + expect(response.statusCode).toBe(401); + expect(response.json()).toEqual({ + error: 'UnauthorizedError', + errors: { + _: ['Unauthorized'], + }, + statusCode: 401, + }); + }); + it('should return status 401, when folder name is not provided', async () => { const { accessToken } = await authService.createTokens(user.id); @@ -142,7 +184,7 @@ describe('POST /api/folder', () => { expect(response.json()).toEqual({ error: 'ValidationError', errors: { - parentId: ['Item id must be a number'], + parentId: ['Parent id must be a number'], }, statusCode: 400, }); diff --git a/src/modules/item/folder/__test__/delete.test.ts b/src/modules/item/folder/__test__/delete.test.ts index 1b8479b..91d9785 100644 --- a/src/modules/item/folder/__test__/delete.test.ts +++ b/src/modules/item/folder/__test__/delete.test.ts @@ -35,7 +35,6 @@ describe('DELETE /api/folder/:id', () => { const { accessToken } = await authService.createTokens(user.id); const folder = await folderService.createFolder({ - mimeType: 'application/vnd.cloudstore.folder', name: 'Folder1', ownerId: user.id, parentId: null, @@ -58,7 +57,6 @@ describe('DELETE /api/folder/:id', () => { it('should return status 401, when unauthorized', async () => { const folder = await folderService.createFolder({ - mimeType: 'application/vnd.cloudstore.folder', name: 'Folder1', ownerId: user.id, parentId: null, @@ -83,11 +81,10 @@ describe('DELETE /api/folder/:id', () => { }); }); - it('should return status 401, when folder id is provided but you do not own it', async () => { + it('should return status 401, when folder id is not accessible to you', async () => { const { accessToken } = await authService.createTokens(user.id); const folder = await folderService.createFolder({ - mimeType: 'application/vnd.cloudstore.folder', name: 'Folder1', ownerId: otherUser.id, parentId: null, diff --git a/src/modules/item/folder/__test__/edit.test.ts b/src/modules/item/folder/__test__/edit.test.ts index 38738c9..85a908e 100644 --- a/src/modules/item/folder/__test__/edit.test.ts +++ b/src/modules/item/folder/__test__/edit.test.ts @@ -32,7 +32,6 @@ describe('PUT /api/folder', () => { const { accessToken } = await authService.createTokens(user.id); const folder = await folderService.createFolder({ - mimeType: 'application/vnd.cloudstore.folder', name: 'Folder1', ownerId: user.id, parentId: null, @@ -65,7 +64,6 @@ describe('PUT /api/folder', () => { it('should return status 401, when unauthorized', async () => { const folder = await folderService.createFolder({ - mimeType: 'application/vnd.cloudstore.folder', name: 'Folder1', ownerId: user.id, parentId: null, @@ -95,11 +93,10 @@ describe('PUT /api/folder', () => { }); }); - it('should return status 401, when folder id is provided but you do not own it', async () => { + it('should return status 401, when folder id is not accessible to you', async () => { const { accessToken } = await authService.createTokens(user.id); const folder = await folderService.createFolder({ - mimeType: 'application/vnd.cloudstore.folder', name: 'Folder1', ownerId: otherUser.id, parentId: null, diff --git a/src/modules/item/folder/__test__/read.test.ts b/src/modules/item/folder/__test__/read.test.ts index 4cf904c..c9dc3ca 100644 --- a/src/modules/item/folder/__test__/read.test.ts +++ b/src/modules/item/folder/__test__/read.test.ts @@ -32,7 +32,6 @@ describe('GET /api/folder/:id', () => { const { accessToken } = await authService.createTokens(user.id); const folder = await folderService.createFolder({ - mimeType: 'application/vnd.cloudstore.folder', name: 'Folder1', ownerId: user.id, parentId: null, @@ -58,7 +57,6 @@ describe('GET /api/folder/:id', () => { it('should return status 401, when unauthorized', async () => { const folder = await folderService.createFolder({ - mimeType: 'application/vnd.cloudstore.folder', name: 'Folder1', ownerId: user.id, parentId: null, @@ -83,11 +81,10 @@ describe('GET /api/folder/:id', () => { }); }); - it('should return status 401, when folder id is provided but you do not own it', async () => { + it('should return status 401, when folder id is not accessible to you', async () => { const { accessToken } = await authService.createTokens(user.id); const folder = await folderService.createFolder({ - mimeType: 'application/vnd.cloudstore.folder', name: 'Folder1', ownerId: otherUser.id, parentId: null, diff --git a/src/modules/item/folder/folder.controller.ts b/src/modules/item/folder/folder.controller.ts index 77ab3c0..2f61f2b 100644 --- a/src/modules/item/folder/folder.controller.ts +++ b/src/modules/item/folder/folder.controller.ts @@ -1,12 +1,15 @@ import { FastifyReply, FastifyRequest } from 'fastify'; import { ReadInput, EditInput, AddInput, DeleteInput } from './folder.schema'; import FolderService from './folder.service'; +import AccessService from '../sharing/access.service'; -export default class ItemController { +export default class FolderController { private folderService: FolderService; + private accessService: AccessService; - constructor(folderService: FolderService) { + constructor(folderService: FolderService, accessService: AccessService) { this.folderService = folderService; + this.accessService = accessService; } public async readHandler( @@ -18,7 +21,7 @@ export default class ItemController { try { const folder = await this.folderService.getByItemId(request.params.id); - if (folder.ownerId !== request.user.sub) { + if (!(await this.accessService.hasAccessToItem(folder.id, request.user.sub))) { return reply.unauthorized(); } @@ -42,7 +45,7 @@ export default class ItemController { try { const folder = await this.folderService.getByItemId(request.body.id); - if (folder.ownerId !== request.user.sub) { + if (!(await this.accessService.hasAccessToItem(folder.id, request.user.sub))) { return reply.unauthorized(); } @@ -66,9 +69,16 @@ export default class ItemController { reply: FastifyReply, ) { try { + if ( + request.body.parentId !== null && + request.body.parentId !== undefined && + !(await this.accessService.hasAccessToItem(request.body.parentId, request.user.sub)) + ) { + return reply.unauthorized(); + } + const folder = await this.folderService.createFolder({ name: request.body.name, - mimeType: 'application/vnd.cloudstore.folder', color: request.body.color, ownerId: request.user.sub, parentId: request.body.parentId ?? null, @@ -90,7 +100,7 @@ export default class ItemController { try { const folder = await this.folderService.getByItemId(request.params.id); - if (folder.ownerId !== request.user.sub) { + if (!(await this.accessService.hasAccessToItem(folder.id, request.user.sub))) { return reply.unauthorized(); } diff --git a/src/modules/item/folder/folder.route.ts b/src/modules/item/folder/folder.route.ts index 75cacf8..fdb0bb3 100644 --- a/src/modules/item/folder/folder.route.ts +++ b/src/modules/item/folder/folder.route.ts @@ -1,9 +1,16 @@ import { FastifyInstance } from 'fastify'; import FolderController from './folder.controller'; import FolderService from './folder.service'; +import AccessService from '../sharing/access.service'; +import ItemService from '../item.service'; +import SharingService from '../sharing/sharing.service'; export default async (fastify: FastifyInstance) => { - const folderController = new FolderController(new FolderService()); + const folderService = new FolderService(); + const folderController = new FolderController( + folderService, + new AccessService(new ItemService(), new SharingService()), + ); fastify.get( '/:id', diff --git a/src/modules/item/folder/folder.schema.ts b/src/modules/item/folder/folder.schema.ts index ac9f760..8711143 100644 --- a/src/modules/item/folder/folder.schema.ts +++ b/src/modules/item/folder/folder.schema.ts @@ -80,7 +80,7 @@ const editFolderSchema = { parentId: { type: ['number', 'null'], errorMessage: { - type: 'folder.itemid.type', + type: 'folder.parentId.type', }, }, }, @@ -144,7 +144,7 @@ const addFolderSchema = { parentId: { type: ['number', 'null'], errorMessage: { - type: 'folder.itemid.type', + type: 'folder.parentId.type', }, }, }, @@ -218,14 +218,13 @@ export type DeleteInput = FromSchema; export type AddFolder = { name: string; color: string; - mimeType: string; ownerId: number; parentId: number | null; }; export type ItemFolder = prismaItemFolderType & { item: Item }; export type Folder = Omit & Item; -export type UpdateFolder = { id: number } & Partial & UpdateItem; +export type UpdateFolder = { id: number } & Partial & Omit; export const folderSchemas = [ addFolderSchema, diff --git a/src/modules/item/folder/folder.service.ts b/src/modules/item/folder/folder.service.ts index f79a364..5dd672d 100644 --- a/src/modules/item/folder/folder.service.ts +++ b/src/modules/item/folder/folder.service.ts @@ -9,7 +9,7 @@ export default class FolderService { item: { create: { name: input.name, - mimeType: input.mimeType, + mimeType: 'application/vnd.cloudstore.folder', ownerId: input.ownerId, parentId: input.parentId, },