mirror of
https://github.com/wassname/talk.git
synced 2026-07-03 04:55:04 +08:00
Replaced node_redis with ioredis
This commit is contained in:
+1
-1
@@ -114,6 +114,7 @@
|
||||
"immutability-helper": "^2.2.0",
|
||||
"imports-loader": "^0.7.1",
|
||||
"inquirer": "^3.2.2",
|
||||
"ioredis": "^3.1.4",
|
||||
"istanbul": "^1.1.0-alpha.1",
|
||||
"joi": "^10.6.0",
|
||||
"json-loader": "^0.5.7",
|
||||
@@ -162,7 +163,6 @@
|
||||
"react-toastify": "^1.5.0",
|
||||
"react-transition-group": "^1.1.3",
|
||||
"recompose": "^0.23.1",
|
||||
"redis": "^2.8.0",
|
||||
"redux": "^3.6.0",
|
||||
"redux-thunk": "^2.1.0",
|
||||
"resolve": "^1.4.0",
|
||||
|
||||
+64
-180
@@ -1,6 +1,5 @@
|
||||
const redis = require('./redis');
|
||||
const debug = require('debug')('talk:services:cache');
|
||||
const crypto = require('crypto');
|
||||
|
||||
const cache = module.exports = {};
|
||||
|
||||
@@ -52,60 +51,6 @@ cache.wrap = async (key, expiry, work, kf = keyfunc) => {
|
||||
return value;
|
||||
};
|
||||
|
||||
// This is designed to increment a key and add an expiry iff the key already
|
||||
// exists.
|
||||
const INCR_SCRIPT = `
|
||||
if redis.call('GET', KEYS[1]) ~= false then
|
||||
redis.call('INCR', KEYS[1])
|
||||
redis.call('EXPIRE', KEYS[1], ARGV[1])
|
||||
end
|
||||
`;
|
||||
|
||||
// This is designed to decrement a key and add an expiry iff the key already
|
||||
// exists.
|
||||
const DECR_SCRIPT = `
|
||||
if redis.call('GET', KEYS[1]) ~= false then
|
||||
redis.call('DECR', KEYS[1])
|
||||
redis.call('EXPIRE', KEYS[1], ARGV[1])
|
||||
end
|
||||
`;
|
||||
|
||||
// Load the script into redis and track the script hash that we will use to exec
|
||||
// increments on.
|
||||
const loadScript = (name, script) => new Promise((resolve, reject) => {
|
||||
|
||||
let shasum = crypto.createHash('sha1');
|
||||
shasum.update(script);
|
||||
|
||||
let hash = shasum.digest('hex');
|
||||
|
||||
cache.client
|
||||
.script('EXISTS', hash, (err, [exists]) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
|
||||
if (exists) {
|
||||
debug(`already loaded ${name} as SHA[${hash}], not loading again`);
|
||||
|
||||
return resolve(hash);
|
||||
}
|
||||
|
||||
debug(`${name} not loaded as SHA[${hash}], loading`);
|
||||
|
||||
cache.client
|
||||
.script('load', script, (err, hash) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
|
||||
debug(`loaded ${name} as SHA[${hash}]`);
|
||||
|
||||
resolve(hash);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Init sets up the scripts used in Redis with the incr/decr commands.
|
||||
*/
|
||||
@@ -114,93 +59,77 @@ cache.init = async () => {
|
||||
// Create the redis instance.
|
||||
cache.client = redis.createClient();
|
||||
|
||||
// Load the INCR_SCRIPT and DECR_SCRIPT into Redis.
|
||||
let [incrScriptHash, decrScriptHash] = await Promise.all([
|
||||
loadScript('INCR_SCRIPT', INCR_SCRIPT),
|
||||
loadScript('DECR_SCRIPT', DECR_SCRIPT)
|
||||
]);
|
||||
// This is designed to increment a key and add an expiry iff the key already
|
||||
// exists.
|
||||
const INCR_SCRIPT = `
|
||||
if redis.call('GET', KEYS[1]) ~= false then
|
||||
redis.call('INCR', KEYS[1])
|
||||
redis.call('EXPIRE', KEYS[1], ARGV[1])
|
||||
end
|
||||
`;
|
||||
|
||||
// Set the globally scoped cache hashes.
|
||||
cache.INCR_SCRIPT_HASH = incrScriptHash;
|
||||
cache.DECR_SCRIPT_HASH = decrScriptHash;
|
||||
cache.client.defineCommand('increx', {
|
||||
numberOfKeys: 1,
|
||||
lua: INCR_SCRIPT,
|
||||
});
|
||||
|
||||
// This is designed to decrement a key and add an expiry iff the key already
|
||||
// exists.
|
||||
const DECR_SCRIPT = `
|
||||
if redis.call('GET', KEYS[1]) ~= false then
|
||||
redis.call('DECR', KEYS[1])
|
||||
redis.call('EXPIRE', KEYS[1], ARGV[1])
|
||||
end
|
||||
`;
|
||||
|
||||
cache.client.defineCommand('decrex', {
|
||||
numberOfKeys: 1,
|
||||
lua: DECR_SCRIPT,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* This will increment a key in redis and update the expiry iff it already
|
||||
* exists, otherwise it will do nothing.
|
||||
*/
|
||||
cache.incr = (key, expiry, kf = keyfunc) => new Promise((resolve, reject) => {
|
||||
cache.client
|
||||
.evalsha(cache.INCR_SCRIPT_HASH, 1, kf(key), expiry, (err) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
|
||||
return resolve();
|
||||
});
|
||||
});
|
||||
cache.incr = async (key, expiry, kf = keyfunc) => cache.client.increx(kf(key), expiry);
|
||||
|
||||
/**
|
||||
* This will decrement a key in redis and update the expiry iff it already
|
||||
* exists, otherwise it will do nothing.
|
||||
*/
|
||||
cache.decr = (key, expiry, kf = keyfunc) => new Promise((resolve, reject) => {
|
||||
cache.client
|
||||
.evalsha(cache.DECR_SCRIPT_HASH, 1, kf(key), expiry, (err) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
|
||||
return resolve();
|
||||
});
|
||||
});
|
||||
cache.decr = async (key, expiry, kf = keyfunc) => cache.client.decrex(kf(key, expiry));
|
||||
|
||||
/**
|
||||
* This will increment many keys in redis and update the expiry iff it already
|
||||
* exists, otherwise it will do nothing.
|
||||
*/
|
||||
cache.incrMany = (keys, expiry, kf = keyfunc) => {
|
||||
cache.incrMany = async (keys, expiry, kf = keyfunc) => {
|
||||
let multi = cache.client.multi();
|
||||
|
||||
keys.forEach((key) => {
|
||||
for (const key of keys) {
|
||||
|
||||
// Queue up the evalsha command.
|
||||
multi.evalsha(cache.INCR_SCRIPT_HASH, 1, kf(key), expiry);
|
||||
});
|
||||
multi.increx(kf(key), expiry);
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
multi.exec((err) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
return multi.exec();
|
||||
};
|
||||
|
||||
/**
|
||||
* This will decrement many keys in redis and update the expiry iff it already
|
||||
* exists, otherwise it will do nothing.
|
||||
*/
|
||||
cache.decrMany = (keys, expiry, kf = keyfunc) => {
|
||||
cache.decrMany = async (keys, expiry, kf = keyfunc) => {
|
||||
let multi = cache.client.multi();
|
||||
|
||||
keys.forEach((key) => {
|
||||
for (const key of keys) {
|
||||
|
||||
// Queue up the evalsha command.
|
||||
multi.evalsha(cache.DECR_SCRIPT_HASH, 1, kf(key), expiry);
|
||||
});
|
||||
multi.decrex(kf(key), expiry);
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
multi.exec((err) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
return multi.exec();
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -257,28 +186,12 @@ cache.wrapMany = async (keys, expiry, work, kf = keyfunc) => {
|
||||
* @param {Mixed} key Either an array of items composing a key or a string
|
||||
* @return {Promise}
|
||||
*/
|
||||
cache.get = (key, kf = keyfunc) => new Promise((resolve, reject) => {
|
||||
cache.client.get(kf(key), (err, reply) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
cache.get = async (key, kf = keyfunc) => cache.client.get(kf(key)).then((reply) => {
|
||||
if (reply !== null) {
|
||||
|
||||
if (reply !== null) {
|
||||
let value;
|
||||
|
||||
try {
|
||||
|
||||
// Parse the stored cache value from JSON.
|
||||
value = JSON.parse(reply);
|
||||
} catch (e) {
|
||||
return reject(e);
|
||||
}
|
||||
|
||||
return resolve(value);
|
||||
}
|
||||
|
||||
resolve(null);
|
||||
});
|
||||
// Parse the stored cache value from JSON.
|
||||
return JSON.parse(reply);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -288,31 +201,22 @@ cache.get = (key, kf = keyfunc) => new Promise((resolve, reject) => {
|
||||
* @param {Function} [kf=keyfunc] optional key function to use to turn the
|
||||
* provided key into a string for the cache.
|
||||
*/
|
||||
cache.getMany = (keys, kf = keyfunc) => new Promise((resolve, reject) => {
|
||||
cache.client.mget(keys.map(kf), (err, replies) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
cache.getMany = async (keys, kf = keyfunc) => cache.client.mget(keys.map(kf)).then((replies) => {
|
||||
|
||||
// Parse the replies.
|
||||
for (let i = 0; i < replies.length; i++) {
|
||||
let value = null;
|
||||
|
||||
if (replies[i] != null) {
|
||||
|
||||
// Parse the stored cache value from JSON.
|
||||
value = JSON.parse(replies[i]);
|
||||
}
|
||||
|
||||
// Parse the replies.
|
||||
for (let i = 0; i < replies.length; i++) {
|
||||
let value = null;
|
||||
replies[i] = value;
|
||||
}
|
||||
|
||||
if (replies[i] != null) {
|
||||
try {
|
||||
|
||||
// Parse the stored cache value from JSON.
|
||||
value = JSON.parse(replies[i]);
|
||||
} catch (e) {
|
||||
return reject(e);
|
||||
}
|
||||
}
|
||||
|
||||
replies[i] = value;
|
||||
}
|
||||
|
||||
return resolve(replies);
|
||||
});
|
||||
return replies;
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -322,7 +226,7 @@ cache.getMany = (keys, kf = keyfunc) => new Promise((resolve, reject) => {
|
||||
* @param {Function} [kf=keyfunc] optional key function to use to turn the
|
||||
* provided key into a string for the cache.
|
||||
*/
|
||||
cache.setMany = (keys, values, expiry, kf = keyfunc) => {
|
||||
cache.setMany = async (keys, values, expiry, kf = keyfunc) => {
|
||||
let multi = cache.client.multi();
|
||||
|
||||
keys.forEach((key, index) => {
|
||||
@@ -334,15 +238,7 @@ cache.setMany = (keys, values, expiry, kf = keyfunc) => {
|
||||
multi.set(kf(key), reply, 'EX', expiry);
|
||||
});
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
multi.exec((err) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
return multi.exec();
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -350,18 +246,12 @@ cache.setMany = (keys, values, expiry, kf = keyfunc) => {
|
||||
* @param {Mixed} key Either an array of items composing a key or a string
|
||||
* @return {Promise}
|
||||
*/
|
||||
cache.invalidate = (key, kf = keyfunc) => new Promise((resolve, reject) => {
|
||||
cache.invalidate = async (key, kf = keyfunc) => {
|
||||
|
||||
debug(`invalidate: ${kf(key)}`);
|
||||
|
||||
cache.client.del(kf(key), (err) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
return cache.client.del(kf(key));
|
||||
};
|
||||
|
||||
/**
|
||||
* This sets a value on the key with the expiry and then resolves once it is
|
||||
@@ -371,16 +261,10 @@ cache.invalidate = (key, kf = keyfunc) => new Promise((resolve, reject) => {
|
||||
* @param {Integer} expiry Time in seconds for the cache entry to live for
|
||||
* @return {Promise}
|
||||
*/
|
||||
cache.set = (key, value, expiry, kf = keyfunc) => new Promise((resolve, reject) => {
|
||||
cache.set = async (key, value, expiry, kf = keyfunc) => {
|
||||
|
||||
// Serialize the value as JSON.
|
||||
let reply = JSON.stringify(value);
|
||||
|
||||
cache.client.set(kf(key), reply, 'EX', expiry, (err) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
|
||||
return resolve();
|
||||
});
|
||||
});
|
||||
return cache.client.set(kf(key), reply, 'EX', expiry);
|
||||
};
|
||||
|
||||
+15
-22
@@ -159,40 +159,33 @@ async function ValidateUserLogin(loginProfile, user, done) {
|
||||
/**
|
||||
* Revoke the token on the request.
|
||||
*/
|
||||
const HandleLogout = (req, res, next) => {
|
||||
const HandleLogout = async (req, res, next) => {
|
||||
const {jwt} = req;
|
||||
|
||||
const now = new Date();
|
||||
const expiry = (jwt.exp - now.getTime() / 1000).toFixed(0);
|
||||
|
||||
client().set(`jtir[${jwt.jti}]`, now.toISOString(), 'EX', expiry, (err) => {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
try {
|
||||
await client().set(`jtir[${jwt.jti}]`, now.toISOString(), 'EX', expiry);
|
||||
} catch (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
// Only clear the cookie on logout if enabled.
|
||||
if (JWT_CLEAR_COOKIE_LOGOUT) {
|
||||
debug('clearing the login cookie');
|
||||
res.clearCookie(JWT_SIGNING_COOKIE_NAME);
|
||||
}
|
||||
// Only clear the cookie on logout if enabled.
|
||||
if (JWT_CLEAR_COOKIE_LOGOUT) {
|
||||
debug('clearing the login cookie');
|
||||
res.clearCookie(JWT_SIGNING_COOKIE_NAME);
|
||||
}
|
||||
|
||||
res.status(204).end();
|
||||
});
|
||||
res.status(204).end();
|
||||
};
|
||||
|
||||
const checkGeneralTokenBlacklist = (jwt) => new Promise((resolve, reject) => {
|
||||
client().get(`jtir[${jwt.jti}]`, (err, expiry) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
|
||||
const checkGeneralTokenBlacklist = (jwt) => client().get(`jtir[${jwt.jti}]`)
|
||||
.then((expiry) => {
|
||||
if (expiry != null) {
|
||||
return reject(new errors.ErrAuthentication('token was revoked'));
|
||||
throw new errors.ErrAuthentication('token was revoked');
|
||||
}
|
||||
|
||||
return resolve();
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Check if the given token is already blacklisted, throw an error if it is.
|
||||
|
||||
+4
-11
@@ -1,4 +1,4 @@
|
||||
const redis = require('redis');
|
||||
const Redis = require('ioredis');
|
||||
const debug = require('debug')('talk:services:redis');
|
||||
const enabled = require('debug').enabled('talk:services:redis');
|
||||
const {
|
||||
@@ -14,9 +14,10 @@ const attachMonitors = (client) => {
|
||||
|
||||
// Debug events.
|
||||
if (enabled) {
|
||||
client.on('ready', () => debug('client ready'));
|
||||
client.on('connect', () => debug('client connected'));
|
||||
client.on('ready', () => debug('client ready'));
|
||||
client.on('reconnecting', () => debug('client connection lost, attempting to reconnect'));
|
||||
client.on('close', () => debug('client closed the connection'));
|
||||
client.on('end', () => debug('client ended'));
|
||||
}
|
||||
|
||||
@@ -64,19 +65,11 @@ const connectionOptions = {
|
||||
};
|
||||
|
||||
const createClient = () => {
|
||||
let client = redis.createClient(connectionOptions);
|
||||
let client = new Redis(connectionOptions);
|
||||
|
||||
// Attach the monitors that will print debug messages to the console.
|
||||
attachMonitors(client);
|
||||
|
||||
client.ping((err) => {
|
||||
if (err) {
|
||||
console.error('Can\'t ping the redis server!');
|
||||
|
||||
throw err;
|
||||
}
|
||||
});
|
||||
|
||||
return client;
|
||||
};
|
||||
|
||||
|
||||
+104
-138
@@ -69,33 +69,25 @@ module.exports = class UsersService {
|
||||
* Indicating that the account should be flagged as "login recaptcha required"
|
||||
* where future login attempts must be made with the recaptcha flag.
|
||||
*/
|
||||
static recordLoginAttempt(email) {
|
||||
static async recordLoginAttempt(email) {
|
||||
const rdskey = `la[${email.toLowerCase().trim()}]`;
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
client()
|
||||
.multi()
|
||||
.incr(rdskey)
|
||||
.expire(rdskey, RECAPTCHA_WINDOW_SECONDS)
|
||||
.exec((err, replies) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
const replies = await client()
|
||||
.multi()
|
||||
.incr(rdskey)
|
||||
.expire(rdskey, RECAPTCHA_WINDOW_SECONDS)
|
||||
.exec();
|
||||
|
||||
// if this is new or has no expiry
|
||||
if (replies[0] === 1 || replies[1] === -1) {
|
||||
// if this is new or has no expiry
|
||||
if (replies[0] === 1 || replies[1] === -1) {
|
||||
|
||||
// then expire it after the timeout
|
||||
client().expire(rdskey, RECAPTCHA_WINDOW_SECONDS);
|
||||
}
|
||||
// then expire it after the timeout
|
||||
client().expire(rdskey, RECAPTCHA_WINDOW_SECONDS);
|
||||
}
|
||||
|
||||
if (replies[0] >= RECAPTCHA_INCORRECT_TRIGGER) {
|
||||
return reject(errors.ErrLoginAttemptMaximumExceeded);
|
||||
}
|
||||
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
if (replies[0] >= RECAPTCHA_INCORRECT_TRIGGER) {
|
||||
throw errors.ErrLoginAttemptMaximumExceeded;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -104,27 +96,17 @@ module.exports = class UsersService {
|
||||
*
|
||||
* errors.ErrLoginAttemptMaximumExceeded
|
||||
*/
|
||||
static checkLoginAttempts(email) {
|
||||
static async checkLoginAttempts(email) {
|
||||
const rdskey = `la[${email.toLowerCase().trim()}]`;
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
client()
|
||||
.get(rdskey, (err, reply) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
const attempts = await client().get(rdskey);
|
||||
if (!attempts) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!reply) {
|
||||
return resolve();
|
||||
}
|
||||
|
||||
if (reply >= RECAPTCHA_INCORRECT_TRIGGER) {
|
||||
return reject(errors.ErrLoginAttemptMaximumExceeded);
|
||||
}
|
||||
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
if (attempts >= RECAPTCHA_INCORRECT_TRIGGER) {
|
||||
throw errors.ErrLoginAttemptMaximumExceeded;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -217,24 +199,15 @@ module.exports = class UsersService {
|
||||
});
|
||||
}
|
||||
|
||||
static changePassword(id, password) {
|
||||
return new Promise((resolve, reject) => {
|
||||
bcrypt.hash(password, SALT_ROUNDS, (err, hashedPassword) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
static async changePassword(id, password) {
|
||||
const hashedPassword = await bcrypt.hash(password, SALT_ROUNDS);
|
||||
|
||||
resolve(hashedPassword);
|
||||
});
|
||||
})
|
||||
.then((hashedPassword) => {
|
||||
return UserModel.update({id}, {
|
||||
$inc: {__v: 1},
|
||||
$set: {
|
||||
password: hashedPassword
|
||||
}
|
||||
});
|
||||
});
|
||||
return UserModel.update({id}, {
|
||||
$inc: {__v: 1},
|
||||
$set: {
|
||||
password: hashedPassword
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -301,54 +274,48 @@ module.exports = class UsersService {
|
||||
* @param {String} username name of the display user
|
||||
* @param {Function} done callback
|
||||
*/
|
||||
static createLocalUser(email, password, username) {
|
||||
static async createLocalUser(email, password, username) {
|
||||
|
||||
if (!email) {
|
||||
return Promise.reject(errors.ErrMissingEmail);
|
||||
throw errors.ErrMissingEmail;
|
||||
}
|
||||
|
||||
email = email.toLowerCase().trim();
|
||||
username = username.trim();
|
||||
|
||||
return Promise.all([
|
||||
await Promise.all([
|
||||
UsersService.isValidUsername(username),
|
||||
UsersService.isValidPassword(password)
|
||||
])
|
||||
.then(() => { // username is valid
|
||||
return new Promise((resolve, reject) => {
|
||||
bcrypt.hash(password, SALT_ROUNDS, (err, hashedPassword) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
]);
|
||||
|
||||
let user = new UserModel({
|
||||
username,
|
||||
lowercaseUsername: username.toLowerCase(),
|
||||
password: hashedPassword,
|
||||
roles: [],
|
||||
profiles: [
|
||||
{
|
||||
id: email,
|
||||
provider: 'local'
|
||||
}
|
||||
]
|
||||
});
|
||||
const hashedPassword = await bcrypt.hash(password, SALT_ROUNDS);
|
||||
|
||||
user.save((err) => {
|
||||
if (err) {
|
||||
if (err.code === 11000) {
|
||||
if (err.message.match('Username')) {
|
||||
return reject(errors.ErrUsernameTaken);
|
||||
}
|
||||
return reject(errors.ErrEmailTaken);
|
||||
}
|
||||
return reject(err);
|
||||
}
|
||||
return resolve(user);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
let user = new UserModel({
|
||||
username,
|
||||
lowercaseUsername: username.toLowerCase(),
|
||||
password: hashedPassword,
|
||||
roles: [],
|
||||
profiles: [
|
||||
{
|
||||
id: email,
|
||||
provider: 'local'
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
try {
|
||||
user = await user.save();
|
||||
} catch (err) {
|
||||
if (err.code === 11000) {
|
||||
if (err.message.match('Username')) {
|
||||
throw errors.ErrUsernameTaken;
|
||||
}
|
||||
throw errors.ErrEmailTaken;
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
|
||||
return user;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -387,14 +354,14 @@ module.exports = class UsersService {
|
||||
* @param {String} role role to add
|
||||
* @param {Function} done callback after the operation is complete
|
||||
*/
|
||||
static addRoleToUser(id, role) {
|
||||
static async addRoleToUser(id, role) {
|
||||
const roles = [];
|
||||
|
||||
// Check to see if the user role is in the allowable set of roles.
|
||||
if (role && USER_ROLES.indexOf(role) === -1) {
|
||||
|
||||
// User role is not supported! Error out here.
|
||||
return Promise.reject(new Error(`role ${role} is not supported`));
|
||||
throw new Error(`role ${role} is not supported`);
|
||||
} else if(role) {
|
||||
roles.push(role);
|
||||
}
|
||||
@@ -408,13 +375,13 @@ module.exports = class UsersService {
|
||||
* @param {String} role role to remove
|
||||
* @param {Function} done callback after the operation is complete
|
||||
*/
|
||||
static removeRoleFromUser(id, role) {
|
||||
static async removeRoleFromUser(id, role) {
|
||||
|
||||
// Check to see if the user role is in the allowable set of roles.
|
||||
if (USER_ROLES.indexOf(role) === -1) {
|
||||
|
||||
// User role is not supported! Error out here.
|
||||
return Promise.reject(new Error(`role ${role} is not supported`));
|
||||
throw new Error(`role ${role} is not supported`);
|
||||
}
|
||||
|
||||
return UserModel.update({id}, {
|
||||
@@ -430,13 +397,13 @@ module.exports = class UsersService {
|
||||
* @param {String} status status to set
|
||||
* @param {Function} done callback after the operation is complete
|
||||
*/
|
||||
static setStatus(id, status) {
|
||||
static async setStatus(id, status) {
|
||||
|
||||
// Check to see if the user status is in the allowable set of roles.
|
||||
if (USER_STATUS.indexOf(status) === -1) {
|
||||
|
||||
// User status is not supported! Error out here.
|
||||
return Promise.reject(new Error(`status ${status} is not supported`));
|
||||
throw new Error(`status ${status} is not supported`);
|
||||
}
|
||||
|
||||
// TODO: current updating status behavior is weird.
|
||||
@@ -583,53 +550,52 @@ module.exports = class UsersService {
|
||||
* Creates a JWT from a user email. Only works for local accounts.
|
||||
* @param {String} email of the local user
|
||||
*/
|
||||
static createPasswordResetToken(email, loc) {
|
||||
static async createPasswordResetToken(email, loc) {
|
||||
if (!email || typeof email !== 'string') {
|
||||
return Promise.reject('email is required when creating a JWT for resetting passord');
|
||||
throw new Error('email is required when creating a JWT for resetting passord');
|
||||
}
|
||||
|
||||
email = email.toLowerCase();
|
||||
|
||||
return Promise.all([
|
||||
const [user, settings] = await Promise.all([
|
||||
UserModel.findOne({profiles: {$elemMatch: {id: email}}}),
|
||||
SettingsService.retrieve()
|
||||
])
|
||||
.then(([user, settings]) => {
|
||||
if (!user) {
|
||||
SettingsService.retrieve(),
|
||||
]);
|
||||
|
||||
// Since we don't want to reveal that the email does/doesn't exist
|
||||
// just go ahead and resolve the Promise with null and check in the
|
||||
// endpoint.
|
||||
return;
|
||||
}
|
||||
let redirectDomain;
|
||||
try {
|
||||
const {hostname, port} = url.parse(loc);
|
||||
redirectDomain = hostname;
|
||||
if (port) {
|
||||
redirectDomain += `:${port}`;
|
||||
}
|
||||
} catch (e) {
|
||||
return Promise.reject('redirect location is invalid');
|
||||
}
|
||||
if (!user) {
|
||||
|
||||
if (settings.domains.whitelist.indexOf(redirectDomain) === -1) {
|
||||
return Promise.reject('redirect location is not on the list of acceptable domains');
|
||||
}
|
||||
// Since we don't want to reveal that the email does/doesn't exist
|
||||
// just go ahead and resolve the Promise with null and check in the
|
||||
// endpoint.
|
||||
return;
|
||||
}
|
||||
let redirectDomain;
|
||||
try {
|
||||
const {hostname, port} = url.parse(loc);
|
||||
redirectDomain = hostname;
|
||||
if (port) {
|
||||
redirectDomain += `:${port}`;
|
||||
}
|
||||
} catch (e) {
|
||||
throw new Error('redirect location is invalid');
|
||||
}
|
||||
|
||||
const payload = {
|
||||
jti: uuid.v4(),
|
||||
email,
|
||||
loc,
|
||||
userId: user.id,
|
||||
version: user.__v
|
||||
};
|
||||
if (settings.domains.whitelist.indexOf(redirectDomain) === -1) {
|
||||
throw new Error('redirect location is not on the list of acceptable domains');
|
||||
}
|
||||
|
||||
return JWT_SECRET.sign(payload, {
|
||||
expiresIn: '1d',
|
||||
subject: PASSWORD_RESET_JWT_SUBJECT
|
||||
});
|
||||
});
|
||||
const payload = {
|
||||
jti: uuid.v4(),
|
||||
email,
|
||||
loc,
|
||||
userId: user.id,
|
||||
version: user.__v
|
||||
};
|
||||
|
||||
return JWT_SECRET.sign(payload, {
|
||||
expiresIn: '1d',
|
||||
subject: PASSWORD_RESET_JWT_SUBJECT
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -755,7 +721,7 @@ module.exports = class UsersService {
|
||||
*/
|
||||
static async createEmailConfirmToken(userID = null, email, referer = ROOT_URL) {
|
||||
if (!email || typeof email !== 'string') {
|
||||
return Promise.reject('email is required when creating a JWT for resetting passord');
|
||||
throw new Error('email is required when creating a JWT for resetting passord');
|
||||
}
|
||||
|
||||
// Conform the email to lowercase.
|
||||
|
||||
@@ -3,12 +3,6 @@ const cache = require('../../services/cache');
|
||||
const client = createClient();
|
||||
|
||||
beforeEach(() => Promise.all([
|
||||
new Promise((resolve, reject) => client.flushdb((err) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
|
||||
return resolve();
|
||||
})),
|
||||
client.flushdb(),
|
||||
cache.init(),
|
||||
]));
|
||||
|
||||
@@ -1112,7 +1112,7 @@ bluebird@2.9.24:
|
||||
version "2.9.24"
|
||||
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-2.9.24.tgz#14a2e75f0548323dc35aa440d92007ca154e967c"
|
||||
|
||||
bluebird@3.5.0:
|
||||
bluebird@3.5.0, bluebird@^3.3.4:
|
||||
version "3.5.0"
|
||||
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.0.tgz#791420d7f551eea2897453a8a77653f96606d67c"
|
||||
|
||||
@@ -1602,6 +1602,10 @@ clone@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.2.tgz#260b7a99ebb1edfe247538175f783243cb19d149"
|
||||
|
||||
cluster-key-slot@^1.0.6:
|
||||
version "1.0.8"
|
||||
resolved "https://registry.yarnpkg.com/cluster-key-slot/-/cluster-key-slot-1.0.8.tgz#7654556085a65330932a2e8b5976f8e2d0b3e414"
|
||||
|
||||
co@^4.6.0:
|
||||
version "4.6.0"
|
||||
resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184"
|
||||
@@ -2236,6 +2240,10 @@ delegates@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a"
|
||||
|
||||
denque@^1.1.0:
|
||||
version "1.2.2"
|
||||
resolved "https://registry.yarnpkg.com/denque/-/denque-1.2.2.tgz#e06cf7cf0da8badc88cbdaabf8fc0a70d659f1d4"
|
||||
|
||||
depd@1.1.0, depd@~1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.0.tgz#e1bd82c6aab6ced965b97b88b17ed3e528ca18c3"
|
||||
@@ -2908,6 +2916,10 @@ flatten@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/flatten/-/flatten-1.0.2.tgz#dae46a9d78fbe25292258cc1e780a41d95c03782"
|
||||
|
||||
flexbuffer@0.0.6:
|
||||
version "0.0.6"
|
||||
resolved "https://registry.yarnpkg.com/flexbuffer/-/flexbuffer-0.0.6.tgz#039fdf23f8823e440c38f3277e6fef1174215b30"
|
||||
|
||||
for-in@^1.0.1:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80"
|
||||
@@ -3810,6 +3822,34 @@ invert-kv@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-1.0.0.tgz#104a8e4aaca6d3d8cd157a8ef8bfab2d7a3ffdb6"
|
||||
|
||||
ioredis@^3.1.4:
|
||||
version "3.1.4"
|
||||
resolved "https://registry.yarnpkg.com/ioredis/-/ioredis-3.1.4.tgz#8688293f5f2f1757e1c812ad17cce49f46d811bc"
|
||||
dependencies:
|
||||
bluebird "^3.3.4"
|
||||
cluster-key-slot "^1.0.6"
|
||||
debug "^2.2.0"
|
||||
denque "^1.1.0"
|
||||
flexbuffer "0.0.6"
|
||||
lodash.assign "^4.2.0"
|
||||
lodash.bind "^4.2.1"
|
||||
lodash.clone "^4.5.0"
|
||||
lodash.clonedeep "^4.5.0"
|
||||
lodash.defaults "^4.2.0"
|
||||
lodash.difference "^4.5.0"
|
||||
lodash.flatten "^4.4.0"
|
||||
lodash.foreach "^4.5.0"
|
||||
lodash.isempty "^4.4.0"
|
||||
lodash.keys "^4.2.0"
|
||||
lodash.noop "^3.0.1"
|
||||
lodash.partial "^4.2.1"
|
||||
lodash.pick "^4.4.0"
|
||||
lodash.sample "^4.2.1"
|
||||
lodash.shuffle "^4.2.0"
|
||||
lodash.values "^4.3.0"
|
||||
redis-commands "^1.2.0"
|
||||
redis-parser "^2.4.0"
|
||||
|
||||
ip-regex@^1.0.0:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-1.0.3.tgz#dc589076f659f419c222039a33316f1c7387effd"
|
||||
@@ -4586,7 +4626,7 @@ lodash.assignin@^4.0.9:
|
||||
version "4.2.0"
|
||||
resolved "https://registry.yarnpkg.com/lodash.assignin/-/lodash.assignin-4.2.0.tgz#ba8df5fb841eb0a3e8044232b0e263a8dc6a28a2"
|
||||
|
||||
lodash.bind@^4.1.4:
|
||||
lodash.bind@^4.1.4, lodash.bind@^4.2.1:
|
||||
version "4.2.1"
|
||||
resolved "https://registry.yarnpkg.com/lodash.bind/-/lodash.bind-4.2.1.tgz#7ae3017e939622ac31b7d7d7dcb1b34db1690d35"
|
||||
|
||||
@@ -4594,6 +4634,14 @@ lodash.camelcase@^4.3.0:
|
||||
version "4.3.0"
|
||||
resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6"
|
||||
|
||||
lodash.clone@^4.5.0:
|
||||
version "4.5.0"
|
||||
resolved "https://registry.yarnpkg.com/lodash.clone/-/lodash.clone-4.5.0.tgz#195870450f5a13192478df4bc3d23d2dea1907b6"
|
||||
|
||||
lodash.clonedeep@^4.5.0:
|
||||
version "4.5.0"
|
||||
resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef"
|
||||
|
||||
lodash.create@3.1.1:
|
||||
version "3.1.1"
|
||||
resolved "https://registry.yarnpkg.com/lodash.create/-/lodash.create-3.1.1.tgz#d7f2849f0dbda7e04682bb8cd72ab022461debe7"
|
||||
@@ -4609,19 +4657,23 @@ lodash.defaults@^3.1.2:
|
||||
lodash.assign "^3.0.0"
|
||||
lodash.restparam "^3.0.0"
|
||||
|
||||
lodash.defaults@^4.0.1:
|
||||
lodash.defaults@^4.0.1, lodash.defaults@^4.2.0:
|
||||
version "4.2.0"
|
||||
resolved "https://registry.yarnpkg.com/lodash.defaults/-/lodash.defaults-4.2.0.tgz#d09178716ffea4dde9e5fb7b37f6f0802274580c"
|
||||
|
||||
lodash.difference@^4.5.0:
|
||||
version "4.5.0"
|
||||
resolved "https://registry.yarnpkg.com/lodash.difference/-/lodash.difference-4.5.0.tgz#9ccb4e505d486b91651345772885a2df27fd017c"
|
||||
|
||||
lodash.filter@^4.4.0:
|
||||
version "4.6.0"
|
||||
resolved "https://registry.yarnpkg.com/lodash.filter/-/lodash.filter-4.6.0.tgz#668b1d4981603ae1cc5a6fa760143e480b4c4ace"
|
||||
|
||||
lodash.flatten@^4.2.0:
|
||||
lodash.flatten@^4.2.0, lodash.flatten@^4.4.0:
|
||||
version "4.4.0"
|
||||
resolved "https://registry.yarnpkg.com/lodash.flatten/-/lodash.flatten-4.4.0.tgz#f31c22225a9632d2bbf8e4addbef240aa765a61f"
|
||||
|
||||
lodash.foreach@^4.3.0:
|
||||
lodash.foreach@^4.3.0, lodash.foreach@^4.5.0:
|
||||
version "4.5.0"
|
||||
resolved "https://registry.yarnpkg.com/lodash.foreach/-/lodash.foreach-4.5.0.tgz#1a6a35eace401280c7f06dddec35165ab27e3e53"
|
||||
|
||||
@@ -4633,6 +4685,10 @@ lodash.isarray@^3.0.0:
|
||||
version "3.0.4"
|
||||
resolved "https://registry.yarnpkg.com/lodash.isarray/-/lodash.isarray-3.0.4.tgz#79e4eb88c36a8122af86f844aa9bcd851b5fbb55"
|
||||
|
||||
lodash.isempty@^4.4.0:
|
||||
version "4.4.0"
|
||||
resolved "https://registry.yarnpkg.com/lodash.isempty/-/lodash.isempty-4.4.0.tgz#6f86cbedd8be4ec987be9aaf33c9684db1b31e7e"
|
||||
|
||||
lodash.isequal@^4.1.1, lodash.isequal@^4.4.0:
|
||||
version "4.5.0"
|
||||
resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0"
|
||||
@@ -4653,6 +4709,10 @@ lodash.keys@^3.0.0:
|
||||
lodash.isarguments "^3.0.0"
|
||||
lodash.isarray "^3.0.0"
|
||||
|
||||
lodash.keys@^4.2.0:
|
||||
version "4.2.0"
|
||||
resolved "https://registry.yarnpkg.com/lodash.keys/-/lodash.keys-4.2.0.tgz#a08602ac12e4fb83f91fc1fb7a360a4d9ba35205"
|
||||
|
||||
lodash.map@^4.4.0, lodash.map@^4.5.1:
|
||||
version "4.6.0"
|
||||
resolved "https://registry.yarnpkg.com/lodash.map/-/lodash.map-4.6.0.tgz#771ec7839e3473d9c4cde28b19394c3562f4f6d3"
|
||||
@@ -4665,10 +4725,18 @@ lodash.merge@^4.4.0:
|
||||
version "4.6.0"
|
||||
resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.0.tgz#69884ba144ac33fe699737a6086deffadd0f89c5"
|
||||
|
||||
lodash.noop@^3.0.1:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/lodash.noop/-/lodash.noop-3.0.1.tgz#38188f4d650a3a474258439b96ec45b32617133c"
|
||||
|
||||
lodash.once@^4.0.0:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac"
|
||||
|
||||
lodash.partial@^4.2.1:
|
||||
version "4.2.1"
|
||||
resolved "https://registry.yarnpkg.com/lodash.partial/-/lodash.partial-4.2.1.tgz#49f3d8cfdaa3bff8b3a91d127e923245418961d4"
|
||||
|
||||
lodash.pick@^4.2.1, lodash.pick@^4.4.0:
|
||||
version "4.4.0"
|
||||
resolved "https://registry.yarnpkg.com/lodash.pick/-/lodash.pick-4.4.0.tgz#52f05610fff9ded422611441ed1fc123a03001b3"
|
||||
@@ -4685,6 +4753,14 @@ lodash.restparam@^3.0.0:
|
||||
version "3.6.1"
|
||||
resolved "https://registry.yarnpkg.com/lodash.restparam/-/lodash.restparam-3.6.1.tgz#936a4e309ef330a7645ed4145986c85ae5b20805"
|
||||
|
||||
lodash.sample@^4.2.1:
|
||||
version "4.2.1"
|
||||
resolved "https://registry.yarnpkg.com/lodash.sample/-/lodash.sample-4.2.1.tgz#5e4291b0c753fa1abeb0aab8fb29df1b66f07f6d"
|
||||
|
||||
lodash.shuffle@^4.2.0:
|
||||
version "4.2.0"
|
||||
resolved "https://registry.yarnpkg.com/lodash.shuffle/-/lodash.shuffle-4.2.0.tgz#145b5053cf875f6f5c2a33f48b6e9948c6ec7b4b"
|
||||
|
||||
lodash.some@^4.4.0:
|
||||
version "4.6.0"
|
||||
resolved "https://registry.yarnpkg.com/lodash.some/-/lodash.some-4.6.0.tgz#1bb9f314ef6b8baded13b549169b2a945eb68e4d"
|
||||
@@ -4697,6 +4773,10 @@ lodash.uniq@^4.5.0:
|
||||
version "4.5.0"
|
||||
resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773"
|
||||
|
||||
lodash.values@^4.3.0:
|
||||
version "4.3.0"
|
||||
resolved "https://registry.yarnpkg.com/lodash.values/-/lodash.values-4.3.0.tgz#a3a6c2b0ebecc5c2cba1c17e6e620fe81b53d347"
|
||||
|
||||
lodash@3.10.1, lodash@^3.3.1:
|
||||
version "3.10.1"
|
||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-3.10.1.tgz#5bf45e8e49ba4189e17d482789dfd15bd140b7b6"
|
||||
@@ -6644,7 +6724,7 @@ redis-commands@^1.2.0:
|
||||
version "1.3.1"
|
||||
resolved "https://registry.yarnpkg.com/redis-commands/-/redis-commands-1.3.1.tgz#81d826f45fa9c8b2011f4cd7a0fe597d241d442b"
|
||||
|
||||
redis-parser@^2.0.0, redis-parser@^2.6.0:
|
||||
redis-parser@^2.0.0, redis-parser@^2.4.0, redis-parser@^2.6.0:
|
||||
version "2.6.0"
|
||||
resolved "https://registry.yarnpkg.com/redis-parser/-/redis-parser-2.6.0.tgz#52ed09dacac108f1a631c07e9b69941e7a19504b"
|
||||
|
||||
@@ -6652,7 +6732,7 @@ redis@^0.12.1:
|
||||
version "0.12.1"
|
||||
resolved "https://registry.yarnpkg.com/redis/-/redis-0.12.1.tgz#64df76ad0fc8acebaebd2a0645e8a48fac49185e"
|
||||
|
||||
redis@^2.6.3, redis@^2.8.0:
|
||||
redis@^2.6.3:
|
||||
version "2.8.0"
|
||||
resolved "https://registry.yarnpkg.com/redis/-/redis-2.8.0.tgz#202288e3f58c49f6079d97af7a10e1303ae14b02"
|
||||
dependencies:
|
||||
|
||||
Reference in New Issue
Block a user