фича: автообновление сессии

This commit is contained in:
Vladislav Ostapov 2025-01-17 19:09:44 +03:00
parent a4214fd007
commit 3537965393
16 changed files with 170 additions and 96 deletions

View File

@ -25,7 +25,26 @@ else()
message(FATAL_ERROR "You must set `MODEM_TYPE` \"SCPC\" or \"TDMA\"!")
endif()
add_compile_options(-Wall -Wextra -Wsign-conversion)
SET(PROJECT_GIT_REVISION "0")
FIND_PACKAGE(Git)
IF (GIT_FOUND)
EXECUTE_PROCESS (
COMMAND ${GIT_EXECUTABLE} rev-parse HEAD
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
OUTPUT_VARIABLE GIT_HEAD
# ERROR_VARIABLE ERROR_RESULT
# RESULT_VARIABLE INFO_RESULT
ERROR_QUIET
OUTPUT_STRIP_TRAILING_WHITESPACE
)
IF ( ${GIT_HEAD} MATCHES "^.+$" )
STRING ( SUBSTRING ${GIT_HEAD} 0 8 VERSION_REVISION )
SET ( PROJECT_GIT_REVISION ${VERSION_REVISION} )
ENDIF()
ENDIF()
add_compile_options(-Wall -Wextra -Wsign-conversion -DPROJECT_GIT_REVISION="${PROJECT_GIT_REVISION}")
# максимальный размер тела запроса 200mb
add_definitions(-DHTTP_MAX_PAYLOAD=200000000)

View File

@ -213,7 +213,7 @@
"values": [{"label": "РРУ", "value": "false"}, {"label": "АРУ", "value": "true"}]
},
{"widget": "number", "label": "Усиление, дБ", "name": "rxManualGain", "min": -40, "step": 0.01, "max": 40, "v_show": "paramRxtx.rxAgcEn === false"},
{"widget": "watch", "label": "Текущее усиление", "model": "rxManualGain", "v_show": "paramRxtx.rxAgcEn === true"},
{"widget": "watch", "label": "Текущее усиление", "model": "paramRxtx.rxManualGain", "v_show": "paramRxtx.rxAgcEn === true"},
{"widget": "checkbox", "label": "Инверсия спектра", "name": "rxSpectrumInversion"},
{"widget": "number", "label": "Центральная частота, КГц", "name": "rxCentralFreq", "min": 900000, "step": 0.01},
{"widget": "number", "label": "Символьная скорость, Бод", "name": "rxBaudrate", "min": 0, "step": 1},

View File

@ -12,7 +12,7 @@
}
this.submitStatus.{{ g['group'] }} = true
fetch('/api/set/{{ g["group"] }}', {method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify(query) })
fetch('/api/set/{{ g["group"] }}', {method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify(query), credentials: 'same-origin' })
.then(async (resp) => { let vals = await resp.json(); if (vals['status'] !== 'ok') { throw new Error(vals['error'] ? vals['error'] : "Server returns undefined error") } this.update{{ g['group'] | title }}Settings(vals) })
.catch((reason) => { alert(`Ошибка при применении настроек: ${reason}`) })
.finally(() => { this.submitStatus.{{ g['group'] }} = false })

View File

@ -93,7 +93,7 @@
resetPacketsStatistics() {
fetch('/api/resetPacketStatistics', {
method: 'POST'
method: 'POST', credentials: 'same-origin'
}).then(() => {
this.statRx.packetsOk = 0
this.statRx.packetsBad = 0

View File

@ -60,7 +60,7 @@
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(query)
body: JSON.stringify(query), credentials: 'same-origin'
}).then(async (resp) => {
this.submitStatusQos = false
if (resp['error']) { throw new Error(resp['error']) }

View File

@ -191,7 +191,7 @@
}
} else {
try {
let d = await fetch("/api/get/statistics")
let d = await fetch("/api/get/statistics", { credentials: 'same-origin' })
this.updateStatistics(await d.json())
} catch (e) {
this.initState = "Ошибка обновления статистики"

View File

@ -1,8 +1,15 @@
#include "jwt.h"
#include <random>
#include "utils.h"
#include <sys/time.h>
static int64_t milliseconds() {
timeval tv{};
gettimeofday(&tv,nullptr);
return ((tv.tv_sec * 1000000l) + tv.tv_usec) / 1000;
}
std::string http::auth::jwt::secretKey;
void http::auth::jwt::generateSecretKey() {
@ -16,37 +23,45 @@ void http::auth::jwt::generateSecretKey() {
}
}
// payload, signature
static std::pair<std::string, std::string> parseJwtFromCookie(const std::string& input) {
std::string val1, val2;
size_t dotPos = input.find('.');
// Если точка найдена
if (dotPos != std::string::npos) {
val1 = input.substr(0, dotPos);
// Если в val1 есть еще точки, нужно найти последнюю точку
size_t lastDotPos = val1.find_last_of('.');
if (lastDotPos != std::string::npos) {
val1 = val1.substr(0, lastDotPos + 1);
}
val2 = input.substr(dotPos + 1);
} else {
// Точка не найдена, val1 - вся строка
val1 = input;
}
return std::make_pair(http::utils::b64Decode(val1), val2);
}
http::auth::jwt::Jwt http::auth::jwt::Jwt::fromCookies(const std::string &cookie) {
auto pc = utils::parseCookies(cookie);
Jwt t;
if (pc.find("auth") != pc.end()) {
auto tmp = parseJwtFromCookie(pc.at("auth"));
t.payload = tmp.first;
t.signature = tmp.second;
const auto auth = pc.at("auth");
int firstDot = -1;
int secondDot = -1;
for (size_t i = 0; i < auth.size(); i++) {
if (auth[i] == '.') {
if (firstDot < 0) { firstDot = static_cast<int>(i); }
else if (secondDot < 0) { secondDot = static_cast<int>(i); }
else {
// так быть не должно
return t;
}
}
}
if (firstDot < 0 || secondDot < 0 || secondDot - firstDot == 0) {
// так тоже быть не должно
return t;
}
try {
t.payload = auth.substr(0, firstDot);
t.signature = auth.substr(secondDot + 1);
t.lastUpdate = std::stol(auth.substr(firstDot + 1, secondDot - firstDot - 1));
// теперь проверим, что сигнатура верная, только тогда декодируем строку юзера
auto realSignature = utils::sha256(t.payload + std::to_string(t.lastUpdate) + secretKey);
if (t.signature != realSignature) {
t.payload.clear();
} else {
t.payload = utils::b64Decode(t.payload);
}
} catch (std::exception& e) {
t.payload.clear();
t.lastUpdate = 0;
t.signature.clear();
}
}
return t;
@ -55,6 +70,7 @@ http::auth::jwt::Jwt http::auth::jwt::Jwt::fromCookies(const std::string &cookie
http::auth::jwt::Jwt http::auth::jwt::Jwt::fromUser(const std::string &user) {
Jwt t;
t.payload = user;
t.lastUpdate = milliseconds();
return t;
}
@ -63,18 +79,40 @@ bool http::auth::jwt::Jwt::isValid() {
return false;
}
auto realSignature = utils::sha256(this->payload + secretKey);
return signature == realSignature;
// проверка сигнатуры не нужна, она была на стадии парсинга куки
// auto realSignature = utils::sha256(utils::b64Encode(this->payload) + std::to_string(this->lastUpdate) + secretKey);
// if (signature != realSignature) {
// return false;
// }
const auto currTime = milliseconds();
return currTime <= lastUpdate + SESSION_LIVE_MS && currTime >= lastUpdate;
}
std::string http::auth::jwt::Jwt::getUsername() {
return payload;
}
std::string http::auth::jwt::Jwt::asCookie() {
signature = utils::sha256(this->payload + secretKey);
auto val = utils::b64Encode(payload) + "." + signature;
return "auth=" + val + ";Path=/; Max-Age=86400; HttpOnly; SameSite=Lax";
std::string http::auth::jwt::Jwt::asCookie(bool isSecure) {
this->lastUpdate = milliseconds();
const auto uTime = std::to_string(this->lastUpdate);
const auto encodedPayload = utils::b64Encode(payload);
signature = utils::sha256(encodedPayload + uTime + secretKey);
const auto val = encodedPayload + "." + uTime + "." + signature;
std::string cookie = "auth=";
cookie += val;
cookie += ";Path=/; Max-Age=";
cookie += std::to_string(SESSION_LIVE_MS / 1000);
if (isSecure) {
cookie += "; Secure";
}
cookie += "; HttpOnly; SameSite=Lax";
return cookie;
}
bool http::auth::jwt::Jwt::needUpdate() const {
return milliseconds() >= lastUpdate + SESSION_UPDATE_THRESHOLD;
}
http::auth::jwt::Jwt::~Jwt() = default;

View File

@ -6,7 +6,10 @@
namespace http::auth::jwt {
extern std::string secretKey;
constexpr const char* EMPTY_AUTH_COOKIE = "auth=;Path=/; Max-Age=86400; HttpOnly; SameSite=Lax";;
constexpr const char* EMPTY_AUTH_COOKIE = "auth=;Path=/; Max-Age=86400; HttpOnly; SameSite=Lax";
constexpr int64_t SESSION_LIVE_MS = 24 * 60 * 60 * 1000; // 24 часа
constexpr int64_t SESSION_UPDATE_THRESHOLD = 10 * 60 * 1000; // 10 минут
void generateSecretKey();
@ -17,6 +20,7 @@ namespace http::auth::jwt {
*/
class Jwt {
std::string payload;
int64_t lastUpdate = 0;
std::string signature;
public:
static Jwt fromCookies(const std::string& cookie);
@ -26,7 +30,9 @@ namespace http::auth::jwt {
std::string getUsername();
std::string asCookie();
std::string asCookie(bool isSecure = false);
bool needUpdate() const;
~Jwt();
};

View File

@ -44,12 +44,12 @@ http::auth::User::~User() = default;
http::auth::AuthProvider::AuthProvider() = default;
std::shared_ptr<http::auth::User> http::auth::AuthProvider::doAuth(const std::string &username, const std::string &password, server::Reply &rep) {
std::shared_ptr<http::auth::User> http::auth::AuthProvider::doAuth(const std::string &username, const std::string &password, const server::Request &req, server::Reply &rep) {
for (const auto& u: users) {
if (u->username == username) {
if (u->checkPassword(password)) {
auto t = jwt::Jwt::fromUser(u->username);
rep.headers.push_back({.name = "Set-Cookie", .value = t.asCookie()});
rep.headers.push_back({.name = "Set-Cookie", .value = t.asCookie(req.isSecure)});
return u;
}
BOOST_LOG_TRIVIAL(warning) << "http::auth::AuthProvider::doAuth(): Failed to login " << username << ", password: " << password << " (incorrect password)";
@ -60,13 +60,17 @@ std::shared_ptr<http::auth::User> http::auth::AuthProvider::doAuth(const std::st
return nullptr;
}
std::shared_ptr<http::auth::User> http::auth::AuthProvider::getSession(const server::Request &req) {
std::shared_ptr<http::auth::User> http::auth::AuthProvider::getSession(const server::Request &req, server::Reply &rep) {
auto t = jwt::Jwt::fromCookies(req.getHeaderValue("cookie"));
if (t.isValid()) {
const auto name = t.getUsername();
// токен валидный, ищем юзера
for (auto& u: users) {
if (u->username == name) {
// на всякий случай тут проверяем, что токен пора обновлять
if (t.needUpdate()) {
rep.headers.push_back({.name = "Set-Cookie", .value = t.asCookie(req.isSecure)});
}
return u;
}
}
@ -84,7 +88,7 @@ http::auth::AuthRequiredResource::AuthRequiredResource(const std::string &path,
BasicResource(path), provider_(provider), generator_(std::move(generator)), perms(perms) {}
void http::auth::AuthRequiredResource::handle(const server::Request &req, server::Reply &rep) {
if (auto user = this->provider_.getSession(req)) {
if (auto user = this->provider_.getSession(req, rep)) {
if (user->checkPremisions(this->perms)) {
this->generator_(req, rep);
return;

View File

@ -70,20 +70,23 @@ namespace http::auth {
* @note Класс устанавливает заголовок 'Set-Cookie' в ответе, и этот заголовок должен дойти до пользователя!
*/
class AuthProvider {
void updateSessionHook();
public:
AuthProvider();
/**
* Авторизовать пользователя.
*
* @param req
* @param rep
* @return true, в случае успешной авторизации
*/
std::shared_ptr<http::auth::User> doAuth(const std::string &username, const std::string &password, server::Reply &rep);
std::shared_ptr<http::auth::User> doAuth(const std::string &username, const std::string &password, const server::Request &req, server::Reply &rep);
std::vector<std::shared_ptr<User>> users;
std::shared_ptr<User> getSession(const server::Request &req);
std::shared_ptr<User> getSession(const server::Request &req, server::Reply &rep);
~AuthProvider();
};

View File

@ -147,7 +147,7 @@ public:
void registerResources(http::server::Server& s) {
s.resources.emplace_back(std::make_unique<http::resource::GenericResource>("/", [this](const auto& req, auto& rep) {
auto user = auth.getSession(req);
auto user = auth.getSession(req, rep);
if (user == nullptr) {
http::server::httpRedirect(rep, "/login");
} else {
@ -157,7 +157,7 @@ public:
s.resources.emplace_back(std::make_unique<http::resource::GenericResource>("/login", [this](const auto& req, auto& rep) {
if (req.method == "GET") {
auto user = auth.getSession(req);
auto user = auth.getSession(req, rep);
if (user == nullptr) {
sf->serve(LOGIN_HTML, rep);
} else {
@ -172,7 +172,7 @@ public:
boost::property_tree::ptree pt;
read_json(is, pt);
auto u = auth.doAuth(pt.get<std::string>("username"), pt.get<std::string>("password"), rep);
auto u = auth.doAuth(pt.get<std::string>("username"), pt.get<std::string>("password"), req, rep);
if (u == nullptr) {
throw std::runtime_error("invalid session");
}
@ -206,10 +206,10 @@ public:
s.resources.emplace_back(std::make_unique<http::auth::AuthRequiredResource>("/api/get/statistics", this->auth, http::auth::User::WATCH_STATISTICS, [this](const auto& req, auto& rep) {
if (req.method != "GET") {
http::server::stockReply(http::server::bad_request, rep);
return;
}
rep.status = http::server::ok;
rep.headers.clear();
rep.headers.push_back({.name = "Content-Type", .value = toString(mime_types::json)});
std::string result = R"({"mainState":)";
result += api->loadTerminalState();
@ -222,10 +222,10 @@ public:
s.resources.emplace_back(std::make_unique<http::auth::AuthRequiredResource>("/api/get/settings", this->auth, http::auth::User::WATCH_SETTINGS, [this](const auto& req, auto& rep) {
if (req.method != "GET") {
http::server::stockReply(http::server::bad_request, rep);
return;
}
rep.status = http::server::ok;
rep.headers.clear();
rep.headers.push_back({.name = "Content-Type", .value = toString(mime_types::json)});
std::string result = R"({"settings":)";
result += api->loadSettings();
@ -236,10 +236,10 @@ public:
s.resources.emplace_back(std::make_unique<http::auth::AuthRequiredResource>("/api/get/aboutFirmware", this->auth, 0, [this](const auto& req, auto& rep) {
if (req.method != "GET") {
http::server::stockReply(http::server::bad_request, rep);
return;
}
rep.status = http::server::ok;
rep.headers.clear();
rep.headers.push_back({.name = "Content-Type", .value = toString(mime_types::json)});
const auto result = api->loadFirmwareVersion();
rep.content.insert(rep.content.end(), result.c_str(), result.c_str() + result.size());
@ -248,11 +248,11 @@ public:
s.resources.emplace_back(std::make_unique<http::auth::AuthRequiredResource>("/api/resetPacketStatistics", this->auth, http::auth::User::RESET_PACKET_STATISTICS, [this](const auto& req, auto& rep) {
if (req.method != "POST") {
http::server::stockReply(http::server::bad_request, rep);
return;
}
api->resetPacketStatistics();
rep.status = http::server::ok;
rep.headers.clear();
rep.headers.push_back({.name = "Content-Type", .value = toString(mime_types::json)});
const std::string result = R"({"status":"ok")";
rep.content.insert(rep.content.end(), result.c_str(), result.c_str() + result.size());
@ -261,10 +261,10 @@ public:
s.resources.emplace_back(std::make_unique<http::auth::AuthRequiredResource>("/api/set/qos", this->auth, http::auth::User::SETUP_QOS, [this](const auto& req, auto& rep) {
if (req.method != "POST") {
http::server::stockReply(http::server::bad_request, rep);
return;
}
rep.status = http::server::ok;
rep.headers.clear();
rep.headers.push_back({.name = "Content-Type", .value = toString(mime_types::json)});
try {
@ -289,10 +289,10 @@ public:
s.resources.emplace_back(std::make_unique<http::auth::AuthRequiredResource>("/api/set/buclnb", this->auth, http::auth::User::SUPERUSER, [this](const auto& req, auto& rep) {
if (req.method != "POST") {
http::server::stockReply(http::server::bad_request, rep);
return;
}
rep.status = http::server::ok;
rep.headers.clear();
rep.headers.push_back({.name = "Content-Type", .value = toString(mime_types::json)});
try {
@ -317,10 +317,10 @@ public:
s.resources.emplace_back(std::make_unique<http::auth::AuthRequiredResource>("/api/set/cinc", this->auth, http::auth::User::EDIT_SETTINGS, [this](const auto& req, auto& rep) {
if (req.method != "POST") {
http::server::stockReply(http::server::bad_request, rep);
return;
}
rep.status = http::server::ok;
rep.headers.clear();
rep.headers.push_back({.name = "Content-Type", .value = toString(mime_types::json)});
try {
@ -345,10 +345,10 @@ public:
s.resources.emplace_back(std::make_unique<http::auth::AuthRequiredResource>("/api/set/rxtx", this->auth, http::auth::User::EDIT_SETTINGS, [this](const auto& req, auto& rep) {
if (req.method != "POST") {
http::server::stockReply(http::server::bad_request, rep);
return;
}
rep.status = http::server::ok;
rep.headers.clear();
rep.headers.push_back({.name = "Content-Type", .value = toString(mime_types::json)});
try {
@ -373,10 +373,10 @@ public:
s.resources.emplace_back(std::make_unique<http::auth::AuthRequiredResource>("/api/set/network", this->auth, http::auth::User::EDIT_SETTINGS, [this](const auto& req, auto& rep) {
if (req.method != "POST") {
http::server::stockReply(http::server::bad_request, rep);
return;
}
rep.status = http::server::ok;
rep.headers.clear();
rep.headers.push_back({.name = "Content-Type", .value = toString(mime_types::json)});
try {
@ -401,9 +401,9 @@ public:
s.resources.emplace_back(std::make_unique<http::auth::AuthRequiredResource>("/api/reboot", this->auth, 0, [this](const auto& req, auto& rep) {
if (req.method != "POST") {
http::server::stockReply(http::server::bad_request, rep);
return;
}
rep.status = http::server::ok;
rep.headers.clear();
rep.headers.push_back({.name = "Content-Type", .value = toString(mime_types::json)});
const std::string result = R"({"status":"ok"})";
rep.content.insert(rep.content.end(), result.c_str(), result.c_str() + result.size());
@ -413,9 +413,9 @@ public:
s.resources.emplace_back(std::make_unique<http::auth::AuthRequiredResource>("/api/resetSettings", this->auth, http::auth::User::SUPERUSER, [this](const auto& req, auto& rep) {
if (req.method != "POST") {
http::server::stockReply(http::server::bad_request, rep);
return;
}
rep.status = http::server::ok;
rep.headers.clear();
rep.headers.push_back({.name = "Content-Type", .value = toString(mime_types::json)});
const std::string result = R"({"status":"ok"})";
rep.content.insert(rep.content.end(), result.c_str(), result.c_str() + result.size());
@ -431,7 +431,6 @@ public:
onUploadFirmware(req);
rep.status = http::server::ok;
rep.headers.clear();
rep.headers.push_back({.name = "Content-Type", .value = toString(mime_types::json)});
std::string result = R"({"status":"ok","fwsize":)";
result += std::to_string(req.payload.size());

View File

@ -6,10 +6,14 @@
namespace http::server {
const char* SERVER_HEADER_VALUE = "TerminalWebServer v0.1";
const char* SERVER_HEADER_VALUE = "TerminalWebServer"
#ifdef PROJECT_GIT_REVISION
" " PROJECT_GIT_REVISION
#endif
;
Connection::Connection(boost::asio::ip::tcp::socket socket, ConnectionManager &manager, request_handler handler)
: socket_(std::move(socket)), connection_manager_(manager), request_handler_(std::move(handler)), request_(), reply_() {
: socket_(std::move(socket)), connection_manager_(manager), request_handler_(std::move(handler)), request_(false), reply_() {
}
void Connection::start() {
@ -51,7 +55,7 @@ namespace http::server {
void Connection::doWrite() {
reply_.headers.push_back({.name = "Server", .value = SERVER_HEADER_VALUE});
reply_.headers.push_back({.name = "Content-Length", .value = std::to_string(reply_.content.size())});
if (request_.http_version_major == 1) {
if (request_.httpVersionMajor == 1) {
reply_.headers.push_back({.name = "Connection", .value = "keep-alive"});
}
@ -71,7 +75,7 @@ namespace http::server {
}
SslConnection::SslConnection(boost::asio::ip::tcp::socket socket, ConnectionManager &manager, request_handler handler, const std::shared_ptr<boost::asio::ssl::context>& ctx):
stream_(std::move(socket), *ctx), connection_manager_(manager), request_handler_(std::move(handler)), request_(), reply_() {
stream_(std::move(socket), *ctx), connection_manager_(manager), request_handler_(std::move(handler)), request_(true), reply_() {
}
void SslConnection::start() {
@ -125,7 +129,7 @@ namespace http::server {
void SslConnection::doWrite() {
reply_.headers.push_back({.name = "Server", .value = SERVER_HEADER_VALUE});
reply_.headers.push_back({.name = "Content-Length", .value = std::to_string(reply_.content.size())});
if (request_.http_version_major == 1) {
if (request_.httpVersionMajor == 1) {
reply_.headers.push_back({.name = "Connection", .value = "keep-alive"});
}

View File

@ -21,7 +21,7 @@ namespace http::server {
/// A request received from a client.
class Request {
public:
Request();
Request(bool secure);
void reset();
std::string getHeaderValue(const std::string& headerName) const;
@ -29,9 +29,10 @@ namespace http::server {
std::string method;
std::string queryUri;
std::unique_ptr<Url> url;
bool is_keep_alive{};
int http_version_major{};
int http_version_minor{};
bool isKeepAlive{};
const bool isSecure;
int httpVersionMajor{};
int httpVersionMinor{};
std::vector<header> headers;
std::vector<char> payload;

View File

@ -51,7 +51,7 @@ namespace http::server {
Url::~Url() = default;
Request::Request() = default;
Request::Request(bool secure): isSecure(secure) {}
void Request::reset() {
method = "";
@ -59,9 +59,9 @@ namespace http::server {
if (url != nullptr) {
url.reset(nullptr);
}
is_keep_alive = false;
http_version_major = 0;
http_version_minor = 0;
isKeepAlive = false;
httpVersionMajor = 0;
httpVersionMinor = 0;
headers.clear();
payload.clear();
}
@ -151,8 +151,8 @@ namespace http::server {
}
case http_version_slash:
if (input == '/') {
req.http_version_major = 0;
req.http_version_minor = 0;
req.httpVersionMajor = 0;
req.httpVersionMinor = 0;
state_ = http_version_major_start;
return indeterminate;
} else {
@ -160,7 +160,7 @@ namespace http::server {
}
case http_version_major_start:
if (is_digit(input)) {
req.http_version_major = req.http_version_major * 10 + input - '0';
req.httpVersionMajor = req.httpVersionMajor * 10 + input - '0';
state_ = http_version_major;
return indeterminate;
} else {
@ -171,14 +171,14 @@ namespace http::server {
state_ = http_version_minor_start;
return indeterminate;
} else if (is_digit(input)) {
req.http_version_major = req.http_version_major * 10 + input - '0';
req.httpVersionMajor = req.httpVersionMajor * 10 + input - '0';
return indeterminate;
} else {
return bad;
}
case http_version_minor_start:
if (is_digit(input)) {
req.http_version_minor = req.http_version_minor * 10 + input - '0';
req.httpVersionMinor = req.httpVersionMinor * 10 + input - '0';
state_ = http_version_minor;
return indeterminate;
} else {
@ -189,7 +189,7 @@ namespace http::server {
state_ = expecting_newline_1;
return indeterminate;
} else if (is_digit(input)) {
req.http_version_minor = req.http_version_minor * 10 + input - '0';
req.httpVersionMinor = req.httpVersionMinor * 10 + input - '0';
return indeterminate;
} else {
return bad;

View File

@ -269,7 +269,7 @@
</select>
</label>
<label v-show="paramRxtx.rxAgcEn === false"><span>Усиление, дБ</span><input type="number" v-model="paramRxtx.rxManualGain" min="-40" max="40" step="0.01"/></label>
<label v-show="paramRxtx.rxAgcEn === true"><span>Текущее усиление</span><input type="text" readonly v-model="rxManualGain"/></label>
<label v-show="paramRxtx.rxAgcEn === true"><span>Текущее усиление</span><input type="text" readonly v-model="paramRxtx.rxManualGain"/></label>
<label>
<span>Инверсия спектра</span>
<span class="toggle-input"><input type="checkbox" v-model="paramRxtx.rxSpectrumInversion" /><span class="slider"></span></span>
@ -774,7 +774,7 @@
}
this.submitStatus.rxtx = true
fetch('/api/set/rxtx', {method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify(query) })
fetch('/api/set/rxtx', {method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify(query), credentials: 'same-origin' })
.then(async (resp) => { let vals = await resp.json(); if (vals['status'] !== 'ok') { throw new Error(vals['error'] ? vals['error'] : "Server returns undefined error") } this.updateRxtxSettings(vals) })
.catch((reason) => { alert(`Ошибка при применении настроек: ${reason}`) })
.finally(() => { this.submitStatus.rxtx = false })
@ -793,7 +793,7 @@
}
this.submitStatus.cinc = true
fetch('/api/set/cinc', {method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify(query) })
fetch('/api/set/cinc', {method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify(query), credentials: 'same-origin' })
.then(async (resp) => { let vals = await resp.json(); if (vals['status'] !== 'ok') { throw new Error(vals['error'] ? vals['error'] : "Server returns undefined error") } this.updateCincSettings(vals) })
.catch((reason) => { alert(`Ошибка при применении настроек: ${reason}`) })
.finally(() => { this.submitStatus.cinc = false })
@ -812,7 +812,7 @@
}
this.submitStatus.buclnb = true
fetch('/api/set/buclnb', {method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify(query) })
fetch('/api/set/buclnb', {method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify(query), credentials: 'same-origin' })
.then(async (resp) => { let vals = await resp.json(); if (vals['status'] !== 'ok') { throw new Error(vals['error'] ? vals['error'] : "Server returns undefined error") } this.updateBuclnbSettings(vals) })
.catch((reason) => { alert(`Ошибка при применении настроек: ${reason}`) })
.finally(() => { this.submitStatus.buclnb = false })
@ -826,7 +826,7 @@
}
this.submitStatus.tcpaccel = true
fetch('/api/set/tcpaccel', {method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify(query) })
fetch('/api/set/tcpaccel', {method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify(query), credentials: 'same-origin' })
.then(async (resp) => { let vals = await resp.json(); if (vals['status'] !== 'ok') { throw new Error(vals['error'] ? vals['error'] : "Server returns undefined error") } this.updateTcpaccelSettings(vals) })
.catch((reason) => { alert(`Ошибка при применении настроек: ${reason}`) })
.finally(() => { this.submitStatus.tcpaccel = false })
@ -843,7 +843,7 @@
}
this.submitStatus.network = true
fetch('/api/set/network', {method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify(query) })
fetch('/api/set/network', {method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify(query), credentials: 'same-origin' })
.then(async (resp) => { let vals = await resp.json(); if (vals['status'] !== 'ok') { throw new Error(vals['error'] ? vals['error'] : "Server returns undefined error") } this.updateNetworkSettings(vals) })
.catch((reason) => { alert(`Ошибка при применении настроек: ${reason}`) })
.finally(() => { this.submitStatus.network = false })
@ -1001,7 +1001,7 @@
resetPacketsStatistics() {
fetch('/api/resetPacketStatistics', {
method: 'POST'
method: 'POST', credentials: 'same-origin'
}).then(() => {
this.statRx.packetsOk = 0
this.statRx.packetsBad = 0
@ -1075,7 +1075,7 @@
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(query)
body: JSON.stringify(query), credentials: 'same-origin'
}).then(async (resp) => {
this.submitStatusQos = false
if (resp['error']) { throw new Error(resp['error']) }
@ -1327,7 +1327,7 @@
}
} else {
try {
let d = await fetch("/api/get/statistics")
let d = await fetch("/api/get/statistics", { credentials: 'same-origin' })
this.updateStatistics(await d.json())
} catch (e) {
this.initState = "Ошибка обновления статистики"

View File

@ -435,7 +435,7 @@
}
this.submitStatus.rxtx = true
fetch('/api/set/rxtx', {method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify(query) })
fetch('/api/set/rxtx', {method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify(query), credentials: 'same-origin' })
.then(async (resp) => { let vals = await resp.json(); if (vals['status'] !== 'ok') { throw new Error(vals['error'] ? vals['error'] : "Server returns undefined error") } this.updateRxtxSettings(vals) })
.catch((reason) => { alert(`Ошибка при применении настроек: ${reason}`) })
.finally(() => { this.submitStatus.rxtx = false })
@ -454,7 +454,7 @@
}
this.submitStatus.buclnb = true
fetch('/api/set/buclnb', {method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify(query) })
fetch('/api/set/buclnb', {method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify(query), credentials: 'same-origin' })
.then(async (resp) => { let vals = await resp.json(); if (vals['status'] !== 'ok') { throw new Error(vals['error'] ? vals['error'] : "Server returns undefined error") } this.updateBuclnbSettings(vals) })
.catch((reason) => { alert(`Ошибка при применении настроек: ${reason}`) })
.finally(() => { this.submitStatus.buclnb = false })
@ -471,7 +471,7 @@
}
this.submitStatus.network = true
fetch('/api/set/network', {method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify(query) })
fetch('/api/set/network', {method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify(query), credentials: 'same-origin' })
.then(async (resp) => { let vals = await resp.json(); if (vals['status'] !== 'ok') { throw new Error(vals['error'] ? vals['error'] : "Server returns undefined error") } this.updateNetworkSettings(vals) })
.catch((reason) => { alert(`Ошибка при применении настроек: ${reason}`) })
.finally(() => { this.submitStatus.network = false })
@ -585,7 +585,7 @@
resetPacketsStatistics() {
fetch('/api/resetPacketStatistics', {
method: 'POST'
method: 'POST', credentials: 'same-origin'
}).then(() => {
this.statRx.packetsOk = 0
this.statRx.packetsBad = 0
@ -684,7 +684,7 @@
}
} else {
try {
let d = await fetch("/api/get/statistics")
let d = await fetch("/api/get/statistics", { credentials: 'same-origin' })
this.updateStatistics(await d.json())
} catch (e) {
this.initState = "Ошибка обновления статистики"