mirror of
https://github.com/LemmyNet/lemmy-ui.git
synced 2024-11-25 13:51:13 +00:00
Add nonce-based CSP header (#1907)
* Remove websocket config * Add nonce based CSP
This commit is contained in:
parent
1d73a57297
commit
27222a4f30
5 changed files with 31 additions and 17 deletions
|
@ -8,7 +8,7 @@
|
|||
"scripts": {
|
||||
"analyze": "webpack --mode=none",
|
||||
"prebuild:dev": "yarn clean && node generate_translations.js",
|
||||
"build:dev": "webpack --env COMMIT_HASH=$(git rev-parse --short HEAD) --mode=development",
|
||||
"build:dev": "webpack --env LEMMY_UI_DISABLE_CSP=true --env COMMIT_HASH=$(git rev-parse --short HEAD) --mode=development",
|
||||
"prebuild:prod": "yarn clean && node generate_translations.js",
|
||||
"build:prod": "webpack --env COMMIT_HASH=$(git rev-parse --short HEAD) --mode=production",
|
||||
"clean": "yarn run rimraf dist",
|
||||
|
|
|
@ -120,7 +120,7 @@ export default async (req: Request, res: Response) => {
|
|||
|
||||
const root = renderToString(wrapper);
|
||||
|
||||
res.send(await createSsrHtml(root, isoData));
|
||||
res.send(await createSsrHtml(root, isoData, res.locals.cspNonce));
|
||||
} catch (err) {
|
||||
// If an error is caught here, the error page couldn't even be rendered
|
||||
console.error(err);
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import * as crypto from "crypto";
|
||||
import type { NextFunction, Request, Response } from "express";
|
||||
import { hasJwtCookie } from "./utils/has-jwt-cookie";
|
||||
|
||||
|
@ -8,9 +9,20 @@ export function setDefaultCsp({
|
|||
res: Response;
|
||||
next: NextFunction;
|
||||
}) {
|
||||
res.locals.cspNonce = crypto.randomBytes(16).toString("hex");
|
||||
|
||||
res.setHeader(
|
||||
"Content-Security-Policy",
|
||||
`default-src 'self'; manifest-src *; connect-src *; img-src * data:; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; form-action 'self'; base-uri 'self'; frame-src *; media-src * data:`
|
||||
`default-src 'self';
|
||||
manifest-src *;
|
||||
connect-src *;
|
||||
img-src * data:;
|
||||
script-src 'self' 'nonce-${res.locals.cspNonce}';
|
||||
style-src 'self' 'unsafe-inline';
|
||||
form-action 'self';
|
||||
base-uri 'self';
|
||||
frame-src *;
|
||||
media-src * data:`.replace(/\s+/g, " ")
|
||||
);
|
||||
|
||||
next();
|
||||
|
|
|
@ -4,7 +4,7 @@ import { renderToString } from "inferno-server";
|
|||
import serialize from "serialize-javascript";
|
||||
import sharp from "sharp";
|
||||
import { favIconPngUrl, favIconUrl } from "../../shared/config";
|
||||
import { ILemmyConfig, IsoDataOptionalSite } from "../../shared/interfaces";
|
||||
import { IsoDataOptionalSite } from "../../shared/interfaces";
|
||||
import { buildThemeList } from "./build-themes-list";
|
||||
import { fetchIconPng } from "./fetch-icon-png";
|
||||
|
||||
|
@ -14,7 +14,8 @@ let appleTouchIcon: string | undefined = undefined;
|
|||
|
||||
export async function createSsrHtml(
|
||||
root: string,
|
||||
isoData: IsoDataOptionalSite
|
||||
isoData: IsoDataOptionalSite,
|
||||
cspNonce: string
|
||||
) {
|
||||
const site = isoData.site_res;
|
||||
|
||||
|
@ -22,6 +23,12 @@ export async function createSsrHtml(
|
|||
(await buildThemeList())[0]
|
||||
}.css" />`;
|
||||
|
||||
const customHtmlHeaderScriptTag = new RegExp("<script", "g");
|
||||
const customHtmlHeaderWithNonce = customHtmlHeader.replace(
|
||||
customHtmlHeaderScriptTag,
|
||||
`<script nonce="${cspNonce}"`
|
||||
);
|
||||
|
||||
if (!appleTouchIcon) {
|
||||
appleTouchIcon = site?.site_view.site.icon
|
||||
? `data:image/png;base64,${await sharp(
|
||||
|
@ -45,28 +52,28 @@ export async function createSsrHtml(
|
|||
process.env["LEMMY_UI_DEBUG"] === "true"
|
||||
? renderToString(
|
||||
<>
|
||||
<script src="//cdn.jsdelivr.net/npm/eruda"></script>
|
||||
<script>eruda.init();</script>
|
||||
<script
|
||||
nonce={cspNonce}
|
||||
src="//cdn.jsdelivr.net/npm/eruda"
|
||||
></script>
|
||||
<script nonce={cspNonce}>eruda.init();</script>
|
||||
</>
|
||||
)
|
||||
: "";
|
||||
|
||||
const helmet = Helmet.renderStatic();
|
||||
|
||||
const config: ILemmyConfig = { wsHost: process.env.LEMMY_UI_LEMMY_WS_HOST };
|
||||
|
||||
return `
|
||||
<!DOCTYPE html>
|
||||
<html ${helmet.htmlAttributes.toString()}>
|
||||
<head>
|
||||
<script>window.isoData = ${serialize(isoData)}</script>
|
||||
<script>window.lemmyConfig = ${serialize(config)}</script>
|
||||
<script nonce="${cspNonce}">window.isoData = ${serialize(isoData)}</script>
|
||||
|
||||
<!-- A remote debugging utility for mobile -->
|
||||
${erudaStr}
|
||||
|
||||
<!-- Custom injected script -->
|
||||
${customHtmlHeader}
|
||||
${customHtmlHeaderWithNonce}
|
||||
|
||||
${helmet.title.toString()}
|
||||
${helmet.meta.toString()}
|
||||
|
|
|
@ -18,14 +18,9 @@ export type IsoDataOptionalSite<T extends RouteData = any> = Partial<
|
|||
> &
|
||||
Pick<IsoData<T>, Exclude<keyof IsoData<T>, "site_res">>;
|
||||
|
||||
export interface ILemmyConfig {
|
||||
wsHost?: string;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
isoData: IsoData;
|
||||
lemmyConfig?: ILemmyConfig;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue