diff --git a/install/data/defaults.json b/install/data/defaults.json index 8fd574fb3a..ec5137ac49 100644 --- a/install/data/defaults.json +++ b/install/data/defaults.json @@ -133,5 +133,6 @@ "timeagoCutoff": 30, "necroThreshold": 7, "categoryWatchState": "watching", - "submitPluginUsage": 1 + "submitPluginUsage": 1, + "maxUserSessions": 10 } \ No newline at end of file diff --git a/public/language/en-GB/admin/settings/cookies.json b/public/language/en-GB/admin/settings/cookies.json index a6244febdd..1ffd2dced4 100644 --- a/public/language/en-GB/admin/settings/cookies.json +++ b/public/language/en-GB/admin/settings/cookies.json @@ -8,5 +8,6 @@ "consent.blank-localised-default": "Leave blank to use NodeBB localised defaults", "settings": "Settings", "cookie-domain": "Session cookie domain", + "max-user-sessions": "Max active sessions per user", "blank-default": "Leave blank for default" } \ No newline at end of file diff --git a/src/user/auth.js b/src/user/auth.js index da8551d831..10a943b088 100644 --- a/src/user/auth.js +++ b/src/user/auth.js @@ -107,9 +107,18 @@ module.exports = function (User) { return; } await cleanExpiredSessions(uid); + await revokeSessionsAboveThreshold(uid, meta.config.maxUserSessions); await db.sortedSetAdd('uid:' + uid + ':sessions', Date.now(), sessionId); }; + async function revokeSessionsAboveThreshold(uid, maxUserSessions) { + 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))); + } + } + User.auth.revokeSession = async function (sessionId, uid) { winston.verbose('[user.auth] Revoking session ' + sessionId + ' for user ' + uid); const sessionObj = await getSessionFromStore(sessionId); diff --git a/src/views/admin/settings/cookies.tpl b/src/views/admin/settings/cookies.tpl index 94cec56bb6..367c86d257 100644 --- a/src/views/admin/settings/cookies.tpl +++ b/src/views/admin/settings/cookies.tpl @@ -53,6 +53,14 @@
++ [[admin/settings/cookies:blank-default]] +
+