mirror of
https://github.com/nodemailer/wildduck.git
synced 2024-12-27 18:58:54 +08:00
Reset existing sessions if password is updated
This commit is contained in:
parent
7fbf868b33
commit
7383276cda
4 changed files with 92 additions and 16 deletions
57
api.js
57
api.js
|
@ -17,6 +17,7 @@ const crypto = require('crypto');
|
|||
const Gelf = require('gelf');
|
||||
const os = require('os');
|
||||
const util = require('util');
|
||||
const ObjectID = require('mongodb').ObjectID;
|
||||
|
||||
const usersRoutes = require('./lib/api/users');
|
||||
const addressesRoutes = require('./lib/api/addresses');
|
||||
|
@ -229,15 +230,27 @@ server.use(
|
|||
}
|
||||
|
||||
if (tokenData && tokenData.user && tokenData.role && config.api.roles[tokenData.role]) {
|
||||
let signData;
|
||||
if ('authVersion' in tokenData) {
|
||||
// cast value to number
|
||||
tokenData.authVersion = Number(tokenData.authVersion) || 0;
|
||||
signData = {
|
||||
token: accessToken,
|
||||
user: tokenData.user,
|
||||
authVersion: tokenData.authVersion,
|
||||
role: tokenData.role
|
||||
};
|
||||
} else {
|
||||
signData = {
|
||||
token: accessToken,
|
||||
user: tokenData.user,
|
||||
role: tokenData.role
|
||||
};
|
||||
}
|
||||
|
||||
let signature = crypto
|
||||
.createHmac('sha256', config.api.accessControl.secret)
|
||||
.update(
|
||||
JSON.stringify({
|
||||
token: accessToken,
|
||||
user: tokenData.user,
|
||||
role: tokenData.role
|
||||
})
|
||||
)
|
||||
.update(JSON.stringify(signData))
|
||||
.digest('hex');
|
||||
|
||||
if (signature !== tokenData.s) {
|
||||
|
@ -268,9 +281,14 @@ server.use(
|
|||
req.role = tokenData.role;
|
||||
req.user = tokenData.user;
|
||||
|
||||
// make a reference to original method, otherwise might be overrided
|
||||
let setAuthToken = userHandler.setAuthToken.bind(userHandler);
|
||||
|
||||
req.accessToken = {
|
||||
hash: tokenHash,
|
||||
user: tokenData.user
|
||||
user: tokenData.user,
|
||||
// if called then refreshes token data for current hash
|
||||
update: async () => setAuthToken(tokenData.user, accessToken)
|
||||
};
|
||||
} else {
|
||||
// expired token, clear it
|
||||
|
@ -296,6 +314,29 @@ server.use(
|
|||
return fail();
|
||||
}
|
||||
|
||||
if (/^[0-9a-f]{24}$/i.test(req.user)) {
|
||||
let tokenAuthVersion = Number(tokenData.authVersion) || 0;
|
||||
let userData = await db.users.collection('users').findOne(
|
||||
{
|
||||
_id: new ObjectID(req.user)
|
||||
},
|
||||
{ projection: { authVersion: true } }
|
||||
);
|
||||
let userAuthVersion = Number(userData && userData.authVersion) || 0;
|
||||
if (!userData || tokenAuthVersion < userAuthVersion) {
|
||||
// unknown user or expired session
|
||||
try {
|
||||
await db.redis
|
||||
.multi()
|
||||
.del('tn:token:' + tokenHash)
|
||||
.exec();
|
||||
} catch (err) {
|
||||
// ignore
|
||||
}
|
||||
return fail();
|
||||
}
|
||||
}
|
||||
|
||||
return next();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1510,6 +1510,15 @@ module.exports = (db, server, userHandler) => {
|
|||
return next();
|
||||
}
|
||||
|
||||
if (success && result.value.password && req.accessToken && typeof req.accessToken.update === 'function') {
|
||||
try {
|
||||
// update access token data for current session after password change
|
||||
await req.accessToken.update();
|
||||
} catch (err) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
res.json({
|
||||
success
|
||||
});
|
||||
|
|
|
@ -1266,6 +1266,9 @@ class UserHandler {
|
|||
pendingSeed: '',
|
||||
pendingSeedChanged: false,
|
||||
|
||||
// incremented every time password is changed
|
||||
authVersion: 1,
|
||||
|
||||
// default email address
|
||||
address: '', // set this later
|
||||
|
||||
|
@ -2897,6 +2900,13 @@ class UserHandler {
|
|||
updateQuery.$push = $push;
|
||||
}
|
||||
|
||||
if (passwordChanged) {
|
||||
if (!updateQuery.$inc) {
|
||||
updateQuery.$inc = {};
|
||||
}
|
||||
updateQuery.$inc.authVersion = 1;
|
||||
}
|
||||
|
||||
let result;
|
||||
try {
|
||||
result = await this.users.collection('users').findOneAndUpdate(
|
||||
|
@ -2957,6 +2967,8 @@ class UserHandler {
|
|||
sess: data.sess,
|
||||
ip: data.ip
|
||||
});
|
||||
|
||||
await this.logout(user, 'User password was changed');
|
||||
} catch (err) {
|
||||
// ignore
|
||||
}
|
||||
|
@ -3238,8 +3250,7 @@ class UserHandler {
|
|||
};
|
||||
}
|
||||
|
||||
async generateAuthToken(user) {
|
||||
let accessToken = crypto.randomBytes(20).toString('hex');
|
||||
async setAuthToken(user, accessToken) {
|
||||
let tokenHash = crypto
|
||||
.createHash('sha256')
|
||||
.update(accessToken)
|
||||
|
@ -3247,11 +3258,20 @@ class UserHandler {
|
|||
let key = 'tn:token:' + tokenHash;
|
||||
let ttl = config.api.accessControl.tokenTTL || consts.ACCESS_TOKEN_DEFAULT_TTL;
|
||||
|
||||
let userData = await this.users.collection('users').findOne(
|
||||
{
|
||||
_id: new ObjectID(user)
|
||||
},
|
||||
{ projection: { authVersion: true } }
|
||||
);
|
||||
let authVersion = Number(userData && userData.authVersion) || 0;
|
||||
|
||||
let tokenData = {
|
||||
user: user.toString(),
|
||||
role: 'user',
|
||||
created: Date.now(),
|
||||
ttl,
|
||||
authVersion,
|
||||
// signature
|
||||
s: crypto
|
||||
.createHmac('sha256', config.api.accessControl.secret)
|
||||
|
@ -3259,6 +3279,7 @@ class UserHandler {
|
|||
JSON.stringify({
|
||||
token: accessToken,
|
||||
user: user.toString(),
|
||||
authVersion,
|
||||
role: 'user'
|
||||
})
|
||||
)
|
||||
|
@ -3273,6 +3294,11 @@ class UserHandler {
|
|||
|
||||
return accessToken;
|
||||
}
|
||||
|
||||
async generateAuthToken(user) {
|
||||
let accessToken = crypto.randomBytes(20).toString('hex');
|
||||
return await this.setAuthToken(user, accessToken);
|
||||
}
|
||||
}
|
||||
|
||||
function rateLimitResponse(res) {
|
||||
|
|
12
package.json
12
package.json
|
@ -19,9 +19,9 @@
|
|||
"apidoc": "0.17.7",
|
||||
"browserbox": "0.9.1",
|
||||
"chai": "4.2.0",
|
||||
"eslint": "6.1.0",
|
||||
"eslint": "6.2.2",
|
||||
"eslint-config-nodemailer": "1.2.0",
|
||||
"eslint-config-prettier": "6.0.0",
|
||||
"eslint-config-prettier": "6.1.0",
|
||||
"grunt": "1.0.4",
|
||||
"grunt-cli": "1.3.2",
|
||||
"grunt-eslint": "22.0.0",
|
||||
|
@ -56,12 +56,12 @@
|
|||
"mailsplit": "4.4.1",
|
||||
"mobileconfig": "2.3.1",
|
||||
"mongo-cursor-pagination": "7.1.0",
|
||||
"mongodb": "3.2.7",
|
||||
"mongodb": "3.3.1",
|
||||
"mongodb-extended-json": "1.10.1",
|
||||
"node-forge": "0.8.5",
|
||||
"nodemailer": "6.3.0",
|
||||
"npmlog": "4.1.2",
|
||||
"openpgp": "4.5.5",
|
||||
"openpgp": "4.6.0",
|
||||
"pem": "1.14.2",
|
||||
"pwnedpasswords": "1.0.4",
|
||||
"qrcode": "1.4.1",
|
||||
|
@ -72,9 +72,9 @@
|
|||
"speakeasy": "2.0.0",
|
||||
"u2f": "0.1.3",
|
||||
"utf7": "1.0.2",
|
||||
"uuid": "3.3.2",
|
||||
"uuid": "3.3.3",
|
||||
"wild-config": "1.4.0",
|
||||
"yargs": "13.3.0"
|
||||
"yargs": "14.0.0"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
|
Loading…
Reference in a new issue