diff --git a/tdrive/backend/node/src/core/platform/services/admin/controller/delete-user-controller.ts b/tdrive/backend/node/src/core/platform/services/admin/controller/delete-user-controller.ts index 0fb2be94..7de45fd6 100644 --- a/tdrive/backend/node/src/core/platform/services/admin/controller/delete-user-controller.ts +++ b/tdrive/backend/node/src/core/platform/services/admin/controller/delete-user-controller.ts @@ -1,3 +1,4 @@ +import { UserPrimaryKey } from "src/services/user/entities/user"; import gr from "../../../../../services/global-resolver"; import type { ExecutionContext } from "../../../../platform/framework/api/crud-service"; @@ -13,27 +14,50 @@ export class AdminDeleteUserController { } /** Begin or forward the deletion process of a user, if `deleteData` is false, only anonymises the user entry */ - async deleteUser(userId: string, deleteData: boolean): Promise<"failed" | "deleting" | "done"> { + async deleteUser( + userId: string, + deleteData: boolean, + username?: string, + ): Promise<{ status: "failed" | "deleting" | "done"; userId?: string }> { try { - if ( - await gr.services.users.anonymizeAndDelete( - { id: userId }, - { - user: { server_request: true }, - } as unknown as ExecutionContext, - deleteData, - ) - ) - return "done"; + let pk: UserPrimaryKey = { id: userId }; + if (username) { + pk = { username_canonical: username }; + } + + const data = await gr.services.users.anonymizeAndDelete( + pk, + { + user: { server_request: true }, + } as unknown as ExecutionContext, + deleteData, + ); + + if (data.isDeleted) + return { + status: "done", + userId: data.userId, + }; const existingUser = await (await this.getRepos()).user.findOne({ id: userId }); if (existingUser?.deleted) { - if (existingUser.delete_process_started_epoch > 0) return "deleting"; + if (existingUser.delete_process_started_epoch > 0) { + return { + status: "deleting", + userId: existingUser.id, + }; + } } } catch (err) { - adminLogger.error({ err, userId }, "User deletion error"); - return "failed"; + adminLogger.error("[DELETE USER] ", JSON.stringify({ err, userId }), "User deletion error"); + return { + status: "failed", + userId, + }; } - return "done"; + return { + status: "done", + userId, + }; } /** Get an array of 2 item arrays with `[ user IDs, delete_process_started_epoch ]` that are incompletely deleted */ diff --git a/tdrive/backend/node/src/core/platform/services/admin/web/delete-user-routes.ts b/tdrive/backend/node/src/core/platform/services/admin/web/delete-user-routes.ts index 13cc44b2..c5c451de 100644 --- a/tdrive/backend/node/src/core/platform/services/admin/web/delete-user-routes.ts +++ b/tdrive/backend/node/src/core/platform/services/admin/web/delete-user-routes.ts @@ -14,15 +14,19 @@ function authenticateAdminQuery(request: FastifyRequest, reply: FastifyReply) { return true; } -type TUserDeleteQueryBody = TQueryBody & { userId: string; deleteData: boolean }; +type TUserDeleteQueryBody = TQueryBody & { userId: string; deleteData: boolean; username?: string }; function getUserIfValidQuery(request: FastifyRequest, reply: FastifyReply) { if (!authenticateAdminQuery(request, reply)) return { userId: "", deleteData: false }; const body = request.body as TUserDeleteQueryBody; - if (!body.userId?.length) { + if (!body.userId?.length && !body.username) { reply.status(400).send(); - return { userId: "", deleteData: false }; + return { userId: "", username: "", deleteData: false }; } - return body; + return { + userId: body.userId || "", + username: body.username, + deleteData: body.deleteData, + }; } const routes: FastifyPluginCallback = async (fastify: FastifyInstance, _opts, next) => { @@ -31,13 +35,13 @@ const routes: FastifyPluginCallback = async (fastify: FastifyInstance, _opts, ne const controller = new AdminDeleteUserController(); if (config?.endpointSecret?.length) { fastify.post(urlRoot, async (request, reply) => { - const { userId, deleteData } = getUserIfValidQuery(request, reply); - if (!userId) return false; + const { userId, deleteData, username } = getUserIfValidQuery(request, reply); + if (!userId && !username) return false; if (userId == "e2e_simulate_timeout") return new Promise(() => { return; }); - return { status: await controller.deleteUser(userId, deleteData) }; + return await controller.deleteUser(userId, deleteData, username); }); fastify.post(`${urlRoot}/pending`, async (request, reply) => { diff --git a/tdrive/backend/node/src/services/user/entities/user.ts b/tdrive/backend/node/src/services/user/entities/user.ts index 94fb6c15..d928ef95 100644 --- a/tdrive/backend/node/src/services/user/entities/user.ts +++ b/tdrive/backend/node/src/services/user/entities/user.ts @@ -149,9 +149,12 @@ export type UserNotificationPreferences = { }; }; -export type UserPrimaryKey = Pick; +export type UserPrimaryKey = { + id?: uuid; + username_canonical?: string; +}; export function getInstance(user: Partial): User { - user.creation_date = !isNumber(user.creation_date) ? Date.now() : user.creation_date; + user.creation_date = !isNumber(user?.creation_date) ? Date.now() : user?.creation_date; return merge(new User(), user); } diff --git a/tdrive/backend/node/src/services/user/services/users/service.ts b/tdrive/backend/node/src/services/user/services/users/service.ts index 0755217f..a64647e0 100644 --- a/tdrive/backend/node/src/services/user/services/users/service.ts +++ b/tdrive/backend/node/src/services/user/services/users/service.ts @@ -185,7 +185,10 @@ export class UserServiceImpl { user: user, }); - return deleteData && (await gr.platformServices.admin.deleteUser(userCopy)); + return { + isDeleted: deleteData && (await gr.platformServices.admin.deleteUser(userCopy)), + userId: user.id, + }; } }