Extract Privacy module

Centralizes how we redact sensitive information.
This commit is contained in:
Daniel Gasienica 2018-03-06 16:20:04 -05:00
parent 0c317c5498
commit 49e0850fb2
4 changed files with 60 additions and 42 deletions

View file

@ -8,10 +8,9 @@ const _ = require('lodash');
const debuglogs = require('./modules/debuglogs'); const debuglogs = require('./modules/debuglogs');
const Errors = require('./modules/types/errors'); const Errors = require('./modules/types/errors');
const Privacy = require('./modules/privacy');
const ipc = electron.ipcRenderer; const ipc = electron.ipcRenderer;
const PHONE_REGEX = /\+\d{7,12}(\d{3})/g;
const GROUP_REGEX = /(group\()([^)]+)(\))/g;
// Default Bunyan levels: https://github.com/trentm/node-bunyan#levels // Default Bunyan levels: https://github.com/trentm/node-bunyan#levels
// To make it easier to visually scan logs, we make all levels the same length // To make it easier to visually scan logs, we make all levels the same length
@ -25,20 +24,7 @@ const LEVELS = {
10: 'trace', 10: 'trace',
}; };
// Backwards-compatible logging, simple strings and no level (defaulted to INFO) // Backwards-compatible logging, simple strings and no level (defaulted to INFO)
function redactPhone(text) {
return text.replace(PHONE_REGEX, '+[REDACTED]$1');
}
function redactGroup(text) {
return text.replace(
GROUP_REGEX,
(match, before, id, after) => `${before}[REDACTED]${id.slice(-3)}${after}`
);
}
function now() { function now() {
const date = new Date(); const date = new Date();
return date.toJSON(); return date.toJSON();
@ -61,8 +47,8 @@ function log(...args) {
return item; return item;
}); });
const toSend = redactAll(str.join(' ')); const logText = Privacy.redactAll(str.join(' '));
ipc.send('log-info', toSend); ipc.send('log-info', logText);
} }
if (window.console) { if (window.console) {
@ -96,11 +82,7 @@ function formatLine(entry) {
} }
function format(entries) { function format(entries) {
return redactAll(entries.map(formatLine).join('\n'))); return Privacy.redactAll(entries.map(formatLine).join('\n'));
}
function redactAll(string) {
return Errors.redactSensitivePaths(redactGroup(redactPhone(string)));
} }
function fetch() { function fetch() {

53
js/modules/privacy.js Normal file
View file

@ -0,0 +1,53 @@
/* eslint-env node */
const Path = require('path');
const isString = require('lodash/isString');
const compose = require('lodash/fp/compose');
const PHONE_NUMBER_PATTERN = /\+\d{7,12}(\d{3})/g;
const GROUP_ID_PATTERN = /(group\()([^)]+)(\))/g;
const APP_ROOT_PATH = Path.join(__dirname, '..', '..', '..');
const APP_ROOT_PATH_PATTERN = new RegExp(APP_ROOT_PATH, 'g');
const REDACTION_PLACEHOLDER = '[REDACTED]';
// redactPhoneNumbers :: String -> String
exports.redactPhoneNumbers = (text) => {
if (!isString(text)) {
throw new TypeError('`text` must be a string');
}
return text.replace(PHONE_NUMBER_PATTERN, `+${REDACTION_PLACEHOLDER}$1`);
};
// redactGroupIds :: String -> String
exports.redactGroupIds = (text) => {
if (!isString(text)) {
throw new TypeError('`text` must be a string');
}
return text.replace(
GROUP_ID_PATTERN,
(match, before, id, after) =>
`${before}${REDACTION_PLACEHOLDER}${id.slice(-3)}${after}`
);
};
// redactSensitivePaths :: String -> String
exports.redactSensitivePaths = (text) => {
if (!isString(text)) {
throw new TypeError('`text` must be a string');
}
return text.replace(APP_ROOT_PATH_PATTERN, REDACTION_PLACEHOLDER);
};
// redactAll :: String -> String
exports.redactAll = compose(
exports.redactSensitivePaths,
exports.redactGroupIds,
exports.redactPhoneNumbers
);

View file

@ -1,26 +1,9 @@
/* eslint-env node */
const Path = require('path');
const ensureError = require('ensure-error'); const ensureError = require('ensure-error');
const isString = require('lodash/isString');
const Privacy = require('../privacy');
const APP_ROOT_PATH = Path.join(__dirname, '..', '..', '..');
const APP_ROOT_PATH_PATTERN = new RegExp(APP_ROOT_PATH, 'g');
// toLogFormat :: Error -> String // toLogFormat :: Error -> String
exports.toLogFormat = (error) => { exports.toLogFormat = (error) => {
const normalizedError = ensureError(error); const normalizedError = ensureError(error);
const stackWithRedactedPaths = exports.redactSensitivePaths(normalizedError.stack); return Privacy.redactAll(normalizedError.stack);
return stackWithRedactedPaths;
};
// redactSensitivePaths :: String -> String
exports.redactSensitivePaths = (logLine) => {
if (!isString(logLine)) {
return logLine;
}
return logLine.replace(APP_ROOT_PATH_PATTERN, '<REDACTED_PATH>');
}; };

View file

@ -27,7 +27,7 @@ describe('Errors', () => {
); );
assert.include( assert.include(
formattedStack, formattedStack,
'<REDACTED_PATH>', '[REDACTED]',
'Formatted stack has redactions' 'Formatted stack has redactions'
); );
return; return;