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