Merge remote-tracking branch 'origin/release-candidate' into 1054-drawings-control-panel-separate-global-mission-drawings-control-from-navpoints-control

This commit is contained in:
MarcoJayUsai
2025-03-26 16:17:29 +01:00
8 changed files with 205 additions and 132 deletions

View File

@@ -43,7 +43,7 @@ export interface OlympusConfig {
authentication?: { authentication?: {
// Only sent when in localhost mode for autologin // Only sent when in localhost mode for autologin
gameMasterPassword: string; gameMasterPassword: string;
blueCommanderPasword: string; blueCommanderPassword: string;
redCommanderPassword: string; redCommanderPassword: string;
}; };
} }

View File

@@ -275,11 +275,9 @@ export class MissionManager {
var requestRefresh = false; var requestRefresh = false;
if (this.#commandModeOptions.commandMode === NONE && commandModeOptions.commandMode !== NONE) requestRefresh = true; if (this.#commandModeOptions.commandMode === NONE && commandModeOptions.commandMode !== NONE) requestRefresh = true;
/* Refresh the page if we have lost Game Master priviledges */
if (this.#commandModeOptions.commandMode === GAME_MASTER && commandModeOptions.commandMode !== GAME_MASTER) location.reload();
/* Check if any option has changed */ /* Check if any option has changed */
var commandModeOptionsChanged = var commandModeOptionsChanged =
commandModeOptions.commandMode !== this.getCommandModeOptions().commandMode ||
!commandModeOptions.eras.every((value: string, idx: number) => { !commandModeOptions.eras.every((value: string, idx: number) => {
return value === this.getCommandModeOptions().eras[idx]; return value === this.getCommandModeOptions().eras[idx];
}) || }) ||

View File

@@ -16,7 +16,7 @@ import { DrawSubState, ERAS_ORDER, IADSTypes, NO_SUBSTATE, OlympusState, Olympus
import { AppStateChangedEvent, CoalitionAreasChangedEvent, CoalitionAreaSelectedEvent, DrawingsInitEvent, DrawingsUpdatedEvent } from "../../events"; import { AppStateChangedEvent, CoalitionAreasChangedEvent, CoalitionAreaSelectedEvent, DrawingsInitEvent, DrawingsUpdatedEvent } from "../../events";
import { FaCopy, FaPencil, FaRegCompass, FaXmark } from "react-icons/fa6"; import { FaCopy, FaPencil, FaRegCompass, FaXmark } from "react-icons/fa6";
import { deepCopyTable } from "../../other/utils"; import { deepCopyTable } from "../../other/utils";
import { DCSDrawingsContainer, DCSEmptyLayer } from "../../map/drawings/drawingsmanager"; import { DCSDrawing, DCSDrawingsContainer, DCSEmptyLayer } from "../../map/drawings/drawingsmanager";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { OlSearchBar } from "../components/olsearchbar"; import { OlSearchBar } from "../components/olsearchbar";
@@ -67,6 +67,10 @@ export function DrawingMenu(props: { open: boolean; onClose: () => void }) {
CoalitionAreasChangedEvent.on((coalitionAreas) => setCoalitionAreas([...coalitionAreas])); CoalitionAreasChangedEvent.on((coalitionAreas) => setCoalitionAreas([...coalitionAreas]));
}, []); }, []);
function getDrawingLabelColor(drawing: DCSDrawingsContainer | DCSDrawing) {
return drawing.getVisibility() ? `text-gray-200` : `text-gray-600`;
}
function renderDrawingsContainerControls(container: DCSDrawingsContainer, containerSearchString: string) { function renderDrawingsContainerControls(container: DCSDrawingsContainer, containerSearchString: string) {
if (container.hasSearchString(containerSearchString)) { if (container.hasSearchString(containerSearchString)) {
/* The following snippet automatically open containers that contains searched drawings */ /* The following snippet automatically open containers that contains searched drawings */
@@ -98,8 +102,10 @@ export function DrawingMenu(props: { open: boolean; onClose: () => void }) {
<FontAwesomeIcon <FontAwesomeIcon
icon={container.getVisibility() ? faEye : faEyeSlash} icon={container.getVisibility() ? faEye : faEyeSlash}
className={` className={`
my-auto w-6 cursor-pointer text-gray-400 transition-transform my-auto w-6 cursor-pointer
hover:scale-125 hover:text-gray-200 ${getDrawingLabelColor(container)}
transition-transform
hover:scale-125 hover:text-gray-50
`} `}
onClick={() => { onClick={() => {
if (container === mainDrawingsContainer.container) { if (container === mainDrawingsContainer.container) {
@@ -111,7 +117,9 @@ export function DrawingMenu(props: { open: boolean; onClose: () => void }) {
/> />
<div <div
className={` className={`
w-40 w-max-40 overflow-hidden text-ellipsis text-nowrap bg- w-40
${getDrawingLabelColor(container)}
w-max-40 overflow-hidden text-ellipsis text-nowrap bg-
`} `}
> >
{container.getName()} {container.getName()}
@@ -139,15 +147,20 @@ export function DrawingMenu(props: { open: boolean; onClose: () => void }) {
<FontAwesomeIcon <FontAwesomeIcon
icon={drawing.getVisibility() ? faEye : faEyeSlash} icon={drawing.getVisibility() ? faEye : faEyeSlash}
className={` className={`
my-auto w-6 cursor-pointer text-gray-400 my-auto w-6 cursor-pointer
${getDrawingLabelColor(drawing)}
transition-transform transition-transform
hover:scale-125 hover:text-gray-200 hover:scale-125 hover:text-gray-50
`} `}
onClick={() => { onClick={() => {
drawing.setVisibility(!drawing.getVisibility()); drawing.setVisibility(!drawing.getVisibility());
}} }}
/> />
<div className={`overflow-hidden text-ellipsis text-nowrap`}>{drawing.getName()}</div> <div className={`
overflow-hidden
${getDrawingLabelColor(drawing)}
text-ellipsis text-nowrap
`}>{drawing.getName()}</div>
<FontAwesomeIcon <FontAwesomeIcon
icon={faMapLocation} icon={faMapLocation}
className={` className={`

View File

@@ -5,7 +5,7 @@ import { OlNumberInput } from "../components/olnumberinput";
import { getApp } from "../../olympusapp"; import { getApp } from "../../olympusapp";
import { ServerStatus } from "../../interfaces"; import { ServerStatus } from "../../interfaces";
import { CommandModeOptionsChangedEvent, ServerStatusUpdatedEvent } from "../../events"; import { CommandModeOptionsChangedEvent, ServerStatusUpdatedEvent } from "../../events";
import { BLUE_COMMANDER, COMMAND_MODE_OPTIONS_DEFAULTS, ERAS_ORDER, GAME_MASTER, RED_COMMANDER } from "../../constants/constants"; import { COMMAND_MODE_OPTIONS_DEFAULTS, ERAS_ORDER, GAME_MASTER } from "../../constants/constants";
import { secondsToTimeString } from "../../other/utils"; import { secondsToTimeString } from "../../other/utils";
import { FaQuestionCircle } from "react-icons/fa"; import { FaQuestionCircle } from "react-icons/fa";
import { FaMinus, FaPlus } from "react-icons/fa6"; import { FaMinus, FaPlus } from "react-icons/fa6";

View File

@@ -19,15 +19,25 @@ import { FaChevronLeft, FaChevronRight, FaFloppyDisk } from "react-icons/fa6";
import { import {
CommandModeOptionsChangedEvent, CommandModeOptionsChangedEvent,
ConfigLoadedEvent, ConfigLoadedEvent,
EnabledCommandModesChangedEvent,
HiddenTypesChangedEvent, HiddenTypesChangedEvent,
MapOptionsChangedEvent, MapOptionsChangedEvent,
MapSourceChangedEvent, MapSourceChangedEvent,
SessionDataChangedEvent, SessionDataChangedEvent,
SessionDataSavedEvent, SessionDataSavedEvent,
} from "../../events"; } from "../../events";
import { BLUE_COMMANDER, COMMAND_MODE_OPTIONS_DEFAULTS, MAP_HIDDEN_TYPES_DEFAULTS, MAP_OPTIONS_DEFAULTS, RED_COMMANDER } from "../../constants/constants"; import {
BLUE_COMMANDER,
COMMAND_MODE_OPTIONS_DEFAULTS,
GAME_MASTER,
LoginSubState,
MAP_HIDDEN_TYPES_DEFAULTS,
MAP_OPTIONS_DEFAULTS,
OlympusState,
RED_COMMANDER,
} from "../../constants/constants";
import { OlympusConfig } from "../../interfaces"; import { OlympusConfig } from "../../interfaces";
import { FaCheck, FaSpinner } from "react-icons/fa"; import { FaCheck, FaRedo, FaSpinner } from "react-icons/fa";
import { OlExpandingTooltip } from "../components/olexpandingtooltip"; import { OlExpandingTooltip } from "../components/olexpandingtooltip";
export function Header() { export function Header() {
@@ -44,6 +54,8 @@ export function Header() {
const [isLatestVersion, setIsLatestVersion] = useState(false); const [isLatestVersion, setIsLatestVersion] = useState(false);
const [isBetaVersion, setIsBetaVersion] = useState(false); const [isBetaVersion, setIsBetaVersion] = useState(false);
const [isDevVersion, setIsDevVersion] = useState(false); const [isDevVersion, setIsDevVersion] = useState(false);
const [enabledCommandModes, setEnabledCommandModes] = useState([] as string[]);
const [loadingNewCommandMode, setLoadingNewCommandMode] = useState(false);
useEffect(() => { useEffect(() => {
HiddenTypesChangedEvent.on((hiddenTypes) => setMapHiddenTypes({ ...hiddenTypes })); HiddenTypesChangedEvent.on((hiddenTypes) => setMapHiddenTypes({ ...hiddenTypes }));
@@ -60,9 +72,11 @@ export function Header() {
}); });
CommandModeOptionsChangedEvent.on((commandModeOptions) => { CommandModeOptionsChangedEvent.on((commandModeOptions) => {
setCommandModeOptions(commandModeOptions); setCommandModeOptions(commandModeOptions);
setLoadingNewCommandMode(false);
}); });
SessionDataChangedEvent.on(() => setSavingSessionData(true)); SessionDataChangedEvent.on(() => setSavingSessionData(true));
SessionDataSavedEvent.on(() => setSavingSessionData(false)); SessionDataSavedEvent.on(() => setSavingSessionData(false));
EnabledCommandModesChangedEvent.on((enabledCommandModes) => setEnabledCommandModes(enabledCommandModes));
/* Check if we are running the latest version */ /* Check if we are running the latest version */
const request = new Request("https://raw.githubusercontent.com/Pax1601/DCSOlympus/main/version.json"); const request = new Request("https://raw.githubusercontent.com/Pax1601/DCSOlympus/main/version.json");
@@ -213,14 +227,79 @@ export function Header() {
)} )}
</div> </div>
{commandModeOptions.commandMode === GAME_MASTER && (
<div
className={`
flex h-full cursor-pointer rounded-md border-2 border-transparent
bg-olympus-600 px-4 text-gray-200
hover:bg-olympus-400
`}
onClick={() => {
if (enabledCommandModes.length > 0) {
let blueCommandModeIndex = enabledCommandModes.indexOf(BLUE_COMMANDER);
let redCommandModeIndex = enabledCommandModes.indexOf(RED_COMMANDER);
if (blueCommandModeIndex >= 0) getApp().getServerManager().setActiveCommandMode(BLUE_COMMANDER);
else if (redCommandModeIndex >= 0) getApp().getServerManager().setActiveCommandMode(RED_COMMANDER);
setLoadingNewCommandMode(true);
}
}}
>
<span className="my-auto font-bold">Game Master</span>
{enabledCommandModes.length > 0 && (
<>{loadingNewCommandMode ? <FaSpinner className={`
my-auto ml-2 animate-spin text-white
`} /> : <FaRedo className={`my-auto ml-2 text-gray-200`} />}</>
)}
</div>
)}
{commandModeOptions.commandMode === BLUE_COMMANDER && ( {commandModeOptions.commandMode === BLUE_COMMANDER && (
<div className={`flex h-full rounded-md bg-blue-600 px-4 text-white`}> <div
<span className="my-auto font-bold">BLUE Commander ({commandModeOptions.spawnPoints.blue} points)</span> className={`
flex h-full cursor-pointer rounded-md border-2 border-transparent
bg-blue-600 px-4 text-gray-200
hover:bg-blue-400
`}
onClick={() => {
if (enabledCommandModes.length > 0) {
let gameMasterCommandModeIndex = enabledCommandModes.indexOf(GAME_MASTER);
let redCommandModeIndex = enabledCommandModes.indexOf(RED_COMMANDER);
if (redCommandModeIndex >= 0) getApp().getServerManager().setActiveCommandMode(RED_COMMANDER);
else if (gameMasterCommandModeIndex >= 0) getApp().getServerManager().setActiveCommandMode(GAME_MASTER);
setLoadingNewCommandMode(true);
}
}}
>
<span className="my-auto font-bold">BLUE Commander</span>
{enabledCommandModes.length > 0 && (
<>{loadingNewCommandMode ? <FaSpinner className={`
my-auto ml-2 animate-spin text-gray-200
`} /> : <FaRedo className={`my-auto ml-2 text-gray-200`} />}</>
)}
</div> </div>
)} )}
{commandModeOptions.commandMode === RED_COMMANDER && ( {commandModeOptions.commandMode === RED_COMMANDER && (
<div className={`flex h-full rounded-md bg-red-600 px-4 text-white`}> <div
<span className="my-auto font-bold">BLUE Commander ({commandModeOptions.spawnPoints.blue} points)</span> className={`
flex h-full cursor-pointer rounded-md border-2 border-transparent
bg-red-600 px-4 text-gray-200
hover:bg-red-500
`}
onClick={() => {
if (enabledCommandModes.length > 0) {
let gameMasterCommandModeIndex = enabledCommandModes.indexOf(GAME_MASTER);
let blueCommandModeIndex = enabledCommandModes.indexOf(BLUE_COMMANDER);
if (gameMasterCommandModeIndex >= 0) getApp().getServerManager().setActiveCommandMode(GAME_MASTER);
else if (blueCommandModeIndex >= 0) getApp().getServerManager().setActiveCommandMode(BLUE_COMMANDER);
setLoadingNewCommandMode(true);
}
}}
>
<span className="my-auto font-bold">RED Commander</span>
{enabledCommandModes.length > 0 && (
<>{loadingNewCommandMode ? <FaSpinner className={`
my-auto ml-2 animate-spin text-gray-200
`} /> : <FaRedo className={`my-auto ml-2 text-gray-200`} />}</>
)}
</div> </div>
)} )}
<div className={`flex h-fit flex-row items-center justify-start gap-1`}> <div className={`flex h-fit flex-row items-center justify-start gap-1`}>

View File

@@ -10,42 +10,7 @@ import expressBasicAuth from "express-basic-auth";
/* Load the proxy middleware plugin */ /* Load the proxy middleware plugin */
import httpProxyMiddleware = require("http-proxy-middleware"); import httpProxyMiddleware = require("http-proxy-middleware");
import { getUserFromCustomHeaders, connectionIsLocal } from "./utils";
function checkCustomHeaders(config, usersConfig, groupsConfig, req) {
let user = req.auth?.user ?? null;
let group = null;
/* Check if custom authorization headers are enabled */
if (
"customAuthHeaders" in config["frontend"] &&
config["frontend"]["customAuthHeaders"]["enabled"]
) {
/* If so, check that the custom headers are indeed present */
if (
config["frontend"]["customAuthHeaders"]["username"].toLowerCase() in
req.headers &&
config["frontend"]["customAuthHeaders"]["group"].toLowerCase() in
req.headers
) {
/* If they are, assign the group */
group =
req.headers[
config["frontend"]["customAuthHeaders"]["group"].toLowerCase()
];
/* Check that the user is in an existing group */
if (group in groupsConfig) {
user =
req.headers[
config["frontend"]["customAuthHeaders"]["username"].toLowerCase()
];
usersConfig[user] = { password: null, roles: groupsConfig[group] };
}
}
}
return user;
}
module.exports = function (configLocation, viteProxy) { module.exports = function (configLocation, viteProxy) {
/* Config specific routers */ /* Config specific routers */
@@ -53,41 +18,25 @@ module.exports = function (configLocation, viteProxy) {
const resourcesRouter = require("./routes/resources")(configLocation); const resourcesRouter = require("./routes/resources")(configLocation);
const adminRouter = require("./routes/admin")(configLocation); const adminRouter = require("./routes/admin")(configLocation);
/* Database routers */
const databasesLocation = path.join(path.dirname(configLocation), "..", "Mods", "Services", "Olympus", "databases");
const databasesRouter = require("./routes/api/databases")(databasesLocation);
/* Default routers */ /* Default routers */
const airbasesRouter = require("./routes/api/airbases"); const airbasesRouter = require("./routes/api/airbases");
const databasesRouter = require("./routes/api/databases")(
path.join(
path.dirname(configLocation),
"..",
"Mods",
"Services",
"Olympus",
"databases"
)
);
const speechRouter = require("./routes/api/speech")(); const speechRouter = require("./routes/api/speech")();
/* Read the users configuration file */ /* Read the users configuration file */
let usersConfig = {}; let usersConfig = {};
if ( if (fs.existsSync(path.join(path.dirname(configLocation), "olympusUsers.json"))) {
fs.existsSync(path.join(path.dirname(configLocation), "olympusUsers.json")) let rawdata = fs.readFileSync(path.join(path.dirname(configLocation), "olympusUsers.json"), { encoding: "utf-8" });
) {
let rawdata = fs.readFileSync(
path.join(path.dirname(configLocation), "olympusUsers.json"),
{ encoding: "utf-8" }
);
usersConfig = JSON.parse(rawdata); usersConfig = JSON.parse(rawdata);
} }
/* Read the groups configuration file */ /* Read the groups configuration file */
let groupsConfig = {}; let groupsConfig = {};
if ( if (fs.existsSync(path.join(path.dirname(configLocation), "olympusGroups.json"))) {
fs.existsSync(path.join(path.dirname(configLocation), "olympusGroups.json")) let rawdata = fs.readFileSync(path.join(path.dirname(configLocation), "olympusGroups.json"), { encoding: "utf-8" });
) {
let rawdata = fs.readFileSync(
path.join(path.dirname(configLocation), "olympusGroups.json"),
{ encoding: "utf-8" }
);
groupsConfig = JSON.parse(rawdata); groupsConfig = JSON.parse(rawdata);
} }
@@ -109,23 +58,21 @@ module.exports = function (configLocation, viteProxy) {
const app = express(); const app = express();
/* Define the authentication */ /* Define the authentication */
const defaultUsers = { const commandRoles = {
"Game master": config["authentication"]["gameMasterPassword"], "Game master": config["authentication"]["gameMasterPassword"],
"Blue commander": config["authentication"]["blueCommanderPassword"], "Blue commander": config["authentication"]["blueCommanderPassword"],
"Red commander": config["authentication"]["redCommanderPassword"], "Red commander": config["authentication"]["redCommanderPassword"],
}; };
if (config["authentication"]["adminPassword"]) { if (config["authentication"]["adminPassword"]) {
defaultUsers["Admin"] = config["authentication"]["adminPassword"]; commandRoles["Admin"] = config["authentication"]["adminPassword"];
} }
let users = {}; let users = {};
Object.keys(usersConfig).forEach( Object.keys(usersConfig).forEach((user) => (users[user] = usersConfig[user].password));
(user) => (users[user] = usersConfig[user].password)
);
const auth = expressBasicAuth({ const auth = expressBasicAuth({
users: { ...defaultUsers, ...users }, users: { ...commandRoles, ...users },
}); });
/* Define middleware */ /* Define logging middleware */
app.use( app.use(
logger("dev", { logger("dev", {
skip: function (req, res) { skip: function (req, res) {
@@ -135,28 +82,18 @@ module.exports = function (configLocation, viteProxy) {
); );
/* Authorization middleware */ /* Authorization middleware */
if ( if ("customAuthHeaders" in config["frontend"] && config["frontend"]["customAuthHeaders"]["enabled"]) {
"customAuthHeaders" in config["frontend"] &&
config["frontend"]["customAuthHeaders"]["enabled"]
) {
/* Custom authorization will be used */ /* Custom authorization will be used */
app.use("/", async (req, res, next) => { app.use("/", async (req, res, next) => {
const user = checkCustomHeaders(config, usersConfig, groupsConfig, req); const user = getUserFromCustomHeaders(config, usersConfig, groupsConfig, req);
const customHeadersUsername = config["frontend"]["customAuthHeaders"]["username"].toLowerCase();
const customHeadersGroup = config["frontend"]["customAuthHeaders"]["group"].toLowerCase();
if (user) { if (user) {
/* If the user is preauthorized, set the authorization headers to the response */ /* If the user is preauthorized, set the authorization headers to the response */
res.set( res.set(customHeadersUsername, req.headers[customHeadersUsername]);
config["frontend"]["customAuthHeaders"]["username"], res.set(customHeadersGroup, req.headers[customHeadersGroup]);
req.headers[
config["frontend"]["customAuthHeaders"]["username"].toLowerCase()
]
);
res.set(
config["frontend"]["customAuthHeaders"]["group"],
req.headers[
config["frontend"]["customAuthHeaders"]["group"].toLowerCase()
]
);
} }
next(); next();
@@ -169,10 +106,7 @@ module.exports = function (configLocation, viteProxy) {
/* Define the middleware to replace the authorization header for the olympus backend */ /* Define the middleware to replace the authorization header for the olympus backend */
app.use("/olympus", async (req, res, next) => { app.use("/olympus", async (req, res, next) => {
/* Check if custom authorization headers are being used */ /* Check if custom authorization headers are being used */
const user = const user = req.auth?.user ?? getUserFromCustomHeaders(config, usersConfig, groupsConfig, req);
//@ts-ignore
req.auth?.user ??
checkCustomHeaders(config, usersConfig, groupsConfig, req);
/* If either simple authentication or custom authentication has succeded */ /* If either simple authentication or custom authentication has succeded */
if (user) { if (user) {
@@ -185,12 +119,10 @@ module.exports = function (configLocation, viteProxy) {
/* Check that the user is authorized to that role */ /* Check that the user is authorized to that role */
if (userConfig.roles.includes(req.headers["x-command-mode"])) { if (userConfig.roles.includes(req.headers["x-command-mode"])) {
/* Check that the role is valid */ /* Check that the role is valid */
//@ts-ignore if (req.headers["x-command-mode"] in commandRoles) {
if (req.headers["x-command-mode"] in defaultUsers) {
/* Apply the authorization headers */ /* Apply the authorization headers */
req.headers.authorization = `Basic ${btoa( req.headers.authorization = `Basic ${btoa(
//@ts-ignore user + ":" + commandRoles[req.headers["x-command-mode"]]
user + ":" + defaultUsers[req.headers["x-command-mode"]]
)}`; )}`;
} else { } else {
res.sendStatus(401); // Unauthorized res.sendStatus(401); // Unauthorized
@@ -201,23 +133,33 @@ module.exports = function (configLocation, viteProxy) {
} else { } else {
/* No role has been specified, continue with the highest role */ /* No role has been specified, continue with the highest role */
/* Check that the role is valid */ /* Check that the role is valid */
if (userConfig.roles[0] in defaultUsers) { if (userConfig.roles[0] in commandRoles) {
/* Apply the authorization headers */ /* Apply the authorization headers */
req.headers.authorization = `Basic ${btoa( req.headers.authorization = `Basic ${btoa(userConfig.roles[0] + ":" + commandRoles[userConfig.roles[0]])}`;
userConfig.roles[0] + ":" + defaultUsers[userConfig.roles[0]]
)}`;
} else { } else {
res.sendStatus(401); // Unauthorized res.sendStatus(401); // Unauthorized
} }
} }
} else { } else {
if (!(user in defaultUsers)) res.sendStatus(401); // Unauthorized if (!(user in commandRoles)) res.sendStatus(401); // Unauthorized
} }
/* Send back the roles that the user is enabled to */ /* Send back the roles that the user is enabled to */
if (userConfig) res.set("X-Enabled-Command-Modes", `${userConfig.roles}`); if (connectionIsLocal(config, req)) {
else if (user in defaultUsers) /* If the connection is local, all roles are enabled */
res.set("X-Enabled-Command-Modes", `${user}`); res.set("X-Enabled-Command-Modes", "Game master,Blue commander,Red commander");
if (req.headers["x-command-mode"]) {
const commandMode = req.headers["x-command-mode"];
/* Apply the authorization headers */
if (commandMode in commandRoles) {
req.headers.authorization = `Basic ${btoa(commandMode + ":" + commandRoles[commandMode])}`;
}
}
} else {
/* If the connection is not local, only the roles that the user is enabled to are enabled */
if (userConfig) res.set("X-Enabled-Command-Modes", `${userConfig.roles}`);
else if (user in commandRoles) res.set("X-Enabled-Command-Modes", `${user}`);
}
next(); next();
} else { } else {
res.sendStatus(401); // Unauthorized res.sendStatus(401); // Unauthorized
@@ -225,28 +167,27 @@ module.exports = function (configLocation, viteProxy) {
}); });
/* Proxy middleware */ /* Proxy middleware */
/* If a port is defined we assume the backend is of the type IP:port */
if (config["backend"]["port"]) { if (config["backend"]["port"]) {
app.use( app.use(
"/olympus", "/olympus",
httpProxyMiddleware.createProxyMiddleware({ httpProxyMiddleware.createProxyMiddleware({
target: `http://${ target: `http://${backendAddress === "*" ? "localhost" : backendAddress}:${config["backend"]["port"]}/olympus`,
backendAddress === "*" ? "localhost" : backendAddress
}:${config["backend"]["port"]}/olympus`,
changeOrigin: true, changeOrigin: true,
}) })
); );
} else { } else {
/* Otherwise we assume it is a url */
app.use( app.use(
"/olympus", "/olympus",
httpProxyMiddleware.createProxyMiddleware({ httpProxyMiddleware.createProxyMiddleware({
target: `https://${ target: `https://${backendAddress === "*" ? "localhost" : backendAddress}/olympus`,
backendAddress === "*" ? "localhost" : backendAddress
}/olympus`,
changeOrigin: true, changeOrigin: true,
}) })
); );
} }
/* More middleware */
app.use(bodyParser.json({ limit: "50mb" })); app.use(bodyParser.json({ limit: "50mb" }));
app.use(bodyParser.urlencoded({ limit: "50mb", extended: true })); app.use(bodyParser.urlencoded({ limit: "50mb", extended: true }));
app.use(express.static(path.join(__dirname, "..", "public"))); app.use(express.static(path.join(__dirname, "..", "public")));
@@ -259,10 +200,12 @@ module.exports = function (configLocation, viteProxy) {
app.use("/api/speech", speechRouter); app.use("/api/speech", speechRouter);
app.use("/resources", resourcesRouter); app.use("/resources", resourcesRouter);
/* Admin routers */
app.use("/admin", auth); app.use("/admin", auth);
app.use("/admin", adminRouter); app.use("/admin", adminRouter);
/* Set default index */ /* Set default index */
/* If we are in Vite mode, proxy the requests to the vite server */
if (viteProxy) { if (viteProxy) {
app.use( app.use(
"/", "/",
@@ -272,6 +215,7 @@ module.exports = function (configLocation, viteProxy) {
}) })
); );
} else { } else {
/* Otherwise serve the static files */
app.get("/", function (req, res) { app.get("/", function (req, res) {
res.sendFile(path.join(__dirname, "..", "public", "index.html")); res.sendFile(path.join(__dirname, "..", "public", "index.html"));
}); });
@@ -279,10 +223,7 @@ module.exports = function (configLocation, viteProxy) {
/* Start the audio backend */ /* Start the audio backend */
if (config["audio"]) { if (config["audio"]) {
let audioBackend = new AudioBackend( let audioBackend = new AudioBackend(config["audio"]["SRSPort"], config["audio"]["WSPort"]);
config["audio"]["SRSPort"],
config["audio"]["WSPort"]
);
audioBackend.start(); audioBackend.start();
} }

View File

@@ -1,6 +1,7 @@
import express = require("express"); import express = require("express");
import fs = require("fs"); import fs = require("fs");
import path = require("path"); import path = require("path");
import { connectionIsLocal as checkConnectionIsLocal } from "../utils";
const router = express.Router(); const router = express.Router();
@@ -26,10 +27,7 @@ module.exports = function (configLocation) {
let rawdata = fs.readFileSync(configLocation, "utf-8"); let rawdata = fs.readFileSync(configLocation, "utf-8");
const config = JSON.parse(rawdata); const config = JSON.parse(rawdata);
/* Check if the connection is local */ const local = checkConnectionIsLocal(config, req);
let local = false;
if (config.frontend.autoconnectWhenLocal)
local = req.headers[config.frontend.proxyHeader] === undefined;
let resConfig = { let resConfig = {
frontend: { ...config.frontend }, frontend: { ...config.frontend },

View File

@@ -28,3 +28,47 @@ export function doubleToByteArray(number) {
export function byteArrayToDouble(array) { export function byteArrayToDouble(array) {
return new DataView(array.reverse().buffer).getFloat64(0); return new DataView(array.reverse().buffer).getFloat64(0);
} }
export function connectionIsLocal(config, req) {
/* Check if the connection is local, and if autoconnection is enabled */
let local = false;
if (config.frontend.autoconnectWhenLocal) {
var ip = req.connection.remoteAddress;
var host = req.get('host');
/* If the request address is not localhost, we are not local */
if (!(ip === "127.0.0.1" || ip === "::ffff:127.0.0.1" || ip === "::1" || host.indexOf("localhost") !== -1)) {
local = false;
} else {
/* If the request address is localhost, we are local unless a proxyHeader is present (to be used with reverse proxies) */
local = req.headers[config.frontend.proxyHeader] === undefined;
}
}
return local;
}
export function getUserFromCustomHeaders(config, usersConfig, groupsConfig, req) {
let user = req.auth?.user ?? null;
let group = null;
/* Check if custom authorization headers are enabled */
if ("customAuthHeaders" in config["frontend"] && config["frontend"]["customAuthHeaders"]["enabled"]) {
/* If so, check that the custom headers are indeed present */
if (
config["frontend"]["customAuthHeaders"]["username"].toLowerCase() in req.headers &&
config["frontend"]["customAuthHeaders"]["group"].toLowerCase() in req.headers
) {
/* If they are, assign the group */
group = req.headers[config["frontend"]["customAuthHeaders"]["group"].toLowerCase()];
/* Check that the user is in an existing group */
if (group in groupsConfig) {
user = req.headers[config["frontend"]["customAuthHeaders"]["username"].toLowerCase()];
usersConfig[user] = { password: null, roles: groupsConfig[group] };
}
}
}
return user;
}