ability to show your login sessions, ref #353

indiachan-spamvector
Thomas Lynch 2 years ago
parent ef0ef8b606
commit f2b4ec2dd2
  1. 3
      controllers/pages.js
  2. 36
      helpers/usesession.js
  3. 1
      models/pages/index.js
  4. 18
      models/pages/sessions.js
  5. 1
      package-lock.json
  6. 3
      package.json
  7. 35
      redis.js
  8. 1
      views/pages/account.pug
  9. 27
      views/pages/sessions.pug

@ -21,7 +21,7 @@ const express = require('express')
manageBoard, manageThread, manageLogs, manageCatalog, manageCustomPages, manageStaff, editStaff } = require(__dirname+'/../models/pages/manage/')
, { globalManageSettings, globalManageReports, globalManageBans, globalManageBoards, editNews, editAccount, editRole,
globalManageRecent, globalManageAccounts, globalManageNews, globalManageLogs, globalManageRoles } = require(__dirname+'/../models/pages/globalmanage/')
, { changePassword, blockBypass, home, register, login, create, myPermissions,
, { changePassword, blockBypass, home, register, login, create, myPermissions, sessions,
board, catalog, banners, randombanner, news, captchaPage, overboard, overboardCatalog,
captcha, thread, modlog, modloglist, account, boardlist, customPage, csrfPage } = require(__dirname+'/../models/pages/')
, threadParamConverter = paramConverter({ processThreadIdParam: true })
@ -118,6 +118,7 @@ router.get('/bypass_minimal.html', setMinimal, blockBypass); //block bypass page
//accounts
router.get('/account.html', useSession, sessionRefresh, isLoggedIn, calcPerms, csrf, account); //page showing boards you are mod/owner of, links to password rese, logout, etc
router.get('/sessions.html', useSession, sessionRefresh, isLoggedIn, calcPerms, csrf, sessions);
router.get('/mypermissions.html', useSession, sessionRefresh, isLoggedIn, calcPerms, csrf, myPermissions);
router.get('/login.html', login);
router.get('/register.html', register);

@ -1,6 +1,7 @@
'use strict';
const session = require('express-session')
, uid = require('uid-safe').sync
, redisStore = require('connect-redis')(session)
, { cookieSecret } = require(__dirname+'/../configs/secrets.js')
, config = require(__dirname+'/../config.js')
@ -14,19 +15,28 @@ module.exports = (req, res, next) => {
const { secureCookies } = config.get;
const proto = req.headers['x-forwarded-proto'];
const sessionMiddleware = sessionMiddlewareCache[proto] || (sessionMiddlewareCache[proto] = session({
secret: cookieSecret,
store: new redisStore({
client: redisClient,
}),
resave: false,
saveUninitialized: false,
rolling: true,
cookie: {
httpOnly: true,
secure: secureCookies && production && (proto === 'https'),
sameSite: 'strict',
maxAge: DAY,
}
secret: cookieSecret,
store: new redisStore({
client: redisClient,
}),
resave: false,
saveUninitialized: false,
rolling: true,
cookie: {
httpOnly: true,
secure: secureCookies && production && (proto === 'https'),
sameSite: 'strict',
maxAge: DAY,
},
genid: (req) => {
//add user identifier to session id
//https://github.com/expressjs/session/blob/master/index.js#L518
let id = uid(24);
if (req.path === '/login' && req.body.username) {
id += `:${req.body.username}`;
};
return id;
},
}));
return sessionMiddleware(req, res, next);

@ -5,6 +5,7 @@ module.exports = {
blockBypass: require(__dirname+'/blockbypass.js'),
register: require(__dirname+'/register.js'),
account: require(__dirname+'/account.js'),
sessions: require(__dirname+'/sessions.js'),
myPermissions: require(__dirname+'/mypermissions.js'),
home: require(__dirname+'/home.js'),
login: require(__dirname+'/login.js'),

@ -0,0 +1,18 @@
'use strict';
const redis = require(__dirname+'/../../redis.js');
module.exports = async (req, res, next) => {
const sessions = await redis.getPattern(`sess:*:${res.locals.user.username}`);
res
.set('Cache-Control', 'private, max-age=5')
.render('sessions', {
user: res.locals.user,
permissions: res.locals.permissions,
currentSessionKey: `sess:${req.sessionID}`,
sessions,
});
}

1
package-lock.json generated

@ -53,6 +53,7 @@
"socket.io": "^4.3.0",
"socket.io-redis": "^6.1.1",
"socks-proxy-agent": "^6.1.0",
"uid-safe": "^2.1.5",
"unix-crypt-td-js": "^1.1.4"
}
},

@ -7,6 +7,7 @@
"dependencies": {
"@fatchan/express-fileupload": "^1.3.1",
"bcrypt": "^5.0.1",
"big-bitfield": "git+https://gitgud.io/fatchan/big-bitfield.git",
"bull": "^3.29.3",
"cache-pug-templates": "^2.0.3",
"connect-redis": "^6.0.0",
@ -22,7 +23,6 @@
"fs": "0.0.1-security",
"fs-extra": "^10.0.0",
"gm": "git+https://gitgud.io/fatchan/gm.git",
"big-bitfield": "git+https://gitgud.io/fatchan/big-bitfield.git",
"gulp": "^4.0.2",
"gulp-clean-css": "^4.3.0",
"gulp-concat": "^2.6.1",
@ -49,6 +49,7 @@
"socket.io": "^4.3.0",
"socket.io-redis": "^6.1.1",
"socks-proxy-agent": "^6.1.0",
"uid-safe": "^2.1.5",
"unix-crypt-td-js": "^1.1.4"
},
"scripts": {

@ -96,6 +96,41 @@ module.exports = {
}
},
getPattern: (pattern) => {
return new Promise((resolve, reject) => {
const stream = sharedClient.scanStream({
match: pattern
});
const dataMap = {};
stream.on('data', async (keys) => {
if (keys.length > 0) {
stream.pause(); //dont want end() called during this, its async
const pipeline = sharedClient.pipeline();
for (let i = 0; i < keys.length; i++) {
pipeline.get(keys[i]);
}
let results;
try {
results = await pipeline.exec();
} catch (e) {
stream.destroy();
reject(e);
}
for (let i = 0; i < results.length; i++) {
dataMap[keys[i]] = JSON.parse(results[i][1]);
}
stream.resume();
}
});
stream.on('end', () => {
resolve(dataMap);
});
stream.on('error', (err) => {
reject(err);
});
});
},
deletePattern: (pattern) => {
return new Promise((resolve, reject) => {
const stream = sharedClient.scanStream({

@ -16,6 +16,7 @@ block content
li: a(href='/register.html') Register an account
li: a(href='/changepassword.html') Change password
li: a(href='/mypermissions.html') My Permissions
li: a(href='/sessions.html') Login sessions
form(action='/forms/logout' method='post')
input(type='submit' value='Log out')

@ -0,0 +1,27 @@
extends ../layout.pug
block head
title Login Sessions
block content
.board-header
h1.board-title Login Sessions
br
hr(size=1)
h4.mv-5 Login sessions:
form.form-post.nogrow(action=`/forms/deletesessions` method='POST' enctype='application/x-www-form-urlencoded')
input(type='hidden' name='_csrf' value=csrf)
.table-container.flex-left.text-center
table
tr
th
th ID
th Expires
each session, sessionId in sessions
tr(class=(sessionId === currentSessionKey ? 'bold' : ''))
td: input(type='checkbox', name='checkedsessionids' value=sessionId)
td #{sessionId} #{sessionId === currentSessionKey ? '(current)' : ''}
- const expiryDate = new Date(session.cookie.expires)
td: time.reltime(datetime=expiryDate.toISOString()) #{expiryDate.toLocaleString(undefined, {hourCycle:'h23'})}
h4.mv-5 Delete Selected:
input(type='submit', value='delete')
Loading…
Cancel
Save