mirror of
https://github.com/nodemailer/wildduck.git
synced 2025-01-01 05:06:44 +08:00
compare passwords against haveibeenpwned
This commit is contained in:
parent
9c7da7cd6e
commit
2a7b3db231
7 changed files with 56 additions and 11 deletions
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -1 +1 @@
|
|||
define({
"name": "wildduck",
"version": "1.0.0",
"description": "WildDuck API docs",
"title": "WildDuck API",
"url": "https://api.wildduck.email",
"sampleUrl": false,
"defaultVersion": "0.0.0",
"apidoc": "0.3.0",
"generator": {
"name": "apidoc",
"time": "2018-09-11T12:11:47.264Z",
"url": "http://apidocjs.com",
"version": "0.17.6"
}
});
|
||||
define({
"name": "wildduck",
"version": "1.0.0",
"description": "WildDuck API docs",
"title": "WildDuck API",
"url": "https://api.wildduck.email",
"sampleUrl": false,
"defaultVersion": "0.0.0",
"apidoc": "0.3.0",
"generator": {
"name": "apidoc",
"time": "2018-09-12T10:00:20.826Z",
"url": "http://apidocjs.com",
"version": "0.17.6"
}
});
|
||||
|
|
|
@ -1 +1 @@
|
|||
{
"name": "wildduck",
"version": "1.0.0",
"description": "WildDuck API docs",
"title": "WildDuck API",
"url": "https://api.wildduck.email",
"sampleUrl": false,
"defaultVersion": "0.0.0",
"apidoc": "0.3.0",
"generator": {
"name": "apidoc",
"time": "2018-09-11T12:11:47.264Z",
"url": "http://apidocjs.com",
"version": "0.17.6"
}
}
|
||||
{
"name": "wildduck",
"version": "1.0.0",
"description": "WildDuck API docs",
"title": "WildDuck API",
"url": "https://api.wildduck.email",
"sampleUrl": false,
"defaultVersion": "0.0.0",
"apidoc": "0.3.0",
"generator": {
"name": "apidoc",
"time": "2018-09-12T10:00:20.826Z",
"url": "http://apidocjs.com",
"version": "0.17.6"
}
}
|
||||
|
|
|
@ -12,6 +12,7 @@ const libmime = require('libmime');
|
|||
const consts = require('../consts');
|
||||
const roles = require('../roles');
|
||||
const util = require('util');
|
||||
const pwnedpasswords = require('pwnedpasswords');
|
||||
|
||||
module.exports = (db, server, userHandler) => {
|
||||
const createUser = util.promisify(userHandler.create.bind(userHandler));
|
||||
|
@ -311,6 +312,7 @@ module.exports = (db, server, userHandler) => {
|
|||
* @apiParam {String} [name] Name of the User
|
||||
* @apiParam {String} password Password for the account. Set to boolean <code>false</code> to disable password usage
|
||||
* @apiParam {Boolean} [hashedPassword] If <code>true</code> then password is already hashed, so store as. Hash needs to be bcrypt <code>$2a</code>, <code>$2y</code> or <code>$2b</code>. Additionally md5 hashes <code>$1</code> are allowed but these are rehashed on first successful authentication
|
||||
* @apiParam {Boolean} [allowUnsafe=true] If <code>false</code> then validates provided passwords against Have I Been Pwned API
|
||||
* @apiParam {String} [address] Default email address for the User (autogenerated if not set)
|
||||
* @apiParam {Boolean} [emptyAddress] If true then do not autogenerate missing email address for the User. Only needed if you want to create an user account that does not have any email address associated
|
||||
* @apiParam {Boolean} [requirePasswordChange] If true then requires the user to change password, useful if password for the account was autogenerated
|
||||
|
@ -392,13 +394,17 @@ module.exports = (db, server, userHandler) => {
|
|||
.max(128)
|
||||
.required(),
|
||||
password: Joi.string()
|
||||
.allow(false)
|
||||
.max(256)
|
||||
.allow([false, ''])
|
||||
.required(),
|
||||
hashedPassword: Joi.boolean()
|
||||
.truthy(['Y', 'true', 'yes', 'on', '1', 1])
|
||||
.falsy(['N', 'false', 'no', 'off', '0', 0, ''])
|
||||
.default(false),
|
||||
allowUnsafe: Joi.boolean()
|
||||
.truthy(['Y', 'true', 'yes', 'on', '1', 1])
|
||||
.falsy(['N', 'false', 'no', 'off', '0', 0, ''])
|
||||
.default(true),
|
||||
|
||||
address: Joi.string().email(),
|
||||
emptyAddress: Joi.boolean()
|
||||
|
@ -499,6 +505,21 @@ module.exports = (db, server, userHandler) => {
|
|||
return next();
|
||||
}
|
||||
|
||||
if (result.value.password && !result.value.hashedPassword && !result.value.allowUnsafe) {
|
||||
try {
|
||||
let count = await pwnedpasswords(result.value.password);
|
||||
if (count) {
|
||||
res.json({
|
||||
error: 'Provided password is not secure',
|
||||
code: 'InsecurePasswordError'
|
||||
});
|
||||
return next();
|
||||
}
|
||||
} catch (E) {
|
||||
// ignore errors, soft check only
|
||||
}
|
||||
}
|
||||
|
||||
// permissions check
|
||||
req.validate(roles.can(req.role).createAny('users'));
|
||||
|
||||
|
@ -1050,6 +1071,7 @@ module.exports = (db, server, userHandler) => {
|
|||
* @apiParam {String} [existingPassword] If provided then validates against account password before applying any changes
|
||||
* @apiParam {String} [password] New password for the account. Set to boolean <code>false</code> to disable password usage
|
||||
* @apiParam {Boolean} [hashedPassword] If <code>true</code> then password is already hashed, so store as. Hash needs to be bcrypt <code>$2a</code>, <code>$2y</code> or <code>$2b</code>. Additionally md5 hashes <code>$1</code> are allowed but these are rehashed on first successful authentication
|
||||
* @apiParam {Boolean} [allowUnsafe=true] If <code>false</code> then validates provided passwords against Have I Been Pwned API
|
||||
* @apiParam {String[]} [tags] A list of tags associated with this user
|
||||
* @apiParam {Number} [retention] Default retention time in ms. Set to <code>0</code> to disable
|
||||
* @apiParam {Boolean} [encryptMessages] If <code>true</code> then received messages are encrypted
|
||||
|
@ -1110,9 +1132,16 @@ module.exports = (db, server, userHandler) => {
|
|||
.min(1)
|
||||
.max(256),
|
||||
password: Joi.string()
|
||||
.min(8)
|
||||
.max(256)
|
||||
.allow(false),
|
||||
.allow([false, '']),
|
||||
hashedPassword: Joi.boolean()
|
||||
.truthy(['Y', 'true', 'yes', 'on', '1', 1])
|
||||
.falsy(['N', 'false', 'no', 'off', '0', 0, ''])
|
||||
.default(false),
|
||||
allowUnsafe: Joi.boolean()
|
||||
.truthy(['Y', 'true', 'yes', 'on', '1', 1])
|
||||
.falsy(['N', 'false', 'no', 'off', '0', 0, ''])
|
||||
.default(true),
|
||||
|
||||
language: Joi.string()
|
||||
.min(2)
|
||||
|
@ -1199,6 +1228,21 @@ module.exports = (db, server, userHandler) => {
|
|||
req.validate(roles.can(req.role).updateAny('users'));
|
||||
}
|
||||
|
||||
if (result.value.password && !result.value.hashedPassword && !result.value.allowUnsafe) {
|
||||
try {
|
||||
let count = await pwnedpasswords(result.value.password);
|
||||
if (count) {
|
||||
res.json({
|
||||
error: 'Provided password is not secure',
|
||||
code: 'InsecurePasswordError'
|
||||
});
|
||||
return next();
|
||||
}
|
||||
} catch (E) {
|
||||
// ignore errors, soft check only
|
||||
}
|
||||
}
|
||||
|
||||
let user = new ObjectID(result.value.user);
|
||||
|
||||
let targets = result.value.targets;
|
||||
|
|
|
@ -1042,7 +1042,7 @@ class UserHandler {
|
|||
let hashPassword = done => {
|
||||
if (!data.password) {
|
||||
// Users with an empty password can not log in
|
||||
return done();
|
||||
return done(null, '');
|
||||
}
|
||||
|
||||
if (data.hashedPassword) {
|
||||
|
@ -2534,7 +2534,7 @@ class UserHandler {
|
|||
let flushKeys = [];
|
||||
|
||||
Object.keys(data).forEach(key => {
|
||||
if (['user', 'existingPassword', 'ip', 'sess'].includes(key)) {
|
||||
if (['user', 'existingPassword', 'hashedPassword', 'allowUnsafe', 'ip', 'sess'].includes(key)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -2543,7 +2543,7 @@ class UserHandler {
|
|||
}
|
||||
|
||||
if (key === 'password') {
|
||||
if (data[key] === false) {
|
||||
if (!data[key]) {
|
||||
// removes current password (if set)
|
||||
$set.password = '';
|
||||
} else {
|
||||
|
|
|
@ -64,7 +64,8 @@
|
|||
"nodemailer": "4.6.8",
|
||||
"npmlog": "4.1.2",
|
||||
"openpgp": "4.0.1",
|
||||
"pem": "1.12.6",
|
||||
"pem": "1.13.0",
|
||||
"pwnedpasswords": "1.0.4",
|
||||
"qrcode": "1.2.2",
|
||||
"restify": "7.2.1",
|
||||
"restify-logger": "2.0.1",
|
||||
|
|
Loading…
Reference in a new issue