'use strict'; var async = require('async'), winston = require('winston'), db = require('../database'), meta = require('../meta'), events = require('../events'); module.exports = function(User) { User.auth = {}; User.auth.logAttempt = function(uid, ip, callback) { db.exists('lockout:' + uid, function(err, exists) { if (err) { return callback(err); } if (exists) { return callback(new Error('[[error:account-locked]]')); } db.increment('loginAttempts:' + uid, function(err, attempts) { if (err) { return callback(err); } if ((meta.config.loginAttempts || 5) < attempts) { // Lock out the account db.set('lockout:' + uid, '', function(err) { if (err) { return callback(err); } var duration = 1000 * 60 * (meta.config.lockoutDuration || 60); db.delete('loginAttempts:' + uid); db.pexpire('lockout:' + uid, duration); events.log({ type: 'account-locked', uid: uid, ip: ip }); callback(new Error('[[error:account-locked]]')); }); } else { db.pexpire('loginAttempts:' + uid, 1000 * 60 * 60); callback(); } }); }); }; User.auth.clearLoginAttempts = function(uid) { 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.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.getSortedSetRange, 'uid:' + uid + ':sessions', 0, -1), function(sids, next) { _sids = sids; async.map(sids, db.sessionStore.get.bind(db.sessionStore), next); }, function(sessions, next) { sessions = sessions.map(function(sessionObj, idx) { sessionObj.meta.current = curSessionId === _sids[idx]; return sessionObj; }); // Revoke any sessions that have expired, return filtered list var expiredSids = [], expired; sessions = sessions.filter(function(sessionObj, idx) { expired = !sessionObj || !sessionObj.hasOwnProperty('passport') || !sessionObj.passport.hasOwnProperty('user') || parseInt(sessionObj.passport.user, 10) !== parseInt(uid, 10); if (expired) { expiredSids.push(_sids[idx]); } return !expired; }, []) async.each(expiredSids, function(sid, next) { User.auth.revokeSession(sid, uid, next); }, function(err) { next(null, sessions); }); } ], function(err, sessions) { callback(err, sessions ? sessions.map(function(sessObj) { sessObj.meta.datetimeISO = new Date(sessObj.meta.datetime).toISOString(); return sessObj.meta; }) : undefined); }); }; User.auth.addSession = function(uid, sessionId, callback) { callback = callback || function() {}; db.sortedSetAdd('uid:' + uid + ':sessions', Date.now(), sessionId, callback); }; User.auth.revokeSession = function(sessionId, uid, callback) { winston.verbose('[user.auth] Revoking session ' + sessionId + ' for user ' + uid); db.sessionStore.get(sessionId, function(err, sessionObj) { if (err) { return callback(err); } async.parallel([ function (next) { if (sessionObj && sessionObj.meta && sessionObj.meta.uuid) { db.deleteObjectField('uid:' + uid + ':sessionUUID:sessionId', sessionObj.meta.uuid, next); } else { next(); } }, async.apply(db.sortedSetRemove, 'uid:' + uid + ':sessions', sessionId), async.apply(db.sessionStore.destroy.bind(db.sessionStore), sessionId) ], callback); }); }; User.auth.revokeAllSessions = function(uid, callback) { async.waterfall([ async.apply(db.getSortedSetRange, 'uid:' + uid + ':sessions', 0, -1), function(sids, next) { async.each(sids, User.auth.revokeSession, next); } ], callback); }; };