From 7580b376718d0cde592734e2374c8e92a49ea407 Mon Sep 17 00:00:00 2001 From: shepilov Date: Wed, 16 Apr 2025 18:38:28 +0200 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8New=20endpoint=20that=20is=20marking?= =?UTF-8?q?=20user=20for=20deletion?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .run/Run all e2e [MongoDB].run.xml | 1 + .../controller/delete-user-controller.ts | 20 ++++++++++++- .../services/admin/web/delete-user-routes.ts | 13 ++++++++ .../node/src/services/user/entities/user.ts | 4 +++ .../services/user/services/users/service.ts | 6 ++++ .../node/src/services/user/web/schemas.ts | 1 + .../node/src/services/user/web/types.ts | 1 + .../web/controllers/workspace-users.ts | 1 + tdrive/backend/node/src/utils/users.ts | 1 + tdrive/backend/node/test/e2e/setup/index.ts | 2 ++ .../test/e2e/users/users.deletion.spec.ts | 30 ++++++++++++++++--- 11 files changed, 75 insertions(+), 5 deletions(-) diff --git a/.run/Run all e2e [MongoDB].run.xml b/.run/Run all e2e [MongoDB].run.xml index 8464490f..3943c677 100644 --- a/.run/Run all e2e [MongoDB].run.xml +++ b/.run/Run all e2e [MongoDB].run.xml @@ -10,6 +10,7 @@ + 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 a70bb76a..e757e4e8 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,4 +1,3 @@ -import { UserPrimaryKey } from "src/services/user/entities/user"; import gr from "../../../../../services/global-resolver"; import type { ExecutionContext } from "../../../../platform/framework/api/crud-service"; @@ -13,6 +12,25 @@ export class AdminDeleteUserController { return this._repos; } + async markToDelete( + userId: string, + ): Promise<{ status: "failed" | "deleting" | "done"; userId?: string }> { + try { + await gr.services.console.getClient().userWasDeletedForceLogout(userId); + await gr.services.users.markToDeleted({ id: userId }); + } catch (err) { + adminLogger.error({ err, userId }, "[DELETE USER] Error dustin updating user "); + return { + status: "failed", + userId, + }; + } + return { + status: "done", + userId, + }; + } + /** Begin or forward the deletion process of a user, if `deleteData` is false, only anonymises the user entry */ async deleteUser( userId: string, 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 956bf8c1..9520d8f5 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 @@ -62,6 +62,19 @@ const routes: FastifyPluginCallback = async (fastify: FastifyInstance, _opts, ne return { id: userId }; }); + + fastify.post(`${urlRoot}/mark`, async (request, reply) => { + if (!authenticateAdminQuery(request, reply)) return false; + + const { email } = request.body as TUserGetIdByEmailBody; + const userId = await controller.getUserIdByEmail(email); + + if (!userId) { + return reply.status(404).send({ message: "User not found" }); + } + + return await controller.markToDelete(userId); + }); } next(); }; diff --git a/tdrive/backend/node/src/services/user/entities/user.ts b/tdrive/backend/node/src/services/user/entities/user.ts index 73081359..846ba9e2 100644 --- a/tdrive/backend/node/src/services/user/entities/user.ts +++ b/tdrive/backend/node/src/services/user/entities/user.ts @@ -75,6 +75,10 @@ export default class User { @Column("password", "string") password: string; + //just mark user as deleted not to be able to login + @Column("marked_to_delete", "tdrive_boolean") + marked_to_delete: boolean; + @Column("deleted", "tdrive_boolean") deleted: boolean; 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 cc83de3c..4c2c6a9c 100644 --- a/tdrive/backend/node/src/services/user/services/users/service.ts +++ b/tdrive/backend/node/src/services/user/services/users/service.ts @@ -201,6 +201,12 @@ export class UserServiceImpl { } } + async markToDeleted(pk: UserPrimaryKey) { + const user = await this.get(pk); + user.marked_to_delete = true; + await this.save(user); + } + async search( pagination: Pagination, options?: SearchUserOptions, diff --git a/tdrive/backend/node/src/services/user/web/schemas.ts b/tdrive/backend/node/src/services/user/web/schemas.ts index 976b8667..1ea9e87e 100644 --- a/tdrive/backend/node/src/services/user/web/schemas.ts +++ b/tdrive/backend/node/src/services/user/web/schemas.ts @@ -15,6 +15,7 @@ export const userObjectSchema = { last_name: { type: "string" }, created_at: { type: "number" }, deleted: { type: "boolean" }, + marked_to_delete: { type: "boolean" }, delete_process_started_epoch: { type: "number" }, status: { type: "string" }, diff --git a/tdrive/backend/node/src/services/user/web/types.ts b/tdrive/backend/node/src/services/user/web/types.ts index 88a64c6a..6d3e2ebd 100644 --- a/tdrive/backend/node/src/services/user/web/types.ts +++ b/tdrive/backend/node/src/services/user/web/types.ts @@ -56,6 +56,7 @@ export interface UserObject { full_name: string; created_at: number; deleted: boolean; + marked_to_delete: boolean; status: string; //Single string for the status last_activity: number; last_seen?: number; diff --git a/tdrive/backend/node/src/services/workspaces/web/controllers/workspace-users.ts b/tdrive/backend/node/src/services/workspaces/web/controllers/workspace-users.ts index bfc214ff..05acbf65 100644 --- a/tdrive/backend/node/src/services/workspaces/web/controllers/workspace-users.ts +++ b/tdrive/backend/node/src/services/workspaces/web/controllers/workspace-users.ts @@ -82,6 +82,7 @@ export class WorkspaceUsersCrudController full_name: [user.first_name, user.last_name].join(" "), created_at: user.creation_date, deleted: Boolean(user.deleted), + marked_to_delete: Boolean(user.marked_to_delete), status: user.status_icon, last_activity: user.last_activity, cache: { diff --git a/tdrive/backend/node/src/utils/users.ts b/tdrive/backend/node/src/utils/users.ts index 648ca88d..0a036960 100644 --- a/tdrive/backend/node/src/utils/users.ts +++ b/tdrive/backend/node/src/utils/users.ts @@ -30,6 +30,7 @@ export async function formatUser( full_name: [user.first_name, user.last_name].join(" "), created_at: user.creation_date, deleted: Boolean(user.deleted), + marked_to_delete: Boolean(user.marked_to_delete), delete_process_started_epoch: user.delete_process_started_epoch, status: user.status_icon, last_activity: user.last_activity, diff --git a/tdrive/backend/node/test/e2e/setup/index.ts b/tdrive/backend/node/test/e2e/setup/index.ts index e3f37239..8a31dd4c 100644 --- a/tdrive/backend/node/test/e2e/setup/index.ts +++ b/tdrive/backend/node/test/e2e/setup/index.ts @@ -30,9 +30,11 @@ type TokenPayload = { export type User = { id: string; + email?: string; first_name?: string; isWorkspaceModerator?: boolean; deleted?: boolean, + marked_to_delete?: boolean; delete_process_started_epoch?: number, preferences?: { language?: string; diff --git a/tdrive/backend/node/test/e2e/users/users.deletion.spec.ts b/tdrive/backend/node/test/e2e/users/users.deletion.spec.ts index c8ebb853..f49f9066 100644 --- a/tdrive/backend/node/test/e2e/users/users.deletion.spec.ts +++ b/tdrive/backend/node/test/e2e/users/users.deletion.spec.ts @@ -301,10 +301,32 @@ describe("The users deletion API", () => { }); it("should have no objects left other than user (and untestable files)", async () => { - expect(await (await Promise.all( - Object.entries(listByUserIn) - .flatMap(async ([key, fn]) => (await fn(myUserId)).getEntities().map(e => ({"": e.constructor.name, ...e}))) - )).flat()).toHaveLength(0); + expect((await Promise.all( + Object.entries(listByUserIn) + .flatMap(async ([_, fn]) => (await fn(myUserId)).getEntities().map(e => ({"": e.constructor.name, ...e}))) + )).flat()).toHaveLength(0); }); + + it("'existing user with to_delete flag' should return 404 if user is not found", async () => { + const secret = adminConfig.endpointSecret; + const email = "notexistingemail@test.com"; + const response = await platform!.app.inject({ + method: "POST", + url: `/admin/api/user/delete/mark`, + body: { secret, email }, + }); + expect(response.statusCode).toBe(404); + }); + + it("'existing user with to_delete flag' should return 200", async () => { + const user = await UserApi.getInstance(platform); + const response = await platform!.app.inject({ + method: "POST", + url: `/admin/api/user/delete/mark`, + body: { secret: adminConfig.endpointSecret, email: (await user.getUser()).email }, + }); + expect(response.statusCode).toBe(200); + }); + }); });