sync with dev-16

This commit is contained in:
proddy
2026-04-18 18:54:33 +02:00
parent d6e00c4534
commit 86a20fc97a
23 changed files with 380 additions and 827 deletions

View File

@@ -70,12 +70,16 @@ void ArduinoJsonJWT::parseJWT(String jwt, JsonDocument & jsonDocument) {
*/
String ArduinoJsonJWT::sign(String & payload) {
std::array<unsigned char, 32> hmacResult{};
mbedtls_md_hmac(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256),
reinterpret_cast<const unsigned char *>(_secret.c_str()),
_secret.length(),
reinterpret_cast<const unsigned char *>(payload.c_str()),
payload.length(),
hmacResult.data());
{
mbedtls_md_context_t ctx;
mbedtls_md_type_t md_type = MBEDTLS_MD_SHA256;
mbedtls_md_init(&ctx);
mbedtls_md_setup(&ctx, mbedtls_md_info_from_type(md_type), 1);
mbedtls_md_hmac_starts(&ctx, reinterpret_cast<const unsigned char *>(_secret.c_str()), _secret.length());
mbedtls_md_hmac_update(&ctx, reinterpret_cast<const unsigned char *>(payload.c_str()), payload.length());
mbedtls_md_hmac_finish(&ctx, hmacResult.data());
mbedtls_md_free(&ctx);
}
return encode(reinterpret_cast<const char *>(hmacResult.data()), hmacResult.size());
}

View File

@@ -2,10 +2,75 @@
#include "WWWData.h" // include auto-generated static web resources
#include <string.h>
static constexpr const char CACHE_CONTROL[] = "public,max-age=60";
// Single static-content handler serving all assets embedded in WWWData.h.
class StaticContentHandler : public AsyncWebHandler {
public:
bool canHandle(AsyncWebServerRequest * request) const override {
const auto method = request->method();
return method == HTTP_GET || method == HTTP_HEAD || method == HTTP_OPTIONS;
}
void handleRequest(AsyncWebServerRequest * request) override {
// OPTIONS is handled generically - the server-level CORS headers are
// attached via DefaultHeaders in ESP32React::begin().
if (request->method() == HTTP_OPTIONS) {
request->send(200);
return;
}
const char * url = request->url().c_str();
const WWWAsset * found = lookup(url);
const WWWAsset * asset = found ? found : index_asset();
if (asset == nullptr) {
request->send(404);
return;
}
// If the client already has this exact ETag, respond 304 Not Modified without sending the body.
const String & inm = request->header(asyncsrv::T_INM);
if (inm.length() != 0 && strcmp(inm.c_str(), asset->etag) == 0) {
request->send(304);
return;
}
AsyncWebServerResponse * response = request->beginResponse(200, asset->contentType, asset->content, asset->len);
response->addHeader(asyncsrv::T_Content_Encoding, asyncsrv::T_gzip, false);
response->addHeader(asyncsrv::T_ETag, asset->etag, false);
response->addHeader(asyncsrv::T_Cache_Control, CACHE_CONTROL, false);
request->send(response);
}
private:
// Exact-match lookup in the asset table
static const WWWAsset * lookup(const char * url) {
for (size_t i = 0; i < WWW_ASSETS_COUNT; i++) {
if (strcmp(WWW_ASSETS[i].uri, url) == 0) {
return &WWW_ASSETS[i];
}
}
return nullptr;
}
// Returns the /index.html asset, used as the SPA fallback for any GET
// that didn't match an embedded asset (React Router handles routing on
// the client side).
static const WWWAsset * index_asset() {
static const WWWAsset * cached = nullptr;
if (cached == nullptr) {
cached = lookup("/index.html");
}
return cached;
}
};
ESP32React::ESP32React(AsyncWebServer * server, FS * fs)
: _securitySettingsService(server, fs)
: _server(server)
, _securitySettingsService(server, fs)
, _networkSettingsService(server, fs, &_securitySettingsService)
, _wifiScanner(server, &_securitySettingsService)
, _networkStatus(server, &_securitySettingsService)
@@ -17,50 +82,6 @@ ESP32React::ESP32React(AsyncWebServer * server, FS * fs)
, _mqttSettingsService(server, fs, &_securitySettingsService)
, _mqttStatus(server, &_mqttSettingsService, &_securitySettingsService)
, _authenticationService(server, &_securitySettingsService) {
//
// Serve static web resources
//
ArRequestHandlerFunction indexHtmlHandler = nullptr;
WWWData::registerRoutes([server, &indexHtmlHandler](const char * uri, const String & contentType, const uint8_t * content, size_t len, const String & hash) {
String etag = "\"" + hash + "\""; // RFC9110: ETag must be enclosed in double quotes
ArRequestHandlerFunction requestHandler = [contentType, content, len, etag](AsyncWebServerRequest * request) {
if (request->header(asyncsrv::T_INM) == etag) {
request->send(304);
return;
}
AsyncWebServerResponse * response = request->beginResponse(200, contentType, content, len);
response->addHeader(asyncsrv::T_Content_Encoding, asyncsrv::T_gzip, false);
response->addHeader(asyncsrv::T_ETag, etag, false);
response->addHeader(asyncsrv::T_Cache_Control, CACHE_CONTROL, false);
request->send(response);
};
server->on(uri, HTTP_GET, requestHandler);
// Capture index.html handler to set onNotFound once after all routes are registered
if (strcmp(uri, "/index.html") == 0) {
indexHtmlHandler = requestHandler;
}
});
// Set onNotFound handler once after all routes are registered
// Serving non matching get requests with "/index.html"
// OPTIONS get a straight up 200 response
if (indexHtmlHandler != nullptr) {
server->onNotFound([indexHtmlHandler](AsyncWebServerRequest * request) {
if (request->method() == HTTP_GET) {
indexHtmlHandler(request);
} else if (request->method() == HTTP_OPTIONS) {
request->send(200);
} else {
request->send(404); // not found
}
});
}
}
void ESP32React::begin() {
@@ -78,11 +99,11 @@ void ESP32React::begin() {
_ntpSettingsService.begin();
_mqttSettingsService.begin();
_securitySettingsService.begin();
_server->addHandler(new StaticContentHandler());
}
void ESP32React::loop() {
_networkSettingsService.loop();
_apSettingsService.loop();
_mqttSettingsService.loop();
_ntpSettingsService.loop();
}

View File

@@ -68,6 +68,7 @@ class ESP32React {
}
private:
AsyncWebServer * _server;
SecuritySettingsService _securitySettingsService;
NetworkSettingsService _networkSettingsService;
WiFiScanner _wifiScanner;