Files
echoboard/packages/api/src/middleware/security.ts

55 lines
2.1 KiB
TypeScript

import { FastifyInstance } from "fastify";
import fp from "fastify-plugin";
async function securityPlugin(app: FastifyInstance) {
app.addHook("onSend", async (req, reply) => {
const isEmbed = req.url.startsWith("/api/v1/embed/") || req.url.startsWith("/embed/");
const isAsset = req.url.startsWith("/assets/") || req.url.startsWith("/favicon") || req.url.startsWith("/icon-") || req.url.endsWith(".js") || req.url.endsWith(".css");
if (isEmbed || isAsset) {
// embed routes need to be frameable by third-party sites
reply.header("Content-Security-Policy", [
"default-src 'self'",
"script-src 'self'",
"style-src 'self' 'unsafe-inline'",
"img-src 'self' data:",
"font-src 'self'",
"connect-src 'self'",
"frame-src 'none'",
"object-src 'none'",
"frame-ancestors *",
"base-uri 'self'",
"form-action 'none'",
].join("; "));
reply.header("Cross-Origin-Resource-Policy", "cross-origin");
reply.header("Access-Control-Allow-Origin", "*");
reply.header("Access-Control-Allow-Credentials", "false");
} else {
reply.header("Content-Security-Policy", [
"default-src 'self'",
"script-src 'self'",
"style-src 'self' 'unsafe-inline'",
"img-src 'self' data:",
"font-src 'self'",
"connect-src 'self'",
"frame-src 'none'",
"object-src 'none'",
"frame-ancestors 'none'",
"base-uri 'self'",
"form-action 'self'",
].join("; "));
reply.header("X-Frame-Options", "DENY");
reply.header("Cross-Origin-Opener-Policy", "same-origin");
reply.header("Cross-Origin-Resource-Policy", "same-origin");
}
reply.header("Referrer-Policy", "no-referrer");
reply.header("X-Content-Type-Options", "nosniff");
reply.header("Strict-Transport-Security", "max-age=63072000; includeSubDomains; preload");
reply.header("Permissions-Policy", "camera=(), microphone=(), geolocation=(), interest-cohort=()");
reply.header("X-DNS-Prefetch-Control", "off");
});
}
export default fp(securityPlugin, { name: "security" });