mirror of
https://github.com/NodeBB/NodeBB.git
synced 2025-10-26 16:46:12 +01:00
feat: #7743, user/approval, user/auth
This commit is contained in:
@@ -37,7 +37,7 @@ var app;
|
||||
|
||||
var viewsDir = nconf.get('views_dir');
|
||||
|
||||
Emailer.getTemplates = function (config, cb) {
|
||||
Emailer.getTemplates = function (config, callback) {
|
||||
var emailsPath = path.join(viewsDir, 'emails');
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
@@ -71,7 +71,7 @@ Emailer.getTemplates = function (config, cb) {
|
||||
], next);
|
||||
}, next);
|
||||
},
|
||||
], cb);
|
||||
], callback);
|
||||
};
|
||||
|
||||
Emailer.listServices = function (callback) {
|
||||
@@ -407,3 +407,5 @@ function getHostname() {
|
||||
|
||||
return parsed.hostname;
|
||||
}
|
||||
|
||||
require('./promisify')(Emailer, ['transports']);
|
||||
|
||||
@@ -320,6 +320,9 @@ function pushToUids(uids, notification, callback) {
|
||||
|
||||
Notifications.pushGroup = function (notification, groupName, callback) {
|
||||
callback = callback || function () {};
|
||||
if (!notification) {
|
||||
return callback();
|
||||
}
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
groups.getMembers(groupName, 0, -1, next);
|
||||
@@ -332,6 +335,9 @@ Notifications.pushGroup = function (notification, groupName, callback) {
|
||||
|
||||
Notifications.pushGroups = function (notification, groupNames, callback) {
|
||||
callback = callback || function () {};
|
||||
if (!notification) {
|
||||
return callback();
|
||||
}
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
groups.getMembersOfGroups(groupNames, next);
|
||||
|
||||
@@ -12,181 +12,101 @@ var utils = require('../utils');
|
||||
var plugins = require('../plugins');
|
||||
|
||||
module.exports = function (User) {
|
||||
User.addToApprovalQueue = function (userData, callback) {
|
||||
User.addToApprovalQueue = async function (userData) {
|
||||
userData.userslug = utils.slugify(userData.username);
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
canQueue(userData, next);
|
||||
},
|
||||
function (next) {
|
||||
User.hashPassword(userData.password, next);
|
||||
},
|
||||
function (hashedPassword, next) {
|
||||
await canQueue(userData);
|
||||
const hashedPassword = await User.hashPassword(userData.password);
|
||||
var data = {
|
||||
username: userData.username,
|
||||
email: userData.email,
|
||||
ip: userData.ip,
|
||||
hashedPassword: hashedPassword,
|
||||
};
|
||||
plugins.fireHook('filter:user.addToApprovalQueue', { data: data, userData: userData }, next);
|
||||
},
|
||||
function (results, next) {
|
||||
db.setObject('registration:queue:name:' + userData.username, results.data, next);
|
||||
},
|
||||
function (next) {
|
||||
db.sortedSetAdd('registration:queue', Date.now(), userData.username, next);
|
||||
},
|
||||
function (next) {
|
||||
sendNotificationToAdmins(userData.username, next);
|
||||
},
|
||||
], callback);
|
||||
const results = await plugins.fireHook('filter:user.addToApprovalQueue', { data: data, userData: userData });
|
||||
await db.setObject('registration:queue:name:' + userData.username, results.data);
|
||||
await db.sortedSetAdd('registration:queue', Date.now(), userData.username);
|
||||
await sendNotificationToAdmins(userData.username);
|
||||
};
|
||||
|
||||
function canQueue(userData, callback) {
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
User.isDataValid(userData, next);
|
||||
},
|
||||
function (next) {
|
||||
db.getSortedSetRange('registration:queue', 0, -1, next);
|
||||
},
|
||||
function (usernames, next) {
|
||||
async function canQueue(userData) {
|
||||
await User.isDataValid(userData);
|
||||
const usernames = await db.getSortedSetRange('registration:queue', 0, -1);
|
||||
if (usernames.includes(userData.username)) {
|
||||
return next(new Error('[[error:username-taken]]'));
|
||||
throw new Error('[[error:username-taken]]');
|
||||
}
|
||||
const keys = usernames.filter(Boolean).map(username => 'registration:queue:name:' + username);
|
||||
db.getObjectsFields(keys, ['email'], next);
|
||||
},
|
||||
function (data, next) {
|
||||
const data = await db.getObjectsFields(keys, ['email']);
|
||||
const emails = data.map(data => data && data.email);
|
||||
if (emails.includes(userData.email)) {
|
||||
return next(new Error('[[error:email-taken]]'));
|
||||
throw new Error('[[error:email-taken]]');
|
||||
}
|
||||
next();
|
||||
},
|
||||
], callback);
|
||||
}
|
||||
|
||||
function sendNotificationToAdmins(username, callback) {
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
notifications.create({
|
||||
async function sendNotificationToAdmins(username) {
|
||||
const notifObj = await notifications.create({
|
||||
type: 'new-register',
|
||||
bodyShort: '[[notifications:new_register, ' + username + ']]',
|
||||
nid: 'new_register:' + username,
|
||||
path: '/admin/manage/registration',
|
||||
mergeId: 'new_register',
|
||||
}, next);
|
||||
},
|
||||
function (notification, next) {
|
||||
notifications.pushGroup(notification, 'administrators', next);
|
||||
},
|
||||
], callback);
|
||||
});
|
||||
await notifications.pushGroup(notifObj, 'administrators');
|
||||
}
|
||||
|
||||
User.acceptRegistration = function (username, callback) {
|
||||
var uid;
|
||||
var userData;
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
db.getObject('registration:queue:name:' + username, next);
|
||||
},
|
||||
function (_userData, next) {
|
||||
if (!_userData) {
|
||||
return callback(new Error('[[error:invalid-data]]'));
|
||||
User.acceptRegistration = async function (username) {
|
||||
const userData = await db.getObject('registration:queue:name:' + username);
|
||||
if (!userData) {
|
||||
throw new Error('[[error:invalid-data]]');
|
||||
}
|
||||
userData = _userData;
|
||||
User.create(userData, next);
|
||||
},
|
||||
function (_uid, next) {
|
||||
uid = _uid;
|
||||
User.setUserField(uid, 'password', userData.hashedPassword, next);
|
||||
},
|
||||
function (next) {
|
||||
removeFromQueue(username, next);
|
||||
},
|
||||
function (next) {
|
||||
markNotificationRead(username, next);
|
||||
},
|
||||
function (next) {
|
||||
plugins.fireHook('filter:register.complete', { uid: uid }, next);
|
||||
},
|
||||
function (result, next) {
|
||||
var title = meta.config.title || meta.config.browserTitle || 'NodeBB';
|
||||
var data = {
|
||||
|
||||
const uid = await User.create(userData);
|
||||
await User.setUserField(uid, 'password', userData.hashedPassword);
|
||||
await removeFromQueue(username);
|
||||
await markNotificationRead(username);
|
||||
await plugins.fireHook('filter:register.complete', { uid: uid });
|
||||
await emailer.send('registration_accepted', uid, {
|
||||
username: username,
|
||||
subject: '[[email:welcome-to, ' + title + ']]',
|
||||
subject: '[[email:welcome-to, ' + (meta.config.title || meta.config.browserTitle || 'NodeBB') + ']]',
|
||||
template: 'registration_accepted',
|
||||
uid: uid,
|
||||
};
|
||||
|
||||
emailer.send('registration_accepted', uid, data, next);
|
||||
},
|
||||
function (next) {
|
||||
next(null, uid);
|
||||
},
|
||||
], callback);
|
||||
};
|
||||
|
||||
function markNotificationRead(username, callback) {
|
||||
var nid = 'new_register:' + username;
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
groups.getMembers('administrators', 0, -1, next);
|
||||
},
|
||||
function (uids, next) {
|
||||
async.each(uids, function (uid, next) {
|
||||
notifications.markRead(nid, uid, next);
|
||||
}, next);
|
||||
},
|
||||
], callback);
|
||||
}
|
||||
|
||||
User.rejectRegistration = function (username, callback) {
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
removeFromQueue(username, next);
|
||||
},
|
||||
function (next) {
|
||||
markNotificationRead(username, next);
|
||||
},
|
||||
], callback);
|
||||
};
|
||||
|
||||
function removeFromQueue(username, callback) {
|
||||
async.parallel([
|
||||
async.apply(db.sortedSetRemove, 'registration:queue', username),
|
||||
async.apply(db.delete, 'registration:queue:name:' + username),
|
||||
], function (err) {
|
||||
callback(err);
|
||||
});
|
||||
return uid;
|
||||
};
|
||||
|
||||
async function markNotificationRead(username) {
|
||||
const nid = 'new_register:' + username;
|
||||
const uids = await groups.getMembers('administrators', 0, -1);
|
||||
const promises = uids.map(uid => notifications.markRead(nid, uid));
|
||||
await Promise.all(promises);
|
||||
}
|
||||
|
||||
User.shouldQueueUser = function (ip, callback) {
|
||||
User.rejectRegistration = async function (username) {
|
||||
await removeFromQueue(username);
|
||||
await markNotificationRead(username);
|
||||
};
|
||||
|
||||
async function removeFromQueue(username) {
|
||||
await Promise.all([
|
||||
db.sortedSetRemove('registration:queue', username),
|
||||
db.delete('registration:queue:name:' + username),
|
||||
]);
|
||||
}
|
||||
|
||||
User.shouldQueueUser = async function (ip) {
|
||||
const registrationApprovalType = meta.config.registrationApprovalType;
|
||||
if (registrationApprovalType === 'admin-approval') {
|
||||
setImmediate(callback, null, true);
|
||||
return true;
|
||||
} else if (registrationApprovalType === 'admin-approval-ip') {
|
||||
db.sortedSetCard('ip:' + ip + ':uid', function (err, count) {
|
||||
callback(err, !!count);
|
||||
});
|
||||
} else {
|
||||
setImmediate(callback, null, false);
|
||||
const count = await db.sortedSetCard('ip:' + ip + ':uid');
|
||||
return !!count;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
User.getRegistrationQueue = function (start, stop, callback) {
|
||||
var data;
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
db.getSortedSetRevRangeWithScores('registration:queue', start, stop, next);
|
||||
},
|
||||
function (_data, next) {
|
||||
data = _data;
|
||||
var keys = data.filter(Boolean).map(user => 'registration:queue:name:' + user.value);
|
||||
db.getObjects(keys, next);
|
||||
},
|
||||
function (users, next) {
|
||||
User.getRegistrationQueue = async function (start, stop) {
|
||||
const data = await db.getSortedSetRevRangeWithScores('registration:queue', start, stop);
|
||||
const keys = data.filter(Boolean).map(user => 'registration:queue:name:' + user.value);
|
||||
let users = await db.getObjects(keys);
|
||||
users = users.filter(Boolean).map(function (user, index) {
|
||||
user.timestampISO = utils.toISOString(data[index].score);
|
||||
user.email = validator.escape(String(user.email));
|
||||
@@ -194,14 +114,13 @@ module.exports = function (User) {
|
||||
return user;
|
||||
});
|
||||
|
||||
async.map(users, function (user, next) {
|
||||
users = await async.map(users, async function (user) {
|
||||
// temporary: see http://www.stopforumspam.com/forum/viewtopic.php?id=6392
|
||||
// need to keep this for getIPMatchedUsers
|
||||
user.ip = user.ip.replace('::ffff:', '');
|
||||
getIPMatchedUsers(user, function (err) {
|
||||
next(err, user);
|
||||
});
|
||||
await getIPMatchedUsers(user);
|
||||
user.customActions = [].concat(user.customActions);
|
||||
return user;
|
||||
/*
|
||||
// then spam prevention plugins, using the "filter:user.getRegistrationQueue" hook can be like:
|
||||
user.customActions.push({
|
||||
@@ -211,29 +130,14 @@ module.exports = function (User) {
|
||||
icon: 'fa-flag'
|
||||
});
|
||||
*/
|
||||
}, next);
|
||||
},
|
||||
function (users, next) {
|
||||
plugins.fireHook('filter:user.getRegistrationQueue', { users: users }, next);
|
||||
},
|
||||
function (results, next) {
|
||||
next(null, results.users);
|
||||
},
|
||||
], callback);
|
||||
});
|
||||
const results = await plugins.fireHook('filter:user.getRegistrationQueue', { users: users });
|
||||
return results.users;
|
||||
};
|
||||
|
||||
function getIPMatchedUsers(user, callback) {
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
User.getUidsFromSet('ip:' + user.ip + ':uid', 0, -1, next);
|
||||
},
|
||||
function (uids, next) {
|
||||
User.getUsersFields(uids, ['uid', 'username', 'picture'], next);
|
||||
},
|
||||
function (data, next) {
|
||||
async function getIPMatchedUsers(user) {
|
||||
const uids = await User.getUidsFromSet('ip:' + user.ip + ':uid', 0, -1);
|
||||
const data = User.getUsersFields(uids, ['uid', 'username', 'picture']);
|
||||
user.ipMatch = data;
|
||||
next();
|
||||
},
|
||||
], callback);
|
||||
}
|
||||
};
|
||||
|
||||
188
src/user/auth.js
188
src/user/auth.js
@@ -3,6 +3,8 @@
|
||||
var async = require('async');
|
||||
var winston = require('winston');
|
||||
var validator = require('validator');
|
||||
const util = require('util');
|
||||
const _ = require('lodash');
|
||||
var db = require('../database');
|
||||
var meta = require('../meta');
|
||||
var events = require('../events');
|
||||
@@ -12,95 +14,61 @@ var utils = require('../utils');
|
||||
module.exports = function (User) {
|
||||
User.auth = {};
|
||||
|
||||
User.auth.logAttempt = function (uid, ip, callback) {
|
||||
User.auth.logAttempt = async function (uid, ip) {
|
||||
if (!(parseInt(uid, 10) > 0)) {
|
||||
return setImmediate(callback);
|
||||
return;
|
||||
}
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
db.exists('lockout:' + uid, next);
|
||||
},
|
||||
function (exists, next) {
|
||||
const exists = await db.exists('lockout:' + uid);
|
||||
if (exists) {
|
||||
return callback(new Error('[[error:account-locked]]'));
|
||||
throw new Error('[[error:account-locked]]');
|
||||
}
|
||||
db.increment('loginAttempts:' + uid, next);
|
||||
},
|
||||
function (attempts, next) {
|
||||
const attempts = await db.increment('loginAttempts:' + uid);
|
||||
if (attempts <= meta.config.loginAttempts) {
|
||||
return db.pexpire('loginAttempts:' + uid, 1000 * 60 * 60, callback);
|
||||
return await db.pexpire('loginAttempts:' + uid, 1000 * 60 * 60);
|
||||
}
|
||||
// Lock out the account
|
||||
db.set('lockout:' + uid, '', next);
|
||||
},
|
||||
function (next) {
|
||||
await db.set('lockout:' + uid, '');
|
||||
var duration = 1000 * 60 * meta.config.lockoutDuration;
|
||||
|
||||
db.delete('loginAttempts:' + uid);
|
||||
db.pexpire('lockout:' + uid, duration);
|
||||
await db.delete('loginAttempts:' + uid);
|
||||
await db.pexpire('lockout:' + uid, duration);
|
||||
events.log({
|
||||
type: 'account-locked',
|
||||
uid: uid,
|
||||
ip: ip,
|
||||
});
|
||||
next(new Error('[[error:account-locked]]'));
|
||||
},
|
||||
], callback);
|
||||
throw new Error('[[error:account-locked]]');
|
||||
};
|
||||
|
||||
User.auth.getFeedToken = function (uid, callback) {
|
||||
if (parseInt(uid, 10) <= 0) {
|
||||
return setImmediate(callback);
|
||||
User.auth.getFeedToken = async function (uid) {
|
||||
if (!(parseInt(uid, 10) > 0)) {
|
||||
return;
|
||||
}
|
||||
var token;
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
db.getObjectField('user:' + uid, 'rss_token', next);
|
||||
},
|
||||
function (_token, next) {
|
||||
token = _token || utils.generateUUID();
|
||||
var _token = await db.getObjectField('user:' + uid, 'rss_token');
|
||||
const token = _token || utils.generateUUID();
|
||||
if (!_token) {
|
||||
User.setUserField(uid, 'rss_token', token, next);
|
||||
} else {
|
||||
next();
|
||||
await User.setUserField(uid, 'rss_token', token);
|
||||
}
|
||||
},
|
||||
function (next) {
|
||||
next(null, token);
|
||||
},
|
||||
], callback);
|
||||
return token;
|
||||
};
|
||||
|
||||
User.auth.clearLoginAttempts = function (uid) {
|
||||
db.delete('loginAttempts:' + uid);
|
||||
User.auth.clearLoginAttempts = async function (uid) {
|
||||
await db.delete('loginAttempts:' + uid);
|
||||
};
|
||||
|
||||
User.auth.resetLockout = function (uid, callback) {
|
||||
async.parallel([
|
||||
async.apply(db.delete, 'loginAttempts:' + uid),
|
||||
async.apply(db.delete, 'lockout:' + uid),
|
||||
], callback);
|
||||
User.auth.resetLockout = async function (uid) {
|
||||
await db.deleteAll([
|
||||
'loginAttempts:' + uid,
|
||||
'lockout:' + uid,
|
||||
]);
|
||||
};
|
||||
|
||||
User.auth.getSessions = function (uid, curSessionId, callback) {
|
||||
var _sids;
|
||||
|
||||
// curSessionId is optional
|
||||
if (arguments.length === 2 && typeof curSessionId === 'function') {
|
||||
callback = curSessionId;
|
||||
curSessionId = undefined;
|
||||
}
|
||||
|
||||
async.waterfall([
|
||||
async.apply(db.getSortedSetRevRange, 'uid:' + uid + ':sessions', 0, 19),
|
||||
function (sids, next) {
|
||||
_sids = sids;
|
||||
async.map(sids, db.sessionStore.get.bind(db.sessionStore), next);
|
||||
},
|
||||
function (sessions, next) {
|
||||
User.auth.getSessions = async function (uid, curSessionId) {
|
||||
const sids = await db.getSortedSetRevRange('uid:' + uid + ':sessions', 0, 19);
|
||||
let sessions = await async.map(sids, db.sessionStore.get.bind(db.sessionStore));
|
||||
sessions.forEach(function (sessionObj, idx) {
|
||||
if (sessionObj && sessionObj.meta) {
|
||||
sessionObj.meta.current = curSessionId === _sids[idx];
|
||||
sessionObj.meta.current = curSessionId === sids[idx];
|
||||
}
|
||||
});
|
||||
|
||||
@@ -114,19 +82,13 @@ module.exports = function (User) {
|
||||
parseInt(sessionObj.passport.user, 10) !== parseInt(uid, 10);
|
||||
|
||||
if (expired) {
|
||||
expiredSids.push(_sids[idx]);
|
||||
expiredSids.push(sids[idx]);
|
||||
}
|
||||
|
||||
return !expired;
|
||||
});
|
||||
await Promise.all(expiredSids.map(s => User.auth.revokeSession(s, uid)));
|
||||
|
||||
async.each(expiredSids, function (sid, next) {
|
||||
User.auth.revokeSession(sid, uid, next);
|
||||
}, function (err) {
|
||||
next(err, sessions);
|
||||
});
|
||||
},
|
||||
function (sessions, next) {
|
||||
sessions = sessions.map(function (sessObj) {
|
||||
if (sessObj.meta) {
|
||||
sessObj.meta.datetimeISO = new Date(sessObj.meta.datetime).toISOString();
|
||||
@@ -134,85 +96,53 @@ module.exports = function (User) {
|
||||
}
|
||||
return sessObj.meta;
|
||||
}).filter(Boolean);
|
||||
next(null, sessions);
|
||||
},
|
||||
], callback);
|
||||
return sessions;
|
||||
};
|
||||
|
||||
User.auth.addSession = function (uid, sessionId, callback) {
|
||||
callback = callback || function () {};
|
||||
User.auth.addSession = async function (uid, sessionId) {
|
||||
if (!(parseInt(uid, 10) > 0)) {
|
||||
return setImmediate(callback);
|
||||
return;
|
||||
}
|
||||
db.sortedSetAdd('uid:' + uid + ':sessions', Date.now(), sessionId, callback);
|
||||
await db.sortedSetAdd('uid:' + uid + ':sessions', Date.now(), sessionId);
|
||||
};
|
||||
|
||||
User.auth.revokeSession = function (sessionId, uid, callback) {
|
||||
winston.verbose('[user.auth] Revoking session ' + sessionId + ' for user ' + uid);
|
||||
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
const getSessionFromStore = util.promisify(function (sessionId, callback) {
|
||||
db.sessionStore.get(sessionId, function (err, sessionObj) {
|
||||
next(err, sessionObj || null);
|
||||
callback(err, sessionObj || null);
|
||||
});
|
||||
},
|
||||
function (sessionObj, next) {
|
||||
async.parallel([
|
||||
function (next) {
|
||||
});
|
||||
|
||||
User.auth.revokeSession = async function (sessionId, uid) {
|
||||
winston.verbose('[user.auth] Revoking session ' + sessionId + ' for user ' + uid);
|
||||
const sessionObj = await getSessionFromStore(sessionId);
|
||||
if (sessionObj && sessionObj.meta && sessionObj.meta.uuid) {
|
||||
db.deleteObjectField('uid:' + uid + ':sessionUUID:sessionId', sessionObj.meta.uuid, next);
|
||||
} else {
|
||||
next();
|
||||
await db.deleteObjectField('uid:' + uid + ':sessionUUID:sessionId', sessionObj.meta.uuid);
|
||||
}
|
||||
},
|
||||
await async.parallel([
|
||||
async.apply(db.sortedSetRemove, 'uid:' + uid + ':sessions', sessionId),
|
||||
async.apply(db.sessionStore.destroy.bind(db.sessionStore), sessionId),
|
||||
], function (err) {
|
||||
next(err);
|
||||
});
|
||||
},
|
||||
], callback);
|
||||
]);
|
||||
};
|
||||
|
||||
User.auth.revokeAllSessions = function (uid, callback) {
|
||||
async.waterfall([
|
||||
async.apply(db.getSortedSetRange, 'uid:' + uid + ':sessions', 0, -1),
|
||||
function (sids, next) {
|
||||
async.each(sids, function (sid, next) {
|
||||
User.auth.revokeSession(sid, uid, next);
|
||||
}, next);
|
||||
},
|
||||
], callback);
|
||||
User.auth.revokeAllSessions = async function (uid) {
|
||||
const sids = await db.getSortedSetRange('uid:' + uid + ':sessions', 0, -1);
|
||||
const promises = sids.map(s => User.auth.revokeSession(s, uid));
|
||||
await Promise.all(promises);
|
||||
};
|
||||
|
||||
User.auth.deleteAllSessions = function (callback) {
|
||||
var _ = require('lodash');
|
||||
batch.processSortedSet('users:joindate', function (uids, next) {
|
||||
var sessionKeys = uids.map(function (uid) {
|
||||
return 'uid:' + uid + ':sessions';
|
||||
});
|
||||
|
||||
var sessionUUIDKeys = uids.map(function (uid) {
|
||||
return 'uid:' + uid + ':sessionUUID:sessionId';
|
||||
});
|
||||
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
db.getSortedSetRange(sessionKeys, 0, -1, next);
|
||||
},
|
||||
function (sids, next) {
|
||||
sids = _.flatten(sids);
|
||||
async.parallel([
|
||||
async.apply(db.deleteAll, sessionUUIDKeys),
|
||||
async.apply(db.deleteAll, sessionKeys),
|
||||
User.auth.deleteAllSessions = async function () {
|
||||
await batch.processSortedSet('users:joindate', async function (uids) {
|
||||
const sessionKeys = uids.map(uid => 'uid:' + uid + ':sessions');
|
||||
const sessionUUIDKeys = uids.map(uid => 'uid:' + uid + ':sessionUUID:sessionId');
|
||||
const sids = _.flatten(await db.getSortedSetRange(sessionKeys, 0, -1));
|
||||
await async.parallel([
|
||||
async.apply(db.deleteAll, sessionKeys.concat(sessionUUIDKeys)),
|
||||
function (next) {
|
||||
async.each(sids, function (sid, next) {
|
||||
db.sessionStore.destroy(sid, next);
|
||||
}, next);
|
||||
},
|
||||
], next);
|
||||
},
|
||||
], next);
|
||||
}, { batch: 1000 }, callback);
|
||||
]);
|
||||
}, { batch: 1000 });
|
||||
};
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user