This commit is contained in:
Andris Reinman 2018-08-14 22:45:18 +03:00
parent 196fa4be06
commit 29dfd6c55d
5 changed files with 86 additions and 42 deletions

View file

@ -2,7 +2,7 @@
mongo="mongodb://127.0.0.1:27017/wildduck"
# redis connection string to connect to a single master (see below for Sentinel example)
redis="redis://127.0.0.1:6379/3"
#redis="redis://127.0.0.1:6379/3"
# WildDuck allows using different kind of data in different databases
# If you do not provide a database config value, then main database connection
@ -26,6 +26,11 @@ sender="zone-mta"
#queued="mail"
[redis]
host="127.0.0.1"
port=6379
db=3
## Connect to Redis Sentinel instead of single master
# [redis]
# name="mymaster"

View file

@ -120,7 +120,16 @@ indexes:
name: user
key:
user: 1
_id: -1
_id: -1 # sort newer first
- collection: authlog
type: users # index applies to users database
index:
name: insert
key:
user: 1
created: 1
key: 1
- collection: authlog
type: users # index applies to users database
@ -152,7 +161,7 @@ indexes:
index:
name: user_hashed
key:
user: hashed
user: hashed # sharding
- collection: authlog
type: users # index applies to users database

View file

@ -74,7 +74,7 @@ module.exports.connect = callback => {
module.exports.redisConfig = tools.redisConfig(config.dbs.redis);
module.exports.redis = new Redis(module.exports.redisConfig);
return callback(null, module.exports.database);
module.exports.redis.connect(() => callback(null, module.exports.database));
});
});
});

View file

@ -323,6 +323,7 @@ class UserHandler {
return callback(null, false);
}
// first check if client IP is not used too much
this.rateLimitIP(meta, 0, (err, res) => {
if (err) {
err.code = 'InternalDatabaseError';
@ -330,6 +331,7 @@ class UserHandler {
}
if (!res.success) {
// too many failed attempts from this IP
return rateLimitResponse(res, callback);
}
@ -339,9 +341,8 @@ class UserHandler {
}
if (!query) {
meta.username = username;
meta.result = 'unknown';
return this.logAuthEvent(null, meta, () => callback(null, false));
// nothing to do here
return callback(null, false);
}
this.users.collection('users').findOne(
@ -364,41 +365,36 @@ class UserHandler {
}
if (!userData) {
if (query.unameview) {
meta.username = query.unameview;
} else {
meta.user = query._id;
}
meta.result = 'unknown';
return this.logAuthEvent(null, meta, () => {
// rate limit failed authentication attempts against non-existent users as well
let ustring = (query.unameview || query._id || '').toString();
this.rateLimit(ustring, meta, 1, (err, res) => {
if (err) {
err.code = 'InternalDatabaseError';
return callback(err);
}
if (!res.success) {
return rateLimitResponse(res, callback);
}
callback(null, false);
});
// rate limit failed authentication attempts against non-existent users as well
let ustring = (query.unameview || query._id || '').toString();
return this.rateLimit(ustring, meta, 1, (err, res) => {
if (err) {
err.code = 'InternalDatabaseError';
return callback(err);
}
if (!res.success) {
// does not really matter but respond with a rate limit error, not auth fail error
return rateLimitResponse(res, callback);
}
callback(null, false);
});
}
// check if there are not too many auth attemtp for that user
this.rateLimitUser(userData._id, 0, (err, res) => {
if (err) {
err.code = 'InternalDatabaseError';
return callback(err);
}
if (!res.success) {
// too many failed attempts for this user
return rateLimitResponse(res, callback);
}
if (userData.disabled) {
// disabled users can not log in
meta.result = 'disabled';
// TODO: should we send some specific error message?
return this.logAuthEvent(userData._id, meta, () => callback(null, false));
}
@ -589,6 +585,7 @@ class UserHandler {
if (err) {
// don't really care
}
let authEvent = r && r.insertedId;
this.rateLimitReleaseUser(userData._id, () => false);
this.users.collection('asps').findOneAndUpdate(
{
@ -597,7 +594,7 @@ class UserHandler {
{
$set: {
used: new Date(),
authEvent: r.insertedId
authEvent
}
},
() => {
@ -2701,24 +2698,57 @@ class UserHandler {
}
logAuthEvent(user, entry, callback) {
if (this.authlogExpireDays === false) {
// only log auth events if we have a valid user id and logging is not disabled
if (!user || !tools.isId(user) || this.authlogExpireDays === false) {
return callback();
}
if (user) {
entry.user = user;
} else {
entry.user = entry.user || new ObjectID('000000000000000000000000');
}
let now = new Date();
entry.user = typeof user === 'string' ? new ObjectID(user) : user;
entry.action = entry.action || 'authentication';
entry.created = new Date();
entry.created = now;
if (typeof this.authlogExpireDays === 'number' && this.authlogExpireDays !== 0) {
// this entry expires in set days
entry.expires = new Date(Date.now() + Math.abs(this.authlogExpireDays) * 24 * 3600 * 1000);
}
return this.users.collection('authlog').insertOne(entry, callback);
// key is for merging similar events
entry.key = crypto
.createHash('md5')
.update([entry.protocol, entry.ip, entry.action, entry.result].map(v => (v || '').toString()).join('^'))
.digest();
return this.users.collection('authlog').findOneAndUpdate(
{
user: entry.user,
created: {
$gte: new Date(Date.now() - 3600 * 1000)
},
key: entry.key
},
{
$setOnInsert: entry,
$inc: {
events: 1
},
$set: {
last: now
}
},
{
upsert: true,
projection: { _id: true },
returnOriginal: false
},
(err, r) => {
if (err) {
return callback(err);
}
return callback(null, r && r.value && r.value._id);
}
);
}
logout(user, reason, callback) {

View file

@ -1,6 +1,6 @@
{
"name": "wildduck",
"version": "1.3.0",
"version": "1.4.0",
"description": "IMAP/POP3 server built with Node.js and MongoDB",
"main": "server.js",
"scripts": {
@ -30,7 +30,7 @@
"mailparser": "2.3.2",
"markdown-toc": "1.2.0",
"mocha": "5.2.0",
"request": "2.87.0"
"request": "2.88.0"
},
"dependencies": {
"base32.js": "^0.1.0",
@ -41,10 +41,10 @@
"html-to-text": "4.0.0",
"humanname": "0.2.2",
"iconv-lite": "0.4.23",
"ioredfour": "1.0.2-ioredis",
"ioredis": "3.2.2",
"ioredfour": "1.0.2-ioredis-02",
"ioredis": "4.0.0",
"isemail": "3.1.3",
"joi": "13.5.2",
"joi": "13.6.0",
"js-yaml": "3.12.0",
"key-fingerprint": "1.1.0",
"libbase64": "1.0.3",
@ -54,7 +54,7 @@
"mailsplit": "4.2.3",
"mobileconfig": "2.1.0",
"mongo-cursor-pagination": "^7.1.0",
"mongodb": "3.1.1",
"mongodb": "3.1.3",
"mongodb-extended-json": "1.10.0",
"node-forge": "0.7.5",
"nodemailer": "4.6.7",