support tags

This commit is contained in:
Andris Reinman 2017-11-03 14:11:59 +02:00
parent 98a38fab04
commit d19e9b062e
5 changed files with 154 additions and 30 deletions

View file

@ -125,7 +125,7 @@ function clearExpiredMessages() {
});
};
if (config.imap.disableRetention) {
if (!config.imap.disableRetention) {
// delete all attachments that do not have any active links to message objects
return messageHandler.attachmentStorage.deleteOrphaned(() => done(null, true));
}

View file

@ -53,9 +53,15 @@ indexes:
key:
ns: 1
unameview: 1
- collection: users
type: users # index applies to users database
index:
name: users
key:
tagsview: 1
sparse: true
# Indexes for the addresses collection
- collection: addresses
type: users # index applies to users database
index:

View file

@ -17,12 +17,16 @@ module.exports = (db, server, userHandler) => {
const schema = Joi.object().keys({
query: Joi.string()
.empty('')
.alphanum()
.lowercase()
.max(128),
onlyAddresses: Joi.boolean()
.truthy(['Y', 'true', 'yes', 1])
.default(false),
tags: Joi.string()
.trim()
.empty('')
.max(1024),
requiredTags: Joi.string()
.trim()
.empty('')
.max(1024),
limit: Joi.number()
.default(20)
.min(1)
@ -59,24 +63,60 @@ module.exports = (db, server, userHandler) => {
let filter = query
? {
address: {
$regex: query.replace(/\./g, ''),
$options: ''
}
$or: [
{
address: {
$regex: query.replace(/\./g, ''),
$options: ''
}
},
{
unameview: {
$regex: query.replace(/\./g, ''),
$options: ''
}
}
]
}
: {};
if (result.value.onlyAddresses) {
if (filter.address) {
filter.$and = [].concat(filter.$and || []).concat({ address: filter.address });
delete filter.address;
filter.$and.push({
address: { $ne: '' }
});
} else {
filter.address = { $ne: '' };
}
let tagSeen = new Set();
let requiredTags = (result.value.requiredTags || '')
.split(',')
.map(tag => tag.toLowerCase().trim())
.filter(tag => {
if (tag && !tagSeen.has(tag)) {
tagSeen.add(tag);
return true;
}
return false;
});
let tags = (result.value.tags || '')
.split(',')
.map(tag => tag.toLowerCase().trim())
.filter(tag => {
if (tag && !tagSeen.has(tag)) {
tagSeen.add(tag);
return true;
}
return false;
});
let tagsview = {};
if (requiredTags.length) {
tagsview.$all = requiredTags;
}
if (tags.length) {
tagsview.$in = tags;
}
if (requiredTags.length || tags.length) {
filter.tagsview = tagsview;
}
console.log(require('util').inspect(filter, false, 22));
db.users.collection('users').count(filter, (err, total) => {
if (err) {
@ -94,11 +134,14 @@ module.exports = (db, server, userHandler) => {
username: true,
name: true,
address: true,
tags: true,
storageUsed: true,
forward: true,
targetUrl: true,
quota: true,
activated: true,
disabled: true,
password: true,
encryptMessages: true,
encryptForwarded: true
},
@ -135,6 +178,7 @@ module.exports = (db, server, userHandler) => {
username: userData.username,
name: userData.name,
address: userData.address,
tags: userData.tags || [],
forward: userData.forward,
targetUrl: userData.targetUrl,
encryptMessages: !!userData.encryptMessages,
@ -143,6 +187,8 @@ module.exports = (db, server, userHandler) => {
allowed: Number(userData.quota) || config.maxStorage * 1024 * 1024,
used: Math.max(Number(userData.storageUsed) || 0, 0)
},
hasPasswordSet: !!userData.password,
activated: userData.activated,
disabled: userData.disabled
}))
};
@ -195,6 +241,12 @@ module.exports = (db, server, userHandler) => {
.min(0)
.default(0),
tags: Joi.array().items(
Joi.string()
.trim()
.max(128)
),
pubKey: Joi.string()
.empty('')
.trim()
@ -239,6 +291,23 @@ module.exports = (db, server, userHandler) => {
result.value.pubKey = '';
}
if (result.value.tags) {
let tagSeen = new Set();
let tags = result.value.tags
.map(tag => tag.trim())
.filter(tag => {
if (tag && !tagSeen.has(tag.toLowerCase())) {
tagSeen.add(tag.toLowerCase());
return true;
}
return false;
})
.sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()));
result.value.tags = tags;
result.value.tagsview = tags.map(tag => tag.toLowerCase());
}
checkPubKey(result.value.pubKey, err => {
if (err) {
res.json({
@ -368,6 +437,8 @@ module.exports = (db, server, userHandler) => {
}
},
tags: userData.tags || [],
hasPasswordSet: !!userData.password,
activated: userData.activated,
disabled: userData.disabled
});
@ -425,6 +496,12 @@ module.exports = (db, server, userHandler) => {
recipients: Joi.number().min(0),
forwards: Joi.number().min(0),
tags: Joi.array().items(
Joi.string()
.trim()
.max(128)
),
disabled: Joi.boolean()
.empty('')
.truthy(['Y', 'true', 'yes', 1]),
@ -473,6 +550,22 @@ module.exports = (db, server, userHandler) => {
result.value.pubKey = '';
}
if (result.value.tags) {
let tagSeen = new Set();
let tags = result.value.tags
.map(tag => tag.trim())
.filter(tag => {
if (tag && !tagSeen.has(tag.toLowerCase())) {
tagSeen.add(tag.toLowerCase());
return true;
}
return false;
})
.sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()));
result.value.tags = tags;
result.value.tagsview = tags.map(tag => tag.toLowerCase());
}
checkPubKey(result.value.pubKey, err => {
if (err) {
res.json({
@ -550,7 +643,12 @@ module.exports = (db, server, userHandler) => {
.hex()
.lowercase()
.length(24)
.required()
.required(),
sess: Joi.string().max(255),
ip: Joi.string().ip({
version: ['ipv4', 'ipv6'],
cidr: 'forbidden'
})
});
const result = Joi.validate(req.params, schema, {
@ -668,7 +766,12 @@ module.exports = (db, server, userHandler) => {
.hex()
.lowercase()
.length(24)
.required()
.required(),
sess: Joi.string().max(255),
ip: Joi.string().ip({
version: ['ipv4', 'ipv6'],
cidr: 'forbidden'
})
});
const result = Joi.validate(req.params, schema, {
@ -685,7 +788,7 @@ module.exports = (db, server, userHandler) => {
let user = new ObjectID(result.value.user);
userHandler.reset(user, (err, password) => {
userHandler.reset(user, result.value, (err, password) => {
if (err) {
res.json({
error: err.message

View file

@ -519,7 +519,8 @@ class UserHandler {
// Users with an empty password can not log in
let hash = data.password ? bcrypt.hashSync(data.password, consts.BCRYPT_ROUNDS) : '';
let id = new ObjectID();
this.users.collection('users').insertOne({
userData = {
_id: id,
username: data.username,
@ -564,7 +565,13 @@ class UserHandler {
// until setup value is not true, this account is not usable
activated: false,
disabled: true
}, err => {
};
if (data.tags && data.tags.length) {
userData.tags = data.tags;
}
this.users.collection('users').insertOne(userData, err => {
if (err) {
log.error('DB', 'CREATEFAIL username=%s error=%s', data.username, err.message);
@ -773,7 +780,7 @@ class UserHandler {
});
}
reset(user, callback) {
reset(user, data, callback) {
let password = generatePassword.generate({
length: 12,
uppercase: true,
@ -804,7 +811,15 @@ class UserHandler {
return callback(new Error('Could not update user ' + user));
}
return callback(null, password);
return this.logAuthEvent(
user,
{
action: 'reset',
sess: data.session,
ip: data.ip
},
() => callback(null, password)
);
});
}

View file

@ -44,13 +44,13 @@
"linkify-it": "2.0.3",
"mailsplit": "4.0.2",
"mobileconfig": "2.1.0",
"mongo-cursor-pagination": "5.0.0",
"mongo-cursor-pagination": "6.0.0",
"mongodb": "2.2.33",
"nodemailer": "4.3.1",
"npmlog": "4.1.2",
"openpgp": "2.5.12",
"qrcode": "0.9.0",
"restify": "6.2.3",
"restify": "6.3.0",
"seq-index": "1.1.0",
"smtp-server": "3.3.0",
"speakeasy": "2.0.0",
@ -65,7 +65,7 @@
"url": "git://github.com/wildduck-email/wildduck.git"
},
"optionalDependencies": {
"@ronomon/crypto-async": "2.2.0",
"@ronomon/crypto-async": "2.2.1",
"modern-syslog": "1.1.4"
}
}