From 43552e89bd76722c3e8e4a1069635ff9b1eb97af Mon Sep 17 00:00:00 2001 From: Mikael Araya Date: Sat, 23 Nov 2024 21:02:08 +0300 Subject: [PATCH] Use explicit field value masking on delete user --- package-lock.json | 23 +++----- .../products/deleteUserProductReviews.ts | 2 +- .../resolvers/mutations/users/deleteUser.ts | 2 +- .../src/module/configureUsersModule.ts | 57 +++++++------------ packages/core-users/src/types.ts | 1 + 5 files changed, 32 insertions(+), 53 deletions(-) diff --git a/package-lock.json b/package-lock.json index df4979bb61..b5cb1cb61f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3216,6 +3216,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "dev": true, "license": "MIT", "dependencies": { "event-target-shim": "^5.0.0" @@ -6335,6 +6336,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -8063,20 +8065,6 @@ "node": ">=10" } }, - "node_modules/googleapis/node_modules/gcp-metadata": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-4.3.1.tgz", - "integrity": "sha512-x850LS5N7V1F3UcV7PoupzGsyD6iVwTVvsh3tbXfkctZnBnjW5yu5z1/3k3SehF7TyoTIe78rJs02GMMy+LF+A==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "gaxios": "^4.0.0", - "json-bigint": "^1.0.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/googleapis/node_modules/google-auth-library": { "version": "7.14.1", "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-7.14.1.tgz", @@ -16198,7 +16186,8 @@ "@unchainedshop/core-users": "^3.0.0-alpha4", "@unchainedshop/core-warehousing": "^3.0.0-alpha4", "@unchainedshop/core-worker": "^3.0.0-alpha4", - "@unchainedshop/logger": "^3.0.0-alpha4" + "@unchainedshop/logger": "^3.0.0-alpha4", + "@unchainedshop/utils": "^3.0.0-alpha4" }, "devDependencies": { "@types/node": "^22.9.1", @@ -16232,7 +16221,7 @@ "license": "EUPL-1.2", "dependencies": { "@unchainedshop/events": "^3.0.0-alpha4", - "@unchainedshop/utils": "^3.0.0-alpha4" + "@unchainedshop/mongodb": "^3.0.0-alpha4" }, "devDependencies": { "@types/node": "^22.9.1", @@ -16279,6 +16268,7 @@ "dependencies": { "@unchainedshop/events": "^3.0.0-alpha4", "@unchainedshop/logger": "^3.0.0-alpha4", + "@unchainedshop/mongodb": "^3.0.0-alpha4", "@unchainedshop/utils": "^3.0.0-alpha4" }, "devDependencies": { @@ -16460,6 +16450,7 @@ "@unchainedshop/events": "^3.0.0-alpha4", "@unchainedshop/file-upload": "^3.0.0-alpha4", "@unchainedshop/logger": "^3.0.0-alpha4", + "@unchainedshop/mongodb": "^3.0.0-alpha4", "@unchainedshop/roles": "^3.0.0-alpha4", "@unchainedshop/utils": "^3.0.0-alpha4", "bcryptjs": "^2.4.3", diff --git a/packages/api/src/resolvers/mutations/products/deleteUserProductReviews.ts b/packages/api/src/resolvers/mutations/products/deleteUserProductReviews.ts index 7a6069649a..fc30b9e85c 100644 --- a/packages/api/src/resolvers/mutations/products/deleteUserProductReviews.ts +++ b/packages/api/src/resolvers/mutations/products/deleteUserProductReviews.ts @@ -1,6 +1,6 @@ -import { Context } from '../../../types.js'; import { log } from '@unchainedshop/logger'; import { InvalidIdError, UserNotFoundError } from '../../../errors.js'; +import { Context } from '../../../context.js'; export default async function deleteUserProductReviews( root: never, diff --git a/packages/api/src/resolvers/mutations/users/deleteUser.ts b/packages/api/src/resolvers/mutations/users/deleteUser.ts index 99b779aabb..051b4775d0 100644 --- a/packages/api/src/resolvers/mutations/users/deleteUser.ts +++ b/packages/api/src/resolvers/mutations/users/deleteUser.ts @@ -1,6 +1,6 @@ import { log } from '@unchainedshop/logger'; -import { Context } from '../../../types.js'; import { InvalidIdError, UserNotFoundError } from '../../../errors.js'; +import { Context } from '../../../context.js'; const deleteUser = async (_, { userId }, context: Context) => { const { modules, userAgent, userId: currentUserId } = context; diff --git a/packages/core-users/src/module/configureUsersModule.ts b/packages/core-users/src/module/configureUsersModule.ts index 869433f629..b35698a145 100644 --- a/packages/core-users/src/module/configureUsersModule.ts +++ b/packages/core-users/src/module/configureUsersModule.ts @@ -15,42 +15,29 @@ import { userSettings, UserSettingsOptions } from '../users-settings.js'; import { configureUsersWebAuthnModule, UsersWebAuthnModule } from './configureUsersWebAuthnModule.js'; import * as pbkdf2 from './pbkdf2.js'; import * as sha256 from './sha256.js'; -import type { Address, Contact } from '@unchainedshop/mongodb'; import crypto from 'crypto'; import { UnchainedCore } from '@unchainedshop/core'; -import { UserServices } from '../users-index.js'; -import { FileServices, FilesModule } from '@unchainedshop/core-files'; +import { Context } from 'vm'; -const isDate = (value) => { - const date = new Date(value); - return !Number.isNaN(date.getTime()); -}; - -function maskString(value) { - if (isDate(value)) return value; - return crypto - .createHash('sha256') - .update(JSON.stringify([value, new Date().getTime()])) - .digest('hex'); -} - -const maskUserPropertyValues = (user) => { - if (typeof user !== 'object' || user === null) { - return user; +const maskUserPropertyValues = (user, deletedById: string): User => { + if (!user || typeof user !== 'object') { + throw new Error('Invalid user object'); } - if (Array.isArray(user)) { - return user.map((item) => maskUserPropertyValues(item)); - } - const maskedUser = {}; - Object.keys(user).forEach((key) => { - if (typeof user[key] === 'string' || isDate(user[key])) { - maskedUser[key] = maskString(user[key]); - } else { - maskedUser[key] = maskUserPropertyValues(user[key]); - } - }); - - return maskedUser; + return { + ...user, + username: `deleted-${Date.now()}`, + deleted: new Date(), + deletedBy: deletedById, + emails: null, + roles: null, + profile: null, + lastBillingAddress: {}, + services: {}, + pushSubscriptions: [], + avatarId: null, + initialPassword: null, + lastContact: null, + }; }; export type UsersModule = { @@ -147,6 +134,7 @@ const USER_EVENTS = [ 'USER_UPDATE_BILLING_ADDRESS', 'USER_UPDATE_LAST_CONTACT', 'USER_REMOVE', + 'USER_PURGE', ]; export const removeConfidentialServiceHashes = (rawUser: User): User => { const user = rawUser; @@ -848,11 +836,10 @@ export const configureUsersModule = async ({ {}, ); }, - deleteUser: async ({ userId }, context) => { + deleteUser: async ({ userId }, context: Context) => { const { modules } = context; const { _id, ...user } = await findUserById(userId); - delete user?.services; - const maskedUserData = maskUserPropertyValues({ ...user, meta: null }); + const maskedUserData = maskUserPropertyValues(user, context.userId); await modules.bookmarks.deleteByUserId(userId); await updateUser({ _id }, { $set: { ...maskedUserData, deleted: new Date() } }, {}); return true; diff --git a/packages/core-users/src/types.ts b/packages/core-users/src/types.ts index e45fb4ca98..c7bd08b5d9 100644 --- a/packages/core-users/src/types.ts +++ b/packages/core-users/src/types.ts @@ -71,6 +71,7 @@ export type User = { pushSubscriptions: Array; username?: string; meta?: any; + deletedBy?: string; } & TimestampFields; export type UserQuery = mongodb.Filter & {