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 // delete all attachments that do not have any active links to message objects
return messageHandler.attachmentStorage.deleteOrphaned(() => done(null, true)); return messageHandler.attachmentStorage.deleteOrphaned(() => done(null, true));
} }

View file

@ -53,9 +53,15 @@ indexes:
key: key:
ns: 1 ns: 1
unameview: 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 # Indexes for the addresses collection
- collection: addresses - collection: addresses
type: users # index applies to users database type: users # index applies to users database
index: index:

View file

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

View file

@ -519,7 +519,8 @@ class UserHandler {
// Users with an empty password can not log in // Users with an empty password can not log in
let hash = data.password ? bcrypt.hashSync(data.password, consts.BCRYPT_ROUNDS) : ''; let hash = data.password ? bcrypt.hashSync(data.password, consts.BCRYPT_ROUNDS) : '';
let id = new ObjectID(); let id = new ObjectID();
this.users.collection('users').insertOne({
userData = {
_id: id, _id: id,
username: data.username, username: data.username,
@ -564,7 +565,13 @@ class UserHandler {
// until setup value is not true, this account is not usable // until setup value is not true, this account is not usable
activated: false, activated: false,
disabled: true disabled: true
}, err => { };
if (data.tags && data.tags.length) {
userData.tags = data.tags;
}
this.users.collection('users').insertOne(userData, err => {
if (err) { if (err) {
log.error('DB', 'CREATEFAIL username=%s error=%s', data.username, err.message); 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({ let password = generatePassword.generate({
length: 12, length: 12,
uppercase: true, uppercase: true,
@ -804,7 +811,15 @@ class UserHandler {
return callback(new Error('Could not update user ' + user)); 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", "linkify-it": "2.0.3",
"mailsplit": "4.0.2", "mailsplit": "4.0.2",
"mobileconfig": "2.1.0", "mobileconfig": "2.1.0",
"mongo-cursor-pagination": "5.0.0", "mongo-cursor-pagination": "6.0.0",
"mongodb": "2.2.33", "mongodb": "2.2.33",
"nodemailer": "4.3.1", "nodemailer": "4.3.1",
"npmlog": "4.1.2", "npmlog": "4.1.2",
"openpgp": "2.5.12", "openpgp": "2.5.12",
"qrcode": "0.9.0", "qrcode": "0.9.0",
"restify": "6.2.3", "restify": "6.3.0",
"seq-index": "1.1.0", "seq-index": "1.1.0",
"smtp-server": "3.3.0", "smtp-server": "3.3.0",
"speakeasy": "2.0.0", "speakeasy": "2.0.0",
@ -65,7 +65,7 @@
"url": "git://github.com/wildduck-email/wildduck.git" "url": "git://github.com/wildduck-email/wildduck.git"
}, },
"optionalDependencies": { "optionalDependencies": {
"@ronomon/crypto-async": "2.2.0", "@ronomon/crypto-async": "2.2.1",
"modern-syslog": "1.1.4" "modern-syslog": "1.1.4"
} }
} }