next
Version:
The React Framework
110 lines (109 loc) • 5.31 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
Object.defineProperty(exports, "blockCrossSiteDEV", {
enumerable: true,
get: function() {
return blockCrossSiteDEV;
}
});
const _url = require("../../../lib/url");
const _log = require("../../../build/output/log");
const _csrfprotection = require("../../app-render/csrf-protection");
const allowedDevOriginsDocs = 'https://nextjs.org/docs/app/api-reference/config/next-config-js/allowedDevOrigins';
function getBlockedResourcePath(req) {
var _parseUrl;
return ((_parseUrl = (0, _url.parseUrl)(req.url ?? '')) == null ? void 0 : _parseUrl.pathname) ?? req.url ?? '/_next/*';
}
function formatBlockedCrossSiteMessage(source, resourcePath) {
const lines = [
`Blocked cross-origin request to Next.js dev resource ${resourcePath}${getBlockedSourceDescription(source)}.`,
'Cross-origin access to Next.js dev resources is blocked by default for safety.'
];
// `source` has 3 meanings here:
// - `'null'`: browser explicitly sent `Origin: null` for an opaque/sandboxed origin
// - hostname string: we parsed an allowlistable host from Origin/Referer
// - `undefined` (and effectively empty string): the request did not include a usable host
if (source === 'null') {
lines.push('', 'This request came from a privacy-sensitive or opaque origin, so Next.js cannot determine which host to allow.', 'If you need it to succeed, load the dev server from a normal origin and add that host to "allowedDevOrigins".');
} else if (source) {
lines.push('', 'To allow this host in development, add it to "allowedDevOrigins" in next.config.js and restart the dev server:', '', '// next.config.js', 'module.exports = {', ` allowedDevOrigins: ['${source}'],`, '}');
} else {
lines.push('', 'This request did not include an allowlistable source host.', 'If you need it to succeed, make sure the browser sends an Origin or Referer from a host listed in "allowedDevOrigins".');
}
lines.push('', `Read more: ${allowedDevOriginsDocs}`);
return lines.join('\n');
}
function getBlockedSourceDescription(source) {
if (source === 'null') {
return ' from a privacy-sensitive or opaque origin';
}
if (source) {
return ` from "${source}"`;
}
return ' from an unknown source';
}
function blockRequest(req, res, source) {
(0, _log.warnOnce)(formatBlockedCrossSiteMessage(source, getBlockedResourcePath(req)));
if ('statusCode' in res) {
res.statusCode = 403;
}
res.end('Unauthorized');
return true;
}
function parseHostnameFromHeader(header) {
const headerValue = Array.isArray(header) ? header[0] : header;
if (!headerValue || headerValue === 'null') {
return;
}
const parsedHeader = (0, _url.parseUrl)(headerValue);
return parsedHeader == null ? void 0 : parsedHeader.hostname.toLowerCase();
}
function isInternalEndpoint(req) {
if (!req.url) return false;
try {
// TODO: We should standardize on a single prefix for this
const isMiddlewareRequest = req.url.includes('/__nextjs');
const isInternalAsset = req.url.includes('/_next');
// Static media requests are excluded, as they might be loaded via CSS and would fail
// CORS checks.
const isIgnoredRequest = req.url.includes('/_next/image') || req.url.includes('/_next/static/media');
return !isIgnoredRequest && (isInternalAsset || isMiddlewareRequest);
} catch (err) {
return false;
}
}
const blockCrossSiteDEV = (req, res, allowedDevOrigins, hostname)=>{
const allowedOrigins = [
'*.localhost',
'localhost',
...allowedDevOrigins ?? []
];
if (hostname) {
allowedOrigins.push(hostname);
}
// only process internal URLs/middleware
if (!isInternalEndpoint(req)) {
return false;
}
// block non-cors request from cross-site e.g. script tag on
// different host
if (req.headers['sec-fetch-mode'] === 'no-cors' && req.headers['sec-fetch-site'] === 'cross-site') {
// no-cors requests do not send an Origin header, so fall back to Referer
// when validating configured cross-site script loads.
const refererHostname = parseHostnameFromHeader(req.headers['referer']);
if (refererHostname && (0, _csrfprotection.isCsrfOriginAllowed)(refererHostname, allowedOrigins)) {
return false;
}
return blockRequest(req, res, refererHostname);
}
// ensure websocket requests are only fulfilled from allowed origin
const rawOrigin = req.headers['origin'];
const originHeader = Array.isArray(rawOrigin) ? rawOrigin[0] : rawOrigin;
const parsedOrigin = originHeader && originHeader !== 'null' ? (0, _url.parseUrl)(originHeader) : originHeader;
const originLowerCase = parsedOrigin === undefined || typeof parsedOrigin === 'string' ? parsedOrigin : parsedOrigin.hostname.toLowerCase();
// Allow requests with no origin since those are just GET requests from same-site
return originLowerCase !== undefined && !(0, _csrfprotection.isCsrfOriginAllowed)(originLowerCase, allowedOrigins) && blockRequest(req, res, originLowerCase);
};
//# sourceMappingURL=block-cross-site-dev.js.map