mirror of
https://github.com/Pax1601/DCSOlympus.git
synced 2025-10-29 16:56:34 +00:00
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:
@@ -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;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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];
|
||||||
}) ||
|
}) ||
|
||||||
|
|||||||
@@ -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={`
|
||||||
|
|||||||
@@ -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";
|
||||||
|
|||||||
@@ -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`}>
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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 },
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user