diff --git a/apps/server/src/collaboration/extensions/authentication.extension.ts b/apps/server/src/collaboration/extensions/authentication.extension.ts index 675c7cf0..7158c944 100644 --- a/apps/server/src/collaboration/extensions/authentication.extension.ts +++ b/apps/server/src/collaboration/extensions/authentication.extension.ts @@ -24,6 +24,10 @@ export class AuthenticationExtension implements Extension { const userId = jwtPayload.sub; const user = await this.userService.findById(userId); + if (!user) { + throw new UnauthorizedException(); + } + //TODO: Check if the page exists and verify user permissions for page. // if all fails, abort connection diff --git a/apps/server/src/collaboration/extensions/persistence.extension.ts b/apps/server/src/collaboration/extensions/persistence.extension.ts index 11e7ff86..e7f45dd9 100644 --- a/apps/server/src/collaboration/extensions/persistence.extension.ts +++ b/apps/server/src/collaboration/extensions/persistence.extension.ts @@ -7,8 +7,7 @@ import * as Y from 'yjs'; import { PageService } from '../../core/page/services/page.service'; import { Injectable } from '@nestjs/common'; import { TiptapTransformer } from '@hocuspocus/transformer'; -import { jsonToHtml, jsonToText, tiptapExtensions } from '../collaboration.util'; -import { generateText } from '@tiptap/core' +import { jsonToText, tiptapExtensions } from '../collaboration.util'; @Injectable() export class PersistenceExtension implements Extension { @@ -67,7 +66,6 @@ export class PersistenceExtension implements Extension { const ydocState = Buffer.from(Y.encodeStateAsUpdate(document)); const textContent = jsonToText(tiptapJson); - console.log(jsonToText(tiptapJson)); try { await this.pageService.updateState( diff --git a/apps/server/src/core/attachment/attachment.controller.ts b/apps/server/src/core/attachment/attachment.controller.ts index a2c5f800..e688c7bb 100644 --- a/apps/server/src/core/attachment/attachment.controller.ts +++ b/apps/server/src/core/attachment/attachment.controller.ts @@ -12,9 +12,12 @@ import { import { AttachmentService } from './attachment.service'; import { FastifyReply, FastifyRequest } from 'fastify'; import { AttachmentInterceptor } from './attachment.interceptor'; -import { JwtUser } from '../../decorators/jwt-user.decorator'; -import { JwtGuard } from '../auth/guards/JwtGuard'; +import { JwtGuard } from '../auth/guards/jwt.guard'; import * as bytes from 'bytes'; +import { AuthUser } from '../../decorators/auth-user.decorator'; +import { User } from '../user/entities/user.entity'; +import { CurrentWorkspace } from '../../decorators/current-workspace.decorator'; +import { Workspace } from '../workspace/entities/workspace.entity'; @Controller('attachments') export class AttachmentController { @@ -25,9 +28,9 @@ export class AttachmentController { @Post('upload/avatar') @UseInterceptors(AttachmentInterceptor) async uploadAvatar( - @JwtUser() jwtUser, @Req() req: FastifyRequest, @Res() res: FastifyReply, + @AuthUser() user: User, ) { const maxFileSize = bytes('5MB'); @@ -38,7 +41,7 @@ export class AttachmentController { const fileResponse = await this.attachmentService.uploadAvatar( file, - jwtUser.id, + user.id, ); return res.send(fileResponse); @@ -52,9 +55,10 @@ export class AttachmentController { @Post('upload/workspace-logo') @UseInterceptors(AttachmentInterceptor) async uploadWorkspaceLogo( - @JwtUser() jwtUser, @Req() req: FastifyRequest, @Res() res: FastifyReply, + @AuthUser() user: User, + @CurrentWorkspace() workspace: Workspace, ) { const maxFileSize = bytes('5MB'); @@ -63,13 +67,10 @@ export class AttachmentController { limits: { fileSize: maxFileSize, fields: 1, files: 1 }, }); - // TODO FIX - const workspaceId = '123'; - const fileResponse = await this.attachmentService.uploadWorkspaceLogo( file, - workspaceId, - jwtUser.id, + workspace.id, + user.id, ); return res.send(fileResponse); @@ -83,9 +84,10 @@ export class AttachmentController { @Post('upload/file') @UseInterceptors(AttachmentInterceptor) async uploadFile( - @JwtUser() jwtUser, @Req() req: FastifyRequest, @Res() res: FastifyReply, + @AuthUser() user: User, + @CurrentWorkspace() workspace: Workspace, ) { const maxFileSize = bytes('20MB'); @@ -94,12 +96,10 @@ export class AttachmentController { limits: { fileSize: maxFileSize, fields: 1, files: 1 }, }); - const workspaceId = '123'; - const fileResponse = await this.attachmentService.uploadWorkspaceLogo( file, - workspaceId, - jwtUser.id, + workspace.id, + user.id, ); return res.send(fileResponse); diff --git a/apps/server/src/core/auth/guards/jwt.guard.ts b/apps/server/src/core/auth/guards/jwt.guard.ts new file mode 100644 index 00000000..9f6e1c69 --- /dev/null +++ b/apps/server/src/core/auth/guards/jwt.guard.ts @@ -0,0 +1,37 @@ +import { + CanActivate, + ExecutionContext, + Injectable, + UnauthorizedException, +} from '@nestjs/common'; +import { TokenService } from '../services/token.service'; +import { UserService } from '../../user/user.service'; + +@Injectable() +export class JwtGuard implements CanActivate { + constructor( + private tokenService: TokenService, + private userService: UserService, + ) {} + async canActivate(context: ExecutionContext): Promise { + const request = context.switchToHttp().getRequest(); + const token: string = await this.tokenService.extractTokenFromHeader( + request, + ); + + if (!token) { + throw new UnauthorizedException('Invalid jwt token'); + } + + try { + const payload = await this.tokenService.verifyJwt(token); + + //fetch user and current workspace data from db + request['user'] = await this.userService.getUserInstance(payload.sub); + } catch (error) { + throw new UnauthorizedException('Could not verify jwt token'); + } + + return true; + } +} diff --git a/apps/server/src/core/comment/comment.controller.ts b/apps/server/src/core/comment/comment.controller.ts index 826870cc..83354e7b 100644 --- a/apps/server/src/core/comment/comment.controller.ts +++ b/apps/server/src/core/comment/comment.controller.ts @@ -4,39 +4,33 @@ import { Body, HttpCode, HttpStatus, - Req, UseGuards, } from '@nestjs/common'; import { CommentService } from './comment.service'; import { CreateCommentDto } from './dto/create-comment.dto'; import { UpdateCommentDto } from './dto/update-comment.dto'; -import { FastifyRequest } from 'fastify'; -import { JwtGuard } from '../auth/guards/JwtGuard'; +import { JwtGuard } from '../auth/guards/jwt.guard'; import { CommentsInput, SingleCommentInput } from './dto/comments.input'; import { ResolveCommentDto } from './dto/resolve-comment.dto'; import { WorkspaceService } from '../workspace/services/workspace.service'; +import { AuthUser } from '../../decorators/auth-user.decorator'; +import { User } from '../user/entities/user.entity'; +import { CurrentWorkspace } from '../../decorators/current-workspace.decorator'; +import { Workspace } from '../workspace/entities/workspace.entity'; @UseGuards(JwtGuard) @Controller('comments') export class CommentController { - constructor( - private readonly commentService: CommentService, - private readonly workspaceService: WorkspaceService, - ) {} + constructor(private readonly commentService: CommentService) {} @HttpCode(HttpStatus.CREATED) @Post('create') async create( - @Req() req: FastifyRequest, @Body() createCommentDto: CreateCommentDto, + @AuthUser() user: User, + @CurrentWorkspace() workspace: Workspace, ) { - const jwtPayload = req['user']; - const userId = jwtPayload.sub; - - const workspaceId = ( - await this.workspaceService.getUserCurrentWorkspace(jwtPayload.sub) - ).id; - return this.commentService.create(userId, workspaceId, createCommentDto); + return this.commentService.create(user.id, workspace.id, createCommentDto); } @HttpCode(HttpStatus.OK) @@ -60,11 +54,10 @@ export class CommentController { @HttpCode(HttpStatus.OK) @Post('resolve') resolve( - @Req() req: FastifyRequest, @Body() resolveCommentDto: ResolveCommentDto, + @AuthUser() user: User, ) { - const userId = req['user'].sub; - return this.commentService.resolveComment(userId, resolveCommentDto); + return this.commentService.resolveComment(user.id, resolveCommentDto); } @HttpCode(HttpStatus.OK) diff --git a/apps/server/src/core/comment/comment.module.ts b/apps/server/src/core/comment/comment.module.ts index f7220579..23f5a0c3 100644 --- a/apps/server/src/core/comment/comment.module.ts +++ b/apps/server/src/core/comment/comment.module.ts @@ -4,17 +4,11 @@ import { CommentController } from './comment.controller'; import { CommentRepository } from './repositories/comment.repository'; import { TypeOrmModule } from '@nestjs/typeorm'; import { AuthModule } from '../auth/auth.module'; -import { WorkspaceModule } from '../workspace/workspace.module'; import { Comment } from './entities/comment.entity'; import { PageModule } from '../page/page.module'; @Module({ - imports: [ - TypeOrmModule.forFeature([Comment]), - AuthModule, - WorkspaceModule, - PageModule, - ], + imports: [TypeOrmModule.forFeature([Comment]), AuthModule, PageModule], controllers: [CommentController], providers: [CommentService, CommentRepository], exports: [CommentService, CommentRepository], diff --git a/apps/server/src/core/page/page.controller.ts b/apps/server/src/core/page/page.controller.ts index d00a901f..810451b2 100644 --- a/apps/server/src/core/page/page.controller.ts +++ b/apps/server/src/core/page/page.controller.ts @@ -10,7 +10,6 @@ import { PageService } from './services/page.service'; import { CreatePageDto } from './dto/create-page.dto'; import { UpdatePageDto } from './dto/update-page.dto'; import { JwtGuard } from '../auth/guards/jwt.guard'; -import { WorkspaceService } from '../workspace/services/workspace.service'; import { MovePageDto } from './dto/move-page.dto'; import { PageDetailsDto } from './dto/page-details.dto'; import { DeletePageDto } from './dto/delete-page.dto'; @@ -18,7 +17,10 @@ import { PageOrderingService } from './services/page-ordering.service'; import { PageHistoryService } from './services/page-history.service'; import { HistoryDetailsDto } from './dto/history-details.dto'; import { PageHistoryDto } from './dto/page-history.dto'; -import { JwtUser } from '../../decorators/jwt-user.decorator'; +import { AuthUser } from '../../decorators/auth-user.decorator'; +import { User } from '../user/entities/user.entity'; +import { CurrentWorkspace } from '../../decorators/current-workspace.decorator'; +import { Workspace } from '../workspace/entities/workspace.entity'; @UseGuards(JwtGuard) @Controller('pages') @@ -27,7 +29,6 @@ export class PageController { private readonly pageService: PageService, private readonly pageOrderService: PageOrderingService, private readonly pageHistoryService: PageHistoryService, - private readonly workspaceService: WorkspaceService, ) {} @HttpCode(HttpStatus.OK) @@ -38,17 +39,18 @@ export class PageController { @HttpCode(HttpStatus.CREATED) @Post('create') - async create(@JwtUser() jwtUser, @Body() createPageDto: CreatePageDto) { - const workspaceId = ( - await this.workspaceService.getUserCurrentWorkspace(jwtUser.id) - ).id; - return this.pageService.create(jwtUser.id, workspaceId, createPageDto); + async create( + @Body() createPageDto: CreatePageDto, + @AuthUser() user: User, + @CurrentWorkspace() workspace: Workspace, + ) { + return this.pageService.create(user.id, workspace.id, createPageDto); } @HttpCode(HttpStatus.OK) @Post('update') - async update(@JwtUser() jwtUser, @Body() updatePageDto: UpdatePageDto) { - return this.pageService.update(updatePageDto.id, updatePageDto, jwtUser.id); + async update(@Body() updatePageDto: UpdatePageDto, @AuthUser() user: User) { + return this.pageService.update(updatePageDto.id, updatePageDto, user.id); } @HttpCode(HttpStatus.OK) diff --git a/apps/server/src/core/search/search.controller.ts b/apps/server/src/core/search/search.controller.ts index 75030794..9b675632 100644 --- a/apps/server/src/core/search/search.controller.ts +++ b/apps/server/src/core/search/search.controller.ts @@ -7,36 +7,29 @@ import { Query, UseGuards, } from '@nestjs/common'; -import { JwtUser } from '../../decorators/jwt-user.decorator'; -import { WorkspaceService } from '../workspace/services/workspace.service'; -import { JwtGuard } from '../auth/guards/JwtGuard'; +import { JwtGuard } from '../auth/guards/jwt.guard'; import { SearchService } from './search.service'; import { SearchDTO } from './dto/search.dto'; +import { CurrentWorkspace } from '../../decorators/current-workspace.decorator'; +import { Workspace } from '../workspace/entities/workspace.entity'; @UseGuards(JwtGuard) @Controller('search') export class SearchController { - constructor( - private readonly searchService: SearchService, - private readonly workspaceService: WorkspaceService, - ) {} + constructor(private readonly searchService: SearchService) {} @HttpCode(HttpStatus.OK) @Post() async pageSearch( @Query('type') type: string, @Body() searchDto: SearchDTO, - @JwtUser() jwtUser, + @CurrentWorkspace() workspace: Workspace, ) { - const workspaceId = ( - await this.workspaceService.getUserCurrentWorkspace(jwtUser.id) - ).id; - if (!type || type === 'page') { return this.searchService.searchPage( searchDto.query, searchDto, - workspaceId, + workspace.id, ); } return; diff --git a/apps/server/src/core/search/search.module.ts b/apps/server/src/core/search/search.module.ts index ed68962c..a268ef08 100644 --- a/apps/server/src/core/search/search.module.ts +++ b/apps/server/src/core/search/search.module.ts @@ -2,11 +2,10 @@ import { Module } from '@nestjs/common'; import { SearchController } from './search.controller'; import { SearchService } from './search.service'; import { AuthModule } from '../auth/auth.module'; -import { WorkspaceModule } from '../workspace/workspace.module'; import { PageModule } from '../page/page.module'; @Module({ - imports: [AuthModule, WorkspaceModule, PageModule], + imports: [AuthModule, PageModule], controllers: [SearchController], providers: [SearchService], }) diff --git a/apps/server/src/core/space/space.controller.ts b/apps/server/src/core/space/space.controller.ts index 3a1a7f2c..d2fa7b8f 100644 --- a/apps/server/src/core/space/space.controller.ts +++ b/apps/server/src/core/space/space.controller.ts @@ -3,12 +3,14 @@ import { HttpCode, HttpStatus, Post, - Req, UseGuards, } from '@nestjs/common'; -import { FastifyRequest } from 'fastify'; import { JwtGuard } from '../auth/guards/jwt.guard'; import { SpaceService } from './space.service'; +import { AuthUser } from '../../decorators/auth-user.decorator'; +import { User } from '../user/entities/user.entity'; +import { CurrentWorkspace } from '../../decorators/current-workspace.decorator'; +import { Workspace } from '../workspace/entities/workspace.entity'; @UseGuards(JwtGuard) @Controller('spaces') @@ -18,5 +20,10 @@ export class SpaceController { // get all spaces user is a member of @HttpCode(HttpStatus.OK) @Post('/') - async getUserSpaces(@Req() req: FastifyRequest) {} + async getUserSpaces( + @AuthUser() user: User, + @CurrentWorkspace() workspace: Workspace, + ) { + return this.spaceService.getUserSpacesInWorkspace(user.id, workspace.id); + } } diff --git a/apps/server/src/core/user/user.controller.ts b/apps/server/src/core/user/user.controller.ts index c17de4bd..4037435d 100644 --- a/apps/server/src/core/user/user.controller.ts +++ b/apps/server/src/core/user/user.controller.ts @@ -4,17 +4,16 @@ import { UseGuards, HttpCode, HttpStatus, - Req, UnauthorizedException, Post, Body, } from '@nestjs/common'; import { UserService } from './user.service'; import { JwtGuard } from '../auth/guards/jwt.guard'; -import { FastifyRequest } from 'fastify'; import { User } from './entities/user.entity'; import { Workspace } from '../workspace/entities/workspace.entity'; import { UpdateUserDto } from './dto/update-user.dto'; +import { AuthUser } from '../../decorators/auth-user.decorator'; @UseGuards(JwtGuard) @Controller('user') @@ -23,9 +22,8 @@ export class UserController { @HttpCode(HttpStatus.OK) @Get('me') - async getUser(@Req() req: FastifyRequest) { - const jwtPayload = req['user']; - const user: User = await this.userService.findById(jwtPayload.sub); + async getUser(@AuthUser() authUser: User) { + const user: User = await this.userService.findById(authUser.id); if (!user) { throw new UnauthorizedException('Invalid user'); @@ -36,11 +34,9 @@ export class UserController { @HttpCode(HttpStatus.OK) @Get('info') - async getUserInfo(@Req() req: FastifyRequest) { - const jwtPayload = req['user']; - + async getUserInfo(@AuthUser() user: User) { const data: { workspace: Workspace; user: User } = - await this.userService.getUserInstance(jwtPayload.sub); + await this.userService.getUserInstance(user.id); return data; } @@ -48,11 +44,9 @@ export class UserController { @HttpCode(HttpStatus.OK) @Post('update') async updateUser( - @Req() req: FastifyRequest, @Body() updateUserDto: UpdateUserDto, + @AuthUser() user: User, ) { - const jwtPayload = req['user']; - - return this.userService.update(jwtPayload.sub, updateUserDto); + return this.userService.update(user.id, updateUserDto); } } diff --git a/apps/server/src/core/workspace/controllers/workspace.controller.ts b/apps/server/src/core/workspace/controllers/workspace.controller.ts index 94ed6820..c0229722 100644 --- a/apps/server/src/core/workspace/controllers/workspace.controller.ts +++ b/apps/server/src/core/workspace/controllers/workspace.controller.ts @@ -6,11 +6,9 @@ import { HttpCode, HttpStatus, Post, - Req, UseGuards, } from '@nestjs/common'; import { WorkspaceService } from '../services/workspace.service'; -import { FastifyRequest } from 'fastify'; import { JwtGuard } from '../../auth/guards/jwt.guard'; import { UpdateWorkspaceDto } from '../dto/update-workspace.dto'; import { CreateWorkspaceDto } from '../dto/create-workspace.dto'; @@ -18,46 +16,32 @@ import { DeleteWorkspaceDto } from '../dto/delete-workspace.dto'; import { UpdateWorkspaceUserRoleDto } from '../dto/update-workspace-user-role.dto'; import { RemoveWorkspaceUserDto } from '../dto/remove-workspace-user.dto'; import { AddWorkspaceUserDto } from '../dto/add-workspace-user.dto'; +import { AuthUser } from '../../../decorators/auth-user.decorator'; +import { User } from '../../user/entities/user.entity'; +import { CurrentWorkspace } from '../../../decorators/current-workspace.decorator'; +import { Workspace } from '../entities/workspace.entity'; @UseGuards(JwtGuard) @Controller('workspace') export class WorkspaceController { constructor(private readonly workspaceService: WorkspaceService) {} - @HttpCode(HttpStatus.OK) - @Post('test') - async test( - @Req() req: FastifyRequest, - //@Body() createWorkspaceDto: CreateWorkspaceDto, - ) { - //const jwtPayload = req['user']; - // const userId = jwtPayload.sub; - // return this.workspaceService.createOrJoinWorkspace(); - } - @HttpCode(HttpStatus.OK) @Post('create') async createWorkspace( - @Req() req: FastifyRequest, @Body() createWorkspaceDto: CreateWorkspaceDto, + @AuthUser() user: User, ) { - const jwtPayload = req['user']; - const userId = jwtPayload.sub; - return this.workspaceService.create(userId, createWorkspaceDto); + return this.workspaceService.create(user.id, createWorkspaceDto); } @HttpCode(HttpStatus.OK) @Post('update') async updateWorkspace( - @Req() req: FastifyRequest, @Body() updateWorkspaceDto: UpdateWorkspaceDto, + @CurrentWorkspace() workspace: Workspace, ) { - const jwtPayload = req['user']; - const workspaceId = ( - await this.workspaceService.getUserCurrentWorkspace(jwtPayload.sub) - ).id; - - return this.workspaceService.update(workspaceId, updateWorkspaceDto); + return this.workspaceService.update(workspace.id, updateWorkspaceDto); } @HttpCode(HttpStatus.OK) @@ -68,29 +52,19 @@ export class WorkspaceController { @HttpCode(HttpStatus.OK) @Get('members') - async getWorkspaceMembers(@Req() req: FastifyRequest) { - const jwtPayload = req['user']; - const workspaceId = ( - await this.workspaceService.getUserCurrentWorkspace(jwtPayload.sub) - ).id; - - return this.workspaceService.getWorkspaceUsers(workspaceId); + async getWorkspaceMembers(@CurrentWorkspace() workspace: Workspace) { + return this.workspaceService.getWorkspaceUsers(workspace.id); } @HttpCode(HttpStatus.OK) @Post('members/add') async addWorkspaceMember( - @Req() req: FastifyRequest, @Body() addWorkspaceUserDto: AddWorkspaceUserDto, + @CurrentWorkspace() workspace: Workspace, ) { - const jwtPayload = req['user']; - const workspaceId = ( - await this.workspaceService.getUserCurrentWorkspace(jwtPayload.sub) - ).id; - return this.workspaceService.addUserToWorkspace( addWorkspaceUserDto.userId, - workspaceId, + workspace.id, addWorkspaceUserDto.role, ); } @@ -98,34 +72,24 @@ export class WorkspaceController { @HttpCode(HttpStatus.OK) @Delete('members/delete') async removeWorkspaceMember( - @Req() req: FastifyRequest, @Body() removeWorkspaceUserDto: RemoveWorkspaceUserDto, + @CurrentWorkspace() workspace: Workspace, ) { - const jwtPayload = req['user']; - const workspaceId = ( - await this.workspaceService.getUserCurrentWorkspace(jwtPayload.sub) - ).id; - return this.workspaceService.removeUserFromWorkspace( removeWorkspaceUserDto.userId, - workspaceId, + workspace.id, ); } @HttpCode(HttpStatus.OK) @Post('members/role') async updateWorkspaceMemberRole( - @Req() req: FastifyRequest, @Body() workspaceUserRoleDto: UpdateWorkspaceUserRoleDto, + @CurrentWorkspace() workspace: Workspace, ) { - const jwtPayload = req['user']; - const workspaceId = ( - await this.workspaceService.getUserCurrentWorkspace(jwtPayload.sub) - ).id; - return this.workspaceService.updateWorkspaceUserRole( workspaceUserRoleDto, - workspaceId, + workspace.id, ); } } diff --git a/apps/server/src/decorators/auth-user.decorator.ts b/apps/server/src/decorators/auth-user.decorator.ts new file mode 100644 index 00000000..4d68373a --- /dev/null +++ b/apps/server/src/decorators/auth-user.decorator.ts @@ -0,0 +1,17 @@ +import { + createParamDecorator, + ExecutionContext, + UnauthorizedException, +} from '@nestjs/common'; + +export const AuthUser = createParamDecorator( + (data: unknown, ctx: ExecutionContext) => { + const request = ctx.switchToHttp().getRequest(); + + if (!request['user'] || !request['user'].user) { + throw new UnauthorizedException(); + } + + return request['user'] ? request['user'].user : undefined; + }, +); diff --git a/apps/server/src/decorators/current-workspace.decorator.ts b/apps/server/src/decorators/current-workspace.decorator.ts new file mode 100644 index 00000000..4cad4fa6 --- /dev/null +++ b/apps/server/src/decorators/current-workspace.decorator.ts @@ -0,0 +1,17 @@ +import { + createParamDecorator, + ExecutionContext, + UnauthorizedException, +} from '@nestjs/common'; + +export const CurrentWorkspace = createParamDecorator( + (data: unknown, ctx: ExecutionContext) => { + const request = ctx.switchToHttp().getRequest(); + + if (!request['user'] || !request['user'].workspace) { + throw new UnauthorizedException(); + } + + return request['user'] ? request['user'].workspace : undefined; + }, +);