Skip to content

Commit

Permalink
feat: refactor request and response serializers
Browse files Browse the repository at this point in the history
the new reqSerializer, resSerializer removes the sensitive headers instead of
masking then for performance reasons.

Use reqMaskSerializer, resMaskSerializer to get back to the old behavior

chore: better exports
  • Loading branch information
commenthol committed Oct 7, 2023
1 parent ffa4fae commit bfedcaf
Show file tree
Hide file tree
Showing 10 changed files with 280 additions and 51 deletions.
38 changes: 38 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Launch Program",
"program": "${workspaceFolder}/x.js",
"request": "launch",
"skipFiles": [
"<node_internals>/**"
],
"type": "node",
"env": {
"DEBUG_JSON": "1"
}
},
{
"args": [
"-u",
"bdd",
"--timeout",
"999999",
"--colors",
"${workspaceFolder}/test"
],
"internalConsoleOptions": "openOnSessionStart",
"name": "Mocha Tests",
"program": "${workspaceFolder}/node_modules/mocha/bin/_mocha",
"request": "launch",
"skipFiles": [
"<node_internals>/**"
],
"type": "node"
}
]
}
6 changes: 6 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,14 @@
".": {
"import": "./src/index.js",
"require": "./lib/index.cjs",
"browser": "./src/browser.js",
"types": "./types/index.d.ts"
},
"./serializers": {
"import": "./src/serializers/index.js",
"require": "./lib/serializers/index.cjs",
"types": "./types/serializers/index.d.ts"
},
"./package.json": "./package.json"
},
"main": "./lib/index.cjs",
Expand Down
4 changes: 2 additions & 2 deletions src/serializers/index.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
export { errSerializer } from './err.js'
export { reqSerializer } from './req.js'
export { resSerializer, startTimeKey } from './res.js'
export { reqSerializer, reqMaskSerializer } from './req.js'
export { resSerializer, resMaskSerializer, startTimeKey } from './res.js'
66 changes: 49 additions & 17 deletions src/serializers/req.js
Original file line number Diff line number Diff line change
@@ -1,30 +1,62 @@
/**
* request serializer
*
* removes cookie values and authorization headers
* @param {object} [req]
* @returns {object}
*/
export function reqSerializer (req) {
if (typeof req !== 'object' || !req) return

const { authorization, cookie, ...headers } = req.headers || {}

const logReq = {
id: typeof req.id === 'function' ? req.id() : req.id,
method: req.method,
url: req.originalUrl || req.url,
remoteAddress: req.socket?.remoteAddress,
remotePort: req.socket?.remotePort
}
if (Object.keys(headers).length) {
logReq.headers = headers
}

return logReq
}

/**
* request serializer
*
* masks cookie values and authorization headers
* @param {object} [val]
* @param {object} [req]
* @returns {object}
*/
export function reqSerializer (val) {
if (typeof val !== 'object' || !val) return

const _req = {}
_req.id = typeof val.id === 'function' ? val.id() : val.id
_req.method = val.method
_req.url = val.originalUrl || val.url
_req.remoteAddress = val.socket?.remoteAddress
_req.remotePort = val.socket?.remotePort
_req.headers = Object.assign({}, val.headers)

if (_req.headers?.authorization) {
_req.headers.authorization = '***'
export function reqMaskSerializer (req) {
const logReq = reqSerializer(req)

if (!logReq) return

const { authorization, cookie } = req.headers || {}
if (!authorization || !cookie) {
return logReq
}
if (_req.headers?.cookie) {
_req.headers.cookie = maskCookieVal(_req.headers.cookie)

logReq.headers = logReq.headers || {}

if (authorization) {
logReq.headers.authorization = authorization.slice(0, 8) + '***'
}
if (cookie) {
logReq.headers.cookie = maskCookieVal(cookie)
}

return _req
return logReq
}

/**
* @param {string} cookie
* @returns {string}
*/
function maskCookieVal (cookie) {
let masked = ''
const len = cookie.length
Expand Down
68 changes: 55 additions & 13 deletions src/serializers/res.js
Original file line number Diff line number Diff line change
@@ -1,30 +1,72 @@
export const startTimeKey = Symbol('startTime')

const SET_COOKIE = 'set-cookie'
const PROXY_AUTHENTICATE = 'proxy-authenticate'

/**
* response serializer
* masks cookie values
* @param {object} [val]
*
* removes set-cookie and proxy-authenticate headers
* @param {object} [res]
* @returns {object}
*/
export function resSerializer (val) {
if (typeof val !== 'object' || !val) return

const _res = {}
_res.headers = val.getHeaders ? val.getHeaders() : val._headers
_res.statusCode = val.statusCode
if (_res.headers?.[SET_COOKIE]) {
_res.headers[SET_COOKIE] = [].concat(_res.headers[SET_COOKIE]).map(maskCookieVal)
export function resSerializer (res) {
if (typeof res !== 'object' || !res) return

const {
[SET_COOKIE]: _1,
[PROXY_AUTHENTICATE]: _2,
...headers
} = res.getHeaders ? res.getHeaders() : res._headers || {}

const logRes = {
statusCode: res.statusCode
}
if (Object.keys(headers).length) {
logRes.headers = headers
}
if (res[startTimeKey]) {
logRes.ms = Date.now() - res[startTimeKey]
}

return logRes
}

if (val[startTimeKey]) {
_res.ms = Date.now() - val[startTimeKey]
/**
* response serializer
*
* masks set-cookie and proxy-authenticate response headers
* @param {object} [res]
* @returns {object}
*/
export function resMaskSerializer (res) {
const logRes = resSerializer(res)

if (!logRes) return

const { [SET_COOKIE]: setCookie, [PROXY_AUTHENTICATE]: proxyAuthenticate } =
res.getHeaders ? res.getHeaders() : res._headers || {}

if (!setCookie || !proxyAuthenticate) {
return logRes
}

return _res
logRes.headers = logRes.headers || {}

if (proxyAuthenticate) {
logRes.headers[PROXY_AUTHENTICATE] = '***'
}
if (setCookie) {
logRes.headers[SET_COOKIE] = [].concat(setCookie).map(maskCookieVal)
}

return logRes
}

/**
* @param {string} cookie
* @returns {string}
*/
function maskCookieVal (cookie) {
let masked = ''
const len = cookie.length
Expand Down
113 changes: 103 additions & 10 deletions test/serializers.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ import sinon from 'sinon'
import {
errSerializer,
reqSerializer,
reqMaskSerializer,
resSerializer,
resMaskSerializer,
startTimeKey
} from '../src/serializers/index.js'

Expand Down Expand Up @@ -72,9 +74,7 @@ describe('serializers', function () {
method: 'GET',
url: '/path?test=1',
headers: {
'user-agent': 'my-ua/1.0.0',
authorization: '***',
cookie: 'session=***; name=***'
'user-agent': 'my-ua/1.0.0'
},
remoteAddress: '127.0.0.1',
remotePort: 3333
Expand All @@ -89,13 +89,67 @@ describe('serializers', function () {
it('shall serialize a request with originalUrl', function () {
const _req = { ...req, originalUrl: '/mount/test/path?test=1' }
const result = reqSerializer(_req)
assert.deepStrictEqual(result, {
id: 'f90a5d9e-52e6-482e-a6ab-d1c5da1fe9c6',
method: 'GET',
url: '/mount/test/path?test=1',
headers: {
'user-agent': 'my-ua/1.0.0'
},
remoteAddress: '127.0.0.1',
remotePort: 3333
})
})
})

describe('reqMaskSerializer', function () {
const req = {
id: 'f90a5d9e-52e6-482e-a6ab-d1c5da1fe9c6',
method: 'GET',
url: '/path?test=1',
headers: {
'user-agent': 'my-ua/1.0.0',
authorization: 'Basic foo:bar',
cookie: 'session=foobar; name=foo'
},
socket: {
remoteAddress: '127.0.0.1',
remotePort: 3333
},
body: 'mybody'
}

it('shall serialize a request', function () {
const result = reqMaskSerializer(req)
assert.deepStrictEqual(result, {
id: 'f90a5d9e-52e6-482e-a6ab-d1c5da1fe9c6',
method: 'GET',
url: '/path?test=1',
headers: {
'user-agent': 'my-ua/1.0.0',
authorization: 'Basic fo***',
cookie: 'session=***; name=***'
},
remoteAddress: '127.0.0.1',
remotePort: 3333
})
})

it('shall not serialize a request of type string', function () {
const result = reqMaskSerializer('string')
assert.strictEqual(result, undefined)
})

it('shall serialize a request with originalUrl', function () {
const _req = { ...req, originalUrl: '/mount/test/path?test=1' }
const result = reqMaskSerializer(_req)
assert.deepStrictEqual(result, {
id: 'f90a5d9e-52e6-482e-a6ab-d1c5da1fe9c6',
method: 'GET',
url: '/mount/test/path?test=1',
headers: {
'user-agent': 'my-ua/1.0.0',
authorization: '***',
authorization: 'Basic fo***',
cookie: 'session=***; name=***'
},
remoteAddress: '127.0.0.1',
Expand All @@ -119,7 +173,8 @@ describe('serializers', function () {
'set-cookie': [
'foo=bar; Max-Age=10; SameSite=Strict',
'session=foobar'
]
],
'proxy-authenticate': 'foobar'
},
getHeaders: () => res._headers,
body: { foo: 'bar' }
Expand All @@ -132,11 +187,7 @@ describe('serializers', function () {
ms: 3,
statusCode: 500,
headers: {
'user-agent': 'my-server/1.0.0',
'set-cookie': [
'foo=***; Max-Age=10; SameSite=Strict',
'session=***'
]
'user-agent': 'my-server/1.0.0'
}
})
})
Expand All @@ -146,4 +197,46 @@ describe('serializers', function () {
assert.strictEqual(result, undefined)
})
})

describe('resMaskSerializer', function () {
before(function () {
this.clock = sinon.useFakeTimers()
})
after(function () {
this.clock.restore()
})

const res = {
statusCode: 500,
_headers: {
'user-agent': 'my-server/1.0.0',
'set-cookie': [
'foo=bar; Max-Age=10; SameSite=Strict',
'session=foobar'
],
'proxy-authenticate': 'foobar'
},
getHeaders: () => res._headers,
body: { foo: 'bar' }
}

it('shall serialize a response', function () {
res[startTimeKey] = Date.now() - 3
const result = resMaskSerializer(res)
assert.deepStrictEqual(result, {
ms: 3,
statusCode: 500,
headers: {
'user-agent': 'my-server/1.0.0',
'proxy-authenticate': '***',
'set-cookie': ['foo=***; Max-Age=10; SameSite=Strict', 'session=***']
}
})
})

it('shall not serialize a response of type string', function () {
const result = resMaskSerializer('string')
assert.strictEqual(result, undefined)
})
})
})
4 changes: 2 additions & 2 deletions types/serializers/err.d.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/**
* serializer for errors
* @param {object} [val]
* @param {object} [err]
* @returns {object}
*/
export function errSerializer(val?: object): object;
export function errSerializer(err?: object): object;
Loading

0 comments on commit bfedcaf

Please sign in to comment.