mirror of
https://github.com/nodemailer/wildduck.git
synced 2025-10-06 11:57:27 +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
|
// 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));
|
||||||
}
|
}
|
||||||
|
|
|
@ -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:
|
||||||
|
|
145
lib/api/users.js
145
lib/api/users.js
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue