From 06fffbee2f0e7e7471a43282ee8a9a49f5c6d24f Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 25 Aug 2022 13:27:47 +0200 Subject: [PATCH] Added Backend for User Profile --- dto/src/requests/auth.ts | 10 ++++++++++ dto/src/responses/auth.ts | 1 + dto/src/responses/index.ts | 1 + dto/src/responses/user.ts | 27 +++++++++++++++++++++++++++ package.json | 3 ++- src/controller/auth.ts | 13 +++++++++++++ src/controller/user.ts | 27 +++++++++++++++++++++++++++ src/modules/auth.ts | 5 +++-- src/services/auth.ts | 18 ++++++++++++++++++ src/services/filesystem.ts | 6 +++--- 10 files changed, 105 insertions(+), 6 deletions(-) create mode 100644 dto/src/responses/user.ts create mode 100644 src/controller/user.ts diff --git a/dto/src/requests/auth.ts b/dto/src/requests/auth.ts index 5526e9d..d7b9940 100644 --- a/dto/src/requests/auth.ts +++ b/dto/src/requests/auth.ts @@ -34,3 +34,13 @@ export class TfaSetup extends BaseRequest { @IsBoolean() mail: boolean; } + +export class ChangePasswordRequest extends BaseRequest { + @IsNotEmpty() + @IsString() + oldPassword: string; + + @IsNotEmpty() + @IsString() + newPassword: string; +} diff --git a/dto/src/responses/auth.ts b/dto/src/responses/auth.ts index e9177cd..fd97cb5 100644 --- a/dto/src/responses/auth.ts +++ b/dto/src/responses/auth.ts @@ -35,4 +35,5 @@ export class RemoveTfaResponse extends SuccessResponse {} export class RequestEmailTfaResponse extends SuccessResponse {} export class TfaCompletedResponse extends SuccessResponse {} export class SignupResponse extends SuccessResponse {} +export class ChangePasswordResponse extends SuccessResponse {} export class RefreshResponse extends LoginResponse {} diff --git a/dto/src/responses/index.ts b/dto/src/responses/index.ts index 8f26264..c1d0397 100644 --- a/dto/src/responses/index.ts +++ b/dto/src/responses/index.ts @@ -1,3 +1,4 @@ export * from './base'; export * as Auth from './auth'; export * as FS from './fs'; +export * as User from './user'; diff --git a/dto/src/responses/user.ts b/dto/src/responses/user.ts new file mode 100644 index 0000000..706526b --- /dev/null +++ b/dto/src/responses/user.ts @@ -0,0 +1,27 @@ +import { SuccessResponse } from './base'; +import { ValidateConstructor } from '../utils'; +import { IsBoolean, IsNotEmpty, IsString } from 'class-validator'; + +@ValidateConstructor +export class UserInfoResponse extends SuccessResponse { + constructor(name: string, gitlab: boolean, tfaEnabled: boolean) { + super(); + this.name = name; + this.gitlab = gitlab; + this.tfaEnabled = tfaEnabled; + } + + @IsNotEmpty() + @IsString() + name: string; + + @IsBoolean() + gitlab: boolean; + + @IsBoolean() + tfaEnabled: boolean; +} + +export class DeleteUserResponse extends SuccessResponse {} +export class ChangePasswordResponse extends SuccessResponse {} +export class LogoutAllResponse extends SuccessResponse {} diff --git a/package.json b/package.json index 558f98f..6ab3d73 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,8 @@ "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", "test:e2e": "jest --config ./test/jest-e2e.json", "webpack": "webpack --config webpack.config.ts", - "genapi": "ts-node tools/apigen.ts" + "genapi": "ts-node tools/apigen.ts", + "updateDto": "cd dto && yarn build && cd .. && yarn add ./dto && cd frontend && yarn add ../dto" }, "dependencies": { "@fastify/multipart": "^7.1.0", diff --git a/src/controller/auth.ts b/src/controller/auth.ts index 756a9a7..a4fa3c8 100644 --- a/src/controller/auth.ts +++ b/src/controller/auth.ts @@ -134,4 +134,17 @@ export default class AuthController { url: `/set_token?token=${token}` }; } + + @Post('change_password') + async changePassword( + @Request() req, + @Body(new ValidationPipe()) data: Requests.Auth.ChangePasswordRequest + ): Promise { + await this.authService.changePassword( + req.user, + data.oldPassword, + data.newPassword + ); + return new Responses.Auth.ChangePasswordResponse(); + } } diff --git a/src/controller/user.ts b/src/controller/user.ts new file mode 100644 index 0000000..9f674a2 --- /dev/null +++ b/src/controller/user.ts @@ -0,0 +1,27 @@ +import { Controller, Get, Post, Request } from '@nestjs/common'; +import { AuthService } from '../services/auth'; +import { Responses } from 'dto'; + +@Controller('api/user') +export default class UserController { + constructor(private authService: AuthService) {} + + @Get('info') + async getUserInfo( + @Request() req + ): Promise { + return new Responses.User.UserInfoResponse( + req.user.name, + req.user.isGitlabUser, + this.authService.requiresTfa(req.user) + ); + } + + @Post('delete') + async deleteUser( + @Request() req + ): Promise { + await this.authService.deleteUser(req.user); + return new Responses.User.DeleteUserResponse(); + } +} diff --git a/src/modules/auth.ts b/src/modules/auth.ts index 82d5743..d35540d 100644 --- a/src/modules/auth.ts +++ b/src/modules/auth.ts @@ -6,8 +6,9 @@ import { AuthLocalService, AuthJwtService } from '../services/auth'; -import AuthController from '../controller/auth'; import FileSystemService from '../services/filesystem'; +import AuthController from '../controller/auth'; +import UserController from '../controller/user'; @Module({ imports: [TypeOrmModule.forFeature([User, INode, JWTToken])], @@ -17,6 +18,6 @@ import FileSystemService from '../services/filesystem'; AuthJwtService, FileSystemService ], - controllers: [AuthController] + controllers: [AuthController, UserController] }) export default class AuthModule {} diff --git a/src/services/auth.ts b/src/services/auth.ts index 0362e06..224edcb 100644 --- a/src/services/auth.ts +++ b/src/services/auth.ts @@ -1,5 +1,6 @@ import { BadRequestException, + ForbiddenException, Injectable, UnauthorizedException } from '@nestjs/common'; @@ -312,6 +313,23 @@ export class AuthService { } return info && info.username == user.name; } + + async deleteUser(user: User): Promise { + await this.revokeAll(user); + await this.fsService.delete(await user.root, true); + await this.userRepo.remove(user); + } + + async changePassword( + user: User, + oldPW: string, + newPw: string + ): Promise { + if (!(await argon2.verify(user.password, oldPW))) + throw new ForbiddenException('Old password is wrong'); + user.password = await argon2.hash(newPw); + await this.revokeAll(await this.userRepo.save(user)); + } } @Injectable() diff --git a/src/services/filesystem.ts b/src/services/filesystem.ts index a74f756..16c3a28 100644 --- a/src/services/filesystem.ts +++ b/src/services/filesystem.ts @@ -51,12 +51,12 @@ export default class FileSystemService { ); } - async delete(node: INode): Promise { - if (node.parentId == null) + async delete(node: INode, force = false): Promise { + if (node.parentId == null || force) throw new BadRequestException("Can't delete root"); if (!node.isFile) await Promise.all( - (await node.children).map((child) => this.delete(child)) + (await node.children).map((child) => this.delete(child, force)) ); else unlink(`files/${node.id}`, (err) => {