From 8e7785ca649637ec7b53450452fbe0e7257fe57d Mon Sep 17 00:00:00 2001 From: Alex van Andel Date: Mon, 20 Nov 2023 13:28:25 +0000 Subject: [PATCH] fix: Better error reporting & some fixes to deleteUser API (#12439) --- apps/api/pages/api/users/[userId]/_delete.ts | 13 ++++++- .../features/users/lib/userDeletionService.ts | 20 ++++++++++ .../loggedInViewer/deleteMe.handler.ts | 39 ++++++++++--------- 3 files changed, 51 insertions(+), 21 deletions(-) create mode 100644 packages/features/users/lib/userDeletionService.ts diff --git a/apps/api/pages/api/users/[userId]/_delete.ts b/apps/api/pages/api/users/[userId]/_delete.ts index 44e8f0df0f..6b54fd3bee 100644 --- a/apps/api/pages/api/users/[userId]/_delete.ts +++ b/apps/api/pages/api/users/[userId]/_delete.ts @@ -1,5 +1,6 @@ import type { NextApiRequest } from "next"; +import { deleteUser } from "@calcom/features/users/lib/userDeletionService"; import { HttpError } from "@calcom/lib/http-error"; import { defaultResponder } from "@calcom/lib/server"; @@ -41,10 +42,18 @@ export async function deleteHandler(req: NextApiRequest) { // Here we only check for ownership of the user if the user is not admin, otherwise we let ADMIN's edit any user if (!isAdmin && query.userId !== req.userId) throw new HttpError({ statusCode: 403, message: "Forbidden" }); - const user = await prisma.user.findUnique({ where: { id: query.userId } }); + const user = await prisma.user.findUnique({ + where: { id: query.userId }, + select: { + id: true, + email: true, + metadata: true, + }, + }); if (!user) throw new HttpError({ statusCode: 404, message: "User not found" }); - await prisma.user.delete({ where: { id: user.id } }); + await deleteUser(user); + return { message: `User with id: ${user.id} deleted successfully` }; } diff --git a/packages/features/users/lib/userDeletionService.ts b/packages/features/users/lib/userDeletionService.ts new file mode 100644 index 0000000000..33ac05d586 --- /dev/null +++ b/packages/features/users/lib/userDeletionService.ts @@ -0,0 +1,20 @@ +import type { User } from "@prisma/client"; + +import { deleteStripeCustomer } from "@calcom/app-store/stripepayment/lib/customer"; +import { deleteWebUser as syncServicesDeleteWebUser } from "@calcom/lib/sync/SyncServiceManager"; +import prisma from "@calcom/prisma"; + +export async function deleteUser(user: Pick) { + // If 2FA is disabled or totpCode is valid then delete the user from stripe and database + await deleteStripeCustomer(user).catch(console.warn); + // Remove my account + // TODO: Move this to Repository pattern. + const deletedUser = await prisma.user.delete({ + where: { + id: user.id, + }, + }); + + // Sync Services + syncServicesDeleteWebUser(deletedUser); +} diff --git a/packages/trpc/server/routers/loggedInViewer/deleteMe.handler.ts b/packages/trpc/server/routers/loggedInViewer/deleteMe.handler.ts index dac7b1d54c..723b4df4e0 100644 --- a/packages/trpc/server/routers/loggedInViewer/deleteMe.handler.ts +++ b/packages/trpc/server/routers/loggedInViewer/deleteMe.handler.ts @@ -1,8 +1,8 @@ -import { deleteStripeCustomer } from "@calcom/app-store/stripepayment/lib/customer"; import { ErrorCode } from "@calcom/features/auth/lib/ErrorCode"; import { verifyPassword } from "@calcom/features/auth/lib/verifyPassword"; +import { deleteUser } from "@calcom/features/users/lib/userDeletionService"; import { symmetricDecrypt } from "@calcom/lib/crypto"; -import { deleteWebUser as syncServicesDeleteWebUser } from "@calcom/lib/sync/SyncServiceManager"; +import { HttpError } from "@calcom/lib/http-error"; import { totpAuthenticatorCheck } from "@calcom/lib/totp"; import { prisma } from "@calcom/prisma"; import { IdentityProvider } from "@calcom/prisma/enums"; @@ -18,32 +18,43 @@ type DeleteMeOptions = { }; export const deleteMeHandler = async ({ ctx, input }: DeleteMeOptions) => { + // TODO: First check password is part of input and meets requirements. + // Check if input.password is correct const user = await prisma.user.findUnique({ where: { email: ctx.user.email.toLowerCase(), }, + select: { + identityProvider: true, + twoFactorEnabled: true, + twoFactorSecret: true, + password: true, + email: true, + metadata: true, + id: true, + }, }); if (!user) { - throw new Error(ErrorCode.UserNotFound); + throw new HttpError({ statusCode: 404, message: ErrorCode.UserNotFound }); } if (user.identityProvider !== IdentityProvider.CAL) { - throw new Error(ErrorCode.ThirdPartyIdentityProviderEnabled); + throw new HttpError({ statusCode: 400, message: ErrorCode.ThirdPartyIdentityProviderEnabled }); } if (!user.password) { - throw new Error(ErrorCode.UserMissingPassword); + throw new HttpError({ statusCode: 400, message: ErrorCode.UserMissingPassword }); } const isCorrectPassword = await verifyPassword(input.password, user.password); if (!isCorrectPassword) { - throw new Error(ErrorCode.IncorrectPassword); + throw new HttpError({ statusCode: 403, message: ErrorCode.IncorrectPassword }); } if (user.twoFactorEnabled) { if (!input.totpCode) { - throw new Error(ErrorCode.SecondFactorRequired); + throw new HttpError({ statusCode: 400, message: ErrorCode.SecondFactorRequired }); } if (!user.twoFactorSecret) { @@ -67,20 +78,10 @@ export const deleteMeHandler = async ({ ctx, input }: DeleteMeOptions) => { // If user has 2fa enabled, check if input.totpCode is correct const isValidToken = totpAuthenticatorCheck(input.totpCode, secret); if (!isValidToken) { - throw new Error(ErrorCode.IncorrectTwoFactorCode); + throw new HttpError({ statusCode: 403, message: ErrorCode.IncorrectTwoFactorCode }); } } - // If 2FA is disabled or totpCode is valid then delete the user from stripe and database - await deleteStripeCustomer(user).catch(console.warn); - // Remove my account - const deletedUser = await prisma.user.delete({ - where: { - id: ctx.user.id, - }, - }); - - // Sync Services - syncServicesDeleteWebUser(deletedUser); + await deleteUser(user); return; };