add authentication service
This commit is contained in:
parent
7817010533
commit
04e852f7d9
52
src/AsyncAuthJsonWebHandler.h
Normal file
52
src/AsyncAuthJsonWebHandler.h
Normal file
@ -0,0 +1,52 @@
|
||||
#ifndef AsyncAuthJsonWebHandler_H_
|
||||
#define AsyncAuthJsonWebHandler_H_
|
||||
|
||||
#include <ESPAsyncWebServer.h>
|
||||
#include <AsyncJsonWebHandler.h>
|
||||
#include <ArduinoJson.h>
|
||||
#include <SecurityManager.h>
|
||||
|
||||
typedef std::function<void(AsyncWebServerRequest *request, JsonDocument &jsonDocument, Authentication &authentication)> AuthenticationJsonRequestCallback;
|
||||
|
||||
/**
|
||||
* Extends AsyncJsonWebHandler with a wrapper which verifies the user is authenticated.
|
||||
*
|
||||
* TODO - Extend with role checking support, possibly with a callback to verify the user.
|
||||
*/
|
||||
class AsyncAuthJsonWebHandler: public AsyncJsonWebHandler {
|
||||
|
||||
private:
|
||||
SecurityManager *_securityManager;
|
||||
using AsyncJsonWebHandler::onRequest;
|
||||
|
||||
public:
|
||||
|
||||
AsyncAuthJsonWebHandler() :
|
||||
AsyncJsonWebHandler(), _securityManager(NULL) {}
|
||||
|
||||
~AsyncAuthJsonWebHandler() {}
|
||||
|
||||
void setSecurityManager(SecurityManager *securityManager) {
|
||||
_securityManager = securityManager;
|
||||
}
|
||||
|
||||
void onRequest(AuthenticationJsonRequestCallback callback) {
|
||||
AsyncJsonWebHandler::onRequest([this, callback](AsyncWebServerRequest *request, JsonDocument &jsonDocument) {
|
||||
if(!_securityManager) {
|
||||
Serial.print("Security manager not configured for endpoint: ");
|
||||
Serial.println(_uri);
|
||||
request->send(500);
|
||||
return;
|
||||
}
|
||||
Authentication authentication = _securityManager->authenticateRequest(request);
|
||||
if (!authentication.isAuthenticated()) {
|
||||
request->send(401);
|
||||
return;
|
||||
}
|
||||
callback(request, jsonDocument, authentication);
|
||||
});
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
#endif // end AsyncAuthJsonWebHandler_H_
|
@ -17,24 +17,25 @@
|
||||
|
||||
typedef std::function<void(AsyncWebServerRequest *request, JsonDocument &jsonDocument)> JsonRequestCallback;
|
||||
|
||||
class AsyncJsonRequestWebHandler: public AsyncWebHandler {
|
||||
class AsyncJsonWebHandler: public AsyncWebHandler {
|
||||
|
||||
private:
|
||||
|
||||
String _uri;
|
||||
WebRequestMethodComposite _method;
|
||||
JsonRequestCallback _onRequest;
|
||||
size_t _maxContentLength;
|
||||
|
||||
protected:
|
||||
String _uri;
|
||||
|
||||
public:
|
||||
|
||||
AsyncJsonRequestWebHandler() :
|
||||
_uri(),
|
||||
AsyncJsonWebHandler() :
|
||||
_method(HTTP_POST|HTTP_PUT|HTTP_PATCH),
|
||||
_onRequest(NULL),
|
||||
_maxContentLength(ASYNC_JSON_REQUEST_DEFAULT_MAX_SIZE) {}
|
||||
_maxContentLength(ASYNC_JSON_REQUEST_DEFAULT_MAX_SIZE),
|
||||
_uri() {}
|
||||
|
||||
~AsyncJsonRequestWebHandler() {}
|
||||
~AsyncJsonWebHandler() {}
|
||||
|
||||
void setUri(const String& uri) { _uri = uri; }
|
||||
void setMethod(WebRequestMethodComposite method) { _method = method; }
|
||||
@ -61,7 +62,9 @@ class AsyncJsonRequestWebHandler: public AsyncWebHandler {
|
||||
virtual void handleRequest(AsyncWebServerRequest *request) override final {
|
||||
// no request configured
|
||||
if(!_onRequest) {
|
||||
request->send(404);
|
||||
Serial.print("No request callback was configured for endpoint: ");
|
||||
Serial.println(_uri);
|
||||
request->send(500);
|
||||
return;
|
||||
}
|
||||
|
45
src/AuthenticationService.cpp
Normal file
45
src/AuthenticationService.cpp
Normal file
@ -0,0 +1,45 @@
|
||||
#include <AuthenticationService.h>
|
||||
|
||||
AuthenticationService::AuthenticationService(AsyncWebServer* server, SecurityManager* securityManager):
|
||||
_server(server), _securityManager(securityManager) {
|
||||
server->on(VERIFY_AUTHORIZATION_PATH, HTTP_GET, std::bind(&AuthenticationService::verifyAuthorization, this, std::placeholders::_1));
|
||||
|
||||
_signInHandler.setUri(SIGN_IN_PATH);
|
||||
_signInHandler.setMethod(HTTP_POST);
|
||||
_signInHandler.setMaxContentLength(MAX_SECURITY_MANAGER_SETTINGS_SIZE);
|
||||
_signInHandler.onRequest(std::bind(&AuthenticationService::signIn, this, std::placeholders::_1, std::placeholders::_2));
|
||||
server->addHandler(&_signInHandler);
|
||||
}
|
||||
|
||||
AuthenticationService::~AuthenticationService() {}
|
||||
|
||||
/**
|
||||
* Verifys that the request supplied a valid JWT.
|
||||
*/
|
||||
void AuthenticationService::verifyAuthorization(AsyncWebServerRequest *request) {
|
||||
Authentication authentication = _securityManager->authenticateRequest(request);
|
||||
request->send(authentication.isAuthenticated() ? 200: 401);
|
||||
}
|
||||
|
||||
/**
|
||||
* Signs in a user if the username and password match. Provides a JWT to be used in the Authorization header in subsequent requests.
|
||||
*/
|
||||
void AuthenticationService::signIn(AsyncWebServerRequest *request, JsonDocument &jsonDocument){
|
||||
if (jsonDocument.is<JsonObject>()) {
|
||||
String username = jsonDocument["username"];
|
||||
String password = jsonDocument["password"];
|
||||
Authentication authentication = _securityManager->authenticate(username, password);
|
||||
if (authentication.isAuthenticated()) {
|
||||
User* user = authentication.getUser();
|
||||
AsyncJsonResponse * response = new AsyncJsonResponse(MAX_USERS_SIZE);
|
||||
JsonObject jsonObject = response->getRoot();
|
||||
jsonObject["access_token"] = _securityManager->generateJWT(user);
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
}
|
||||
}
|
||||
AsyncWebServerResponse *response = request->beginResponse(401);
|
||||
request->send(response);
|
||||
}
|
||||
|
30
src/AuthenticationService.h
Normal file
30
src/AuthenticationService.h
Normal file
@ -0,0 +1,30 @@
|
||||
#ifndef AuthenticationService_H_
|
||||
#define AuthenticationService_H_
|
||||
|
||||
#include <SecurityManager.h>
|
||||
|
||||
#define VERIFY_AUTHORIZATION_PATH "/rest/verifyAuthorization"
|
||||
#define SIGN_IN_PATH "/rest/signIn"
|
||||
|
||||
#define MAX_AUTHENTICATION_SIZE 256
|
||||
|
||||
class AuthenticationService {
|
||||
|
||||
public:
|
||||
|
||||
AuthenticationService(AsyncWebServer* server, SecurityManager* securityManager) ;
|
||||
~AuthenticationService();
|
||||
|
||||
private:
|
||||
// server instance
|
||||
AsyncWebServer* _server;
|
||||
SecurityManager* _securityManager;
|
||||
AsyncJsonWebHandler _signInHandler;
|
||||
|
||||
// endpoint functions
|
||||
void signIn(AsyncWebServerRequest *request, JsonDocument &jsonDocument);
|
||||
void verifyAuthorization(AsyncWebServerRequest *request);
|
||||
|
||||
};
|
||||
|
||||
#endif // end SecurityManager_h
|
@ -1,23 +1,7 @@
|
||||
#include <SecurityManager.h>
|
||||
|
||||
SecurityManager::SecurityManager(AsyncWebServer* server, FS* fs) : SettingsPersistence(fs, SECURITY_SETTINGS_FILE) {
|
||||
// fetch users
|
||||
server->on(USERS_PATH, HTTP_GET, std::bind(&SecurityManager::fetchUsers, this, std::placeholders::_1));
|
||||
|
||||
// sign in request
|
||||
_signInRequestHandler.setUri(SIGN_IN_PATH);
|
||||
_signInRequestHandler.setMethod(HTTP_POST);
|
||||
_signInRequestHandler.setMaxContentLength(MAX_SECURITY_MANAGER_SETTINGS_SIZE);
|
||||
_signInRequestHandler.onRequest(std::bind(&SecurityManager::signIn, this, std::placeholders::_1, std::placeholders::_2));
|
||||
server->addHandler(&_signInRequestHandler);
|
||||
|
||||
|
||||
// sign in request
|
||||
_testVerifiction.setUri(TEST_VERIFICATION_PATH);
|
||||
_testVerifiction.setMethod(HTTP_POST);
|
||||
_testVerifiction.setMaxContentLength(MAX_SECURITY_MANAGER_SETTINGS_SIZE);
|
||||
_testVerifiction.onRequest(std::bind(&SecurityManager::testVerification, this, std::placeholders::_1, std::placeholders::_2));
|
||||
server->addHandler(&_testVerifiction);
|
||||
}
|
||||
|
||||
SecurityManager::~SecurityManager() {}
|
||||
@ -68,54 +52,6 @@ void SecurityManager::writeToJsonObject(JsonObject& root) {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO - Decide about default role behaviour, don't over-engineer (multiple roles, boolean admin flag???).
|
||||
void SecurityManager::signIn(AsyncWebServerRequest *request, JsonDocument &jsonDocument){
|
||||
if (jsonDocument.is<JsonObject>()) {
|
||||
// authenticate user
|
||||
String username = jsonDocument["username"];
|
||||
String password = jsonDocument["password"];
|
||||
Authentication authentication = authenticate(username, password);
|
||||
|
||||
if (authentication.isAuthenticated()) {
|
||||
User& user = authentication.getUser();
|
||||
|
||||
// create JWT
|
||||
DynamicJsonDocument _jsonDocument(MAX_JWT_SIZE);
|
||||
JsonObject jwt = _jsonDocument.to<JsonObject>();
|
||||
jwt["username"] = user.getUsername();
|
||||
jwt["role"] = user.getRole();
|
||||
|
||||
// send JWT response
|
||||
AsyncJsonResponse * response = new AsyncJsonResponse(MAX_USERS_SIZE);
|
||||
JsonObject jsonObject = response->getRoot();
|
||||
jsonObject["access_token"] = jwtHandler.buildJWT(jwt);
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// authentication failed
|
||||
AsyncWebServerResponse *response = request->beginResponse(401);
|
||||
request->send(response);
|
||||
}
|
||||
|
||||
void SecurityManager::testVerification(AsyncWebServerRequest *request, JsonDocument &jsonDocument){
|
||||
if (jsonDocument.is<JsonObject>()) {
|
||||
String accessToken = jsonDocument["access_token"];
|
||||
DynamicJsonDocument parsedJwt(MAX_JWT_SIZE);
|
||||
jwtHandler.parseJWT(accessToken, parsedJwt);
|
||||
if (parsedJwt.is<JsonObject>()){
|
||||
AsyncWebServerResponse *response = request->beginResponse(200);
|
||||
request->send(response);
|
||||
return;
|
||||
}
|
||||
}
|
||||
// authentication failed
|
||||
AsyncWebServerResponse *response = request->beginResponse(401);
|
||||
request->send(response);
|
||||
}
|
||||
|
||||
void SecurityManager::fetchUsers(AsyncWebServerRequest *request) {
|
||||
AsyncJsonResponse * response = new AsyncJsonResponse(MAX_USERS_SIZE);
|
||||
JsonObject jsonObject = response->getRoot();
|
||||
@ -132,36 +68,56 @@ void SecurityManager::begin() {
|
||||
jwtHandler.setSecret(_jwtSecret);
|
||||
}
|
||||
|
||||
/*
|
||||
* TODO - VERIFY JWT IS CORRECT!
|
||||
*/
|
||||
Authentication SecurityManager::verify(String jwt) {
|
||||
DynamicJsonDocument parsedJwt(MAX_JWT_SIZE);
|
||||
jwtHandler.parseJWT(jwt, parsedJwt);
|
||||
if (parsedJwt.is<JsonObject>()) {
|
||||
String username = parsedJwt["username"];
|
||||
Authentication SecurityManager::authenticateRequest(AsyncWebServerRequest *request) {
|
||||
AsyncWebHeader* authorizationHeader = request->getHeader(AUTHORIZATION_HEADER);
|
||||
if (authorizationHeader) {
|
||||
String value = authorizationHeader->value();
|
||||
value.startsWith(AUTHORIZATION_HEADER_PREFIX);
|
||||
value = value.substring(AUTHORIZATION_HEADER_PREFIX_LEN);
|
||||
return authenticateJWT(value);
|
||||
}
|
||||
return Authentication();
|
||||
}
|
||||
|
||||
Authentication SecurityManager::authenticateJWT(String jwt) {
|
||||
DynamicJsonDocument payloadDocument(MAX_JWT_SIZE);
|
||||
jwtHandler.parseJWT(jwt, payloadDocument);
|
||||
if (payloadDocument.is<JsonObject>()) {
|
||||
JsonObject parsedPayload = payloadDocument.as<JsonObject>();
|
||||
String username = parsedPayload["username"];
|
||||
for (User _user : _users) {
|
||||
if (_user.getUsername() == username){
|
||||
return Authentication::forUser(_user);
|
||||
if (_user.getUsername() == username && validatePayload(parsedPayload, &_user)){
|
||||
return Authentication(_user);
|
||||
}
|
||||
}
|
||||
}
|
||||
return Authentication::notAuthenticated();
|
||||
return Authentication();
|
||||
}
|
||||
|
||||
Authentication SecurityManager::authenticate(String username, String password) {
|
||||
for (User _user : _users) {
|
||||
if (_user.getUsername() == username && _user.getPassword() == password){
|
||||
return Authentication::forUser(_user);
|
||||
return Authentication(_user);
|
||||
}
|
||||
}
|
||||
return Authentication::notAuthenticated();
|
||||
return Authentication();
|
||||
}
|
||||
|
||||
String SecurityManager::generateJWT(User user) {
|
||||
DynamicJsonDocument _jsonDocument(MAX_JWT_SIZE);
|
||||
JsonObject jwt = _jsonDocument.to<JsonObject>();
|
||||
jwt["username"] = user.getUsername();
|
||||
jwt["role"] = user.getRole();
|
||||
return jwtHandler.buildJWT(jwt);
|
||||
inline void populateJWTPayload(JsonObject &payload, User *user) {
|
||||
payload["username"] = user->getUsername();
|
||||
payload["role"] = user->getRole();
|
||||
}
|
||||
|
||||
boolean SecurityManager::validatePayload(JsonObject &parsedPayload, User *user) {
|
||||
DynamicJsonDocument _jsonDocument(MAX_JWT_SIZE);
|
||||
JsonObject payload = _jsonDocument.to<JsonObject>();
|
||||
populateJWTPayload(payload, user);
|
||||
return payload == parsedPayload;
|
||||
}
|
||||
|
||||
String SecurityManager::generateJWT(User *user) {
|
||||
DynamicJsonDocument _jsonDocument(MAX_JWT_SIZE);
|
||||
JsonObject payload = _jsonDocument.to<JsonObject>();
|
||||
populateJWTPayload(payload, user);
|
||||
return jwtHandler.buildJWT(payload);
|
||||
}
|
||||
|
@ -13,18 +13,15 @@
|
||||
#define SECURITY_SETTINGS_FILE "/config/securitySettings.json"
|
||||
|
||||
#define USERS_PATH "/rest/users"
|
||||
#define AUTHENTICATE_PATH "/rest/authenticate"
|
||||
#define SIGN_IN_PATH "/rest/signIn"
|
||||
#define TEST_VERIFICATION_PATH "/rest/verification"
|
||||
|
||||
#define AUTHORIZATION_HEADER "Authorization"
|
||||
#define AUTHORIZATION_HEADER_PREFIX "Bearer "
|
||||
#define AUTHORIZATION_HEADER_PREFIX_LEN 7
|
||||
|
||||
#define MAX_JWT_SIZE 128
|
||||
#define MAX_SECURITY_MANAGER_SETTINGS_SIZE 512
|
||||
#define SECURITY_MANAGER_MAX_USERS 5
|
||||
|
||||
#define ANONYMOUS_USERNAME "_anonymous"
|
||||
#define ANONYMOUS_PASSWORD ""
|
||||
#define ANONYMOUS_ROLE ""
|
||||
|
||||
#define MAX_USERS_SIZE 1024
|
||||
|
||||
class User {
|
||||
@ -45,31 +42,27 @@ class User {
|
||||
}
|
||||
};
|
||||
|
||||
const User NOT_AUTHENTICATED = User(ANONYMOUS_USERNAME, ANONYMOUS_PASSWORD, ANONYMOUS_ROLE);
|
||||
|
||||
class Authentication {
|
||||
private:
|
||||
User _user;
|
||||
User *_user;
|
||||
boolean _authenticated;
|
||||
Authentication(User user, boolean authenticated) : _user(user), _authenticated(authenticated) {}
|
||||
public:
|
||||
// NOOP
|
||||
~Authentication(){}
|
||||
User& getUser() {
|
||||
Authentication(User& user): _user(new User(user)), _authenticated(true) {}
|
||||
Authentication() : _user(NULL), _authenticated(false) {}
|
||||
~Authentication() {
|
||||
if (_user != NULL){
|
||||
delete(_user);
|
||||
}
|
||||
}
|
||||
User* getUser() {
|
||||
return _user;
|
||||
}
|
||||
bool isAuthenticated() {
|
||||
return _authenticated;
|
||||
}
|
||||
static Authentication forUser(User user){
|
||||
return Authentication(user, true);
|
||||
}
|
||||
static Authentication notAuthenticated(){
|
||||
return Authentication(NOT_AUTHENTICATED, false);
|
||||
}
|
||||
};
|
||||
|
||||
class SecurityManager : public SettingsPersistence {
|
||||
class SecurityManager : public SettingsPersistence {
|
||||
|
||||
public:
|
||||
|
||||
@ -79,19 +72,19 @@ class SecurityManager : public SettingsPersistence {
|
||||
void begin();
|
||||
|
||||
/*
|
||||
* Lookup the user by JWT
|
||||
*/
|
||||
Authentication verify(String jwt);
|
||||
|
||||
/*
|
||||
* Authenticate, returning the user if found.
|
||||
* Authenticate, returning the user if found
|
||||
*/
|
||||
Authentication authenticate(String username, String password);
|
||||
|
||||
/*
|
||||
* Check the request header for the Authorization token
|
||||
*/
|
||||
Authentication authenticateRequest(AsyncWebServerRequest *request);
|
||||
|
||||
/*
|
||||
* Generate a JWT for the user provided
|
||||
*/
|
||||
String generateJWT(User user);
|
||||
String generateJWT(User *user);
|
||||
|
||||
protected:
|
||||
|
||||
@ -102,11 +95,6 @@ class SecurityManager : public SettingsPersistence {
|
||||
// jwt handler
|
||||
ArduinoJsonJWT jwtHandler = ArduinoJsonJWT(DEFAULT_JWT_SECRET);
|
||||
|
||||
// server instance
|
||||
AsyncWebServer* _server;
|
||||
AsyncJsonRequestWebHandler _signInRequestHandler;
|
||||
AsyncJsonRequestWebHandler _testVerifiction;
|
||||
|
||||
// access point settings
|
||||
String _jwtSecret;
|
||||
std::list<String> _roles;
|
||||
@ -114,8 +102,17 @@ class SecurityManager : public SettingsPersistence {
|
||||
|
||||
// endpoint functions
|
||||
void fetchUsers(AsyncWebServerRequest *request);
|
||||
void signIn(AsyncWebServerRequest *request, JsonDocument &jsonDocument);
|
||||
void testVerification(AsyncWebServerRequest *request, JsonDocument &jsonDocument);
|
||||
|
||||
/*
|
||||
* Lookup the user by JWT
|
||||
*/
|
||||
Authentication authenticateJWT(String jwt);
|
||||
|
||||
/*
|
||||
* Verify the payload is correct
|
||||
*/
|
||||
boolean validatePayload(JsonObject &parsedPayload, User *user);
|
||||
|
||||
};
|
||||
|
||||
#endif // end SecurityManager_h
|
@ -4,13 +4,13 @@
|
||||
#include <ESPAsyncWebServer.h>
|
||||
#include <FS.h>
|
||||
#include <ArduinoJson.h>
|
||||
#include <AsyncJsonRequestWebHandler.h>
|
||||
#include <AsyncJsonWebHandler.h>
|
||||
#include <AsyncArduinoJson6.h>
|
||||
|
||||
/**
|
||||
* At the moment, not expecting settings service to have to deal with large JSON
|
||||
* files this could be made configurable fairly simply, it's exposed on
|
||||
* AsyncJsonRequestWebHandler with a setter.
|
||||
* AsyncJsonWebHandler with a setter.
|
||||
*/
|
||||
#define MAX_SETTINGS_SIZE 1024
|
||||
|
||||
|
@ -12,7 +12,7 @@
|
||||
#include <SettingsPersistence.h>
|
||||
#include <ESPAsyncWebServer.h>
|
||||
#include <ArduinoJson.h>
|
||||
#include <AsyncJsonRequestWebHandler.h>
|
||||
#include <AsyncJsonWebHandler.h>
|
||||
#include <AsyncArduinoJson6.h>
|
||||
|
||||
/*
|
||||
@ -22,7 +22,7 @@ class SettingsService : public SettingsPersistence {
|
||||
|
||||
private:
|
||||
|
||||
AsyncJsonRequestWebHandler _updateHandler;
|
||||
AsyncJsonWebHandler _updateHandler;
|
||||
|
||||
void fetchConfig(AsyncWebServerRequest *request){
|
||||
AsyncJsonResponse * response = new AsyncJsonResponse(MAX_SETTINGS_SIZE);
|
||||
|
@ -12,12 +12,12 @@
|
||||
#include <ESPAsyncWebServer.h>
|
||||
#include <ArduinoJson.h>
|
||||
#include <AsyncArduinoJson6.h>
|
||||
#include <AsyncJsonRequestWebHandler.h>
|
||||
#include <AsyncJsonWebHandler.h>
|
||||
|
||||
/**
|
||||
* At the moment, not expecting services to have to deal with large JSON
|
||||
* files this could be made configurable fairly simply, it's exposed on
|
||||
* AsyncJsonRequestWebHandler with a setter.
|
||||
* AsyncJsonWebHandler with a setter.
|
||||
*/
|
||||
#define MAX_SETTINGS_SIZE 1024
|
||||
|
||||
@ -31,7 +31,7 @@ class SimpleService {
|
||||
|
||||
private:
|
||||
|
||||
AsyncJsonRequestWebHandler _updateHandler;
|
||||
AsyncJsonWebHandler _updateHandler;
|
||||
|
||||
void fetchConfig(AsyncWebServerRequest *request){
|
||||
AsyncJsonResponse * response = new AsyncJsonResponse(MAX_SETTINGS_SIZE);
|
||||
|
12
src/main.cpp
12
src/main.cpp
@ -10,15 +10,18 @@
|
||||
#endif
|
||||
|
||||
#include <FS.h>
|
||||
|
||||
#include <SecurityManager.h>
|
||||
#include <WiFiSettingsService.h>
|
||||
#include <WiFiStatus.h>
|
||||
#include <WiFiScanner.h>
|
||||
#include <APSettingsService.h>
|
||||
#include <NTPSettingsService.h>
|
||||
#include <NTPStatus.h>
|
||||
#include <OTASettingsService.h>
|
||||
#include <AuthenticationService.h>
|
||||
#include <WiFiScanner.h>
|
||||
#include <WiFiStatus.h>
|
||||
#include <NTPStatus.h>
|
||||
#include <APStatus.h>
|
||||
#include <SecurityManager.h>
|
||||
|
||||
|
||||
#define SERIAL_BAUD_RATE 115200
|
||||
|
||||
@ -30,6 +33,7 @@ WiFiSettingsService wifiSettingsService = WiFiSettingsService(&server, &SPIFFS);
|
||||
APSettingsService apSettingsService = APSettingsService(&server, &SPIFFS);
|
||||
NTPSettingsService ntpSettingsService = NTPSettingsService(&server, &SPIFFS);
|
||||
OTASettingsService otaSettingsService = OTASettingsService(&server, &SPIFFS);
|
||||
AuthenticationService authenticationService = AuthenticationService(&server, &securityManager);
|
||||
|
||||
WiFiScanner wifiScanner = WiFiScanner(&server);
|
||||
WiFiStatus wifiStatus = WiFiStatus(&server);
|
||||
|
Loading…
Reference in New Issue
Block a user