mirror of
https://github.com/nodemailer/wildduck.git
synced 2024-12-27 18:58:54 +08:00
support tags
This commit is contained in:
parent
98a38fab04
commit
d19e9b062e
5 changed files with 154 additions and 30 deletions
2
imap.js
2
imap.js
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -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:
|
||||
|
|
145
lib/api/users.js
145
lib/api/users.js
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue