🚫👤feat: delete user from UI (#1526)

* initial commit

* fix: UserController bugs; fix: lint errors

* fix: delete files

* language support

* style(DeleteAccount): update to the latest style

* style: fix after merge main

* chore: Add canDeleteAccount middleware for user deletion endpoint

* chore: renamed to ALLOW_ACCOUNT_DELETION

* fix(canDeleteAccount): use uppercase admin role

* chore: imports order

* chore: Enable account deletion by default if omitted/commented out

* chore: Add logging for user account deletion

* chore: Bump data-provider package version to 0.6.6

* chore: Import Transaction model in UserController

* chore: Update CONFIG_VERSION to 1.1.4

* chore: Update user account deletion logging

* chore: Refactor user account deletion logic

---------

Co-authored-by: Berry-13 <root@Berry>
Co-authored-by: Danny Avila <messagedaniel@protonmail.com>
Co-authored-by: Danny Avila <danny@librechat.ai>
This commit is contained in:
Marco Beretta 2024-06-06 01:35:12 +02:00 committed by GitHub
parent f69b317171
commit a7f5b57272
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
19 changed files with 348 additions and 17 deletions

View file

@ -1,5 +1,15 @@
const { updateUserPluginsService } = require('~/server/services/UserService');
const {
User,
Session,
Balance,
deleteFiles,
deleteConvos,
deletePresets,
deleteMessages,
} = require('~/models');
const { updateUserPluginAuth, deleteUserPluginAuth } = require('~/server/services/PluginService');
const { updateUserPluginsService, deleteUserKey } = require('~/server/services/UserService');
const { Transaction } = require('~/models/Transaction');
const { logger } = require('~/config');
const getUserController = async (req, res) => {
@ -53,7 +63,30 @@ const updateUserPluginsController = async (req, res) => {
}
};
const deleteUserController = async (req, res) => {
const { user } = req;
try {
await deleteMessages({ user: user.id }); // delete user messages
await Session.deleteMany({ user: user.id }); // delete user sessions
await Transaction.deleteMany({ user: user.id }); // delete user transactions
await deleteUserKey({ userId: user.id, all: true }); // delete user keys
await Balance.deleteMany({ user: user._id }); // delete user balances
await deletePresets(user.id); // delete user presets
await deleteConvos(user.id); // delete user convos
await deleteUserPluginAuth(user.id, null, true); // delete user plugin auth
await User.deleteOne({ _id: user.id }); // delete user
await deleteFiles(null, user.id); // delete user files
logger.info(`User deleted account. Email: ${user.email} ID: ${user.id}`);
res.status(200).send({ message: 'User deleted' });
} catch (err) {
logger.error('[deleteUserController]', err);
res.status(500).send({ message: err.message });
}
};
module.exports = {
getUserController,
updateUserPluginsController,
deleteUserController,
};

View file

@ -0,0 +1,27 @@
const { isEnabled } = require('~/server/utils');
const { logger } = require('~/config');
/**
* Checks if the user can delete their account
*
* @async
* @function
* @param {Object} req - Express request object
* @param {Object} res - Express response object
* @param {Function} next - Next middleware function
*
* @returns {Promise<function|Object>} - Returns a Promise which when resolved calls next middleware if the user can delete their account
*/
const canDeleteAccount = async (req, res, next = () => {}) => {
const { user } = req;
const { ALLOW_ACCOUNT_DELETION = true } = process.env;
if (user?.role === 'ADMIN' || isEnabled(ALLOW_ACCOUNT_DELETION)) {
return next();
} else {
logger.error(`[User] [Delete Account] [User cannot delete account] [User: ${user?.id}]`);
return res.status(403).send({ message: 'You do not have permission to delete this account' });
}
};
module.exports = canDeleteAccount;

View file

@ -20,6 +20,7 @@ const validateImageRequest = require('./validateImageRequest');
const moderateText = require('./moderateText');
const noIndex = require('./noIndex');
const importLimiters = require('./importLimiters');
const canDeleteAccount = require('./canDeleteAccount');
module.exports = {
...uploadLimiters,
@ -44,4 +45,5 @@ module.exports = {
noIndex,
...importLimiters,
checkDomainAllowed,
canDeleteAccount,
};

View file

@ -1,10 +1,16 @@
const express = require('express');
const requireJwtAuth = require('../middleware/requireJwtAuth');
const { getUserController, updateUserPluginsController } = require('../controllers/UserController');
const canDeleteAccount = require('../middleware/canDeleteAccount');
const {
getUserController,
updateUserPluginsController,
deleteUserController,
} = require('../controllers/UserController');
const router = express.Router();
router.get('/', requireJwtAuth, getUserController);
router.post('/plugins', requireJwtAuth, updateUserPluginsController);
router.delete('/delete', requireJwtAuth, canDeleteAccount, deleteUserController);
module.exports = router;

View file

@ -88,7 +88,17 @@ const updateUserPluginAuth = async (userId, authField, pluginKey, value) => {
}
};
const deleteUserPluginAuth = async (userId, authField) => {
const deleteUserPluginAuth = async (userId, authField, all = false) => {
if (all) {
try {
const response = await PluginAuth.deleteMany({ userId });
return response;
} catch (err) {
logger.error('[deleteUserPluginAuth]', err);
return err;
}
}
try {
return await PluginAuth.deleteOne({ userId, authField });
} catch (err) {