@whook/whook
Version:
Build strong and efficient REST web services.
140 lines • 5.3 kB
JavaScript
import { autoService, location } from 'knifecycle';
export default location(autoService(initObfuscator), import.meta.url);
const noop = () => undefined;
const DEFAULT_MAX_CLEAR_CHARS = 6;
const DEFAULT_MAX_CLEAR_RATIO = 4;
const DEFAULT_SHIELD_CHAR = '🛡';
const DEFAULT_SENSIBLE_HEADERS = [
{
name: 'authorization',
pattern: /^(Bearer |Basic )(.*)$/i,
clearIndices: [0],
},
{ name: 'cookie', pattern: /^(.*)$/i, clearIndices: [] },
{
name: 'set-cookie',
pattern: /^([^=]+=)([^;]*)(.*)$/i,
clearIndices: [0, 2],
},
];
const DEFAULT_SENSIBLE_PROPS = [
{
name: 'access_token',
pattern: /^(.*)$/i,
clearIndices: [0],
},
{
name: 'refresh_token',
pattern: /^(.*)$/i,
clearIndices: [0],
},
{
name: 'password',
pattern: /^(.*)$/i,
clearIndices: [],
},
{
name: 'token',
pattern: /^(.*)$/i,
clearIndices: [],
},
...DEFAULT_SENSIBLE_HEADERS,
];
/**
* Obfuscate sensible information.
* @param {Object} services
* The service dependencies
* @param {Object} [services.SHIELD_CHAR]
* The char for replacing sensible information
* @param {Object} [services.MAX_CLEAR_CHARS]
* The maximum clear chars to display
* @param {Object} [services.MAX_CLEAR_RATIO]
* The maximum clear chars ratio to display
* @param {Object} [services.SENSIBLE_PROPS]
* Sensible properties names
* @param {Object} [services.SENSIBLE_HEADERS]
* Sensible headers names
* @return {Promise<Object>}
* A promise of an object containing the gathered constants.
* @example
* import { initObfuscator } from '@whook/whook';
* import { alsoInject } from 'knifecycle';
* import { log } from 'node:console';
*
* const obfuscator = await initObfuscator();
*
* log(obfuscator('my very secret information!));
* // my ...on!
*/
async function initObfuscator({ SHIELD_CHAR = DEFAULT_SHIELD_CHAR, MAX_CLEAR_CHARS = DEFAULT_MAX_CLEAR_CHARS, MAX_CLEAR_RATIO = DEFAULT_MAX_CLEAR_RATIO, SENSIBLE_PROPS = DEFAULT_SENSIBLE_PROPS, SENSIBLE_HEADERS = DEFAULT_SENSIBLE_HEADERS, log = noop, }) {
log('debug', '🕶️ - Initializing the obfuscator service.');
return {
obfuscate,
obfuscateSensibleProps,
obfuscateSensibleHeaders,
};
function obfuscate(secret) {
const numClearChars = Math.min(MAX_CLEAR_CHARS, Math.floor(secret.length / MAX_CLEAR_RATIO));
if (numClearChars <= 1) {
return SHIELD_CHAR;
}
return (secret.slice(0, Math.ceil(numClearChars / 2)) +
'...' +
secret.slice(secret.length - Math.floor(numClearChars / 2)));
}
function selectivelyObfuscateAll(pattern, clearIndices, values) {
return values instanceof Array
? values.map((value) => selectivelyObfuscate(pattern, clearIndices, value))
: selectivelyObfuscate(pattern, clearIndices, values);
}
function selectivelyObfuscate(pattern, clearIndices, value) {
// SECURITY: Here, we first test the pattern to ensure
// the selective obfuscation will work, if not, we obfuscate
// the whole value to default to security
return 'string' !== typeof value
? obfuscate('')
: pattern.test(value)
? value.replace(pattern, (...args) => {
return args
.slice(1, -2)
.map((value, index) => clearIndices.includes(index) ? value : obfuscate(value))
.join('');
})
: obfuscate(value);
}
function obfuscateSensibleHeaders(headers) {
return Object.keys(headers).reduce((finalHeaders, headerName) => {
const sensibleHeader = SENSIBLE_HEADERS.find((sensibleHeader) => sensibleHeader.name.toLowerCase() === headerName.toLowerCase());
return {
...finalHeaders,
[headerName]: sensibleHeader
? selectivelyObfuscateAll(sensibleHeader.pattern, sensibleHeader.clearIndices, headers[headerName])
: headers[headerName],
};
}, {});
}
function obfuscateSensibleProps(propValue, propName = '_') {
if (propValue instanceof Array) {
return propValue.map((value) => obfuscateSensibleProps(value, propName));
}
else if (typeof propValue === 'object' && propValue !== null) {
return Object.keys(propValue).reduce((newObject, key) => ({
...newObject,
[key]: obfuscateSensibleProps(propValue[key], key),
}), {});
}
else if (typeof propValue === 'boolean' ||
typeof propValue === 'string' ||
typeof propValue === 'number') {
const sensibleProp = SENSIBLE_PROPS.find((sensibleProp) => sensibleProp.name.toLowerCase() === propName.toLowerCase());
return sensibleProp
? selectivelyObfuscate(sensibleProp.pattern, sensibleProp.clearIndices, propValue.toString())
: propValue;
}
else if (null === propValue) {
return null;
}
return null;
}
}
//# sourceMappingURL=obfuscator.js.map