Implemented basic authentication

Also fixed Tankers and AWACS which now operate as expected
This commit is contained in:
Pax1601 2023-05-10 08:59:02 +02:00
parent 57b74bd1b1
commit 91f8996fba
12 changed files with 226 additions and 104 deletions

View File

@ -12,10 +12,6 @@ var usersRouter = require('./routes/users');
var app = express();
app.use('/demo', basicAuth({
users: { 'admin': 'socks' }
}))
app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
@ -31,7 +27,8 @@ app.set('view engine', 'ejs');
let rawdata = fs.readFileSync('../olympus.json');
let config = JSON.parse(rawdata);
app.get('/config', (req, res) => res.send(config));
if (config["server"] != undefined)
app.get('/config', (req, res) => res.send(config["server"]));
module.exports = app;
@ -43,5 +40,8 @@ app.get('/demo/bullseyes', (req, res) => demoDataGenerator.bullseyes(req, res));
app.get('/demo/airbases', (req, res) => demoDataGenerator.airbases(req, res));
app.get('/demo/mission', (req, res) => demoDataGenerator.mission(req, res));
app.use('/demo', basicAuth({
users: { 'admin': 'socks' }
}))

View File

@ -82,9 +82,9 @@ function setup() {
}
function readConfig(config: any) {
if (config && config["server"] != undefined && config["server"]["address"] != undefined && config["server"]["port"] != undefined) {
const address = config["server"]["address"];
const port = config["server"]["port"];
if (config && config["address"] != undefined && config["port"] != undefined) {
const address = config["address"];
const port = config["port"];
if (typeof address === 'string' && typeof port == 'number')
setAddress(address == "*" ? window.location.hostname : address, <number>port);
}

View File

@ -219,7 +219,7 @@ export class UnitControlPanel extends Panel {
// Default values for "normal" units
this.#radioCallsignDropdown.setOptions(["Enfield", "Springfield", "Uzi", "Colt", "Dodge", "Ford", "Chevy", "Pontiac"]);
this.#radioCallsignDropdown.selectValue(unit.getTaskData().radioCallsign);
this.#radioCallsignDropdown.selectValue(unit.getTaskData().radioCallsign - 1);
// Input values
var tankerCheckbox = this.#advancedSettingsDialog.querySelector("#tanker-checkbox")?.querySelector("input")
@ -241,7 +241,7 @@ export class UnitControlPanel extends Panel {
this.#radioDecimalsDropdown.setValue("." + radioDecimals);
// Make sure its in the valid range
if (!this.#radioCallsignDropdown.selectValue(unit.getTaskData().radioCallsign))
if (!this.#radioCallsignDropdown.selectValue(unit.getTaskData().radioCallsign - 1))
this.#radioCallsignDropdown.selectValue(0);
// Set options for tankers
@ -249,7 +249,7 @@ export class UnitControlPanel extends Panel {
if (roles != undefined && Array.prototype.concat.apply([], roles)?.includes("Tanker")){
this.#advancedSettingsDialog.querySelector("#tanker-checkbox")?.classList.remove("hide");
this.#radioCallsignDropdown.setOptions(["Texaco", "Arco", "Shell"]);
this.#radioCallsignDropdown.selectValue(unit.getTaskData().radioCallsign);
this.#radioCallsignDropdown.selectValue(unit.getTaskData().radioCallsign - 1);
}
else {
this.#advancedSettingsDialog.querySelector("#tanker-checkbox")?.classList.add("hide");
@ -259,7 +259,7 @@ export class UnitControlPanel extends Panel {
if (roles != undefined && Array.prototype.concat.apply([], roles)?.includes("AWACS")){
this.#advancedSettingsDialog.querySelector("#AWACS-checkbox")?.classList.remove("hide");
this.#radioCallsignDropdown.setOptions(["Overlord", "Magic", "Wizard", "Focus", "Darkstar"]);
this.#radioCallsignDropdown.selectValue(unit.getTaskData().radioCallsign);
this.#radioCallsignDropdown.selectValue(unit.getTaskData().radioCallsign - 1);
} else {
this.#advancedSettingsDialog.querySelector("#AWACS-checkbox")?.classList.add("hide");
}
@ -276,7 +276,7 @@ export class UnitControlPanel extends Panel {
const TACANCallsign = <string> this.#advancedSettingsDialog.querySelector("#tacan-callsign")?.querySelector("input")?.value
const radioMHz = Number(this.#advancedSettingsDialog.querySelector("#radio-mhz")?.querySelector("input")?.value);
const radioDecimals = this.#radioDecimalsDropdown.getValue();
const radioCallsign = this.#radioCallsignDropdown.getIndex();
const radioCallsign = this.#radioCallsignDropdown.getIndex() + 1;
const radioCallsignNumber = Number(this.#advancedSettingsDialog.querySelector("#radio-callsign-number")?.querySelector("input")?.value);
var radioFrequency = (radioMHz * 1000 + Number(radioDecimals.substring(1))) * 1000;

View File

@ -52,6 +52,10 @@ export function GET(callback: CallableFunction, uri: string, options?: string) {
setConnected(false);
}
};
xmlHttp.onreadystatechange = function (res) {
console.error("An error occurred during the XMLHttpRequest");
setConnected(false);
};
xmlHttp.onerror = function (res) {
console.error("An error occurred during the XMLHttpRequest");
setConnected(false);

View File

@ -2,5 +2,8 @@
"server": {
"address": "localhost",
"port": 30000
},
"authentication": {
"password": "password"
}
}

View File

@ -185,7 +185,7 @@
<PrecompiledHeader>NotUsing</PrecompiledHeader>
<PrecompiledHeaderFile>
</PrecompiledHeaderFile>
<AdditionalIncludeDirectories>include;..\..\third-party\lua\include;..\utils\include;..\shared\include;..\dcstools\include;..\logger\include;..\luatools\include</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>include;..\..\third-party\base64\include;..\..\third-party\lua\include;..\utils\include;..\shared\include;..\dcstools\include;..\logger\include;..\luatools\include</AdditionalIncludeDirectories>
<LanguageStandard>stdcpp20</LanguageStandard>
</ClCompile>
<Link>

View File

@ -27,5 +27,7 @@ private:
void task();
atomic<bool> runListener;
wstring password = L"";
};

View File

@ -96,14 +96,13 @@ public:
void pushActivePathBack(Coords newActivePathBack);
void popActivePathFront();
void setTargetID(int newTargetID) { targetID = newTargetID; addMeasure(L"targetID", json::value(newTargetID));}
void setIsTanker(bool newIsTanker) { isTanker = newIsTanker; addMeasure(L"isTanker", json::value(newIsTanker));}
void setIsAWACS(bool newIsAWACS) { isAWACS = newIsAWACS; addMeasure(L"isAWACS", json::value(newIsAWACS));}
void setTACANOn(bool newTACANOn);
void setIsTanker(bool newIsTanker);
void setIsAWACS(bool newIsAWACS);
void setTACANChannel(int newTACANChannel);
void setTACANXY(wstring newTACANXY);
void setTACANCallsign(wstring newTACANCallsign);
void setTACAN();
void setRadioOn(bool newRadioOn);
void setEPLRS(bool state);
void setRadioFrequency(int newRadioFrequency);
void setRadioCallsign(int newRadioCallsign);
void setRadioCallsignNumber(int newRadioCallsignNumber);
@ -116,11 +115,9 @@ public:
int getTargetID() { return targetID; }
bool getIsTanker() { return isTanker; }
bool getIsAWACS() { return isAWACS; }
bool getTACANOn() { return TACANOn; }
int getTACANChannel() { return TACANChannel; }
wstring getTACANXY() { return TACANXY; }
wstring getTACANCallsign() { return TACANCallsign; }
bool getRadioOn() { return radioOn; }
int getRadioFrequency() { return radioFrequency; }
int getRadioCallsign() { return radioCallsign; }
int getRadioCallsignNumber() { return radioCallsignNumber; }
@ -182,11 +179,9 @@ protected:
int targetID = NULL;
bool isTanker = false;
bool isAWACS = false;
bool TACANOn = false;
int TACANChannel = 40;
wstring TACANXY = L"X";
wstring TACANCallsign = L"TKR";
bool radioOn = false;
int radioFrequency = 260000000; // MHz
int radioCallsign = 1;
int radioCallsignNumber = 1;

View File

@ -251,13 +251,11 @@ void Scheduler::handleRequest(wstring key, json::value value)
unit->setIsTanker(value[L"isTanker"].as_bool());
unit->setIsAWACS(value[L"isAWACS"].as_bool());
unit->setTACANOn(true); // TODO Remove
unit->setTACANChannel(value[L"TACANChannel"].as_number().to_int32());
unit->setTACANXY(value[L"TACANXY"].as_string());
unit->setTACANCallsign(value[L"TACANCallsign"].as_string());
unit->setTACAN();
unit->setRadioOn(true); // TODO Remove
unit->setRadioFrequency(value[L"radioFrequency"].as_number().to_int32());
unit->setRadioCallsign(value[L"radioCallsign"].as_number().to_int32());
unit->setRadioCallsignNumber(value[L"radioCallsignNumber"].as_number().to_int32());

View File

@ -6,9 +6,11 @@
#include "luatools.h"
#include <exception>
#include <stdexcept>
#include "base64.hpp"
#include <chrono>
using namespace std::chrono;
using namespace base64;
extern UnitsManager* unitsManager;
extern Scheduler* scheduler;
@ -54,10 +56,10 @@ void Server::stop(lua_State* L)
void Server::handle_options(http_request request)
{
http_response response(status_codes::OK);
response.headers().add(U("Allow"), U("GET, POST, PUT, OPTIONS"));
response.headers().add(U("Allow"), U("GET, PUT, OPTIONS"));
response.headers().add(U("Access-Control-Allow-Origin"), U("*"));
response.headers().add(U("Access-Control-Allow-Methods"), U("GET, POST, PUT, OPTIONS"));
response.headers().add(U("Access-Control-Allow-Headers"), U("Content-Type"));
response.headers().add(U("Access-Control-Allow-Methods"), U("GET, PUT, OPTIONS"));
response.headers().add(U("Access-Control-Allow-Headers"), U("Content-Type, Authorization"));
request.reply(response);
}
@ -68,87 +70,102 @@ void Server::handle_get(http_request request)
lock_guard<mutex> guard(mutexLock);
http_response response(status_codes::OK);
response.headers().add(U("Allow"), U("GET, POST, PUT, OPTIONS"));
response.headers().add(U("Access-Control-Allow-Origin"), U("*"));
response.headers().add(U("Access-Control-Allow-Methods"), U("GET, POST, PUT, OPTIONS"));
response.headers().add(U("Access-Control-Allow-Headers"), U("Content-Type"));
string authorization = to_base64("admin:" + to_string(password));
if (password == L"" || (request.headers().has(L"Authorization") && request.headers().find(L"Authorization")->second == L"Basic " + to_wstring(authorization)))
{
std::exception_ptr eptr;
try {
auto answer = json::value::object();
auto path = uri::split_path(uri::decode(request.relative_uri().path()));
std::exception_ptr eptr;
try {
auto answer = json::value::object();
auto path = uri::split_path(uri::decode(request.relative_uri().path()));
if (path.size() > 0)
{
if (path[0] == UNITS_URI)
if (path.size() > 0)
{
map<utility::string_t, utility::string_t> query = request.relative_uri().split_query(request.relative_uri().query());
long long time = 0;
if (query.find(L"time") != query.end())
if (path[0] == UNITS_URI)
{
try {
time = stoll((*(query.find(L"time"))).second);
}
catch (const std::exception& e) {
time = 0;
map<utility::string_t, utility::string_t> query = request.relative_uri().split_query(request.relative_uri().query());
long long time = 0;
if (query.find(L"time") != query.end())
{
try {
time = stoll((*(query.find(L"time"))).second);
}
catch (const std::exception& e) {
time = 0;
}
}
unitsManager->getData(answer, time);
}
unitsManager->getData(answer, time);
}
else if (path[0] == LOGS_URI)
{
auto logs = json::value::object();
getLogsJSON(logs, 100); // By reference, for thread safety. Get the last 100 log entries
answer[L"logs"] = logs;
}
else if (path[0] == AIRBASES_URI)
answer[L"airbases"] = airbases;
else if (path[0] == BULLSEYE_URI)
answer[L"bullseyes"] = bullseyes;
else if (path[0] == MISSION_URI)
answer[L"mission"] = mission;
else if (path[0] == LOGS_URI)
{
auto logs = json::value::object();
getLogsJSON(logs, 100); // By reference, for thread safety. Get the last 100 log entries
answer[L"logs"] = logs;
}
else if (path[0] == AIRBASES_URI)
answer[L"airbases"] = airbases;
else if (path[0] == BULLSEYE_URI)
answer[L"bullseyes"] = bullseyes;
else if (path[0] == MISSION_URI)
answer[L"mission"] = mission;
milliseconds ms = duration_cast<milliseconds>(system_clock::now().time_since_epoch());
answer[L"time"] = json::value::string(to_wstring(ms.count()));
answer[L"sessionHash"] = json::value::string(to_wstring(sessionHash));
milliseconds ms = duration_cast<milliseconds>(system_clock::now().time_since_epoch());
answer[L"time"] = json::value::string(to_wstring(ms.count()));
answer[L"sessionHash"] = json::value::string(to_wstring(sessionHash));
}
response.set_body(answer);
}
catch (...) {
eptr = std::current_exception(); // capture
}
handle_eptr(eptr);
}
else {
response = status_codes::Unauthorized;
}
response.set_body(answer);
}
catch (...) {
eptr = std::current_exception(); // capture
}
handle_eptr(eptr);
response.headers().add(U("Allow"), U("GET, PUT, OPTIONS"));
response.headers().add(U("Access-Control-Allow-Origin"), U("*"));
response.headers().add(U("Access-Control-Allow-Methods"), U("GET, PUT, OPTIONS"));
response.headers().add(U("Access-Control-Allow-Headers"), U("Content-Type, Authorization"));
request.reply(response);
}
void Server::handle_request(http_request request, function<void(json::value const&, json::value&)> action)
{
auto answer = json::value::object();
request.extract_json().then([&answer, &action](pplx::task<json::value> task)
{
try
{
auto const& jvalue = task.get();
if (!jvalue.is_null())
{
action(jvalue, answer);
}
}
catch (http_exception const& e)
{
log(e.what());
}
}).wait();
http_response response(status_codes::OK);
response.headers().add(U("Allow"), U("GET, POST, PUT, OPTIONS"));
string authorization = to_base64("admin:" + to_string(password));
if (password == L"" || (request.headers().has(L"Authorization") && request.headers().find(L"Authorization")->second == L"Basic " + to_wstring(authorization)))
{
auto answer = json::value::object();
request.extract_json().then([&answer, &action](pplx::task<json::value> task)
{
try
{
auto const& jvalue = task.get();
if (!jvalue.is_null())
{
action(jvalue, answer);
}
}
catch (http_exception const& e)
{
log(e.what());
}
}).wait();
response.set_body(answer);
}
else {
response = status_codes::Unauthorized;
}
response.headers().add(U("Allow"), U("GET, PUT, OPTIONS"));
response.headers().add(U("Access-Control-Allow-Origin"), U("*"));
response.headers().add(U("Access-Control-Allow-Methods"), U("GET, POST, PUT, OPTIONS"));
response.headers().add(U("Access-Control-Allow-Headers"), U("Content-Type"));
response.set_body(answer);
response.headers().add(U("Access-Control-Allow-Methods"), U("GET, PUT, OPTIONS"));
response.headers().add(U("Access-Control-Allow-Headers"), U("Content-Type, Authorization"));
request.reply(response);
}
@ -197,9 +214,15 @@ void Server::task()
log(L"Starting server on " + address);
}
else
{
log(L"Error reading configuration file. Starting server on " + address);
if (config.is_object() && config.has_object_field(L"authentication") &&
config[L"authentication"].has_string_field(L"password"))
{
password = config[L"authentication"][L"password"].as_string();
}
else
log(L"Error reading configuration file. No password set.");
free(buf);
}
else

View File

@ -337,9 +337,17 @@ void Unit::landAt(Coords loc) {
setState(State::LAND);
}
void Unit::setTACANOn(bool newTACANOn) {
TACANOn = newTACANOn;
addMeasure(L"TACANOn", json::value(newTACANOn));
void Unit::setIsTanker(bool newIsTanker) {
isTanker = newIsTanker;
resetTask();
addMeasure(L"isTanker", json::value(newIsTanker));
}
void Unit::setIsAWACS(bool newIsAWACS) {
isAWACS = newIsAWACS;
resetTask();
addMeasure(L"isAWACS", json::value(newIsAWACS));
setEPLRS(true);
}
void Unit::setTACANChannel(int newTACANChannel) {
@ -356,11 +364,6 @@ void Unit::setTACANCallsign(wstring newTACANCallsign) {
addMeasure(L"TACANCallsign", json::value(newTACANCallsign));
}
void Unit::setRadioOn(bool newRadioOn) {
radioOn = newRadioOn;
addMeasure(L"radioOn", json::value(newRadioOn));
}
void Unit::setRadioFrequency(int newRadioFrequency) {
radioFrequency = newRadioFrequency;
addMeasure(L"radioFrequency", json::value(newRadioFrequency));
@ -376,6 +379,19 @@ void Unit::setRadioCallsignNumber(int newRadioCallsignNumber) {
addMeasure(L"radioCallsignNumber", json::value(newRadioCallsignNumber));
}
void Unit::setEPLRS(bool state)
{
std::wostringstream commandSS;
commandSS << "{"
<< "id = 'EPLRS',"
<< "params = {"
<< "value = " << (state? "true": "false") << ", "
<< "}"
<< "}";
Command* command = dynamic_cast<Command*>(new SetCommand(ID, commandSS.str()));
scheduler->appendCommand(command);
}
void Unit::setTACAN()
{
std::wostringstream commandSS;
@ -383,9 +399,9 @@ void Unit::setTACAN()
<< "id = 'ActivateBeacon',"
<< "params = {"
<< "type = " << ((TACANXY.compare(L"X") == 0)? 4: 5) << ","
<< "system = 4,"
<< "name = Olympus_TACAN,"
<< "callsign = " << TACANCallsign << ", "
<< "system = 3,"
<< "name = \"Olympus_TACAN\","
<< "callsign = \"" << TACANCallsign << "\", "
<< "frequency = " << TACANChannelToFrequency(TACANChannel, TACANXY) << ","
<< "}"
<< "}";

81
third-party/base64/include/base64.hpp vendored Normal file
View File

@ -0,0 +1,81 @@
#ifndef BASE_64_HPP
#define BASE_64_HPP
#include <algorithm>
#include <string>
namespace base64 {
inline std::string get_base64_chars() {
static std::string base64_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz"
"0123456789+/";
return base64_chars;
}
inline std::string to_base64(std::string const &data) {
int counter = 0;
uint32_t bit_stream = 0;
const std::string base64_chars = get_base64_chars();
std::string encoded;
int offset = 0;
for (unsigned char c : data) {
auto num_val = static_cast<unsigned int>(c);
offset = 16 - counter % 3 * 8;
bit_stream += num_val << offset;
if (offset == 16) {
encoded += base64_chars.at(bit_stream >> 18 & 0x3f);
}
if (offset == 8) {
encoded += base64_chars.at(bit_stream >> 12 & 0x3f);
}
if (offset == 0 && counter != 3) {
encoded += base64_chars.at(bit_stream >> 6 & 0x3f);
encoded += base64_chars.at(bit_stream & 0x3f);
bit_stream = 0;
}
counter++;
}
if (offset == 16) {
encoded += base64_chars.at(bit_stream >> 12 & 0x3f);
encoded += "==";
}
if (offset == 8) {
encoded += base64_chars.at(bit_stream >> 6 & 0x3f);
encoded += '=';
}
return encoded;
}
inline std::string from_base64(std::string const &data) {
int counter = 0;
uint32_t bit_stream = 0;
std::string decoded;
int offset = 0;
const std::string base64_chars = get_base64_chars();
for (unsigned char c : data) {
auto num_val = base64_chars.find(c);
if (num_val != std::string::npos) {
offset = 18 - counter % 4 * 6;
bit_stream += num_val << offset;
if (offset == 12) {
decoded += static_cast<char>(bit_stream >> 16 & 0xff);
}
if (offset == 6) {
decoded += static_cast<char>(bit_stream >> 8 & 0xff);
}
if (offset == 0 && counter != 4) {
decoded += static_cast<char>(bit_stream & 0xff);
bit_stream = 0;
}
} else if (c != '=') {
return std::string();
}
counter++;
}
return decoded;
}
}
#endif // BASE_64_HPP