diff --git a/Dockerfile b/Dockerfile
index 92b3f7e6..00baae14 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -42,6 +42,9 @@ FROM node:alpine as runner
COPY --from=builder /usr/src/app/dist /app/dist
COPY --from=builder /usr/src/app/node_modules /app/node_modules
+RUN chown -R node:node /app
+
+USER node
EXPOSE 1234
WORKDIR /app
CMD node dist/js/server.js
diff --git a/lemmy-translations b/lemmy-translations
index 713ceed9..a1a19aea 160000
--- a/lemmy-translations
+++ b/lemmy-translations
@@ -1 +1 @@
-Subproject commit 713ceed9c7ef84deaa222e68361e670e0763cd83
+Subproject commit a1a19aea1ad7d91195775a5ccea62ccc9076a2c7
diff --git a/package.json b/package.json
index bcdb20c6..5c932a96 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "lemmy-ui",
- "version": "0.18.1",
+ "version": "0.18.2-rc.1",
"description": "An isomorphic UI for lemmy",
"repository": "https://github.com/LemmyNet/lemmy-ui",
"license": "AGPL-3.0",
@@ -69,7 +69,6 @@
"jwt-decode": "^3.1.2",
"lemmy-js-client": "0.18.1",
"lodash.isequal": "^4.5.0",
- "lodash.merge": "^4.6.2",
"markdown-it": "^13.0.1",
"markdown-it-container": "^3.0.0",
"markdown-it-emoji": "^2.0.2",
diff --git a/src/assets/symbols.svg b/src/assets/symbols.svg
index 72214eaf..6e9c6ef9 100644
--- a/src/assets/symbols.svg
+++ b/src/assets/symbols.svg
@@ -258,5 +258,12 @@
+
+
+
+
+
+
+
diff --git a/src/server/handlers/catch-all-handler.tsx b/src/server/handlers/catch-all-handler.tsx
index 4b011045..06d38f31 100644
--- a/src/server/handlers/catch-all-handler.tsx
+++ b/src/server/handlers/catch-all-handler.tsx
@@ -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);
diff --git a/src/server/handlers/robots-handler.ts b/src/server/handlers/robots-handler.ts
index 80678aa0..f26ddc92 100644
--- a/src/server/handlers/robots-handler.ts
+++ b/src/server/handlers/robots-handler.ts
@@ -15,5 +15,6 @@ export default async ({ res }: { res: Response }) => {
Disallow: /admin
Disallow: /password_change
Disallow: /search/
+ Disallow: /modlog
`);
};
diff --git a/src/server/index.tsx b/src/server/index.tsx
index 458d7f03..3b9352bf 100644
--- a/src/server/index.tsx
+++ b/src/server/index.tsx
@@ -29,7 +29,11 @@ server.use(
);
server.use(setCacheControl);
-if (!process.env["LEMMY_UI_DISABLE_CSP"] && !process.env["LEMMY_UI_DEBUG"]) {
+if (
+ !process.env["LEMMY_UI_DISABLE_CSP"] &&
+ !process.env["LEMMY_UI_DEBUG"] &&
+ process.env["NODE_ENV"] !== "development"
+) {
server.use(setDefaultCsp);
}
diff --git a/src/server/middleware.ts b/src/server/middleware.ts
index 0420e47e..a75d49ed 100644
--- a/src/server/middleware.ts
+++ b/src/server/middleware.ts
@@ -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();
diff --git a/src/server/utils/create-ssr-html.tsx b/src/server/utils/create-ssr-html.tsx
index ba85228f..71fdb68f 100644
--- a/src/server/utils/create-ssr-html.tsx
+++ b/src/server/utils/create-ssr-html.tsx
@@ -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("
-
+
+
>
)
: "";
const helmet = Helmet.renderStatic();
- const config: ILemmyConfig = { wsHost: process.env.LEMMY_UI_LEMMY_WS_HOST };
-
return `
-
-
+
${erudaStr}
- ${customHtmlHeader}
+ ${customHtmlHeaderWithNonce}
${helmet.title.toString()}
${helmet.meta.toString()}
diff --git a/src/server/utils/generate-manifest-json.ts b/src/server/utils/generate-manifest-json.ts
index 2f9d8b80..89e106eb 100644
--- a/src/server/utils/generate-manifest-json.ts
+++ b/src/server/utils/generate-manifest-json.ts
@@ -1,4 +1,3 @@
-import { getHttpBaseExternal } from "@utils/env";
import { readFile } from "fs/promises";
import { GetSiteResponse } from "lemmy-js-client";
import path from "path";
@@ -21,15 +20,13 @@ export default async function ({
local_site: { community_creation_admin_only },
},
}: GetSiteResponse) {
- const url = getHttpBaseExternal();
-
const icon = site.icon ? await fetchIconPng(site.icon) : null;
return {
name: site.name,
description: site.description ?? "A link aggregator for the fediverse",
- start_url: url,
- scope: url,
+ start_url: "/",
+ scope: "/",
display: "standalone",
id: "/",
background_color: "#222222",
diff --git a/src/shared/components/app/theme.tsx b/src/shared/components/app/theme.tsx
index 941eea2c..93f6aed3 100644
--- a/src/shared/components/app/theme.tsx
+++ b/src/shared/components/app/theme.tsx
@@ -21,7 +21,10 @@ export class Theme extends Component {
/>
);
- } else if (this.props.defaultTheme != "browser") {
+ } else if (
+ this.props.defaultTheme != "browser" &&
+ this.props.defaultTheme != "browser-compact"
+ ) {
return (
{
/>
);
+ } else if (this.props.defaultTheme == "browser-compact") {
+ return (
+
+
+
+
+ );
} else {
return (
diff --git a/src/shared/components/comment/comment-node.tsx b/src/shared/components/comment/comment-node.tsx
index 6c7d5c00..7b7f29e5 100644
--- a/src/shared/components/comment/comment-node.tsx
+++ b/src/shared/components/comment/comment-node.tsx
@@ -114,7 +114,7 @@ interface CommentNodeProps {
moderators?: CommunityModeratorView[];
admins?: PersonView[];
noBorder?: boolean;
- noIndent?: boolean;
+ isTopLevel?: boolean;
viewOnly?: boolean;
locked?: boolean;
markable?: boolean;
@@ -292,11 +292,7 @@ export class CommentNode extends Component {
mark: this.isCommentNew || this.commentView.comment.distinguished,
})}
>
-
+