feat: Added custom authentication headers support

bugfix: Fixed incorrect saving of quick access spawn
refactor: Removed compactunitspawnmenu and condensed in single unitspawnmenu class to reuse code
This commit is contained in:
Davide Passoni
2024-12-05 19:37:38 +01:00
parent c11a9728e8
commit 258d21672c
17 changed files with 1628 additions and 1379 deletions

View File

@@ -6,10 +6,39 @@ import fs = require("fs");
import bodyParser = require("body-parser");
import cors = require("cors");
import { AudioBackend } from "./audio/audiobackend";
import expressBasicAuth from "express-basic-auth";
/* Load the proxy middleware plugin */
import httpProxyMiddleware = require("http-proxy-middleware");
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) {
/* Config specific routers */
const elevationRouter = require("./routes/api/elevation")(configLocation);
@@ -29,6 +58,30 @@ module.exports = function (configLocation, viteProxy) {
);
const speechRouter = require("./routes/api/speech")();
/* Read the users configuration file */
let usersConfig = {};
if (
fs.existsSync(path.join(path.dirname(configLocation), "olympusUsers.json"))
) {
let rawdata = fs.readFileSync(
path.join(path.dirname(configLocation), "olympusUsers.json"),
{ encoding: "utf-8" }
);
usersConfig = JSON.parse(rawdata);
}
/* Read the groups configuration file */
let groupsConfig = {};
if (
fs.existsSync(path.join(path.dirname(configLocation), "olympusGroups.json"))
) {
let rawdata = fs.readFileSync(
path.join(path.dirname(configLocation), "olympusGroups.json"),
{ encoding: "utf-8" }
);
groupsConfig = JSON.parse(rawdata);
}
/* Load the config and create the express app */
let config = {};
console.log(`Loading configuration file from ${configLocation}`);
@@ -46,8 +99,94 @@ module.exports = function (configLocation, viteProxy) {
/* Start the express app */
const app = express();
/* Define the authentication */
const defaultUsers = {
"Game master": config["authentication"]["gameMasterPassword"],
"Blue commander": config["authentication"]["blueCommanderPassword"],
"Red commander": config["authentication"]["redCommanderPassword"],
};
let users = {};
Object.keys(usersConfig).forEach(
(user) => (users[user] = usersConfig[user].password)
);
const auth = expressBasicAuth({
users: { ...defaultUsers, ...users },
});
/* Define middleware */
app.use(logger("dev"));
/* Authorization middleware */
if ("customAuthHeaders" in config["frontend"] && config["frontend"]["customAuthHeaders"]["enabled"]) {
/* Custom authorization will be used */
app.use("/", async (req, res, next) => {
const user = checkCustomHeaders(config, usersConfig, groupsConfig, req);
if (user) {
/* If the user is preauthorized, set the authorization headers to the response */
res.set(config["frontend"]["customAuthHeaders"]["username"], req.headers[(config["frontend"]["customAuthHeaders"]["username"]).toLowerCase()])
res.set(config["frontend"]["customAuthHeaders"]["group"], req.headers[(config["frontend"]["customAuthHeaders"]["group"]).toLowerCase()])
}
next()
})
} else {
/* Simple internal authorization will be used */
app.use("/olympus", auth);
}
/* Define the middleware to replace the authorization header for the olympus backend */
app.use("/olympus", async (req, res, next) => {
/* Check if custom authorization headers are being used */
const user = req.auth?.user ?? checkCustomHeaders(config, usersConfig, groupsConfig, req);
/* If either simple authentication or custom authentication has succeded */
if (user) {
const userConfig = usersConfig[user];
/* Check that the user is authorized to at least one role */
if (userConfig.roles.length > 0) {
/* If a specific command role is requested, proceed with that role */
if (req.headers["x-command-mode"]) {
/* Check that the user is authorized to that role */
if (userConfig.roles.includes(req.headers["x-command-mode"]) ) {
/* Check that the role is valid */
if (req.headers["x-command-mode"] in defaultUsers) {
/* Apply the authorization headers */
req.headers.authorization = `Basic ${btoa(
user + ":" + defaultUsers[req.headers["x-command-mode"]]
)}`;
} else {
res.sendStatus(401); // Unauthorized
}
} else {
res.sendStatus(401); // Unauthorized
}
} else {
/* No role has been specified, continue with the highest role */
/* Check that the role is valid */
if (userConfig.roles[0] in defaultUsers) {
/* Apply the authorization headers */
req.headers.authorization = `Basic ${btoa(
user + ":" + defaultUsers[userConfig.roles[0]]
)}`;
} else {
res.sendStatus(401); // Unauthorized
}
}
} else {
res.sendStatus(401); // Unauthorized
}
/* Send back the roles that the user is enabled to */
res.set('X-Enabled-Command-Modes', `${userConfig.roles}`);
next();
} else {
res.sendStatus(401); // Unauthorized
}
});
/* Proxy middleware */
app.use(
"/olympus",
httpProxyMiddleware.createProxyMiddleware({
@@ -63,7 +202,7 @@ module.exports = function (configLocation, viteProxy) {
"/vite",
httpProxyMiddleware.createProxyMiddleware({
target: `http://localhost:8080/`,
ws: true
ws: true,
})
);
}
@@ -92,7 +231,10 @@ module.exports = function (configLocation, viteProxy) {
}
if (config["audio"]) {
let audioBackend = new AudioBackend(config["audio"]["SRSPort"], config["audio"]["WSPort"]);
let audioBackend = new AudioBackend(
config["audio"]["SRSPort"],
config["audio"]["WSPort"]
);
audioBackend.start();
}

View File

@@ -1,12 +1,26 @@
import express = require("express");
import fs = require("fs");
import path = require("path");
const router = express.Router();
let sessionHash = "";
let sessionData = {}
let sessionData = {};
module.exports = function (configLocation) {
router.get("/config", function (req, res, next) {
let profiles = null;
if (
fs.existsSync(
path.join(path.dirname(configLocation), "olympusProfiles.json")
)
) {
let rawdata = fs.readFileSync(
path.join(path.dirname(configLocation), "olympusProfiles.json"),
"utf-8"
);
profiles = JSON.parse(rawdata);
}
if (fs.existsSync(configLocation)) {
let rawdata = fs.readFileSync(configLocation, "utf-8");
const config = JSON.parse(rawdata);
@@ -14,7 +28,7 @@ module.exports = function (configLocation) {
JSON.stringify({
frontend: { ...config.frontend },
audio: { ...(config.audio ?? {}) },
profiles: { ...(config.profiles ?? {}) },
profiles: { ...(profiles ?? {}) },
})
);
res.end();
@@ -24,14 +38,34 @@ module.exports = function (configLocation) {
});
router.put("/profile/:profileName", function (req, res, next) {
if (fs.existsSync(configLocation)) {
let rawdata = fs.readFileSync(configLocation, "utf-8");
const config = JSON.parse(rawdata);
if (config.profiles === undefined) config.profiles = {};
config.profiles[req.params.profileName] = req.body;
/* Create empty profiles file*/
if (
!fs.existsSync(
path.join(path.dirname(configLocation), "olympusProfiles.json")
)
) {
fs.writeFileSync(
configLocation,
JSON.stringify(config, null, 2),
path.join(path.dirname(configLocation), "olympusProfiles.json"),
"{}",
"utf-8"
);
}
/* Check that the previous operation was successfull */
if (
fs.existsSync(
path.join(path.dirname(configLocation), "olympusProfiles.json")
)
) {
let rawdata = fs.readFileSync(
path.join(path.dirname(configLocation), "olympusProfiles.json"),
"utf-8"
);
const usersProfiles = JSON.parse(rawdata);
usersProfiles[req.params.profileName] = req.body;
fs.writeFileSync(
path.join(path.dirname(configLocation), "olympusProfiles.json"),
JSON.stringify(usersProfiles, null, 2),
"utf-8"
);
res.end();
@@ -41,14 +75,21 @@ module.exports = function (configLocation) {
});
router.put("/profile/reset/:profileName", function (req, res, next) {
if (fs.existsSync(configLocation)) {
let rawdata = fs.readFileSync(configLocation, "utf-8");
const config = JSON.parse(rawdata);
if (config.profiles[req.params.profileName])
delete config.profiles[req.params.profileName];
if (
fs.existsSync(
path.join(path.dirname(configLocation), "olympusProfiles.json")
)
) {
let rawdata = fs.readFileSync(
path.join(path.dirname(configLocation), "olympusProfiles.json"),
"utf-8"
);
const usersProfiles = JSON.parse(rawdata);
if (req.params.profileName in usersProfiles)
delete usersProfiles[req.params.profileName];
fs.writeFileSync(
configLocation,
JSON.stringify(config, null, 2),
path.join(path.dirname(configLocation), "olympusProfiles.json"),
JSON.stringify(usersProfiles, null, 2),
"utf-8"
);
res.end();
@@ -58,13 +99,14 @@ module.exports = function (configLocation) {
});
router.put("/profile/delete/all", function (req, res, next) {
if (fs.existsSync(configLocation)) {
let rawdata = fs.readFileSync(configLocation, "utf-8");
const config = JSON.parse(rawdata);
config.profiles = {};
if (
fs.existsSync(
path.join(path.dirname(configLocation), "olympusProfiles.json")
)
) {
fs.writeFileSync(
configLocation,
JSON.stringify(config, null, 2),
path.join(path.dirname(configLocation), "olympusProfiles.json"),
"{}",
"utf-8"
);
res.end();
@@ -74,15 +116,19 @@ module.exports = function (configLocation) {
});
router.put("/sessiondata/save/:profileName", function (req, res, next) {
if (req.body.sessionHash === undefined || req.body.sessionData === undefined) res.sendStatus(400);
if (
req.body.sessionHash === undefined ||
req.body.sessionData === undefined
)
res.sendStatus(400);
let thisSessionHash = req.body.sessionHash;
if (thisSessionHash !== sessionHash) {
sessionHash = thisSessionHash;
sessionData = {};
}
sessionData[req.params.profileName] = req.body.sessionData;
res.end()
})
res.end();
});
router.put("/sessiondata/load/:profileName", function (req, res, next) {
if (req.body.sessionHash === undefined) res.sendStatus(400);
@@ -95,7 +141,7 @@ module.exports = function (configLocation) {
res.send(sessionData[req.params.profileName]);
res.end();
}
})
});
return router;
};