'use strict' const fastRedact = require('fast-redact') const { redactFmtSym, wildcardFirstSym } = require('./symbols') const { rx, validator } = fastRedact const validate = validator({ ERR_PATHS_MUST_BE_STRINGS: () => 'pino – redacted paths must be strings', ERR_INVALID_PATH: (s) => `pino – redact paths array contains an invalid path (${s})` }) const CENSOR = '[Redacted]' const strict = false // TODO should this be configurable? function redaction (opts, serialize) { const { paths, censor } = handle(opts) const shape = paths.reduce((o, str) => { rx.lastIndex = 0 const first = rx.exec(str) const next = rx.exec(str) // ns is the top-level path segment, brackets + quoting removed. let ns = first[1] !== undefined ? first[1].replace(/^(?:"|'|`)(.*)(?:"|'|`)$/, '$1') : first[0] if (ns === '*') { ns = wildcardFirstSym } // top level key: if (next === null) { o[ns] = null return o } // path with at least two segments: // if ns is already redacted at the top level, ignore lower level redactions if (o[ns] === null) { return o } const { index } = next const nextPath = `${str.substr(index, str.length - 1)}` o[ns] = o[ns] || [] // shape is a mix of paths beginning with literal values and wildcard // paths [ "a.b.c", "*.b.z" ] should reduce to a shape of // { "a": [ "b.c", "b.z" ], *: [ "b.z" ] } // note: "b.z" is in both "a" and * arrays because "a" matches the wildcard. // (* entry has wildcardFirstSym as key) if (ns !== wildcardFirstSym && o[ns].length === 0) { // first time ns's get all '*' redactions so far o[ns].push(...(o[wildcardFirstSym] || [])) } if (ns === wildcardFirstSym) { // new * path gets added to all previously registered literal ns's. Object.keys(o).forEach(function (k) { if (o[k]) { o[k].push(nextPath) } }) } o[ns].push(nextPath) return o }, {}) // the redactor assigned to the format symbol key // provides top level redaction for instances where // an object is interpolated into the msg string const result = { [redactFmtSym]: fastRedact({ paths, censor, serialize, strict }) } const topCensor = (...args) => typeof censor === 'function' ? serialize(censor(...args)) : serialize(censor) return [...Object.keys(shape), ...Object.getOwnPropertySymbols(shape)].reduce((o, k) => { // top level key: if (shape[k] === null) o[k] = topCensor else o[k] = fastRedact({ paths: shape[k], censor, serialize, strict }) return o }, result) } function handle (opts) { if (Array.isArray(opts)) { opts = { paths: opts, censor: CENSOR } validate(opts) return opts } var { paths, censor = CENSOR, remove } = opts if (Array.isArray(paths) === false) { throw Error('pino – redact must contain an array of strings') } if (remove === true) censor = undefined validate({ paths, censor }) return { paths, censor } } module.exports = redaction