const Koa = require("koa"); const koaStatic = require("koa-static"); const path = require("path"); const mount = require("koa-mount"); /** * @type function * @param {{ host: string, port: number, middleware: any[], allowHost: string | null }} input * @return function */ module.exports = ({ host, port, middleware, allowHost }) => { const assets = new Koa(); assets.use(koaStatic(path.join(__dirname, "assets"))); const app = new Koa(); const validHosts = []; // All non-GET requests must have a path that doesn't start with `/blob/`. const isValidRequest = (request) => { // All requests must use our hostname to prevent DNS rebind attacks. if (validHosts.includes(request.hostname) !== true) { console.log(`Invalid HTTP hostname: ${request.hostname}`); return false; } // All non-GET requests must ... if (request.method !== "GET") { // ...have a referer... if (request.header.referer == null) { console.log("No referer"); return false; } try { const refererUrl = new URL(request.header.referer); // ...with a valid hostname... if (validHosts.includes(refererUrl.hostname) !== true) { console.log(`Invalid referer hostname: ${refererUrl.hostname}`); return false; } // ...and must not originate from a blob path. if (refererUrl.pathname.startsWith("/blob/")) { console.log(`Invalid referer path: ${refererUrl.pathname}`); return false; } } catch (e) { console.log(`Invalid referer URL: ${request.header.referer}`); return false; } } // If all of the above checks pass, this is a valid request. return true; }; app.on("error", (err, ctx) => { // Output full error objects console.error(err); // Avoid printing errors for invalid requests. if (isValidRequest(ctx.request)) { err.message = err.stack; err.expose = true; } return null; }); app.use(mount("/assets", assets)); // headers app.use(async (ctx, next) => { const csp = [ "default-src 'none'", "img-src 'self'", "form-action 'self'", "media-src 'self'", "style-src 'self'", ].join("; "); // Disallow scripts. ctx.set("Content-Security-Policy", csp); // Disallow