fix: prevent deletion of last admin account

This commit is contained in:
Elias Schneider
2024-11-14 17:39:06 +01:00
parent 4ce64206be
commit e1a5d19544
3 changed files with 47 additions and 32 deletions

View File

@@ -3,6 +3,7 @@ import {
Controller,
Delete,
Get,
HttpCode,
Param,
Patch,
Post,
@@ -14,18 +15,18 @@ import { Response } from "express";
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 { ConfigService } from "../config/config.service";
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";
import { ConfigService } from "../config/config.service";
@Controller("users")
export class UserController {
constructor(
private userService: UserSevice,
private config: ConfigService,
private config: ConfigService
) {}
// Own user operations
@@ -42,17 +43,20 @@ export class UserController {
@UseGuards(JwtGuard)
async updateCurrentUser(
@GetUser() user: User,
@Body() data: UpdateOwnUserDTO,
@Body() data: UpdateOwnUserDTO
) {
return new UserDTO().from(await this.userService.update(user.id, data));
}
@Delete("me")
@HttpCode(204)
@UseGuards(JwtGuard)
async deleteCurrentUser(
@GetUser() user: User,
@Res({ passthrough: true }) response: Response,
@Res({ passthrough: true }) response: Response
) {
await this.userService.delete(user.id);
const isSecure = this.config.get("general.secureCookies");
response.cookie("access_token", "accessToken", {
@@ -65,7 +69,6 @@ export class UserController {
maxAge: -1,
secure: isSecure,
});
return new UserDTO().from(await this.userService.delete(user.id));
}
// Global user operations

View File

@@ -2,15 +2,15 @@ import { BadRequestException, Injectable, Logger } from "@nestjs/common";
import { PrismaClientKnownRequestError } from "@prisma/client/runtime/library";
import * as argon from "argon2";
import * as crypto from "crypto";
import { Entry } from "ldapts";
import { AuthSignInDTO } from "src/auth/dto/authSignIn.dto";
import { EmailService } from "src/email/email.service";
import { PrismaService } from "src/prisma/prisma.service";
import { inspect } from "util";
import { ConfigService } from "../config/config.service";
import { FileService } from "../file/file.service";
import { CreateUserDTO } from "./dto/createUser.dto";
import { UpdateUserDto } from "./dto/updateUser.dto";
import { ConfigService } from "../config/config.service";
import { Entry } from "ldapts";
import { AuthSignInDTO } from "src/auth/dto/authSignIn.dto";
import { inspect } from "util";
@Injectable()
export class UserSevice {
@@ -20,7 +20,7 @@ export class UserSevice {
private prisma: PrismaService,
private emailService: EmailService,
private fileService: FileService,
private configService: ConfigService,
private configService: ConfigService
) {}
async list() {
@@ -55,7 +55,7 @@ export class UserSevice {
if (e.code == "P2002") {
const duplicatedField: string = e.meta.target[0];
throw new BadRequestException(
`A user with this ${duplicatedField} already exists`,
`A user with this ${duplicatedField} already exists`
);
}
}
@@ -75,7 +75,7 @@ export class UserSevice {
if (e.code == "P2002") {
const duplicatedField: string = e.meta.target[0];
throw new BadRequestException(
`A user with this ${duplicatedField} already exists`,
`A user with this ${duplicatedField} already exists`
);
}
}
@@ -89,8 +89,18 @@ export class UserSevice {
});
if (!user) throw new BadRequestException("User not found");
if (user.isAdmin) {
const userCount = await this.prisma.user.count({
where: { isAdmin: true },
});
if (userCount === 1) {
throw new BadRequestException("Cannot delete the last admin user");
}
}
await Promise.all(
user.shares.map((share) => this.fileService.deleteAllFiles(share.id)),
user.shares.map((share) => this.fileService.deleteAllFiles(share.id))
);
return await this.prisma.user.delete({ where: { id } });
@@ -98,7 +108,7 @@ export class UserSevice {
async findOrCreateFromLDAP(
providedCredentials: AuthSignInDTO,
ldapEntry: Entry,
ldapEntry: Entry
) {
const fieldNameMemberOf = this.configService.get("ldap.fieldNameMemberOf");
const fieldNameEmail = this.configService.get("ldap.fieldNameEmail");
@@ -112,7 +122,7 @@ export class UserSevice {
isAdmin = entryGroups.includes(adminGroup) ?? false;
} else {
this.logger.warn(
`Trying to create/update a ldap user but the member field ${fieldNameMemberOf} is not present.`,
`Trying to create/update a ldap user but the member field ${fieldNameMemberOf} is not present.`
);
}
@@ -126,7 +136,7 @@ export class UserSevice {
}
} else {
this.logger.warn(
`Trying to create/update a ldap user but the email field ${fieldNameEmail} is not present.`,
`Trying to create/update a ldap user but the email field ${fieldNameEmail} is not present.`
);
}
@@ -174,7 +184,7 @@ export class UserSevice {
})
.catch((error) => {
this.logger.warn(
`Failed to update users ${user.id} placeholder username: ${inspect(error)}`,
`Failed to update users ${user.id} placeholder username: ${inspect(error)}`
);
});
}
@@ -192,13 +202,13 @@ export class UserSevice {
})
.then((newUser) => {
this.logger.log(
`Updated users ${user.id} email from ldap from ${user.email} to ${userEmail}.`,
`Updated users ${user.id} email from ldap from ${user.email} to ${userEmail}.`
);
user.email = newUser.email;
})
.catch((error) => {
this.logger.error(
`Failed to update users ${user.id} email to ${userEmail}: ${inspect(error)}`,
`Failed to update users ${user.id} email to ${userEmail}: ${inspect(error)}`
);
});
}
@@ -209,7 +219,7 @@ export class UserSevice {
if (e.code == "P2002") {
const duplicatedField: string = e.meta.target[0];
throw new BadRequestException(
`A user with this ${duplicatedField} already exists`,
`A user with this ${duplicatedField} already exists`
);
}
}