feat: add user management

This commit is contained in:
Elias Schneider
2022-12-05 15:53:24 +01:00
parent 31b3f6cb2f
commit 7a3967fd6f
25 changed files with 751 additions and 47 deletions

View File

@@ -3,14 +3,20 @@ import {
Controller,
ForbiddenException,
HttpCode,
Patch,
Post,
UseGuards,
} from "@nestjs/common";
import { Throttle } from "@nestjs/throttler";
import { User } from "@prisma/client";
import { ConfigService } from "src/config/config.service";
import { AuthService } from "./auth.service";
import { GetUser } from "./decorator/getUser.decorator";
import { AuthRegisterDTO } from "./dto/authRegister.dto";
import { AuthSignInDTO } from "./dto/authSignIn.dto";
import { RefreshAccessTokenDTO } from "./dto/refreshAccessToken.dto";
import { UpdatePasswordDTO } from "./dto/updatePassword.dto";
import { JwtGuard } from "./guard/jwt.guard";
@Controller("auth")
export class AuthController {
@@ -34,6 +40,12 @@ export class AuthController {
return this.authService.signIn(dto);
}
@Patch("password")
@UseGuards(JwtGuard)
async updatePassword(@GetUser() user: User, @Body() dto: UpdatePasswordDTO) {
await this.authService.updatePassword(user, dto.oldPassword, dto.password);
}
@Post("token")
@HttpCode(200)
async refreshAccessToken(@Body() body: RefreshAccessTokenDTO) {

View File

@@ -1,5 +1,6 @@
import {
BadRequestException,
ForbiddenException,
Injectable,
UnauthorizedException,
} from "@nestjs/common";
@@ -68,6 +69,18 @@ export class AuthService {
return { accessToken, refreshToken };
}
async updatePassword(user: User, oldPassword: string, newPassword: string) {
if (argon.verify(user.password, oldPassword))
throw new ForbiddenException("Invalid password");
const hash = await argon.hash(newPassword);
this.prisma.user.update({
where: { id: user.id },
data: { password: hash },
});
}
async createAccessToken(user: User) {
return this.jwtService.sign(
{

View File

@@ -0,0 +1,8 @@
import { PickType } from "@nestjs/mapped-types";
import { IsString } from "class-validator";
import { UserDTO } from "src/user/dto/user.dto";
export class UpdatePasswordDTO extends PickType(UserDTO, ["password"]) {
@IsString()
oldPassword: string;
}

View File

@@ -6,7 +6,7 @@ import { AppModule } from "./app.module";
async function bootstrap() {
const app = await NestFactory.create<NestExpressApplication>(AppModule);
app.useGlobalPipes(new ValidationPipe());
app.useGlobalPipes(new ValidationPipe({whitelist: true}));
app.useGlobalInterceptors(new ClassSerializerInterceptor(app.get(Reflector)));
app.set("trust proxy", true);

View File

@@ -0,0 +1,14 @@
import { Expose, plainToClass } from "class-transformer";
import { Allow } from "class-validator";
import { UserDTO } from "./user.dto";
export class CreateUserDTO extends UserDTO{
@Expose()
@Allow()
isAdmin: boolean;
from(partial: Partial<CreateUserDTO>) {
return plainToClass(CreateUserDTO, partial, { excludeExtraneousValues: true });
}
}

View File

@@ -2,5 +2,5 @@ import { OmitType, PartialType } from "@nestjs/mapped-types";
import { UserDTO } from "./user.dto";
export class UpdateOwnUserDTO extends PartialType(
OmitType(UserDTO, ["isAdmin"] as const)
OmitType(UserDTO, ["isAdmin", "password"] as const)
) {}

View File

@@ -1,17 +1,10 @@
import { Expose, plainToClass } from "class-transformer";
import {
IsEmail,
IsNotEmpty,
IsString,
Length,
Matches,
} from "class-validator";
import { IsEmail, Length, Matches, MinLength } from "class-validator";
export class UserDTO {
@Expose()
id: string;
@Expose()
@Expose()
@Matches("^[a-zA-Z0-9_.]*$", undefined, {
message: "Username can only contain letters, numbers, dots and underscores",
@@ -23,8 +16,7 @@ export class UserDTO {
@IsEmail()
email: string;
@IsNotEmpty()
@IsString()
@MinLength(8)
password: string;
@Expose()

View File

@@ -12,6 +12,8 @@ import { User } from "@prisma/client";
import { GetUser } from "src/auth/decorator/getUser.decorator";
import { AdministratorGuard } from "src/auth/guard/isAdmin.guard";
import { JwtGuard } from "src/auth/guard/jwt.guard";
import { CreateUserDTO } from "./dto/createUser.dto";
import { UpdateOwnUserDTO } from "./dto/updateOwnUser.dto";
import { UpdateUserDto } from "./dto/updateUser.dto";
import { UserDTO } from "./dto/user.dto";
import { UserSevice } from "./user.service";
@@ -29,7 +31,10 @@ export class UserController {
@Patch("me")
@UseGuards(JwtGuard)
async updateCurrentUser(@GetUser() user: User, @Body() data: UpdateUserDto) {
async updateCurrentUser(
@GetUser() user: User,
@Body() data: UpdateOwnUserDTO
) {
return new UserDTO().from(await this.userService.update(user.id, data));
}
@@ -48,7 +53,7 @@ export class UserController {
@Post()
@UseGuards(JwtGuard, AdministratorGuard)
async create(@Body() user: UserDTO) {
async create(@Body() user: CreateUserDTO) {
return new UserDTO().from(await this.userService.create(user));
}
@@ -60,7 +65,7 @@ export class UserController {
@Delete(":id")
@UseGuards(JwtGuard, AdministratorGuard)
async delete(@Param() id: string) {
async delete(@Param("id") id: string) {
return new UserDTO().from(await this.userService.delete(id));
}
}

View File

@@ -2,6 +2,7 @@ import { BadRequestException, Injectable } from "@nestjs/common";
import { PrismaClientKnownRequestError } from "@prisma/client/runtime";
import * as argon from "argon2";
import { PrismaService } from "src/prisma/prisma.service";
import { CreateUserDTO } from "./dto/createUser.dto";
import { UpdateUserDto } from "./dto/updateUser.dto";
import { UserDTO } from "./dto/user.dto";
@@ -17,7 +18,7 @@ export class UserSevice {
return await this.prisma.user.findUnique({ where: { id } });
}
async create(dto: UserDTO) {
async create(dto: CreateUserDTO) {
const hash = await argon.hash(dto.password);
try {
return await this.prisma.user.create({