1
0
Fork 0
mirror of https://github.com/overleaf/overleaf.git synced 2025-04-02 18:25:42 +00:00

Merge pull request from overleaf/bg-session-mitigation-redis-store-metrics

add CustomSessionStore class to handle session metrics and logging

GitOrigin-RevId: 49d4cda9fd94a8801adb33e894be239dc38ad544
This commit is contained in:
Brian Gough 2024-04-02 11:41:39 +01:00 committed by Copybot
parent b84e76b201
commit cdf31ed91c
2 changed files with 86 additions and 36 deletions
services/web/app/src/infrastructure

View file

@ -0,0 +1,84 @@
const session = require('express-session')
const RedisStore = require('connect-redis')(session)
const metrics = require('@overleaf/metrics')
const logger = require('@overleaf/logger')
const SessionManager = require('../Features/Authentication/SessionManager')
const MAX_SESSION_SIZE_THRESHOLD = 4096
// Define a custom session store to record session metrics and log large
// anonymous sessions for debugging purposes
class CustomSessionStore extends RedisStore {
static largestSessionSize = 3 * 1024 // ignore sessions smaller than 3KB
static metric(method, sess) {
let type // type of session: 'logged-in', 'anonymous', or 'na' (not available)
if (sess) {
type = SessionManager.isUserLoggedIn(sess) ? 'logged-in' : 'anonymous'
} else {
type = 'na'
}
const size = sess ? JSON.stringify(sess).length : 0
// record the number of redis session operations
metrics.inc('session.store.count', 1, {
method,
type,
status: size > MAX_SESSION_SIZE_THRESHOLD ? 'oversize' : 'normal',
})
// record the redis session bandwidth for get/set operations
if (method === 'get' || method === 'set') {
metrics.count('session.store.bytes', size, { method, type })
}
// log the largest anonymous session seen so far
if (type === 'anonymous' && size > CustomSessionStore.largestSessionSize) {
CustomSessionStore.largestSessionSize = size
logger.warn(
{ redactedSession: redactSession(sess), largestSessionSize: size },
'largest session size seen'
)
}
}
// Override the get, set, touch, and destroy methods to record metrics
get(sid, cb) {
super.get(sid, (err, ...args) => {
if (args[0]) {
CustomSessionStore.metric('get', args[0])
}
cb(err, ...args)
})
}
set(sid, sess, cb) {
CustomSessionStore.metric('set', sess)
super.set(sid, sess, cb)
}
touch(sid, sess, cb) {
CustomSessionStore.metric('touch', sess)
super.touch(sid, sess, cb)
}
destroy(sid, cb) {
// for the destroy method we don't have access to the session object itself
CustomSessionStore.metric('destroy')
super.destroy(sid, cb)
}
}
// Helper function to return a redacted version of session object
// so we can identify the largest keys without exposing sensitive
// data
function redactSession(sess) {
// replace all string values with '***' of the same length
return JSON.parse(
JSON.stringify(sess, (key, value) => {
if (typeof value === 'string') {
return '*'.repeat(value.length)
}
return value
})
)
}
module.exports = CustomSessionStore

View file

@ -16,7 +16,7 @@ const SessionAutostartMiddleware = require('./SessionAutostartMiddleware')
const SessionStoreManager = require('./SessionStoreManager')
const AnalyticsManager = require('../Features/Analytics/AnalyticsManager')
const session = require('express-session')
const RedisStore = require('connect-redis')(session)
const CustomSessionStore = require('./CustomSessionStore')
const bodyParser = require('./BodyParserWrapper')
const methodOverride = require('method-override')
const cookieParser = require('cookie-parser')
@ -48,42 +48,8 @@ const STATIC_CACHE_AGE = Settings.cacheStaticAssets
? oneDayInMilliseconds * 365
: 0
// Define a custom session store to record the largest session sizes
// seen for anonymous users
class CustomSessionStore extends RedisStore {
static largestSessionSize = 2048 // ignore sessions smaller than 2KB
static trackAnonymousSessionSize(sess) {
const isLoggedIn = SessionManager.isUserLoggedIn(sess)
if (!isLoggedIn) {
const len = JSON.stringify(sess, (key, value) => {
if (key === 'hashedPassword' && value?.length > 0) {
return '*'.repeat(value.length)
}
return value
}).length
if (len > CustomSessionStore.largestSessionSize) {
CustomSessionStore.largestSessionSize = len
logger.warn({ sess, sessionSize: len }, 'largest session size seen')
}
}
}
set(sid, sess, cb) {
CustomSessionStore.trackAnonymousSessionSize(sess)
super.set(sid, sess, cb)
}
touch(sid, sess, cb) {
CustomSessionStore.trackAnonymousSessionSize(sess)
super.touch(sid, sess, cb)
}
}
// Init the session store
const sessionStore = new CustomSessionStore(
new RedisStore({ client: sessionsRedisClient })
)
const sessionStore = new CustomSessionStore({ client: sessionsRedisClient })
const app = express()