Files
NodeBB/src/user/auth.js

167 lines
5.4 KiB
JavaScript
Raw Normal View History

2014-07-29 00:42:33 -04:00
'use strict';
2019-10-03 23:31:42 -04:00
const winston = require('winston');
const validator = require('validator');
2019-07-11 23:43:00 -04:00
const util = require('util');
const _ = require('lodash');
2019-10-03 23:31:42 -04:00
const db = require('../database');
const meta = require('../meta');
const events = require('../events');
const batch = require('../batch');
const utils = require('../utils');
2014-05-11 11:45:20 -04:00
module.exports = function (User) {
2014-05-11 11:45:20 -04:00
User.auth = {};
2019-07-11 23:43:00 -04:00
User.auth.logAttempt = async function (uid, ip) {
2018-12-10 14:40:11 -05:00
if (!(parseInt(uid, 10) > 0)) {
2019-07-11 23:43:00 -04:00
return;
}
2021-02-03 23:59:08 -07:00
const exists = await db.exists(`lockout:${uid}`);
2019-07-11 23:43:00 -04:00
if (exists) {
throw new Error('[[error:account-locked]]');
}
2021-02-03 23:59:08 -07:00
const attempts = await db.increment(`loginAttempts:${uid}`);
2019-07-11 23:43:00 -04:00
if (attempts <= meta.config.loginAttempts) {
2021-02-03 23:59:08 -07:00
return await db.pexpire(`loginAttempts:${uid}`, 1000 * 60 * 60);
2019-07-11 23:43:00 -04:00
}
// Lock out the account
2021-02-03 23:59:08 -07:00
await db.set(`lockout:${uid}`, '');
2019-10-03 23:31:42 -04:00
const duration = 1000 * 60 * meta.config.lockoutDuration;
2019-07-11 23:43:00 -04:00
2021-02-03 23:59:08 -07:00
await db.delete(`loginAttempts:${uid}`);
await db.pexpire(`lockout:${uid}`, duration);
await events.log({
2019-07-11 23:43:00 -04:00
type: 'account-locked',
uid: uid,
ip: ip,
});
throw new Error('[[error:account-locked]]');
2014-05-11 11:45:20 -04:00
};
2019-07-11 23:43:00 -04:00
User.auth.getFeedToken = async function (uid) {
if (!(parseInt(uid, 10) > 0)) {
return;
}
2021-02-03 23:59:08 -07:00
const _token = await db.getObjectField(`user:${uid}`, 'rss_token');
2019-07-11 23:43:00 -04:00
const token = _token || utils.generateUUID();
if (!_token) {
await User.setUserField(uid, 'rss_token', token);
2017-06-20 16:12:55 -04:00
}
2019-07-11 23:43:00 -04:00
return token;
2017-06-20 16:12:55 -04:00
};
2019-07-11 23:43:00 -04:00
User.auth.clearLoginAttempts = async function (uid) {
2021-02-03 23:59:08 -07:00
await db.delete(`loginAttempts:${uid}`);
2014-05-11 11:45:20 -04:00
};
2014-08-14 08:34:38 -04:00
2019-07-11 23:43:00 -04:00
User.auth.resetLockout = async function (uid) {
await db.deleteAll([
2021-02-03 23:59:08 -07:00
`loginAttempts:${uid}`,
`lockout:${uid}`,
2019-07-11 23:43:00 -04:00
]);
2014-10-08 12:18:32 -04:00
};
2021-02-04 02:07:29 -07:00
const getSessionFromStore = util.promisify(
(sid, callback) => db.sessionStore.get(sid, (err, sessObj) => callback(err, sessObj || null))
);
const sessionStoreDestroy = util.promisify(
(sid, callback) => db.sessionStore.destroy(sid, err => callback(err))
);
2019-10-03 23:31:42 -04:00
2019-07-11 23:43:00 -04:00
User.auth.getSessions = async function (uid, curSessionId) {
await cleanExpiredSessions(uid);
2021-02-03 23:59:08 -07:00
const sids = await db.getSortedSetRevRange(`uid:${uid}:sessions`, 0, 19);
2019-10-03 23:31:42 -04:00
let sessions = await Promise.all(sids.map(sid => getSessionFromStore(sid)));
2021-02-04 00:01:39 -07:00
sessions = sessions.map((sessObj, idx) => {
if (sessObj && sessObj.meta) {
sessObj.meta.current = curSessionId === sids[idx];
sessObj.meta.datetimeISO = new Date(sessObj.meta.datetime).toISOString();
sessObj.meta.ip = validator.escape(String(sessObj.meta.ip));
2019-07-11 23:43:00 -04:00
}
return sessObj && sessObj.meta;
}).filter(Boolean);
return sessions;
};
2019-07-11 23:43:00 -04:00
async function cleanExpiredSessions(uid) {
2021-02-03 23:59:08 -07:00
const uuidMapping = await db.getObject(`uid:${uid}:sessionUUID:sessionId`);
2020-05-16 22:17:20 -04:00
if (!uuidMapping) {
return;
}
const expiredUUIDs = [];
2019-10-03 23:31:42 -04:00
const expiredSids = [];
await Promise.all(Object.keys(uuidMapping).map(async (uuid) => {
const sid = uuidMapping[uuid];
const sessionObj = await getSessionFromStore(sid);
2019-10-03 23:31:42 -04:00
const expired = !sessionObj || !sessionObj.hasOwnProperty('passport') ||
2021-11-18 16:42:18 -05:00
!sessionObj.passport.hasOwnProperty('user') ||
2019-07-11 23:43:00 -04:00
parseInt(sessionObj.passport.user, 10) !== parseInt(uid, 10);
if (expired) {
expiredUUIDs.push(uuid);
expiredSids.push(sid);
2019-07-11 23:43:00 -04:00
}
}));
2021-02-03 23:59:08 -07:00
await db.deleteObjectFields(`uid:${uid}:sessionUUID:sessionId`, expiredUUIDs);
await db.sortedSetRemove(`uid:${uid}:sessions`, expiredSids);
}
User.auth.addSession = async function (uid, sessionId, uuid) {
if (!(parseInt(uid, 10) > 0)) {
2019-07-11 23:43:00 -04:00
return;
}
await cleanExpiredSessions(uid);
await Promise.all([
db.sortedSetAdd(`uid:${uid}:sessions`, Date.now(), sessionId),
db.setObjectField(`uid:${uid}:sessionUUID:sessionId`, uuid, sessionId),
]);
await revokeSessionsAboveThreshold(uid, meta.config.maxUserSessions);
};
async function revokeSessionsAboveThreshold(uid, maxUserSessions) {
2021-02-03 23:59:08 -07:00
const activeSessions = await db.getSortedSetRange(`uid:${uid}:sessions`, 0, -1);
if (activeSessions.length > maxUserSessions) {
const sessionsToRevoke = activeSessions.slice(0, activeSessions.length - maxUserSessions);
await Promise.all(sessionsToRevoke.map(sessionId => User.auth.revokeSession(sessionId, uid)));
}
}
2019-07-11 23:43:00 -04:00
User.auth.revokeSession = async function (sessionId, uid) {
2021-02-03 23:59:08 -07:00
winston.verbose(`[user.auth] Revoking session ${sessionId} for user ${uid}`);
2019-07-11 23:43:00 -04:00
const sessionObj = await getSessionFromStore(sessionId);
if (sessionObj && sessionObj.meta && sessionObj.meta.uuid) {
2021-02-03 23:59:08 -07:00
await db.deleteObjectField(`uid:${uid}:sessionUUID:sessionId`, sessionObj.meta.uuid);
2019-07-11 23:43:00 -04:00
}
2019-10-03 23:31:42 -04:00
await Promise.all([
2021-02-03 23:59:08 -07:00
db.sortedSetRemove(`uid:${uid}:sessions`, sessionId),
2019-10-03 23:31:42 -04:00
sessionStoreDestroy(sessionId),
2019-07-11 23:43:00 -04:00
]);
};
User.auth.revokeAllSessions = async function (uids, except) {
2019-09-07 18:22:03 -04:00
uids = Array.isArray(uids) ? uids : [uids];
2021-02-03 23:59:08 -07:00
const sids = await db.getSortedSetsMembers(uids.map(uid => `uid:${uid}:sessions`));
2019-09-07 18:22:03 -04:00
const promises = [];
uids.forEach((uid, index) => {
const ids = sids[index].filter(id => id !== except);
if (ids.length) {
promises.push(ids.map(s => User.auth.revokeSession(s, uid)));
}
2019-09-07 18:22:03 -04:00
});
2019-07-11 23:43:00 -04:00
await Promise.all(promises);
};
2016-12-15 14:47:42 +03:00
2019-07-11 23:43:00 -04:00
User.auth.deleteAllSessions = async function () {
2021-02-04 00:01:39 -07:00
await batch.processSortedSet('users:joindate', async (uids) => {
2021-02-03 23:59:08 -07:00
const sessionKeys = uids.map(uid => `uid:${uid}:sessions`);
const sessionUUIDKeys = uids.map(uid => `uid:${uid}:sessionUUID:sessionId`);
2019-07-11 23:43:00 -04:00
const sids = _.flatten(await db.getSortedSetRange(sessionKeys, 0, -1));
2019-10-03 23:31:42 -04:00
2019-10-04 19:19:01 -04:00
await Promise.all([
2019-10-03 23:31:42 -04:00
db.deleteAll(sessionKeys.concat(sessionUUIDKeys)),
...sids.map(sid => sessionStoreDestroy(sid)),
2019-10-04 19:19:01 -04:00
]);
2019-07-11 23:43:00 -04:00
}, { batch: 1000 });
2016-12-15 14:47:42 +03:00
};
2017-02-18 02:30:48 -07:00
};