UNPKG

nodecaf

Version:

Nodecaf is a light framework for developing RESTful Apps in a quick and convenient manner.

148 lines (116 loc) 4.29 kB
// @ts-check /** * @typedef {import('./internal.d.ts').Response} Nodecaf.Response * @typedef {import('./internal.d.ts').CorsOptions} Nodecaf.CorsOptions * @typedef {import('./internal.d.ts').RouteHandlerArgs} Nodecaf.RouteHandlerArgs */ const DEFAULT_OPTIONS = { origin: '*', methods: 'GET,HEAD,PUT,PATCH,POST,DELETE', preflightContinue: false }; /** * @param {Nodecaf.Response} res * @param {string} newValue */ function setVaryHeader(res, newValue){ const ha = res.get('Vary'); const curVal = Array.isArray(ha) ? ha[0] : ha; if(curVal == '*') return; if(newValue == '*') return res.set('Vary', '*'); const values = curVal?.split(',').map(item => item.trim().toLowerCase()) ?? []; if(!values.includes(newValue.toLowerCase())) values.push(newValue.toLowerCase()); res.set('Vary', values.join(', ')); } /** * @param {string} requestOrigin * @param {Nodecaf.CorsOptions['origin']} allowedOrigin * @returns {boolean} */ function isOriginAllowed(requestOrigin, allowedOrigin) { if(Array.isArray(allowedOrigin)) return allowedOrigin.some((ao) => isOriginAllowed(requestOrigin, ao)); if(typeof allowedOrigin === 'string') return requestOrigin === allowedOrigin; if(allowedOrigin instanceof RegExp && typeof requestOrigin === 'string') return allowedOrigin.test(requestOrigin); return Boolean(allowedOrigin); } /** * @param {Nodecaf.CorsOptions} opts * @param {string} origin * @param {Nodecaf.Response} res */ function initCors(opts, origin, res){ const options = { ...DEFAULT_OPTIONS, ...opts }; /* Configure Origin */ if(!options.origin || options.origin === '*') res.set('Access-Control-Allow-Origin', '*'); else if(typeof options.origin === 'string') { res.set('Access-Control-Allow-Origin', options.origin); setVaryHeader(res, 'Origin'); } else{ const originAllowed = isOriginAllowed(origin, options.origin) res.set('Access-Control-Allow-Origin', originAllowed ? origin : 'false'); setVaryHeader(res, 'Origin'); } /* Configure Credentials */ if(options.credentials === true) res.set('Access-Control-Allow-Credentials', 'true'); /* Configure Exposed Headers */ const exposedHeaders = options.exposedHeaders; if(exposedHeaders?.length) res.set('Access-Control-Expose-Headers', Array.isArray(exposedHeaders) ? exposedHeaders.join(',') : exposedHeaders); } /** * @param {Nodecaf.CorsOptions} opts * @param {string} neededHeaders * @param {Nodecaf.Response} res */ function handleOptions(opts, neededHeaders, res){ const options = { ...DEFAULT_OPTIONS, ...opts }; /* Configure Methods */ const methods = options.methods; res.set('Access-Control-Allow-Methods', Array.isArray(methods) ? methods.join(',') : methods); /* Configure Allowed Headers */ let allowedHeaders = options.allowedHeaders; if(!allowedHeaders) { allowedHeaders = neededHeaders; setVaryHeader(res, 'Access-Control-request-Headers'); } if(allowedHeaders?.length) res.set('Access-Control-Allow-Headers', Array.isArray(allowedHeaders) ? allowedHeaders.join(',') : allowedHeaders); /* Configure Max Age */ if(typeof options.maxAge === 'number' || typeof options.maxAge === 'string'){ const maxAge = options.maxAge.toString(); maxAge.length && res.set('Access-Control-Max-Age', maxAge); } res.status(204).set('Content-Length', '0').end(); } /** * @param {Nodecaf.CorsOptions | undefined} opts * @param {string} method * @param {Nodecaf.RouteHandlerArgs['headers']} headers * @param {Nodecaf.Response} res */ function cors(opts, method, headers, res){ const origin = headers['origin']; const neededHeaders = headers['access-control-request-headers'] ?? ''; if(opts && origin){ initCors(opts, origin, res); if(method === 'OPTIONS') handleOptions(opts, neededHeaders, res) } } module.exports = { cors };