mirror of
https://github.com/swissmakers/swiss-datashare.git
synced 2026-04-11 10:27:01 +02:00
feat: add user management
This commit is contained in:
@@ -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) {
|
||||
|
||||
@@ -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(
|
||||
{
|
||||
|
||||
8
backend/src/auth/dto/updatePassword.dto.ts
Normal file
8
backend/src/auth/dto/updatePassword.dto.ts
Normal 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;
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
14
backend/src/user/dto/createUser.dto.ts
Normal file
14
backend/src/user/dto/createUser.dto.ts
Normal 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 });
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
) {}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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({
|
||||
|
||||
Reference in New Issue
Block a user