mirror of
https://github.com/nodemailer/wildduck.git
synced 2025-10-17 09:16:22 +08:00
use new permissions system for users api
This commit is contained in:
parent
fffc259eb0
commit
3b645d39f3
2 changed files with 865 additions and 765 deletions
|
@ -10,6 +10,13 @@
|
|||
"authentication": {
|
||||
"create:any": ["*"],
|
||||
"read:any": ["*"]
|
||||
},
|
||||
|
||||
"users": {
|
||||
"create:any": ["*"],
|
||||
"read:any": ["*"],
|
||||
"update:any": ["*"],
|
||||
"delete:any": ["*"]
|
||||
}
|
||||
},
|
||||
|
||||
|
|
291
lib/api/users.js
291
lib/api/users.js
|
@ -10,8 +10,16 @@ const openpgp = require('openpgp');
|
|||
const addressparser = require('nodemailer/lib/addressparser');
|
||||
const libmime = require('libmime');
|
||||
const consts = require('../consts');
|
||||
const roles = require('../roles');
|
||||
const util = require('util');
|
||||
|
||||
module.exports = (db, server, userHandler) => {
|
||||
const createUser = util.promisify(userHandler.create.bind(userHandler));
|
||||
const updateUser = util.promisify(userHandler.update.bind(userHandler));
|
||||
const logoutUser = util.promisify(userHandler.logout.bind(userHandler));
|
||||
const resetUser = util.promisify(userHandler.reset.bind(userHandler));
|
||||
const deleteUser = util.promisify(userHandler.delete.bind(userHandler));
|
||||
|
||||
/**
|
||||
* @api {get} /users List registered Users
|
||||
* @apiName GetUsers
|
||||
|
@ -138,6 +146,9 @@ module.exports = (db, server, userHandler) => {
|
|||
return next();
|
||||
}
|
||||
|
||||
// permissions check
|
||||
req.validate(roles.can(req.role).readAny('users'));
|
||||
|
||||
let query = result.value.query;
|
||||
let limit = result.value.limit;
|
||||
let page = result.value.page;
|
||||
|
@ -361,7 +372,9 @@ module.exports = (db, server, userHandler) => {
|
|||
* "error": "This username already exists"
|
||||
* }
|
||||
*/
|
||||
server.post('/users', (req, res, next) => {
|
||||
server.post(
|
||||
'/users',
|
||||
tools.asyncifyJson(async (req, res, next) => {
|
||||
res.charSet('utf-8');
|
||||
|
||||
const schema = Joi.object().keys({
|
||||
|
@ -476,6 +489,9 @@ module.exports = (db, server, userHandler) => {
|
|||
return next();
|
||||
}
|
||||
|
||||
// permissions check
|
||||
req.validate(roles.can(req.role).createAny('users'));
|
||||
|
||||
let targets = result.value.targets;
|
||||
|
||||
if (targets) {
|
||||
|
@ -557,10 +573,20 @@ module.exports = (db, server, userHandler) => {
|
|||
return next();
|
||||
}
|
||||
|
||||
checkPubKey(result.value.pubKey)
|
||||
.then(() => {
|
||||
userHandler.create(result.value, (err, id) => {
|
||||
if (err) {
|
||||
try {
|
||||
await checkPubKey(result.value.pubKey);
|
||||
} catch (err) {
|
||||
res.json({
|
||||
error: 'PGP key validation failed. ' + err.message,
|
||||
code: 'InputValidationError'
|
||||
});
|
||||
return next();
|
||||
}
|
||||
|
||||
let id;
|
||||
try {
|
||||
id = await createUser(result.value);
|
||||
} catch (err) {
|
||||
res.json({
|
||||
error: err.message,
|
||||
code: err.code,
|
||||
|
@ -575,16 +601,8 @@ module.exports = (db, server, userHandler) => {
|
|||
});
|
||||
|
||||
return next();
|
||||
});
|
||||
})
|
||||
.catch(err => {
|
||||
res.json({
|
||||
error: 'PGP key validation failed. ' + err.message,
|
||||
code: 'InputValidationError'
|
||||
});
|
||||
return next();
|
||||
});
|
||||
});
|
||||
);
|
||||
|
||||
/**
|
||||
* @api {get} /users/resolve/:username Resolve ID for an username
|
||||
|
@ -619,7 +637,9 @@ module.exports = (db, server, userHandler) => {
|
|||
* "error": "This user does not exist"
|
||||
* }
|
||||
*/
|
||||
server.get('/users/resolve/:username', (req, res, next) => {
|
||||
server.get(
|
||||
'/users/resolve/:username',
|
||||
tools.asyncifyJson(async (req, res, next) => {
|
||||
res.charSet('utf-8');
|
||||
|
||||
const schema = Joi.object().keys({
|
||||
|
@ -644,9 +664,14 @@ module.exports = (db, server, userHandler) => {
|
|||
return next();
|
||||
}
|
||||
|
||||
// permissions check
|
||||
req.validate(roles.can(req.role).readAny('users'));
|
||||
|
||||
let username = result.value.username;
|
||||
|
||||
db.users.collection('users').findOne(
|
||||
let userData;
|
||||
try {
|
||||
userData = await db.users.collection('users').findOne(
|
||||
{
|
||||
unameview: username.replace(/\./g, '')
|
||||
},
|
||||
|
@ -654,15 +679,16 @@ module.exports = (db, server, userHandler) => {
|
|||
projection: {
|
||||
_id: true
|
||||
}
|
||||
},
|
||||
(err, userData) => {
|
||||
if (err) {
|
||||
}
|
||||
);
|
||||
} catch (err) {
|
||||
res.json({
|
||||
error: 'MongoDB Error: ' + err.message,
|
||||
code: 'InternalDatabaseError'
|
||||
});
|
||||
return next();
|
||||
}
|
||||
|
||||
if (!userData) {
|
||||
res.json({
|
||||
error: 'This user does not exist',
|
||||
|
@ -670,15 +696,15 @@ module.exports = (db, server, userHandler) => {
|
|||
});
|
||||
return next();
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
id: userData._id
|
||||
});
|
||||
|
||||
return next();
|
||||
}
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
/**
|
||||
* @api {get} /users/:id Request User information
|
||||
|
@ -793,7 +819,9 @@ module.exports = (db, server, userHandler) => {
|
|||
* "error": "This user does not exist"
|
||||
* }
|
||||
*/
|
||||
server.get('/users/:user', (req, res, next) => {
|
||||
server.get(
|
||||
'/users/:user',
|
||||
tools.asyncifyJson(async (req, res, next) => {
|
||||
res.charSet('utf-8');
|
||||
|
||||
const schema = Joi.object().keys({
|
||||
|
@ -817,20 +845,29 @@ module.exports = (db, server, userHandler) => {
|
|||
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);
|
||||
|
||||
db.users.collection('users').findOne(
|
||||
{
|
||||
let userData;
|
||||
|
||||
try {
|
||||
userData = await db.users.collection('users').findOne({
|
||||
_id: user
|
||||
},
|
||||
(err, userData) => {
|
||||
if (err) {
|
||||
});
|
||||
} catch (err) {
|
||||
res.json({
|
||||
error: 'MongoDB Error: ' + err.message,
|
||||
code: 'InternalDatabaseError'
|
||||
});
|
||||
return next();
|
||||
}
|
||||
|
||||
if (!userData) {
|
||||
res.json({
|
||||
error: 'This user does not exist',
|
||||
|
@ -839,7 +876,9 @@ module.exports = (db, server, userHandler) => {
|
|||
return next();
|
||||
}
|
||||
|
||||
db.redis
|
||||
let response;
|
||||
try {
|
||||
response = await db.redis
|
||||
.multi()
|
||||
// sending counters are stored in Redis
|
||||
|
||||
|
@ -867,8 +906,8 @@ module.exports = (db, server, userHandler) => {
|
|||
.get('pdw:' + userData._id.toString())
|
||||
.ttl('pdw:' + userData._id.toString())
|
||||
|
||||
.exec((err, result) => {
|
||||
if (err) {
|
||||
.exec();
|
||||
} catch (err) {
|
||||
// ignore
|
||||
errors.notify(err, { userId: user });
|
||||
}
|
||||
|
@ -876,28 +915,31 @@ module.exports = (db, server, userHandler) => {
|
|||
let recipients = Number(userData.recipients) || config.maxRecipients || consts.MAX_RECIPIENTS;
|
||||
let forwards = Number(userData.forwards) || config.maxForwards || consts.MAX_FORWARDS;
|
||||
|
||||
let recipientsSent = Number(result && result[0] && result[0][1]) || 0;
|
||||
let recipientsTtl = Number(result && result[1] && result[1][1]) || 0;
|
||||
let recipientsSent = Number(response && response[0] && response[0][1]) || 0;
|
||||
let recipientsTtl = Number(response && response[1] && response[1][1]) || 0;
|
||||
|
||||
let forwardsSent = Number(result && result[2] && result[2][1]) || 0;
|
||||
let forwardsTtl = Number(result && result[3] && result[3][1]) || 0;
|
||||
let forwardsSent = Number(response && response[2] && response[2][1]) || 0;
|
||||
let forwardsTtl = Number(response && response[3] && response[3][1]) || 0;
|
||||
|
||||
let received = Number(result && result[4] && result[4][1]) || 0;
|
||||
let receivedTtl = Number(result && result[5] && result[5][1]) || 0;
|
||||
let received = Number(response && response[4] && response[4][1]) || 0;
|
||||
let receivedTtl = Number(response && response[5] && response[5][1]) || 0;
|
||||
|
||||
let imapUpload = Number(result && result[6] && result[6][1]) || 0;
|
||||
let imapUploadTtl = Number(result && result[7] && result[7][1]) || 0;
|
||||
let imapUpload = Number(response && response[6] && response[6][1]) || 0;
|
||||
let imapUploadTtl = Number(response && response[7] && response[7][1]) || 0;
|
||||
|
||||
let imapDownload = Number(result && result[8] && result[8][1]) || 0;
|
||||
let imapDownloadTtl = Number(result && result[9] && result[9][1]) || 0;
|
||||
let imapDownload = Number(response && response[8] && response[8][1]) || 0;
|
||||
let imapDownloadTtl = Number(response && response[9] && response[9][1]) || 0;
|
||||
|
||||
let pop3Download = Number(result && result[10] && result[10][1]) || 0;
|
||||
let pop3DownloadTtl = Number(result && result[11] && result[11][1]) || 0;
|
||||
let pop3Download = Number(response && response[10] && response[10][1]) || 0;
|
||||
let pop3DownloadTtl = Number(response && response[11] && response[11][1]) || 0;
|
||||
|
||||
getKeyInfo(userData.pubKey).then(keyInfo => {
|
||||
if (err) {
|
||||
let keyInfo;
|
||||
try {
|
||||
keyInfo = await getKeyInfo(userData.pubKey);
|
||||
} catch (err) {
|
||||
errors.notify(err, { userId: user, source: 'pgp' });
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
id: user,
|
||||
|
@ -970,11 +1012,8 @@ module.exports = (db, server, userHandler) => {
|
|||
});
|
||||
|
||||
return next();
|
||||
});
|
||||
});
|
||||
}
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
/**
|
||||
* @api {put} /users/:id Update User information
|
||||
|
@ -1033,7 +1072,9 @@ module.exports = (db, server, userHandler) => {
|
|||
* "error": "This user does not exist"
|
||||
* }
|
||||
*/
|
||||
server.put('/users/:user', (req, res, next) => {
|
||||
server.put(
|
||||
'/users/:user',
|
||||
tools.asyncifyJson(async (req, res, next) => {
|
||||
res.charSet('utf-8');
|
||||
|
||||
const schema = Joi.object().keys({
|
||||
|
@ -1130,6 +1171,13 @@ module.exports = (db, server, userHandler) => {
|
|||
return next();
|
||||
}
|
||||
|
||||
// permissions check
|
||||
if (req.user && req.user === result.value.user) {
|
||||
req.validate(roles.can(req.role).updateOwn('users'));
|
||||
} else {
|
||||
req.validate(roles.can(req.role).updateAny('users'));
|
||||
}
|
||||
|
||||
let user = new ObjectID(result.value.user);
|
||||
|
||||
let targets = result.value.targets;
|
||||
|
@ -1192,30 +1240,33 @@ module.exports = (db, server, userHandler) => {
|
|||
result.value.tagsview = tags.map(tag => tag.toLowerCase());
|
||||
}
|
||||
|
||||
checkPubKey(result.value.pubKey)
|
||||
.then(() => {
|
||||
userHandler.update(user, result.value, (err, success) => {
|
||||
if (err) {
|
||||
try {
|
||||
await checkPubKey(result.value.pubKey);
|
||||
} catch (err) {
|
||||
res.json({
|
||||
error: 'PGP key validation failed. ' + err.message,
|
||||
code: 'InputValidationError'
|
||||
});
|
||||
return next();
|
||||
}
|
||||
|
||||
let success;
|
||||
try {
|
||||
success = await updateUser(user, result.value);
|
||||
} catch (err) {
|
||||
res.json({
|
||||
error: err.message,
|
||||
code: err.code
|
||||
});
|
||||
return next();
|
||||
}
|
||||
|
||||
res.json({
|
||||
success
|
||||
});
|
||||
return next();
|
||||
});
|
||||
})
|
||||
.catch(err => {
|
||||
res.json({
|
||||
error: 'PGP key validation failed. ' + err.message,
|
||||
code: 'InputValidationError'
|
||||
});
|
||||
return next();
|
||||
});
|
||||
});
|
||||
);
|
||||
|
||||
/**
|
||||
* @api {put} /users/:id/logout Log out User
|
||||
|
@ -1254,7 +1305,9 @@ module.exports = (db, server, userHandler) => {
|
|||
* "error": "This user does not exist"
|
||||
* }
|
||||
*/
|
||||
server.put('/users/:user/logout', (req, res, next) => {
|
||||
server.put(
|
||||
'/users/:user/logout',
|
||||
tools.asyncifyJson(async (req, res, next) => {
|
||||
res.charSet('utf-8');
|
||||
|
||||
const schema = Joi.object().keys({
|
||||
|
@ -1286,20 +1339,30 @@ module.exports = (db, server, userHandler) => {
|
|||
return next();
|
||||
}
|
||||
|
||||
userHandler.logout(result.value.user, result.value.reason || 'Logout requested from API', (err, success) => {
|
||||
if (err) {
|
||||
// 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 success;
|
||||
try {
|
||||
success = await logoutUser(result.value.user, result.value.reason || 'Logout requested from API');
|
||||
} catch (err) {
|
||||
res.json({
|
||||
error: err.message,
|
||||
code: err.code
|
||||
});
|
||||
return next();
|
||||
}
|
||||
|
||||
res.json({
|
||||
success
|
||||
});
|
||||
return next();
|
||||
});
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
/**
|
||||
* @api {post} /users/:id/quota/reset Recalculate User quota
|
||||
|
@ -1339,7 +1402,9 @@ module.exports = (db, server, userHandler) => {
|
|||
* "error": "This user does not exist"
|
||||
* }
|
||||
*/
|
||||
server.post('/users/:user/quota/reset', (req, res, next) => {
|
||||
server.post(
|
||||
'/users/:user/quota/reset',
|
||||
tools.asyncifyJson(async (req, res, next) => {
|
||||
res.charSet('utf-8');
|
||||
|
||||
const schema = Joi.object().keys({
|
||||
|
@ -1368,9 +1433,18 @@ module.exports = (db, server, userHandler) => {
|
|||
return next();
|
||||
}
|
||||
|
||||
// permissions check
|
||||
if (req.user && req.user === result.value.user) {
|
||||
req.validate(roles.can(req.role).updateOwn('users'));
|
||||
} else {
|
||||
req.validate(roles.can(req.role).updateAny('users'));
|
||||
}
|
||||
|
||||
let user = new ObjectID(result.value.user);
|
||||
|
||||
db.users.collection('users').findOne(
|
||||
let userData;
|
||||
try {
|
||||
userData = await db.users.collection('users').findOne(
|
||||
{
|
||||
_id: user
|
||||
},
|
||||
|
@ -1378,9 +1452,9 @@ module.exports = (db, server, userHandler) => {
|
|||
projection: {
|
||||
storageUsed: true
|
||||
}
|
||||
},
|
||||
(err, userData) => {
|
||||
if (err) {
|
||||
}
|
||||
);
|
||||
} catch (err) {
|
||||
res.json({
|
||||
error: 'MongoDB Error: ' + err.message,
|
||||
code: 'InternalDatabaseError'
|
||||
|
@ -1396,9 +1470,11 @@ module.exports = (db, server, userHandler) => {
|
|||
return next();
|
||||
}
|
||||
|
||||
let storageData;
|
||||
try {
|
||||
// calculate mailbox size by aggregating the size's of all messages
|
||||
// NB! Scattered query
|
||||
db.database
|
||||
storageData = db.database
|
||||
.collection('messages')
|
||||
.aggregate(
|
||||
[
|
||||
|
@ -1424,8 +1500,8 @@ module.exports = (db, server, userHandler) => {
|
|||
}
|
||||
}
|
||||
)
|
||||
.toArray((err, result) => {
|
||||
if (err) {
|
||||
.toArray();
|
||||
} catch (err) {
|
||||
res.json({
|
||||
error: 'MongoDB Error: ' + err.message,
|
||||
code: 'InternalDatabaseError'
|
||||
|
@ -1433,10 +1509,12 @@ module.exports = (db, server, userHandler) => {
|
|||
return next();
|
||||
}
|
||||
|
||||
let storageUsed = (result && result[0] && result[0].storageUsed) || 0;
|
||||
let storageUsed = (storageData && storageData[0] && storageData[0].storageUsed) || 0;
|
||||
|
||||
let updateResponse;
|
||||
try {
|
||||
// update quota counter
|
||||
db.users.collection('users').findOneAndUpdate(
|
||||
updateResponse = await db.users.collection('users').findOneAndUpdate(
|
||||
{
|
||||
_id: userData._id
|
||||
},
|
||||
|
@ -1446,10 +1524,13 @@ module.exports = (db, server, userHandler) => {
|
|||
}
|
||||
},
|
||||
{
|
||||
returnOriginal: false
|
||||
},
|
||||
(err, result) => {
|
||||
if (err) {
|
||||
returnOriginal: false,
|
||||
projection: {
|
||||
storageUsed: true
|
||||
}
|
||||
}
|
||||
);
|
||||
} catch (err) {
|
||||
res.json({
|
||||
error: 'MongoDB Error: ' + err.message,
|
||||
code: 'InternalDatabaseError'
|
||||
|
@ -1457,7 +1538,7 @@ module.exports = (db, server, userHandler) => {
|
|||
return next();
|
||||
}
|
||||
|
||||
if (!result || !result.value) {
|
||||
if (!updateResponse || !updateResponse.value) {
|
||||
res.json({
|
||||
error: 'This user does not exist',
|
||||
code: 'UserNotFound'
|
||||
|
@ -1467,15 +1548,11 @@ module.exports = (db, server, userHandler) => {
|
|||
|
||||
res.json({
|
||||
success: true,
|
||||
storageUsed: Number(result.value.storageUsed) || 0
|
||||
storageUsed: Number(updateResponse.value.storageUsed) || 0
|
||||
});
|
||||
return next();
|
||||
}
|
||||
})
|
||||
);
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
/**
|
||||
* @api {post} /users/:id/password/reset Reset password for an User
|
||||
|
@ -1520,7 +1597,9 @@ module.exports = (db, server, userHandler) => {
|
|||
* "error": "This user does not exist"
|
||||
* }
|
||||
*/
|
||||
server.post('/users/:user/password/reset', (req, res, next) => {
|
||||
server.post(
|
||||
'/users/:user/password/reset',
|
||||
tools.asyncifyJson(async (req, res, next) => {
|
||||
res.charSet('utf-8');
|
||||
|
||||
const schema = Joi.object().keys({
|
||||
|
@ -1552,24 +1631,30 @@ module.exports = (db, server, userHandler) => {
|
|||
return next();
|
||||
}
|
||||
|
||||
// permissions check
|
||||
req.validate(roles.can(req.role).updateAny('users'));
|
||||
|
||||
let user = new ObjectID(result.value.user);
|
||||
|
||||
userHandler.reset(user, result.value, (err, password) => {
|
||||
if (err) {
|
||||
let password;
|
||||
try {
|
||||
password = await resetUser(user, result.value);
|
||||
} catch (err) {
|
||||
res.json({
|
||||
error: err.message,
|
||||
code: err.code
|
||||
});
|
||||
return next();
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
password,
|
||||
validAfter: result.value || new Date()
|
||||
validAfter: (result.value && result.value.validAfter) || new Date()
|
||||
});
|
||||
return next();
|
||||
});
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
/**
|
||||
* @api {delete} /users/:id Delete an User
|
||||
|
@ -1604,7 +1689,9 @@ module.exports = (db, server, userHandler) => {
|
|||
* "error": "This user does not exist"
|
||||
* }
|
||||
*/
|
||||
server.del('/users/:user', (req, res, next) => {
|
||||
server.del(
|
||||
'/users/:user',
|
||||
tools.asyncifyJson(async (req, res, next) => {
|
||||
res.charSet('utf-8');
|
||||
|
||||
const schema = Joi.object().keys({
|
||||
|
@ -1641,9 +1728,15 @@ module.exports = (db, server, userHandler) => {
|
|||
return next();
|
||||
}
|
||||
|
||||
// permissions check
|
||||
req.validate(roles.can(req.role).deleteAny('users'));
|
||||
|
||||
let user = new ObjectID(result.value.user);
|
||||
userHandler.delete(user, {}, (err, status) => {
|
||||
if (err) {
|
||||
|
||||
let status;
|
||||
try {
|
||||
status = await deleteUser(user, {});
|
||||
} catch (err) {
|
||||
res.json({
|
||||
error: err.message,
|
||||
code: err.code
|
||||
|
@ -1654,8 +1747,8 @@ module.exports = (db, server, userHandler) => {
|
|||
success: status
|
||||
});
|
||||
return next();
|
||||
});
|
||||
});
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
async function getKeyInfo(pubKey) {
|
||||
|
|
Loading…
Add table
Reference in a new issue