Settings = require('settings-sharelatex') logger = require("logger-sharelatex") Async = require('async') _ = require('underscore') UserSessionsRedis = require('./UserSessionsRedis') rclient = UserSessionsRedis.client() module.exports = UserSessionsManager = # mimic the key used by the express sessions _sessionKey: (sessionId) -> return "sess:#{sessionId}" trackSession: (user, sessionId, callback=(err)-> ) -> if !user? logger.log {sessionId}, "no user to track, returning" return callback(null) if !sessionId? logger.log {user_id: user._id}, "no sessionId to track, returning" return callback(null) logger.log {user_id: user._id, sessionId}, "onLogin handler" sessionSetKey = UserSessionsRedis.sessionSetKey(user) value = UserSessionsManager._sessionKey sessionId rclient.multi() .sadd(sessionSetKey, value) .expire(sessionSetKey, "#{Settings.cookieSessionLength}") .exec (err, response) -> if err? logger.err {err, user_id: user._id, sessionSetKey}, "error while adding session key to UserSessions set" return callback(err) UserSessionsManager._checkSessions(user, () ->) callback() untrackSession: (user, sessionId, callback=(err)-> ) -> if !user? logger.log {sessionId}, "no user to untrack, returning" return callback(null) if !sessionId? logger.log {user_id: user._id}, "no sessionId to untrack, returning" return callback(null) logger.log {user_id: user._id, sessionId}, "onLogout handler" sessionSetKey = UserSessionsRedis.sessionSetKey(user) value = UserSessionsManager._sessionKey sessionId rclient.multi() .srem(sessionSetKey, value) .expire(sessionSetKey, "#{Settings.cookieSessionLength}") .exec (err, response) -> if err? logger.err {err, user_id: user._id, sessionSetKey}, "error while removing session key from UserSessions set" return callback(err) UserSessionsManager._checkSessions(user, () ->) callback() getAllUserSessions: (user, exclude, callback=(err, sessionKeys)->) -> exclude = _.map(exclude, UserSessionsManager._sessionKey) sessionSetKey = UserSessionsRedis.sessionSetKey(user) rclient.smembers sessionSetKey, (err, sessionKeys) -> if err? logger.err user_id: user._id, "error getting all session keys for user from redis" return callback(err) sessionKeys = _.filter sessionKeys, (k) -> !(_.contains(exclude, k)) if sessionKeys.length == 0 logger.log {user_id: user._id}, "no other sessions found, returning" return callback(null, []) Async.mapSeries sessionKeys, ((k, cb) -> rclient.get(k, cb)), (err, sessions) -> if err? logger.err {user_id: user._id}, "error getting all sessions for user from redis" return callback(err) result = [] for session in sessions if session is null continue session = JSON.parse(session) session_user = session?.user or session?.passport?.user result.push { ip_address: session_user.ip_address, session_created: session_user.session_created } return callback(null, result) revokeAllUserSessions: (user, retain, callback=(err)->) -> if !retain? retain = [] retain = retain.map((i) -> UserSessionsManager._sessionKey(i)) if !user? logger.log {}, "no user to revoke sessions for, returning" return callback(null) logger.log {user_id: user._id}, "revoking all existing sessions for user" sessionSetKey = UserSessionsRedis.sessionSetKey(user) rclient.smembers sessionSetKey, (err, sessionKeys) -> if err? logger.err {err, user_id: user._id, sessionSetKey}, "error getting contents of UserSessions set" return callback(err) keysToDelete = _.filter(sessionKeys, (k) -> k not in retain) if keysToDelete.length == 0 logger.log {user_id: user._id}, "no sessions in UserSessions set to delete, returning" return callback(null) logger.log {user_id: user._id, count: keysToDelete.length}, "deleting sessions for user" deletions = keysToDelete.map (k) -> (cb) -> rclient.del k, cb Async.series deletions, (err, _result) -> if err? logger.err {err, user_id: user._id, sessionSetKey}, "errror revoking all sessions for user" return callback(err) rclient.srem sessionSetKey, keysToDelete, (err) -> if err? logger.err {err, user_id: user._id, sessionSetKey}, "error removing session set for user" return callback(err) callback(null) touch: (user, callback=(err)->) -> if !user logger.log {}, "no user to touch sessions for, returning" return callback(null) sessionSetKey = UserSessionsRedis.sessionSetKey(user) rclient.expire sessionSetKey, "#{Settings.cookieSessionLength}", (err, response) -> if err? logger.err {err, user_id: user._id}, "error while updating ttl on UserSessions set" return callback(err) callback(null) _checkSessions: (user, callback=(err)->) -> if !user logger.log {}, "no user, returning" return callback(null) logger.log {user_id: user._id}, "checking sessions for user" sessionSetKey = UserSessionsRedis.sessionSetKey(user) rclient.smembers sessionSetKey, (err, sessionKeys) -> if err? logger.err {err, user_id: user._id, sessionSetKey}, "error getting contents of UserSessions set" return callback(err) logger.log {user_id: user._id, count: sessionKeys.length}, "checking sessions for user" Async.series( sessionKeys.map( (key) -> (next) -> rclient.get key, (err, val) -> if err? return next(err) if !val? logger.log {user_id: user._id, key}, ">> removing key from UserSessions set" rclient.srem sessionSetKey, key, (err, result) -> if err? return next(err) return next(null) else next() ) , (err, results) -> logger.log {user_id: user._id}, "done checking sessions for user" return callback(err) )