adds support for X-Forwarded-Proto

This commit is contained in:
Mose Müller 2024-11-21 14:30:33 +01:00
parent 0e9ec7a66a
commit 5827cda316
4 changed files with 31 additions and 13 deletions

View File

@ -11,6 +11,7 @@
<script> <script>
// this will be set by the python backend if the service is behind a proxy which strips a prefix. The frontend can use this to build the paths to the resources. // this will be set by the python backend if the service is behind a proxy which strips a prefix. The frontend can use this to build the paths to the resources.
window.__FORWARDED_PREFIX__ = ""; window.__FORWARDED_PREFIX__ = "";
window.__FORWARDED_PROTO__ = "";
</script>` </script>`
<body> <body>

View File

@ -1,6 +1,6 @@
import { useCallback, useEffect, useReducer, useState } from "react"; import { useCallback, useEffect, useReducer, useState } from "react";
import { Navbar, Form, Offcanvas, Container } from "react-bootstrap"; import { Navbar, Form, Offcanvas, Container } from "react-bootstrap";
import { authority, socket } from "./socket"; import { authority, socket, forwardedProto } from "./socket";
import "./App.css"; import "./App.css";
import { import {
Notifications, Notifications,
@ -68,12 +68,12 @@ const App = () => {
useEffect(() => { useEffect(() => {
// Allow the user to add a custom css file // Allow the user to add a custom css file
fetch(`http://${authority}/custom.css`, { credentials: "include" }) fetch(`${forwardedProto}://${authority}/custom.css`, { credentials: "include" })
.then((response) => { .then((response) => {
if (response.ok) { if (response.ok) {
// If the file exists, create a link element for the custom CSS // If the file exists, create a link element for the custom CSS
const link = document.createElement("link"); const link = document.createElement("link");
link.href = `http://${authority}/custom.css`; link.href = `${forwardedProto}://${authority}/custom.css`;
link.type = "text/css"; link.type = "text/css";
link.rel = "stylesheet"; link.rel = "stylesheet";
document.head.appendChild(link); document.head.appendChild(link);
@ -83,7 +83,9 @@ const App = () => {
socket.on("connect", () => { socket.on("connect", () => {
// Fetch data from the API when the client connects // Fetch data from the API when the client connects
fetch(`http://${authority}/service-properties`, { credentials: "include" }) fetch(`${forwardedProto}://${authority}/service-properties`, {
credentials: "include",
})
.then((response) => response.json()) .then((response) => response.json())
.then((data: State) => { .then((data: State) => {
dispatch({ type: "SET_DATA", data }); dispatch({ type: "SET_DATA", data });
@ -91,7 +93,7 @@ const App = () => {
document.title = data.name; // Setting browser tab title document.title = data.name; // Setting browser tab title
}); });
fetch(`http://${authority}/web-settings`, { credentials: "include" }) fetch(`${forwardedProto}://${authority}/web-settings`, { credentials: "include" })
.then((response) => response.json()) .then((response) => response.json())
.then((data: Record<string, WebSetting>) => setWebSettings(data)); .then((data: Record<string, WebSetting>) => setWebSettings(data));
setConnectionStatus("connected"); setConnectionStatus("connected");

View File

@ -10,10 +10,16 @@ const port = process.env.NODE_ENV === "development" ? 8001 : window.location.por
export const forwardedPrefix: string = export const forwardedPrefix: string =
(window as any) /* eslint-disable-line @typescript-eslint/no-explicit-any */ (window as any) /* eslint-disable-line @typescript-eslint/no-explicit-any */
.__FORWARDED_PREFIX__ || ""; .__FORWARDED_PREFIX__ || "";
// Get the forwarded protocol type from the global variable
export const forwardedProto: string =
(window as any) /* eslint-disable-line @typescript-eslint/no-explicit-any */
.__FORWARDED_PROTO__ || "http";
export const authority = `${hostname}:${port}${forwardedPrefix}`; export const authority = `${hostname}:${port}${forwardedPrefix}`;
const URL = `ws://${hostname}:${port}/`; const wsProto = forwardedProto === "http" ? "ws" : "wss";
const URL = `${wsProto}://${hostname}:${port}/`;
console.debug("Websocket: ", URL); console.debug("Websocket: ", URL);
export const socket = io(URL, { export const socket = io(URL, {
path: `${forwardedPrefix}/ws/socket.io`, path: `${forwardedPrefix}/ws/socket.io`,

View File

@ -104,12 +104,8 @@ class WebServer:
async def index( async def index(
request: aiohttp.web.Request, request: aiohttp.web.Request,
) -> aiohttp.web.Response | aiohttp.web.FileResponse: ) -> aiohttp.web.Response | aiohttp.web.FileResponse:
# Read the X-Forwarded-Prefix header from the request forwarded_proto = request.headers["X-Forwarded-Proto"]
forwarded_prefix = request.headers.get("X-Forwarded-Prefix", "") escaped_proto = html.escape(forwarded_proto)
if forwarded_prefix != "":
# Escape the forwarded prefix to prevent XSS
escaped_prefix = html.escape(forwarded_prefix)
# Read the index.html file # Read the index.html file
index_file_path = self.frontend_src / "index.html" index_file_path = self.frontend_src / "index.html"
@ -117,8 +113,21 @@ class WebServer:
async with await anyio.open_file(index_file_path) as f: async with await anyio.open_file(index_file_path) as f:
html_content = await f.read() html_content = await f.read()
# Inject the escaped forwarded prefix into the HTML # Inject the escaped forwarded protocol into the HTML
modified_html = html_content.replace( modified_html = html_content.replace(
'window.__FORWARDED_PROTO__ = "";',
f'window.__FORWARDED_PROTO__ = "{escaped_proto}";',
)
# Read the X-Forwarded-Prefix header from the request
forwarded_prefix = request.headers.get("X-Forwarded-Prefix", "")
if forwarded_prefix != "":
# Escape the forwarded prefix to prevent XSS
escaped_prefix = html.escape(forwarded_prefix)
# Inject the escaped forwarded prefix into the HTML
modified_html = modified_html.replace(
'window.__FORWARDED_PREFIX__ = "";', 'window.__FORWARDED_PREFIX__ = "";',
f'window.__FORWARDED_PREFIX__ = "{escaped_prefix}";', f'window.__FORWARDED_PREFIX__ = "{escaped_prefix}";',
) )