mirror of
https://github.com/nodemailer/wildduck.git
synced 2025-10-06 20:05:58 +08:00
added new API endpoint to get info about deleted users
This commit is contained in:
parent
c50c5097f0
commit
5536bc0f93
5 changed files with 162 additions and 2 deletions
|
@ -1,6 +1,6 @@
|
||||||
# If enabled then WildDuck exposes a LMTP interface for pushing messages to mail store
|
# If enabled then WildDuck exposes a LMTP interface for pushing messages to mail store
|
||||||
# NB! If you are using WildDuck plugin for Haraka then LMTP is not needed
|
# NB! If you are using WildDuck plugin for Haraka then LMTP is not needed
|
||||||
enabled=true
|
enabled=false
|
||||||
port=2424
|
port=2424
|
||||||
|
|
||||||
# by default bind to localhost only
|
# by default bind to localhost only
|
||||||
|
|
|
@ -2350,6 +2350,29 @@ paths:
|
||||||
type: string
|
type: string
|
||||||
|
|
||||||
'/users/{id}/restore':
|
'/users/{id}/restore':
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- Users
|
||||||
|
summary: Return recovery info for a deleted user
|
||||||
|
operationId: restoreUserInfo
|
||||||
|
parameters:
|
||||||
|
- name: sess
|
||||||
|
in: query
|
||||||
|
description: Session identifier for the logs
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
- name: ip
|
||||||
|
in: query
|
||||||
|
description: IP address for the logs
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Success
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/RecoverInfoResponse'
|
||||||
post:
|
post:
|
||||||
tags:
|
tags:
|
||||||
- Users
|
- Users
|
||||||
|
@ -4109,6 +4132,50 @@ components:
|
||||||
description: Unique ID (24 byte hex)
|
description: Unique ID (24 byte hex)
|
||||||
example: '609d201236d1d936948f23b1'
|
example: '609d201236d1d936948f23b1'
|
||||||
|
|
||||||
|
RecoverInfoResponse:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- success
|
||||||
|
- user
|
||||||
|
- username
|
||||||
|
- storageUsed
|
||||||
|
- tags
|
||||||
|
- deleted
|
||||||
|
- recoverableAddresses
|
||||||
|
properties:
|
||||||
|
success:
|
||||||
|
type: boolean
|
||||||
|
description: Indicates successful response
|
||||||
|
example: true
|
||||||
|
user:
|
||||||
|
type: string
|
||||||
|
description: ID of the deleted User
|
||||||
|
example: '609d201236d1d936948f23b1'
|
||||||
|
username:
|
||||||
|
type: string
|
||||||
|
description: Username of the User
|
||||||
|
example: andris
|
||||||
|
storageUsed:
|
||||||
|
type: number
|
||||||
|
description: Calculated quota usage for the user
|
||||||
|
example: 2423070
|
||||||
|
tags:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
description: List of tags associated with the User
|
||||||
|
example: ['domain:andrisreinman.com']
|
||||||
|
deleted:
|
||||||
|
type: string
|
||||||
|
description: Datestring of the time the user was deleted
|
||||||
|
format: date-time
|
||||||
|
recoverableAddresses:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
description: List of email addresses that can be restored
|
||||||
|
example: ['andris@andrisreinman.com']
|
||||||
|
|
||||||
GetAllowedDomainResponse:
|
GetAllowedDomainResponse:
|
||||||
required:
|
required:
|
||||||
- success
|
- success
|
||||||
|
|
|
@ -192,7 +192,6 @@ const acquireCert = async (domain, acmeOptions, certificateData, certHandler) =>
|
||||||
}
|
}
|
||||||
|
|
||||||
let lock = await getLock(domainOpLockKey, 10 * 60 * 1000, 3 * 60 * 1000);
|
let lock = await getLock(domainOpLockKey, 10 * 60 * 1000, 3 * 60 * 1000);
|
||||||
console.log(lock);
|
|
||||||
if (!lock.success) {
|
if (!lock.success) {
|
||||||
return certificateData;
|
return certificateData;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1401,6 +1401,66 @@ module.exports = (db, server, userHandler) => {
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
server.get(
|
||||||
|
'/users/:user/restore',
|
||||||
|
tools.asyncifyJson(async (req, res, next) => {
|
||||||
|
res.charSet('utf-8');
|
||||||
|
|
||||||
|
const schema = Joi.object().keys({
|
||||||
|
user: Joi.string().hex().lowercase().length(24).required(),
|
||||||
|
sess: sessSchema,
|
||||||
|
ip: sessIPSchema
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = schema.validate(req.params, {
|
||||||
|
abortEarly: false,
|
||||||
|
convert: true
|
||||||
|
});
|
||||||
|
|
||||||
|
if (result.error) {
|
||||||
|
res.status(400);
|
||||||
|
res.json({
|
||||||
|
error: result.error.message,
|
||||||
|
code: 'InputValidationError',
|
||||||
|
details: tools.validationErrors(result)
|
||||||
|
});
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
|
||||||
|
// permissions check
|
||||||
|
if (req.user && req.user === result.value.user) {
|
||||||
|
req.validate(roles.can(req.role).readOwn('users'));
|
||||||
|
} else {
|
||||||
|
req.validate(roles.can(req.role).readAny('users'));
|
||||||
|
}
|
||||||
|
|
||||||
|
let user = new ObjectID(result.value.user);
|
||||||
|
|
||||||
|
let userInfo;
|
||||||
|
try {
|
||||||
|
userInfo = await userHandler.restoreInfo(user);
|
||||||
|
} catch (err) {
|
||||||
|
res.status(err.responseCode || 500); // TODO: use response code specific status
|
||||||
|
res.json({
|
||||||
|
error: err.message,
|
||||||
|
code: err.code
|
||||||
|
});
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
|
||||||
|
res.json(
|
||||||
|
Object.assign(
|
||||||
|
{
|
||||||
|
success: !!userInfo
|
||||||
|
},
|
||||||
|
userInfo
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
return next();
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
server.post(
|
server.post(
|
||||||
'/users/:user/restore',
|
'/users/:user/restore',
|
||||||
tools.asyncifyJson(async (req, res, next) => {
|
tools.asyncifyJson(async (req, res, next) => {
|
||||||
|
|
|
@ -3551,6 +3551,40 @@ class UserHandler {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Return information about deleted user
|
||||||
|
async restoreInfo(user) {
|
||||||
|
let result = {
|
||||||
|
user: user.toString()
|
||||||
|
};
|
||||||
|
|
||||||
|
let existingAccount = await this.users.collection('deletedusers').findOne({ _id: user });
|
||||||
|
if (!existingAccount) {
|
||||||
|
let err = new Error('Delete account was not found');
|
||||||
|
err.responseCode = 404;
|
||||||
|
err.code = 'AccountNotFound';
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
|
||||||
|
result.username = existingAccount.username;
|
||||||
|
result.storageUsed = existingAccount.storageUsed;
|
||||||
|
result.tags = existingAccount.tags;
|
||||||
|
|
||||||
|
result.deleted = existingAccount.deleteInfo.deletedAt.toISOString();
|
||||||
|
|
||||||
|
// Step 2. restore addresses
|
||||||
|
let recoverableAddresses = [];
|
||||||
|
for (let address of existingAccount.deleteInfo.addresses || []) {
|
||||||
|
let existingAddress = await this.users.collection('addresses').findOne(address);
|
||||||
|
if (!existingAddress || existingAddress.user.equals(user)) {
|
||||||
|
recoverableAddresses.push(address.address);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result.recoverableAddresses = recoverableAddresses;
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
// This method restores a user that is queued for deletion
|
// This method restores a user that is queued for deletion
|
||||||
async restore(user, options) {
|
async restore(user, options) {
|
||||||
options = options || {};
|
options = options || {};
|
||||||
|
|
Loading…
Add table
Reference in a new issue