mirror of
https://github.com/emsesp/EMS-ESP32.git
synced 2026-06-14 11:56:32 +03:00
merge all URI handlers into a single dispatch function
This commit is contained in:
@@ -5,10 +5,10 @@
|
|||||||
#include "ESPAsyncWebServer.h"
|
#include "ESPAsyncWebServer.h"
|
||||||
#include "AsyncJson.h"
|
#include "AsyncJson.h"
|
||||||
|
|
||||||
#include <list>
|
#include <memory>
|
||||||
|
|
||||||
#define FACTORY_JWT_SECRET "ems-esp"
|
#define FACTORY_JWT_SECRET "ems-esp"
|
||||||
#define ACCESS_TOKEN_PARAMATER "access_token"
|
#define ACCESS_TOKEN_PARAMETER "access_token"
|
||||||
#define AUTHORIZATION_HEADER "Authorization"
|
#define AUTHORIZATION_HEADER "Authorization"
|
||||||
#define AUTHORIZATION_HEADER_PREFIX "Bearer "
|
#define AUTHORIZATION_HEADER_PREFIX "Bearer "
|
||||||
#define AUTHORIZATION_HEADER_PREFIX_LEN 7
|
#define AUTHORIZATION_HEADER_PREFIX_LEN 7
|
||||||
@@ -31,21 +31,15 @@ class User {
|
|||||||
|
|
||||||
class Authentication {
|
class Authentication {
|
||||||
public:
|
public:
|
||||||
User * user;
|
std::unique_ptr<User> user;
|
||||||
boolean authenticated;
|
boolean authenticated = false;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Authentication(User & user)
|
explicit Authentication(const User & u)
|
||||||
: user(new User(user))
|
: user(std::make_unique<User>(u))
|
||||||
, authenticated(true) {
|
, authenticated(true) {
|
||||||
}
|
}
|
||||||
Authentication()
|
Authentication() = default;
|
||||||
: user(nullptr)
|
|
||||||
, authenticated(false) {
|
|
||||||
}
|
|
||||||
~Authentication() {
|
|
||||||
delete (user);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef std::function<boolean(Authentication & authentication)> AuthenticationPredicate;
|
typedef std::function<boolean(Authentication & authentication)> AuthenticationPredicate;
|
||||||
@@ -66,10 +60,8 @@ class AuthenticationPredicates {
|
|||||||
class SecurityManager {
|
class SecurityManager {
|
||||||
public:
|
public:
|
||||||
virtual Authentication authenticateRequest(AsyncWebServerRequest * request) = 0;
|
virtual Authentication authenticateRequest(AsyncWebServerRequest * request) = 0;
|
||||||
virtual ArRequestFilterFunction filterRequest(AuthenticationPredicate predicate) = 0;
|
|
||||||
virtual ArRequestHandlerFunction wrapRequest(ArRequestHandlerFunction onRequest, AuthenticationPredicate predicate) = 0;
|
|
||||||
virtual ArJsonRequestHandlerFunction wrapCallback(ArJsonRequestHandlerFunction onRequest, AuthenticationPredicate predicate) = 0;
|
|
||||||
|
|
||||||
|
// Json endpoints - default POST. Registered with the shared dispatcher.
|
||||||
void addEndpoint(AsyncWebServer * server,
|
void addEndpoint(AsyncWebServer * server,
|
||||||
const String & path,
|
const String & path,
|
||||||
AuthenticationPredicate predicate,
|
AuthenticationPredicate predicate,
|
||||||
|
|||||||
@@ -10,22 +10,9 @@ SecuritySettingsService::SecuritySettingsService(AsyncWebServer * server, FS * f
|
|||||||
SecuritySettingsService::~SecuritySettingsService() {
|
SecuritySettingsService::~SecuritySettingsService() {
|
||||||
}
|
}
|
||||||
|
|
||||||
ArRequestFilterFunction SecuritySettingsService::filterRequest(AuthenticationPredicate predicate) {
|
// Return the admin user on all requests - disabling security features
|
||||||
return [predicate](AsyncWebServerRequest * request) { return true; };
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return the admin user on all request - disabling security features
|
|
||||||
Authentication SecuritySettingsService::authenticateRequest(AsyncWebServerRequest * request) {
|
Authentication SecuritySettingsService::authenticateRequest(AsyncWebServerRequest * request) {
|
||||||
return Authentication(ADMIN_USER);
|
return Authentication(ADMIN_USER);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return the function unwrapped
|
|
||||||
ArRequestHandlerFunction SecuritySettingsService::wrapRequest(ArRequestHandlerFunction onRequest, AuthenticationPredicate predicate) {
|
|
||||||
return onRequest;
|
|
||||||
}
|
|
||||||
|
|
||||||
ArJsonRequestHandlerFunction SecuritySettingsService::wrapCallback(ArJsonRequestHandlerFunction onRequest, AuthenticationPredicate predicate) {
|
|
||||||
return onRequest;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
@@ -31,9 +31,6 @@ class SecuritySettingsService : public SecurityManager {
|
|||||||
|
|
||||||
// minimal set of functions to support framework with security settings disabled
|
// minimal set of functions to support framework with security settings disabled
|
||||||
Authentication authenticateRequest(AsyncWebServerRequest * request);
|
Authentication authenticateRequest(AsyncWebServerRequest * request);
|
||||||
ArRequestFilterFunction filterRequest(AuthenticationPredicate predicate);
|
|
||||||
ArRequestHandlerFunction wrapRequest(ArRequestHandlerFunction onRequest, AuthenticationPredicate predicate);
|
|
||||||
ArJsonRequestHandlerFunction wrapCallback(ArJsonRequestHandlerFunction onRequest, AuthenticationPredicate predicate);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -4,11 +4,13 @@
|
|||||||
|
|
||||||
AuthenticationService::AuthenticationService(AsyncWebServer * server, SecurityManager * securityManager)
|
AuthenticationService::AuthenticationService(AsyncWebServer * server, SecurityManager * securityManager)
|
||||||
: _securityManager(securityManager) {
|
: _securityManager(securityManager) {
|
||||||
// none of these need authentication
|
// None of these need authentication: verifyAuthorization checks the JWT itself, and signIn IS the authentication flow.
|
||||||
server->on(VERIFY_AUTHORIZATION_PATH, HTTP_GET, [this](AsyncWebServerRequest * request) { verifyAuthorization(request); });
|
securityManager->addEndpoint(server, VERIFY_AUTHORIZATION_PATH, AuthenticationPredicates::NONE_REQUIRED, [this](AsyncWebServerRequest * request) {
|
||||||
auto * handler = new AsyncCallbackJsonWebHandler(SIGN_IN_PATH);
|
verifyAuthorization(request);
|
||||||
handler->onRequest([this](AsyncWebServerRequest * request, JsonVariant json) { signIn(request, json); });
|
});
|
||||||
server->addHandler(handler);
|
securityManager->addEndpoint(server, SIGN_IN_PATH, AuthenticationPredicates::NONE_REQUIRED, [this](AsyncWebServerRequest * request, JsonVariant json) {
|
||||||
|
signIn(request, json);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verifies that the request supplied a valid JWT.
|
// Verifies that the request supplied a valid JWT.
|
||||||
@@ -24,10 +26,9 @@ void AuthenticationService::signIn(AsyncWebServerRequest * request, JsonVariant
|
|||||||
String password = json["password"];
|
String password = json["password"];
|
||||||
Authentication authentication = _securityManager->authenticate(username, password);
|
Authentication authentication = _securityManager->authenticate(username, password);
|
||||||
if (authentication.authenticated) {
|
if (authentication.authenticated) {
|
||||||
User * user = authentication.user;
|
|
||||||
auto * response = new emsesp::PsramAsyncJsonResponse(false);
|
auto * response = new emsesp::PsramAsyncJsonResponse(false);
|
||||||
JsonObject jsonObject = response->getRoot();
|
JsonObject jsonObject = response->getRoot();
|
||||||
jsonObject["access_token"] = _securityManager->generateJWT(user);
|
jsonObject["access_token"] = _securityManager->generateJWT(authentication.user.get());
|
||||||
response->setLength();
|
response->setLength();
|
||||||
request->send(response);
|
request->send(response);
|
||||||
return;
|
return;
|
||||||
|
|||||||
126
src/ESP32React/SecurityManager.cpp
Normal file
126
src/ESP32React/SecurityManager.cpp
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
#include "SecurityManager.h"
|
||||||
|
|
||||||
|
void SecurityManager::addEndpoint(AsyncWebServer * server,
|
||||||
|
const String & path,
|
||||||
|
AuthenticationPredicate predicate,
|
||||||
|
ArJsonRequestHandlerFunction function,
|
||||||
|
WebRequestMethodComposite method) {
|
||||||
|
ensureRestDispatcher(server);
|
||||||
|
const bool requiresAuth = !isPublicPredicate(predicate);
|
||||||
|
_restRoutes.push_back({AsyncURIMatcher(path), method, std::move(predicate), {}, std::move(function), true, requiresAuth});
|
||||||
|
}
|
||||||
|
|
||||||
|
void SecurityManager::addEndpoint(AsyncWebServer * server,
|
||||||
|
const String & path,
|
||||||
|
AuthenticationPredicate predicate,
|
||||||
|
ArRequestHandlerFunction function,
|
||||||
|
WebRequestMethodComposite method) {
|
||||||
|
ensureRestDispatcher(server);
|
||||||
|
const bool requiresAuth = !isPublicPredicate(predicate);
|
||||||
|
_restRoutes.push_back({AsyncURIMatcher(path), method, std::move(predicate), std::move(function), {}, false, requiresAuth});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Detects routes registered with AuthenticationPredicates::NONE_REQUIRED so we can
|
||||||
|
// skip the JWT parse in dispatchRest. Relies on std::function::target returning the
|
||||||
|
// raw function pointer when the predicate was assigned directly from the static fn;
|
||||||
|
// if a caller wraps NONE_REQUIRED in a lambda this falls back to "requires auth"
|
||||||
|
// which is correctness-preserving (just no optimization).
|
||||||
|
bool SecurityManager::isPublicPredicate(const AuthenticationPredicate & predicate) {
|
||||||
|
using Fn = bool (*)(const Authentication &);
|
||||||
|
auto * fn = predicate.target<Fn>();
|
||||||
|
return fn != nullptr && *fn == &AuthenticationPredicates::NONE_REQUIRED;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lazily attach the single catch-all handler. Idempotent.
|
||||||
|
void SecurityManager::ensureRestDispatcher(AsyncWebServer * server) {
|
||||||
|
if (_restDispatcherInstalled || server == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_restDispatcherInstalled = true;
|
||||||
|
server->addHandler(new RestCatchAllHandler(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
void SecurityManager::dispatchRest(AsyncWebServerRequest * request, JsonVariant json) {
|
||||||
|
WebRequestMethod method = request->method();
|
||||||
|
|
||||||
|
for (auto & route : _restRoutes) {
|
||||||
|
if (!route.method.matches(method)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!route.uri.matches(request)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (route.requiresAuth) {
|
||||||
|
Authentication authentication = authenticateRequest(request);
|
||||||
|
if (!route.predicate(authentication)) {
|
||||||
|
request->send(401);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (route.isJson) {
|
||||||
|
route.jsonHandler(request, json);
|
||||||
|
} else {
|
||||||
|
route.plainHandler(request);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// canHandle returned true so some route matched the URI+method;
|
||||||
|
// a mismatch here means the request slipped between the two checks.
|
||||||
|
request->send(404);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- RestCatchAllHandler ----
|
||||||
|
|
||||||
|
bool SecurityManager::RestCatchAllHandler::canHandle(AsyncWebServerRequest * request) const {
|
||||||
|
if (!request->isHTTP()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (const auto & route : _owner->_restRoutes) {
|
||||||
|
if (route.method.matches(request->method()) && route.uri.matches(request)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returning false ensures the server invokes handleBody() so we can buffer JSON bodies.
|
||||||
|
bool SecurityManager::RestCatchAllHandler::isRequestHandlerTrivial() const {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SecurityManager::RestCatchAllHandler::handleBody(AsyncWebServerRequest * request, uint8_t * data, size_t len, size_t index, size_t total) {
|
||||||
|
// Only buffer JSON bodies; everything else is routed with an empty JsonVariant.
|
||||||
|
if (total == 0 || total > kMaxBodySize || !isJsonContent(request)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (index == 0 && request->_tempObject == nullptr) {
|
||||||
|
request->_tempObject = calloc(total + 1, sizeof(uint8_t)); // freed by request destructor
|
||||||
|
if (request->_tempObject == nullptr) {
|
||||||
|
request->abort();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (request->_tempObject != nullptr) {
|
||||||
|
memcpy(static_cast<uint8_t *>(request->_tempObject) + index, data, len);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SecurityManager::RestCatchAllHandler::handleRequest(AsyncWebServerRequest * request) {
|
||||||
|
JsonDocument doc;
|
||||||
|
JsonVariant json;
|
||||||
|
if (request->_tempObject != nullptr) {
|
||||||
|
if (deserializeJson(doc, static_cast<const char *>(request->_tempObject))) {
|
||||||
|
request->send(400);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
json = doc.as<JsonVariant>();
|
||||||
|
}
|
||||||
|
_owner->dispatchRest(request, json);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SecurityManager::RestCatchAllHandler::isJsonContent(AsyncWebServerRequest * request) {
|
||||||
|
return request->contentType().equalsIgnoreCase("application/json");
|
||||||
|
}
|
||||||
@@ -6,9 +6,10 @@
|
|||||||
#include <ESPAsyncWebServer.h>
|
#include <ESPAsyncWebServer.h>
|
||||||
#include <AsyncJson.h>
|
#include <AsyncJson.h>
|
||||||
|
|
||||||
#include <list>
|
#include <memory>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
#define ACCESS_TOKEN_PARAMATER "access_token"
|
#define ACCESS_TOKEN_PARAMETER "access_token"
|
||||||
#define AUTHORIZATION_HEADER "Authorization"
|
#define AUTHORIZATION_HEADER "Authorization"
|
||||||
#define AUTHORIZATION_HEADER_PREFIX "Bearer "
|
#define AUTHORIZATION_HEADER_PREFIX "Bearer "
|
||||||
#define AUTHORIZATION_HEADER_PREFIX_LEN 7
|
#define AUTHORIZATION_HEADER_PREFIX_LEN 7
|
||||||
@@ -29,20 +30,16 @@ class User {
|
|||||||
|
|
||||||
class Authentication {
|
class Authentication {
|
||||||
public:
|
public:
|
||||||
User * user = nullptr;
|
std::unique_ptr<User> user;
|
||||||
boolean authenticated = false;
|
boolean authenticated = false;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit Authentication(const User & user)
|
explicit Authentication(const User & u)
|
||||||
: user(new User(user))
|
: user(std::make_unique<User>(u))
|
||||||
, authenticated(true) {
|
, authenticated(true) {
|
||||||
}
|
}
|
||||||
|
|
||||||
Authentication() = default;
|
Authentication() = default;
|
||||||
|
|
||||||
~Authentication() {
|
|
||||||
delete user;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef std::function<boolean(Authentication & authentication)> AuthenticationPredicate;
|
typedef std::function<boolean(Authentication & authentication)> AuthenticationPredicate;
|
||||||
@@ -63,6 +60,8 @@ class AuthenticationPredicates {
|
|||||||
|
|
||||||
class SecurityManager {
|
class SecurityManager {
|
||||||
public:
|
public:
|
||||||
|
virtual ~SecurityManager() = default;
|
||||||
|
|
||||||
// Authenticate, returning the user if found
|
// Authenticate, returning the user if found
|
||||||
virtual Authentication authenticate(const String & username, const String & password) = 0;
|
virtual Authentication authenticate(const String & username, const String & password) = 0;
|
||||||
|
|
||||||
@@ -72,40 +71,70 @@ class SecurityManager {
|
|||||||
// Check the request header for the Authorization token
|
// Check the request header for the Authorization token
|
||||||
virtual Authentication authenticateRequest(AsyncWebServerRequest * request) = 0;
|
virtual Authentication authenticateRequest(AsyncWebServerRequest * request) = 0;
|
||||||
|
|
||||||
// Filter a request with the provided predicate, only returning true if the predicate matches.
|
// Json endpoints - default POST. Registered with the shared dispatcher.
|
||||||
virtual ArRequestFilterFunction filterRequest(AuthenticationPredicate predicate) = 0;
|
|
||||||
|
|
||||||
// Wrap the provided request to provide validation against an AuthenticationPredicate.
|
|
||||||
virtual ArRequestHandlerFunction wrapRequest(ArRequestHandlerFunction onRequest, AuthenticationPredicate predicate) = 0;
|
|
||||||
|
|
||||||
// Wrap the provided json request callback to provide validation against an AuthenticationPredicate.
|
|
||||||
virtual ArJsonRequestHandlerFunction wrapCallback(ArJsonRequestHandlerFunction onRequest, AuthenticationPredicate predicate) = 0;
|
|
||||||
|
|
||||||
// Json endpoints - default POST
|
|
||||||
void addEndpoint(AsyncWebServer * server,
|
void addEndpoint(AsyncWebServer * server,
|
||||||
const String & path,
|
const String & path,
|
||||||
AuthenticationPredicate predicate,
|
AuthenticationPredicate predicate,
|
||||||
ArJsonRequestHandlerFunction function,
|
ArJsonRequestHandlerFunction function,
|
||||||
WebRequestMethodComposite method = HTTP_POST) {
|
WebRequestMethodComposite method = HTTP_POST);
|
||||||
auto handler = new AsyncCallbackJsonWebHandler(path);
|
|
||||||
handler->onRequest(
|
|
||||||
wrapCallback([this, function](AsyncWebServerRequest * request, JsonVariant json) { function(request, json); }, AuthenticationPredicates::IS_ADMIN));
|
|
||||||
handler->setMethod(method);
|
|
||||||
server->addHandler(handler);
|
|
||||||
}
|
|
||||||
|
|
||||||
// non-Json endpoints - default GET
|
// non-Json endpoints - default GET. Registered with the shared dispatcher.
|
||||||
void addEndpoint(AsyncWebServer * server,
|
void addEndpoint(AsyncWebServer * server,
|
||||||
const String & path,
|
const String & path,
|
||||||
AuthenticationPredicate predicate,
|
AuthenticationPredicate predicate,
|
||||||
ArRequestHandlerFunction function,
|
ArRequestHandlerFunction function,
|
||||||
WebRequestMethodComposite method = HTTP_GET) {
|
WebRequestMethodComposite method = HTTP_GET);
|
||||||
auto * handler = new AsyncCallbackWebHandler();
|
|
||||||
handler->onRequest(wrapRequest([this, function](AsyncWebServerRequest * request) { function(request); }, predicate));
|
private:
|
||||||
handler->setUri(path);
|
// Single internal route record. Each route holds either a plain or JSON handler.
|
||||||
handler->setMethod(method);
|
// The URI matcher uses backward-compatible mode by default (constructed from a
|
||||||
server->addHandler(handler);
|
// plain String), which preserves the original library handler's matching semantics
|
||||||
|
// (e.g. /api also matches /api/boiler/heating).
|
||||||
|
struct RestRoute {
|
||||||
|
AsyncURIMatcher uri;
|
||||||
|
WebRequestMethodComposite method;
|
||||||
|
AuthenticationPredicate predicate;
|
||||||
|
ArRequestHandlerFunction plainHandler;
|
||||||
|
ArJsonRequestHandlerFunction jsonHandler;
|
||||||
|
bool isJson;
|
||||||
|
bool requiresAuth; // false when predicate is NONE_REQUIRED, lets dispatchRest skip the JWT parse
|
||||||
|
};
|
||||||
|
|
||||||
|
// Single catch-all handler. canHandle() claims a request only if some registered
|
||||||
|
// route matches its URI + method, so non-matching URLs (static files, websockets,
|
||||||
|
// etc.) still fall through to other handlers. handleBody buffers JSON bodies, then
|
||||||
|
// handleRequest hands off to SecurityManager::dispatchRest for routing + auth.
|
||||||
|
//
|
||||||
|
// We can't reuse AsyncCallbackJsonWebHandler here because its canHandle() rejects
|
||||||
|
// POST/PUT/PATCH without an application/json content-type (so a bodyless POST like
|
||||||
|
// /rest/resetCustomizations would fall through to a 404), and both canHandle and
|
||||||
|
// handleRequest are marked final on that class.
|
||||||
|
class RestCatchAllHandler : public AsyncWebHandler {
|
||||||
|
public:
|
||||||
|
explicit RestCatchAllHandler(SecurityManager * owner)
|
||||||
|
: _owner(owner) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool canHandle(AsyncWebServerRequest * request) const override;
|
||||||
|
bool isRequestHandlerTrivial() const override;
|
||||||
|
void handleBody(AsyncWebServerRequest * request, uint8_t * data, size_t len, size_t index, size_t total) override;
|
||||||
|
void handleRequest(AsyncWebServerRequest * request) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
static constexpr size_t kMaxBodySize = 16384;
|
||||||
|
|
||||||
|
static bool isJsonContent(AsyncWebServerRequest * request);
|
||||||
|
|
||||||
|
SecurityManager * _owner;
|
||||||
|
};
|
||||||
|
|
||||||
|
static bool isPublicPredicate(const AuthenticationPredicate & predicate);
|
||||||
|
|
||||||
|
void ensureRestDispatcher(AsyncWebServer * server);
|
||||||
|
void dispatchRest(AsyncWebServerRequest * request, JsonVariant json);
|
||||||
|
|
||||||
|
std::vector<RestRoute> _restRoutes;
|
||||||
|
bool _restDispatcherInstalled = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -8,9 +8,7 @@ SecuritySettingsService::SecuritySettingsService(AsyncWebServer * server, FS * f
|
|||||||
, _jwtHandler(FACTORY_JWT_SECRET) {
|
, _jwtHandler(FACTORY_JWT_SECRET) {
|
||||||
addUpdateHandler([this] { configureJWTHandler(); }, false);
|
addUpdateHandler([this] { configureJWTHandler(); }, false);
|
||||||
|
|
||||||
server->on(GENERATE_TOKEN_PATH,
|
addEndpoint(server, GENERATE_TOKEN_PATH, AuthenticationPredicates::IS_ADMIN, [this](AsyncWebServerRequest * request) { generateToken(request); });
|
||||||
HTTP_GET,
|
|
||||||
SecuritySettingsService::wrapRequest([this](AsyncWebServerRequest * request) { generateToken(request); }, AuthenticationPredicates::IS_ADMIN));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SecuritySettingsService::begin() {
|
void SecuritySettingsService::begin() {
|
||||||
@@ -26,8 +24,8 @@ Authentication SecuritySettingsService::authenticateRequest(AsyncWebServerReques
|
|||||||
value = value.substring(AUTHORIZATION_HEADER_PREFIX_LEN);
|
value = value.substring(AUTHORIZATION_HEADER_PREFIX_LEN);
|
||||||
return authenticateJWT(value);
|
return authenticateJWT(value);
|
||||||
}
|
}
|
||||||
} else if (request->hasParam(ACCESS_TOKEN_PARAMATER)) {
|
} else if (request->hasParam(ACCESS_TOKEN_PARAMETER)) {
|
||||||
auto tokenParamater = request->getParam(ACCESS_TOKEN_PARAMATER);
|
auto tokenParamater = request->getParam(ACCESS_TOKEN_PARAMETER);
|
||||||
String value = tokenParamater->value();
|
String value = tokenParamater->value();
|
||||||
return authenticateJWT(value);
|
return authenticateJWT(value);
|
||||||
}
|
}
|
||||||
@@ -81,35 +79,6 @@ String SecuritySettingsService::generateJWT(const User * user) {
|
|||||||
return _jwtHandler.buildJWT(payload);
|
return _jwtHandler.buildJWT(payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
ArRequestFilterFunction SecuritySettingsService::filterRequest(AuthenticationPredicate predicate) {
|
|
||||||
return [this, predicate](AsyncWebServerRequest * request) {
|
|
||||||
Authentication authentication = authenticateRequest(request);
|
|
||||||
return predicate(authentication);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
ArRequestHandlerFunction SecuritySettingsService::wrapRequest(ArRequestHandlerFunction onRequest, AuthenticationPredicate predicate) {
|
|
||||||
return [this, onRequest, predicate](AsyncWebServerRequest * request) {
|
|
||||||
Authentication authentication = authenticateRequest(request);
|
|
||||||
if (!predicate(authentication)) {
|
|
||||||
request->send(401);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
onRequest(request);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
ArJsonRequestHandlerFunction SecuritySettingsService::wrapCallback(ArJsonRequestHandlerFunction onRequest, AuthenticationPredicate predicate) {
|
|
||||||
return [this, onRequest, predicate](AsyncWebServerRequest * request, JsonVariant json) {
|
|
||||||
Authentication authentication = authenticateRequest(request);
|
|
||||||
if (!predicate(authentication)) {
|
|
||||||
request->send(401);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
onRequest(request, json);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
void SecuritySettingsService::generateToken(AsyncWebServerRequest * request) {
|
void SecuritySettingsService::generateToken(AsyncWebServerRequest * request) {
|
||||||
auto usernameParam = request->getParam("username");
|
auto usernameParam = request->getParam("username");
|
||||||
for (const User & _user : _state.users) {
|
for (const User & _user : _state.users) {
|
||||||
|
|||||||
@@ -75,9 +75,6 @@ class SecuritySettingsService final : public StatefulService<SecuritySettings>,
|
|||||||
Authentication authenticate(const String & username, const String & password) override;
|
Authentication authenticate(const String & username, const String & password) override;
|
||||||
Authentication authenticateRequest(AsyncWebServerRequest * request) override;
|
Authentication authenticateRequest(AsyncWebServerRequest * request) override;
|
||||||
String generateJWT(const User * user) override;
|
String generateJWT(const User * user) override;
|
||||||
ArRequestFilterFunction filterRequest(AuthenticationPredicate predicate) override;
|
|
||||||
ArRequestHandlerFunction wrapRequest(ArRequestHandlerFunction onRequest, AuthenticationPredicate predicate) override;
|
|
||||||
ArJsonRequestHandlerFunction wrapCallback(ArJsonRequestHandlerFunction callback, AuthenticationPredicate predicate) override;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
HttpEndpoint<SecuritySettings> _httpEndpoint;
|
HttpEndpoint<SecuritySettings> _httpEndpoint;
|
||||||
|
|||||||
@@ -25,10 +25,14 @@ uint16_t WebAPIService::api_fails_ = 0;
|
|||||||
|
|
||||||
WebAPIService::WebAPIService(AsyncWebServer * server, SecurityManager * securityManager)
|
WebAPIService::WebAPIService(AsyncWebServer * server, SecurityManager * securityManager)
|
||||||
: _securityManager(securityManager) {
|
: _securityManager(securityManager) {
|
||||||
AsyncCallbackJsonWebHandler * jsonHandler = new AsyncCallbackJsonWebHandler(EMSESP_API_SERVICE_PATH);
|
// parse() does its own per-request admin check (with notoken_api), so no predicate.
|
||||||
jsonHandler->setMethod(HTTP_POST | HTTP_GET);
|
// /api also matches /api/<device>/<entity> via the route's backward-compatible URI matcher.
|
||||||
jsonHandler->onRequest([this](AsyncWebServerRequest * request, JsonVariant json) { webAPIService(request, json); });
|
securityManager->addEndpoint(
|
||||||
server->addHandler(jsonHandler);
|
server,
|
||||||
|
EMSESP_API_SERVICE_PATH,
|
||||||
|
AuthenticationPredicates::NONE_REQUIRED,
|
||||||
|
[this](AsyncWebServerRequest * request, JsonVariant json) { webAPIService(request, json); },
|
||||||
|
HTTP_POST | HTTP_GET);
|
||||||
}
|
}
|
||||||
|
|
||||||
// POST|GET api/
|
// POST|GET api/
|
||||||
|
|||||||
@@ -32,9 +32,6 @@ WebLogService::WebLogService(AsyncWebServer * server, SecurityManager * security
|
|||||||
[this](AsyncWebServerRequest * request, JsonVariant json) { getSetValues(request, json); },
|
[this](AsyncWebServerRequest * request, JsonVariant json) { getSetValues(request, json); },
|
||||||
HTTP_ANY);
|
HTTP_ANY);
|
||||||
|
|
||||||
// Add authentication filter to EventSource
|
|
||||||
// EventSource (SSE) cannot use custom headers, so authentication is done via URL parameter
|
|
||||||
// events_.setFilter(securityManager->filterRequest(AuthenticationPredicates::IS_AUTHENTICATED));
|
|
||||||
server->addHandler(&events_);
|
server->addHandler(&events_);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -211,6 +208,7 @@ void WebLogService::getSetValues(AsyncWebServerRequest * request, JsonVariant js
|
|||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// POST - write the settings
|
// POST - write the settings
|
||||||
level_ = json["level"];
|
level_ = json["level"];
|
||||||
maximum_log_messages_ = json["max_messages"];
|
maximum_log_messages_ = json["max_messages"];
|
||||||
@@ -234,6 +232,7 @@ void WebLogService::getSetValues(AsyncWebServerRequest * request, JsonVariant js
|
|||||||
settings.weblog_buffer = maximum_log_messages_;
|
settings.weblog_buffer = maximum_log_messages_;
|
||||||
return StateUpdateResult::CHANGED;
|
return StateUpdateResult::CHANGED;
|
||||||
});
|
});
|
||||||
|
|
||||||
request->send(200); // OK
|
request->send(200); // OK
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user