Added Backend for User Profile

This commit is contained in:
Mutzi 2022-08-25 13:27:47 +02:00
parent 1f714b5b50
commit 06fffbee2f
10 changed files with 105 additions and 6 deletions

View File

@ -34,3 +34,13 @@ export class TfaSetup extends BaseRequest {
@IsBoolean() @IsBoolean()
mail: boolean; mail: boolean;
} }
export class ChangePasswordRequest extends BaseRequest {
@IsNotEmpty()
@IsString()
oldPassword: string;
@IsNotEmpty()
@IsString()
newPassword: string;
}

View File

@ -35,4 +35,5 @@ export class RemoveTfaResponse extends SuccessResponse {}
export class RequestEmailTfaResponse extends SuccessResponse {} export class RequestEmailTfaResponse extends SuccessResponse {}
export class TfaCompletedResponse extends SuccessResponse {} export class TfaCompletedResponse extends SuccessResponse {}
export class SignupResponse extends SuccessResponse {} export class SignupResponse extends SuccessResponse {}
export class ChangePasswordResponse extends SuccessResponse {}
export class RefreshResponse extends LoginResponse {} export class RefreshResponse extends LoginResponse {}

View File

@ -1,3 +1,4 @@
export * from './base'; export * from './base';
export * as Auth from './auth'; export * as Auth from './auth';
export * as FS from './fs'; export * as FS from './fs';
export * as User from './user';

27
dto/src/responses/user.ts Normal file
View File

@ -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 {}

View File

@ -18,7 +18,8 @@
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", "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", "test:e2e": "jest --config ./test/jest-e2e.json",
"webpack": "webpack --config webpack.config.ts", "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": { "dependencies": {
"@fastify/multipart": "^7.1.0", "@fastify/multipart": "^7.1.0",

View File

@ -134,4 +134,17 @@ export default class AuthController {
url: `/set_token?token=${token}` url: `/set_token?token=${token}`
}; };
} }
@Post('change_password')
async changePassword(
@Request() req,
@Body(new ValidationPipe()) data: Requests.Auth.ChangePasswordRequest
): Promise<Responses.Auth.ChangePasswordResponse> {
await this.authService.changePassword(
req.user,
data.oldPassword,
data.newPassword
);
return new Responses.Auth.ChangePasswordResponse();
}
} }

27
src/controller/user.ts Normal file
View File

@ -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<Responses.User.UserInfoResponse> {
return new Responses.User.UserInfoResponse(
req.user.name,
req.user.isGitlabUser,
this.authService.requiresTfa(req.user)
);
}
@Post('delete')
async deleteUser(
@Request() req
): Promise<Responses.User.DeleteUserResponse> {
await this.authService.deleteUser(req.user);
return new Responses.User.DeleteUserResponse();
}
}

View File

@ -6,8 +6,9 @@ import {
AuthLocalService, AuthLocalService,
AuthJwtService AuthJwtService
} from '../services/auth'; } from '../services/auth';
import AuthController from '../controller/auth';
import FileSystemService from '../services/filesystem'; import FileSystemService from '../services/filesystem';
import AuthController from '../controller/auth';
import UserController from '../controller/user';
@Module({ @Module({
imports: [TypeOrmModule.forFeature([User, INode, JWTToken])], imports: [TypeOrmModule.forFeature([User, INode, JWTToken])],
@ -17,6 +18,6 @@ import FileSystemService from '../services/filesystem';
AuthJwtService, AuthJwtService,
FileSystemService FileSystemService
], ],
controllers: [AuthController] controllers: [AuthController, UserController]
}) })
export default class AuthModule {} export default class AuthModule {}

View File

@ -1,5 +1,6 @@
import { import {
BadRequestException, BadRequestException,
ForbiddenException,
Injectable, Injectable,
UnauthorizedException UnauthorizedException
} from '@nestjs/common'; } from '@nestjs/common';
@ -312,6 +313,23 @@ export class AuthService {
} }
return info && info.username == user.name; return info && info.username == user.name;
} }
async deleteUser(user: User): Promise<void> {
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<void> {
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() @Injectable()

View File

@ -51,12 +51,12 @@ export default class FileSystemService {
); );
} }
async delete(node: INode): Promise<void> { async delete(node: INode, force = false): Promise<void> {
if (node.parentId == null) if (node.parentId == null || force)
throw new BadRequestException("Can't delete root"); throw new BadRequestException("Can't delete root");
if (!node.isFile) if (!node.isFile)
await Promise.all( await Promise.all(
(await node.children).map((child) => this.delete(child)) (await node.children).map((child) => this.delete(child, force))
); );
else else
unlink(`files/${node.id}`, (err) => { unlink(`files/${node.id}`, (err) => {