mirror of
				https://github.com/NodeBB/NodeBB.git
				synced 2025-10-31 11:05:54 +01:00 
			
		
		
		
	feat: revoke user sessions above threshold (#8731)
* feat: revoke user sessions above threshold * fix: removed translations from en-US * fix: defined default maxUserSessions in install\data\defaults.json
This commit is contained in:
		| @@ -133,5 +133,6 @@ | |||||||
|     "timeagoCutoff": 30, |     "timeagoCutoff": 30, | ||||||
|     "necroThreshold": 7, |     "necroThreshold": 7, | ||||||
|     "categoryWatchState": "watching", |     "categoryWatchState": "watching", | ||||||
|     "submitPluginUsage": 1 |     "submitPluginUsage": 1, | ||||||
|  |     "maxUserSessions": 10 | ||||||
| } | } | ||||||
| @@ -8,5 +8,6 @@ | |||||||
| 	"consent.blank-localised-default": "Leave blank to use NodeBB localised defaults", | 	"consent.blank-localised-default": "Leave blank to use NodeBB localised defaults", | ||||||
| 	"settings": "Settings", | 	"settings": "Settings", | ||||||
| 	"cookie-domain": "Session cookie domain", | 	"cookie-domain": "Session cookie domain", | ||||||
|  | 	"max-user-sessions": "Max active sessions per user", | ||||||
| 	"blank-default": "Leave blank for default" | 	"blank-default": "Leave blank for default" | ||||||
| } | } | ||||||
| @@ -107,9 +107,18 @@ module.exports = function (User) { | |||||||
| 			return; | 			return; | ||||||
| 		} | 		} | ||||||
| 		await cleanExpiredSessions(uid); | 		await cleanExpiredSessions(uid); | ||||||
|  | 		await revokeSessionsAboveThreshold(uid, meta.config.maxUserSessions); | ||||||
| 		await db.sortedSetAdd('uid:' + uid + ':sessions', Date.now(), sessionId); | 		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) { | 	User.auth.revokeSession = async function (sessionId, uid) { | ||||||
| 		winston.verbose('[user.auth] Revoking session ' + sessionId + ' for user ' + uid); | 		winston.verbose('[user.auth] Revoking session ' + sessionId + ' for user ' + uid); | ||||||
| 		const sessionObj = await getSessionFromStore(sessionId); | 		const sessionObj = await getSessionFromStore(sessionId); | ||||||
|   | |||||||
| @@ -53,6 +53,14 @@ | |||||||
| 				</p> | 				</p> | ||||||
| 			</div> | 			</div> | ||||||
|  |  | ||||||
|  | 			<div class="form-group"> | ||||||
|  | 				<label for="maxUserSessions">[[admin/settings/cookies:max-user-sessions]]</label> | ||||||
|  | 				<input class="form-control" id="maxUserSessions" type="number" placeholder="10" data-field="maxUserSessions" /><br /> | ||||||
|  | 				<p class="help-block"> | ||||||
|  | 					[[admin/settings/cookies:blank-default]] | ||||||
|  | 				</p> | ||||||
|  | 			</div> | ||||||
|  |  | ||||||
| 			<div class="form-group"> | 			<div class="form-group"> | ||||||
| 				<button id="delete-all-sessions" class="btn btn-danger">Revoke All Sessions</button> | 				<button id="delete-all-sessions" class="btn btn-danger">Revoke All Sessions</button> | ||||||
| 				<p class="help-block"> | 				<p class="help-block"> | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user