overleaf/services/web/test/acceptance/src/CookieMetricsTests.js
Brian Gough 38ac00ba13 Merge pull request #18775 from overleaf/bg-cookie-session-metrics-middleware
add middleware to record session cookie metrics in web

GitOrigin-RevId: f4404455e219d2071d6f0b39e657e9219b7d1c70
2024-06-13 08:04:16 +00:00

255 lines
8 KiB
JavaScript

const Settings = require('@overleaf/settings')
const { expect } = require('chai')
const User = require('./helpers/User').promises
const {
promises: { getMetric },
resetMetrics,
} = require('./helpers/metrics')
const cookieSignature = require('cookie-signature')
async function getSessionCookieMetric(status) {
return getMetric(
line =>
line.includes('session_cookie') && line.includes(`status="${status}"`)
)
}
/*
* Modifies the session cookie by removing the existing signature and signing
* the cookie with a new secret.
*/
function modifyCookieSignature(originalCookie, newSecret) {
const [sessionKey] = originalCookie.value.slice(2).split('.')
return cookieSignature.sign(sessionKey, newSecret)
}
describe('Session cookie', function () {
before(async function () {
this.user = new User()
})
describe('with no session cookie', function () {
before(async function () {
resetMetrics()
const { response } = await this.user.doRequest('GET', '/login')
this.response = response
})
after(function () {
this.user.resetCookies()
})
it('should accept the request', function () {
expect(this.response.statusCode).to.equal(200)
})
it('should return a signed cookie', async function () {
const cookie = this.user.sessionCookie()
expect(cookie).to.exist
expect(cookie.key).to.equal(Settings.cookieName)
expect(cookie.value).to.match(/^s:/)
})
it('should sign the cookie with the current session secret', function () {
const cookie = this.user.sessionCookie()
const unsigned = cookieSignature.unsign(
cookie.value.slice(2), // strip the 's:' prefix
Settings.security.sessionSecret
)
expect(unsigned).not.to.be.false
expect(unsigned).to.match(/^[a-zA-Z0-9_-]+$/)
})
it('should record a "none" cookie metric', async function () {
const count = await getSessionCookieMetric('none')
expect(count).to.equal(1)
})
})
describe('with a signed session cookie', function () {
before(async function () {
// get the first cookie
await this.user.doRequest('GET', '/login')
this.firstCookie = this.user.sessionCookie()
// make a subsequent request
resetMetrics()
const { response } = await this.user.doRequest('GET', '/login')
this.response = response
})
after(function () {
this.user.resetCookies()
})
it('should accept the request', function () {
expect(this.response.statusCode).to.equal(200)
})
it('should return the same signed cookie', async function () {
const cookie = this.user.sessionCookie()
expect(cookie).to.exist
expect(cookie.key).to.equal(Settings.cookieName)
expect(cookie.value).to.equal(this.firstCookie.value)
})
it('should record a "signed" cookie metric', async function () {
const count = await getSessionCookieMetric('signed')
expect(count).to.equal(1)
})
})
describe('with a session cookie signed with the fallback session secret', function () {
before(async function () {
// get the first cookie
await this.user.doRequest('GET', '/login')
this.firstCookie = this.user.sessionCookie()
// sign the session key with the fallback secret
this.user.setSessionCookie(
's:' +
modifyCookieSignature(
this.firstCookie,
Settings.security.sessionSecretFallback
)
)
// make a subsequent request
resetMetrics()
const { response } = await this.user.doRequest('GET', '/login')
this.response = response
})
after(function () {
this.user.resetCookies()
})
it('should accept the request', async function () {
expect(this.response.statusCode).to.equal(200)
})
it('should return the cookie signed with the current secret', function () {
const cookie = this.user.sessionCookie()
expect(cookie).to.exist
expect(cookie.key).to.equal(Settings.cookieName)
expect(cookie.value).to.equal(this.firstCookie.value)
})
it('should record a "signed" cookie metric', async function () {
const count = await getSessionCookieMetric('signed')
expect(count).to.equal(1)
})
})
describe('with a session cookie signed with the upcoming session secret', function () {
before(async function () {
// get the first cookie
await this.user.doRequest('GET', '/login')
this.firstCookie = this.user.sessionCookie()
// sign the session key with the upcoming secret
this.user.setSessionCookie(
's:' +
modifyCookieSignature(
this.firstCookie,
Settings.security.sessionSecretUpcoming
)
)
// make a subsequent request
resetMetrics()
const { response } = await this.user.doRequest('GET', '/login')
this.response = response
})
after(function () {
this.user.resetCookies()
})
it('should accept the request', async function () {
expect(this.response.statusCode).to.equal(200)
})
it('should return the cookie signed with the current secret', function () {
const cookie = this.user.sessionCookie()
expect(cookie).to.exist
expect(cookie.key).to.equal(Settings.cookieName)
expect(cookie.value).to.equal(this.firstCookie.value)
})
it('should record a "signed" cookie metric', async function () {
const count = await getSessionCookieMetric('signed')
expect(count).to.equal(1)
})
})
describe('with a session cookie signed with an invalid secret', function () {
before(async function () {
// get the first cookie
await this.user.doRequest('GET', '/login')
this.firstCookie = this.user.sessionCookie()
// sign the session key with an invalid secret
this.user.setSessionCookie(
's:' + modifyCookieSignature(this.firstCookie, 'invalid-secret')
)
// make a subsequent request
resetMetrics()
const { response } = await this.user.doRequest('GET', '/login')
this.response = response
})
after(function () {
this.user.resetCookies()
})
it('should not reject the request', async function () {
expect(this.response.statusCode).to.equal(200)
})
it('should return a new cookie signed with the current secret', function () {
const cookie = this.user.sessionCookie()
expect(cookie).to.exist
expect(cookie.key).to.equal(Settings.cookieName)
const [sessionKey] = cookie.value.slice(2).split('.')
expect(sessionKey).not.to.equal(this.firstSessionKey)
})
it('should record a "bad-signature" cookie metric', async function () {
const count = await getSessionCookieMetric('bad-signature')
expect(count).to.equal(1)
})
})
describe('with an unsigned session cookie', function () {
before(async function () {
// get the first cookie
await this.user.doRequest('GET', '/login')
this.firstCookie = this.user.sessionCookie()
// use the session key without signing it
const [sessionKey] = this.firstCookie.value.slice(2).split('.')
this.firstSessionKey = sessionKey
this.user.setSessionCookie(sessionKey)
// make a subsequent request
resetMetrics()
const { response } = await this.user.doRequest('GET', '/login')
this.response = response
})
after(function () {
this.user.resetCookies()
})
it('should not reject the request', async function () {
expect(this.response.statusCode).to.equal(200)
})
it('should return a new cookie signed with the current secret', function () {
const cookie = this.user.sessionCookie()
expect(cookie).to.exist
expect(cookie.key).to.equal(Settings.cookieName)
const [sessionKey] = cookie.value.slice(2).split('.')
expect(sessionKey).not.to.equal(this.firstSessionKey)
})
it('should record an "unsigned" cookie metric', async function () {
const count = await getSessionCookieMetric('unsigned')
expect(count).to.equal(1)
})
})
})