X-Git-Url: https://git.squeep.com/?a=blobdiff_plain;f=lib%2Fcommon.js;h=162b751ef23805d30bbf97eb71a69f367adf3d59;hb=0c4ba588448691056be3bab76cfc478a7f8ca320;hp=d1abf3ec3cce231f6b0332b2dcf8a13beadf71d2;hpb=1e7aa924e0c1081558235e54854f1fdd99940346;p=squeep-api-dingus diff --git a/lib/common.js b/lib/common.js index d1abf3e..162b751 100644 --- a/lib/common.js +++ b/lib/common.js @@ -5,29 +5,10 @@ * Utility and miscellaneous functions. */ -const path = require('path'); -const crypto = require('crypto'); +const crypto = require('node:crypto'); const uuid = require('uuid'); const Enum = require('./enum'); - -/** - * @callback ScopeFn - * @param {String} scope - * @returns {String} - */ -/** - * Return a function which prefixes a provided scope with the most- - * relevant part of the filename, for use in logging. - * @param {String} filename - * @returns {ScopeFn} - */ -const fileScope = (filename) => { - let fScope = path.basename(filename, '.js'); - if (fScope === 'index') { - fScope = path.basename(path.dirname(filename)); - } - return (scope) => `${fScope}:${scope}`; -}; +const { fileScope } = require('@squeep/log-helper'); /** * Simple ETag from data. @@ -55,18 +36,6 @@ const generateETag = (_filePath, fileStat, fileData) => { */ const get = (obj, prop, def) => obj && prop && (prop in obj) ? obj[prop] : def; -/** - * @param {http.ClientRequest} req - * @param {http.ServerResponse} res - * @param {Object} ctx - * @deprecated after v1.2.5 (integrated into logger module) - */ -const handlerLogData = (req, res, ctx) => ({ - req: requestLogData(req), - res: responseLogData(res), - ctx, -}); - /** * Determine whether a client has already requested a resource, * based on If-Modified-Since and If-None-Match headers. @@ -171,71 +140,6 @@ const pick = (obj, props) => { return picked; }; -/** - * Return a subset of a request object, suitable for logging. - * Obscures sensitive header values. - * @param {http.ClientRequest} req - * @deprecated after v1.2.5 (integrated into logger module) - */ -const requestLogData = (req) => { - const data = pick(req, [ - 'method', - 'url', - 'httpVersion', - 'headers', - 'trailers', - ]); - scrubHeaderObject(data); - return data; -}; - - -/** - * Remove sensitive header data. - * @param {Object} data - * @param {Object} data.headers - * @deprecated after v1.2.5 (integrated into logger module) - */ -const scrubHeaderObject = (data) => { - if (data?.headers && 'authorization' in data.headers) { - data.headers = Object.assign({}, data.headers, { - authorization: obscureAuthorizationHeader(data.headers['authorization']), - }); - } -}; - - -/** - * Hide sensitive part of an Authorization header. - * @param {String} authHeader - * @returns {String} - * @deprecated after v1.2.5 (integrated into logger module) - */ -const obscureAuthorizationHeader = (authHeader) => { - if (!authHeader) { - return authHeader; - } - const space = authHeader.indexOf(' '); - // This blurs entire string if no space found, because -1. - return authHeader.slice(0, space + 1) + '*'.repeat(authHeader.length - (space + 1)); -}; - - -/** - * Return a subset of a response object, suitable for logging. - * @param {http.ServerResponse} res - * @deprecated after v1.2.5 (integrated into logger module) - */ -const responseLogData = (res) => { - const response = pick(res, [ - 'statusCode', - 'statusMessage', - ]); - response.headers = res.getHeaders(); - return response; -}; - - /** * Store all properties in defaultOptions on target from either options or defaultOptions. * @param {Object} target @@ -269,37 +173,6 @@ const requestId = () => { return uuid.v1(); }; -/** - * Do nothing. - */ -const nop = () => { /**/ }; - -/** - * A logger object which does nothing. - */ -const nullLogger = { - error: nop, - warn: nop, - info: nop, - log: nop, - debug: nop, -}; - -/** - * Populates any absent logger level functions on a logger object. - * @param {Object} logger - * @returns {Object} - * @deprecated after v1.2.9 (this is not our responsibility) - */ -const ensureLoggerLevels = (logger = {}) => { - for (const level in nullLogger) { - if (! (level in logger)) { - logger[level] = nullLogger[level]; - } - } - return logger; -}; - /** * Merges folded header lines * @param {String[]} lines @@ -308,7 +181,7 @@ const ensureLoggerLevels = (logger = {}) => { const unfoldHeaderLines = (lines) => { const foldedLineRE = /^(\t| +)(.*)$/; if (lines) { - lines.reduceRight((_, line, idx) => { + lines.reduceRight((_, line, idx) => { // NOSONAR const result = foldedLineRE.exec(line); if (result && idx) { const prevIdx = idx - 1; @@ -321,24 +194,77 @@ const unfoldHeaderLines = (lines) => { return lines; }; +/** + * Adds a new cookie. + * @param {http.ServerResponse} res + * @param {String} name + * @param {String} value + * @param {Object=} opt + * @param {String=} opt.domain + * @param {Date=} opt.expires + * @param {Boolean=} opt.httpOnly + * @param {Number=} opt.maxAge + * @param {String=} opt.path + * @param {String=} opt.sameSite + * @param {Boolean=} opt.secure + */ +function addCookie(res, name, value, opt = {}) { + const options = { + domain: undefined, + expires: undefined, + httpOnly: false, + maxAge: undefined, + path: undefined, + sameSite: undefined, + secure: false, + ...opt, + }; + // TODO: validate name, value + const cookieParts = [ + `${name}=${value}`, + ]; + if (options.domain) { + cookieParts.push(`Domain=${options.domain}`); + } + if (options.expires) { + if (!(options.expires instanceof Date)) { + throw new TypeError('cookie expires must be Date'); + } + cookieParts.push(`Expires=${options.expires.toUTCString()}`); + } + if (options.httpOnly) { + cookieParts.push('HttpOnly'); + } + if (options.maxAge) { + cookieParts.push(`Max-Age=${options.maxAge}`); + } + if (options.path) { + cookieParts.push(`Path=${options.path}`); + } + if (options.sameSite) { + if (!(['Strict', 'Lax', 'None'].includes(options.sameSite))) { + throw new RangeError('cookie sameSite value not valid'); + } + cookieParts.push(`SameSite=${options.sameSite}`); + } + if (options.secure) { + cookieParts.push('Secure'); + } + res.appendHeader(Enum.Header.SetCookie, cookieParts.join('; ')); +} + + module.exports = { - ensureLoggerLevels, + addCookie, fileScope, generateETag, get, - handlerLogData, httpStatusCodeClass, isClientCached, mergeDeep, mergeEnum, - nop, - nullLogger, - obscureAuthorizationHeader, pick, requestId, - requestLogData, - responseLogData, - scrubHeaderObject, setOptions, splitFirst, unfoldHeaderLines,