mirror of
https://github.com/nodemailer/wildduck.git
synced 2025-09-13 08:34:53 +08:00
updated user delete task
This commit is contained in:
parent
8074083f08
commit
e5f97ebd05
5 changed files with 182 additions and 70 deletions
|
@ -1344,9 +1344,9 @@ module.exports = (db, server, userHandler) => {
|
|||
|
||||
let user = new ObjectID(result.value.user);
|
||||
|
||||
let status;
|
||||
let task;
|
||||
try {
|
||||
status = await userHandler.delete(user, {});
|
||||
task = await userHandler.delete(user, {});
|
||||
} catch (err) {
|
||||
res.json({
|
||||
error: err.message,
|
||||
|
@ -1355,10 +1355,15 @@ module.exports = (db, server, userHandler) => {
|
|||
return next();
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: status,
|
||||
code: 'TaskScheduled'
|
||||
});
|
||||
res.json(
|
||||
Object.assign(
|
||||
{
|
||||
success: !!task,
|
||||
code: 'TaskScheduled'
|
||||
},
|
||||
task || {}
|
||||
)
|
||||
);
|
||||
return next();
|
||||
})
|
||||
);
|
||||
|
|
|
@ -24,7 +24,8 @@ module.exports = {
|
|||
ASP_DELETED: 'asp.deleted',
|
||||
USER_CREATED: 'user.created',
|
||||
USER_PASSWORD_CHANGED: 'user.password.changed',
|
||||
USER_DELETED: 'user.deleted',
|
||||
USER_DELETE_STARTED: 'user.delete.started',
|
||||
USER_DELETE_COMPLETED: 'user.delete.completed',
|
||||
AUTOREPLY_USER_ENABLED: 'autoreply.user.enabled',
|
||||
AUTOREPLY_USER_DISABLED: 'autoreply.user.disabled',
|
||||
MFA_TOTP_ENABLED: 'mfa.totp.enabled',
|
||||
|
|
|
@ -4,114 +4,213 @@ const log = require('npmlog');
|
|||
const db = require('../db');
|
||||
const consts = require('../consts');
|
||||
|
||||
const BATCH_SIZE = 200;
|
||||
const { publish, USER_DELETE_COMPLETED } = require('../events');
|
||||
|
||||
let run = async taskData => {
|
||||
let cursor = await db.users.collection('messages').find(
|
||||
{
|
||||
user: taskData.user,
|
||||
userDeleted: { $ne: true }
|
||||
},
|
||||
{
|
||||
projection: {
|
||||
_id: true
|
||||
}
|
||||
}
|
||||
);
|
||||
const BATCH_SIZE = 500;
|
||||
|
||||
const deleteMessages = async taskData => {
|
||||
let rdate = new Date(Date.now() + consts.DELETED_USER_MESSAGE_RETENTION).getTime();
|
||||
let lastId;
|
||||
|
||||
let messageData;
|
||||
let updateEntries = [];
|
||||
|
||||
let executeBatchUpdate = async () => {
|
||||
await db.database.collection('messages').bulkWrite(updateEntries, {
|
||||
ordered: false,
|
||||
w: 1
|
||||
});
|
||||
log.verbose('Tasks', 'task=user-delete id=%s user=%s message=%s', taskData._id, taskData.user, `Marked ${updateEntries.length} messages for deletion`);
|
||||
updateEntries = [];
|
||||
};
|
||||
let markedAsDeleted = 0;
|
||||
|
||||
try {
|
||||
while ((messageData = await cursor.next())) {
|
||||
updateEntries.push({
|
||||
updateOne: {
|
||||
filter: {
|
||||
_id: messageData._id
|
||||
let done = false;
|
||||
while (!done) {
|
||||
let query = {
|
||||
user: taskData.user,
|
||||
userDeleted: { $ne: true }
|
||||
};
|
||||
|
||||
if (lastId) {
|
||||
query._id = { $gt: lastId };
|
||||
}
|
||||
|
||||
let messages = await db.users
|
||||
.collection('messages')
|
||||
.find(query, {
|
||||
sort: { _id: 1 },
|
||||
projection: {
|
||||
_id: true
|
||||
},
|
||||
update: {
|
||||
$set: {
|
||||
exp: true,
|
||||
rdate,
|
||||
userDeleted: true
|
||||
limit: BATCH_SIZE
|
||||
})
|
||||
.toArray();
|
||||
if (!messages.length) {
|
||||
// all done
|
||||
done = true;
|
||||
break;
|
||||
}
|
||||
messages = messages.map(messageData => messageData._id);
|
||||
lastId = messages[messages.length - 1];
|
||||
|
||||
let updateEntries = [];
|
||||
messages.forEach(message => {
|
||||
updateEntries.push({
|
||||
updateOne: {
|
||||
filter: {
|
||||
_id: message
|
||||
},
|
||||
update: {
|
||||
$set: {
|
||||
exp: true,
|
||||
rdate,
|
||||
userDeleted: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
let bulkResult = await db.database.collection('messages').bulkWrite(updateEntries, {
|
||||
ordered: false,
|
||||
w: 1
|
||||
});
|
||||
|
||||
if (updateEntries.length >= BATCH_SIZE) {
|
||||
try {
|
||||
await executeBatchUpdate();
|
||||
} catch (err) {
|
||||
await cursor.close();
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
markedAsDeleted += (bulkResult && bulkResult.modifiedCount) || 0;
|
||||
}
|
||||
await cursor.close();
|
||||
} catch (err) {
|
||||
log.error('Tasks', 'task=user-delete id=%s user=%s message=%s error=%s', taskData._id, taskData.user, 'Failed to fetch messages', err.message);
|
||||
err.code = 'InternalDatabaseError';
|
||||
err.markedAsDeleted = markedAsDeleted;
|
||||
throw err;
|
||||
} finally {
|
||||
log.verbose('Tasks', 'task=user-delete id=%s user=%s message=%s', taskData._id, taskData.user, `Marked ${markedAsDeleted} messages for deletion`);
|
||||
}
|
||||
return markedAsDeleted;
|
||||
};
|
||||
|
||||
if (updateEntries.length) {
|
||||
await executeBatchUpdate();
|
||||
}
|
||||
const deleteRegistryAddresses = async taskData => {
|
||||
let lastId;
|
||||
|
||||
let deleted = 0;
|
||||
|
||||
try {
|
||||
await db.database.collection('mailboxes').deleteMany({ user: taskData.user });
|
||||
let done = false;
|
||||
while (!done) {
|
||||
let query = {
|
||||
user: taskData.user
|
||||
};
|
||||
|
||||
if (lastId) {
|
||||
query._id = { $gt: lastId };
|
||||
}
|
||||
|
||||
let addresses = await db.users
|
||||
.collection('addressregister')
|
||||
.find(query, {
|
||||
sort: { _id: 1 },
|
||||
projection: {
|
||||
_id: true
|
||||
},
|
||||
limit: BATCH_SIZE
|
||||
})
|
||||
.toArray();
|
||||
if (!addresses.length) {
|
||||
// all done
|
||||
done = true;
|
||||
break;
|
||||
}
|
||||
addresses = addresses.map(addresseData => addresseData._id);
|
||||
lastId = addresses[addresses.length - 1];
|
||||
|
||||
let updateEntries = [];
|
||||
addresses.forEach(message => {
|
||||
updateEntries.push({
|
||||
deleteOne: {
|
||||
filter: {
|
||||
_id: message
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
let bulkResult = await db.database.collection('messages').bulkWrite(updateEntries, {
|
||||
ordered: false,
|
||||
w: 1
|
||||
});
|
||||
|
||||
deleted += (bulkResult && bulkResult.deletedCount) || 0;
|
||||
}
|
||||
} catch (err) {
|
||||
err.deleted = deleted;
|
||||
throw err;
|
||||
} finally {
|
||||
log.verbose('Tasks', 'task=user-delete id=%s user=%s message=%s', taskData._id, taskData.user, `Deleted ${deleted} addresses from registry`);
|
||||
}
|
||||
return deleted;
|
||||
};
|
||||
|
||||
const run = async taskData => {
|
||||
let result = {};
|
||||
|
||||
try {
|
||||
let delRes = await db.database.collection('mailboxes').deleteMany({ user: taskData.user });
|
||||
result.mailboxes = { deleted: delRes.deletedCount };
|
||||
} catch (err) {
|
||||
log.error('Tasks', 'task=user-delete id=%s user=%s message=%s error=%s', taskData._id, taskData.user, 'Failed to delete mailboxes', err.message);
|
||||
err.code = 'InternalDatabaseError';
|
||||
result.mailboxes = { error: err.message };
|
||||
throw err;
|
||||
}
|
||||
|
||||
try {
|
||||
await db.users.collection('asps').deleteMany({ user: taskData.user });
|
||||
let delRes = await db.users.collection('asps').deleteMany({ user: taskData.user });
|
||||
result.asps = { deleted: delRes.deletedCount };
|
||||
} catch (err) {
|
||||
log.error('Tasks', 'task=user-delete id=%s user=%s message=%s error=%s', taskData._id, taskData.user, 'Failed to delete asps', err.message);
|
||||
err.code = 'InternalDatabaseError';
|
||||
result.asps = { error: err.message };
|
||||
throw err;
|
||||
}
|
||||
|
||||
try {
|
||||
await db.users.collection('filters').deleteMany({ user: taskData.user });
|
||||
let delRes = await db.users.collection('filters').deleteMany({ user: taskData.user });
|
||||
result.filters = { deleted: delRes.deletedCount };
|
||||
} catch (err) {
|
||||
log.error('Tasks', 'task=user-delete id=%s user=%s message=%s error=%s', taskData._id, taskData.user, 'Failed to delete filters', err.message);
|
||||
err.code = 'InternalDatabaseError';
|
||||
result.filters = { error: err.message };
|
||||
throw err;
|
||||
}
|
||||
|
||||
try {
|
||||
await db.users.collection('autoreplies').deleteMany({ user: taskData.user });
|
||||
let delRes = await db.users.collection('autoreplies').deleteMany({ user: taskData.user });
|
||||
result.autoreplies = { deleted: delRes.deletedCount };
|
||||
} catch (err) {
|
||||
log.error('Tasks', 'task=user-delete id=%s user=%s message=%s error=%s', taskData._id, taskData.user, 'Failed to delete autoreplies', err.message);
|
||||
err.code = 'InternalDatabaseError';
|
||||
result.autoreplies = { error: err.message };
|
||||
throw err;
|
||||
}
|
||||
|
||||
try {
|
||||
// Should this run in a batch instead? Might have quite a lot of addresses tracked
|
||||
await db.database.collection('addressregister').deleteMany({ user: taskData.user });
|
||||
let deleted = await deleteRegistryAddresses(taskData);
|
||||
result.addressregister = { deleted };
|
||||
} catch (err) {
|
||||
log.error('Tasks', 'task=user-delete id=%s user=%s message=%s error=%s', taskData._id, taskData.user, 'Failed to delete autoreplies', err.message);
|
||||
err.code = 'InternalDatabaseError';
|
||||
result.addressregister = { error: err.message, deleted: err.deleted };
|
||||
throw err;
|
||||
}
|
||||
|
||||
try {
|
||||
// mark messages for deletion
|
||||
let markedAsDeleted = await deleteMessages(taskData);
|
||||
result.messages = { deleted: markedAsDeleted };
|
||||
} catch (err) {
|
||||
log.error('Tasks', 'task=user-delete id=%s user=%s message=%s error=%s', taskData._id, taskData.user, 'Failed to fetch messages', err.message);
|
||||
err.code = 'InternalDatabaseError';
|
||||
result.messages = { error: err.message, deleted: err.markedAsDeleted };
|
||||
throw err;
|
||||
}
|
||||
|
||||
log.verbose('Tasks', 'task=user-delete id=%s user=%s message=%s', taskData._id, taskData.user, `Cleared user specific data`);
|
||||
|
||||
result.task = taskData._id.toString();
|
||||
|
||||
await publish(db.redis, {
|
||||
ev: USER_DELETE_COMPLETED,
|
||||
user: taskData.user,
|
||||
result
|
||||
});
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
|
|
|
@ -24,7 +24,7 @@ const {
|
|||
ASP_CREATED,
|
||||
ASP_DELETED,
|
||||
USER_CREATED,
|
||||
USER_DELETED,
|
||||
USER_DELETE_STARTED,
|
||||
MFA_TOTP_ENABLED,
|
||||
MFA_TOTP_DISABLED,
|
||||
MFA_CUSTOM_ENABLED,
|
||||
|
@ -3332,8 +3332,11 @@ class UserHandler {
|
|||
// ignore
|
||||
}
|
||||
|
||||
let result = {};
|
||||
|
||||
try {
|
||||
await this.users.collection('addresses').deleteMany({ user });
|
||||
let delRes = await this.users.collection('addresses').deleteMany({ user });
|
||||
result.addresses = { deleted: delRes.deletedCount };
|
||||
} catch (err) {
|
||||
log.error('USERDEL', 'Failed to delete addresses for id=%s error=%s', user, err.message);
|
||||
err.code = 'InternalDatabaseError';
|
||||
|
@ -3341,7 +3344,8 @@ class UserHandler {
|
|||
}
|
||||
|
||||
try {
|
||||
await this.users.collection('users').deleteOne({ _id: user });
|
||||
let delRes = await this.users.collection('users').deleteOne({ _id: user });
|
||||
result.users = { deleted: delRes.deletedCount };
|
||||
} catch (err) {
|
||||
log.error('USERDEL', 'Failed to delete user id=%s error=%s', user, err.message);
|
||||
err.code = 'InternalDatabaseError';
|
||||
|
@ -3350,7 +3354,7 @@ class UserHandler {
|
|||
|
||||
// set up a task to delete user messages
|
||||
let now = new Date();
|
||||
await this.database.collection('tasks').insertOne({
|
||||
let insRes = await this.database.collection('tasks').insertOne({
|
||||
task: 'user-delete',
|
||||
locked: false,
|
||||
lockedUntil: now,
|
||||
|
@ -3359,6 +3363,8 @@ class UserHandler {
|
|||
user
|
||||
});
|
||||
|
||||
result.task = insRes.insertedId && insRes.insertedId.toString();
|
||||
|
||||
try {
|
||||
await this.logAuthEvent(user, {
|
||||
action: 'delete user',
|
||||
|
@ -3371,11 +3377,12 @@ class UserHandler {
|
|||
}
|
||||
|
||||
await publish(this.redis, {
|
||||
ev: USER_DELETED,
|
||||
user
|
||||
ev: USER_DELETE_STARTED,
|
||||
user,
|
||||
result
|
||||
});
|
||||
|
||||
return true;
|
||||
return result;
|
||||
}
|
||||
|
||||
async pushDefaultMessages(userData, tags) {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "wildduck",
|
||||
"version": "1.32.1",
|
||||
"version": "1.32.2",
|
||||
"description": "IMAP/POP3 server built with Node.js and MongoDB",
|
||||
"main": "server.js",
|
||||
"scripts": {
|
||||
|
|
Loading…
Add table
Reference in a new issue