diff --git a/examples/SmartSwitch/README.md b/examples/SmartSwitch/README.md
index fd8cd40..4ce4e9a 100644
--- a/examples/SmartSwitch/README.md
+++ b/examples/SmartSwitch/README.md
@@ -4,14 +4,16 @@
## SmartSwitch
* Remote Temperature Control application with schedule (example car block heater or battery charger)
-* Based on ESP_AsyncFSBrowser example
+* Based on ESP_AsyncFSBrowser example with ACE editor
* Wide browser compatibility, no extra server-side needed
* HTTP server and WebSocket, single port
* Standalone, no JS dependencies for the browser from Internet (I hope), ace editor included
* Added ESPAsyncWiFiManager
-* Fallback to an own WIFI_AP, no Internet to sync but by a browser the clock can be set once
* Real Time (NTP) w/ Time Zones
* Memorized settings to EEPROM
* Multiple clients can be connected at same time, they see each other' requests
+* Base Authentication of the editor, static content, WS
+* Or Cookie Authentication including WS part, need lib src changes taken from https://github.com/me-no-dev/ESPAsyncWebServer/pull/684
* Default credentials smart:switch
-* Use latest ESP8266 or ESP32 core(from GitHub)
+* Use latest ESP8266 ESP32 cores from GitHub
+
diff --git a/examples/SmartSwitch/SmartSwitch.ino b/examples/SmartSwitch/SmartSwitch.ino
index 0307cd2..9609e9d 100644
--- a/examples/SmartSwitch/SmartSwitch.ino
+++ b/examples/SmartSwitch/SmartSwitch.ino
@@ -16,9 +16,21 @@ Use latest ESP core lib (from Github)
#define USE_WFM // to use ESPAsyncWiFiManager
//#define DEL_WFM // delete Wifi credentials stored
//(use once then comment and flash again), also HTTP /erase-wifi can do the same live
-
-#define USE_AUTH_STAT // .setAuthentication also for static (editor always requires auth)
-//#define USE_AUTH_WS // .setAuthentication also for ws, broken for Safari iOS
+
+// AUTH COOKIE uses only the password, Base uses both
+#define http_username "smart"
+#define http_password "switch"
+
+//See https://github.com/me-no-dev/ESPAsyncWebServer/pull/684
+#define USE_AUTH_COOKIE
+#define MY_COOKIE_FULL "LLKQ=7;max-age=31536000;"
+#define MY_COOKIE_DEL "LLKQ="
+#define MY_COOKIE "LLKQ=7"
+
+#ifndef USE_AUTH_COOKIE
+ #define USE_AUTH_STAT //Base Auth for stat, /commands and SPIFFSEditor
+ //#define USE_AUTH_WS //Base Auth also for WS, not very supported
+#endif
#include
#ifdef ESP32
@@ -55,7 +67,10 @@ Use latest ESP core lib (from Github)
// DHT
#define DHTTYPE DHT22 // DHT 11 // DHT 22, AM2302, AM2321 // DHT 21, AM2301
-#define DHTPIN 4 //D2
+#define DHTPIN 4 //D2
+
+#define DHT_T_CORR -0.5 //Temperature offset compensation of the sensor (can be -)
+#define DHT_H_CORR 1.5 //Humidity offset compensation of the sensor
DHT dht(DHTPIN, DHTTYPE);
@@ -80,9 +95,7 @@ AsyncWebSocket ws("/ws");
const char* ssid = "MYROUTERSSD";
const char* password = "MYROUTERPASSWD";
#endif
-const char* hostName = "smartsw";
-const char* http_username = "smart";
-const char* http_password = "switch";
+ const char* hostName = "smartsw32";
// RTC
static timeval tv;
@@ -91,6 +104,15 @@ static time_t now;
// HW I/O
const int btnPin = 0; //D3
const int ledPin = 2; //D4
+
+#ifdef ESP32
+ #define LED_ON 0x1
+ #define LED_OFF 0x0
+#elif defined(ESP8266)
+ #define LED_ON 0x0
+ #define LED_OFF 0x1
+#endif
+
int btnState = HIGH;
// Globals
@@ -101,7 +123,7 @@ float t = 0;
float h = 0;
bool udht = false;
bool heat_enabled_prev = false;
-int ledState;
+int ledState = LED_OFF;
struct EE_bl {
byte memid; //here goes the EEMARK stamp
@@ -178,30 +200,32 @@ void showTime()
}
if (heat_enabled_prev) { // smart control (delayed one cycle)
- if (((t - HYST) < ee.tempe)&&(ledState == HIGH)) { // OFF->ON once
- ledState = LOW;
+ if (((t - HYST) < ee.tempe)&&(ledState == LED_OFF)) { // OFF->ON once
+ ledState = LED_ON;
digitalWrite(ledPin, ledState); // apply change
ws.textAll("led,ledon");
}
- if ((((t + HYST) > ee.tempe)&&(ledState == LOW))||(!heat_enabled)) { // ON->OFF once, also turn off at end of period.
- ledState = HIGH;
+ if ((((t + HYST) > ee.tempe)&&(ledState == LED_ON))||(!heat_enabled)) { // ON->OFF once, also turn off at end of period.
+ ledState = LED_OFF;
digitalWrite(ledPin, ledState); // apply change
ws.textAll("led,ledoff");
}
- Serial.printf(ledState ? "LED OFF" : "LED ON");
+
+ Serial.printf(ledState == LED_ON ? "LED ON" : "LED OFF");
Serial.print(F(", Smart enabled\n"));
}
heat_enabled_prev = heat_enabled; //update
}
void updateDHT(){
- h = dht.readHumidity();
- t = dht.readTemperature(); //Celsius or dht.readTemperature(true) for Fahrenheit
- if (isnan(h) || isnan(t)) {
+ float h1 = dht.readHumidity();
+ float t1 = dht.readTemperature(); //Celsius or dht.readTemperature(true) for Fahrenheit
+ if (isnan(h1) || isnan(t1)) {
Serial.print(F("Failed to read from DHT sensor!"));
- h = 0; // debug w/o sensor
- t = 0;
- }
+ } else {
+ h = h1 + DHT_H_CORR;
+ t = t1 + DHT_T_CORR;
+ }
}
void analogSample()
@@ -216,7 +240,7 @@ void checkPhysicalButton()
if (btnState != LOW) { // btnState is used to avoid sequential toggles
ledState = !ledState;
digitalWrite(ledPin, ledState);
- if (ledState) ws.textAll("led,ledoff");
+ if (ledState == LED_OFF) ws.textAll("led,ledoff");
else ws.textAll("led,ledon");
}
btnState = LOW;
@@ -241,6 +265,16 @@ void mytimer() {
}
}
+#ifdef USE_AUTH_COOKIE
+bool myHandshake(AsyncWebServerRequest *request){ // false will 401
+ if (request->hasHeader("Cookie")){
+ String cookie = request->header("Cookie");
+ if (cookie.indexOf(MY_COOKIE) != -1) return true;
+ else return false;
+ } else return false;
+}
+#endif
+
// server
void onWsEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len){
if(type == WS_EVT_CONNECT){
@@ -252,7 +286,7 @@ void onWsEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventT
Serial.printf("[%u] Connected from %d.%d.%d.%d\n", client->id(), ip[0], ip[1], ip[2], ip[3]);
showTime();
analogSample();
- if (ledState) ws.textAll("led,ledoff");
+ if (ledState == LED_OFF) ws.textAll("led,ledoff");
else ws.textAll("led,ledon");
ws.printfAll("Now,Setting,%02d:%02d,%02d:%02d,%+2.1f", ee.hstart, ee.mstart, ee.hstop, ee.mstop, ee.tempe);
@@ -279,11 +313,11 @@ void onWsEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventT
}
if(data[0] == 'L') { // LED
if(data[1] == '1') {
- ledState = LOW;
+ ledState = LED_ON;
ws.textAll("led,ledon"); // for others
}
else if(data[1] == '0') {
- ledState = HIGH;
+ ledState = LED_OFF;
ws.textAll("led,ledoff");
}
digitalWrite(ledPin, ledState); // apply change
@@ -444,31 +478,74 @@ void setup(){
#ifdef USE_AUTH_WS
ws.setAuthentication(http_username,http_password);
#endif
+
+#ifdef USE_AUTH_COOKIE
+ ws.handleHandshake(myHandshake);
+#endif
+
ws.onEvent(onWsEvent);
server.addHandler(&ws);
#ifdef ESP32
+ #ifdef USE_AUTH_STAT
server.addHandler(new SPIFFSEditor(SPIFFS, http_username,http_password));
+ #elif defined(USE_AUTH_COOKIE)
+ server.addHandler(new SPIFFSEditor(SPIFFS)).setFilter(myHandshake);
+ #endif
#elif defined(ESP8266)
+ #ifdef USE_AUTH_STAT
server.addHandler(new SPIFFSEditor(http_username,http_password));
+ #elif defined(USE_AUTH_COOKIE)
+ server.addHandler(new SPIFFSEditor()).setFilter(myHandshake);
+ #endif
#endif
-
- server.on("/free-ram", HTTP_GET, [](AsyncWebServerRequest *request){ // direct request->answer
- request->send(200, "text/plain", String(ESP.getFreeHeap()));
+
+#ifdef USE_AUTH_COOKIE
+ server.on("/lg2n", HTTP_POST, [](AsyncWebServerRequest *request){ // cookie test
+ if((request->hasParam("pa2w",true) && (String(request->getParam("pa2w",true)->value().c_str()) == String(http_password)))||(request->hasParam("lg0f",true))){
+ AsyncWebServerResponse *response = request->beginResponse(301);
+ response->addHeader("Location", "/");
+ response->addHeader("Cache-Control", "no-cache");
+ if(request->hasParam("lg0f",true)) response->addHeader("Set-Cookie", MY_COOKIE_DEL);
+ else response->addHeader("Set-Cookie", MY_COOKIE_FULL);
+ request->send(response);
+ } else request->send(200, "text/plain","Wrong Password!");
});
+#endif
+// below paths need individual auth ////////////////////////////////////////////////
- server.on("/get-time", HTTP_GET, [](AsyncWebServerRequest *request){
+ server.on("/free-ram", HTTP_GET, [](AsyncWebServerRequest *request){ // direct request->answer
+#ifdef USE_AUTH_STAT
+ if(!request->authenticate(http_username, http_password)) return request->requestAuthentication();
+#endif
+ request->send(200, "text/plain", String(ESP.getFreeHeap()));
+#ifdef USE_AUTH COOKIE
+ }).setFilter(myHandshake);
+#else
+ });
+#endif
+
+ server.on("/get-time", HTTP_GET, [](AsyncWebServerRequest *request){
+#ifdef USE_AUTH_STAT
+ if(!request->authenticate(http_username, http_password)) return request->requestAuthentication();
+#endif
if(request->hasParam("btime")){
time_t rtc = (request->getParam("btime")->value()).toInt();
timeval tv = { rtc, 0 };
settimeofday(&tv, nullptr);
}
request->send(200, "text/plain","Got browser time ...");
+#ifdef USE_AUTH COOKIE
+ }).setFilter(myHandshake);
+#else
});
-
+#endif
server.on("/hw-reset", HTTP_GET, [](AsyncWebServerRequest *request){
+#ifdef USE_AUTH_STAT
+ if(!request->authenticate(http_username, http_password)) return request->requestAuthentication();
+#endif
request->onDisconnect([]() {
#ifdef ESP32
ESP.restart();
@@ -477,9 +554,16 @@ void setup(){
#endif
});
request->send(200, "text/plain","Restarting ...");
+#ifdef USE_AUTH COOKIE
+ }).setFilter(myHandshake);
+#else
});
+#endif
server.on("/erase-wifi", HTTP_GET, [](AsyncWebServerRequest *request){
+#ifdef USE_AUTH_STAT
+ if(!request->authenticate(http_username, http_password)) return request->requestAuthentication();
+#endif
request->onDisconnect([]() {
WiFi.disconnect(true);
#ifdef ESP32
@@ -489,12 +573,23 @@ void setup(){
#endif
});
request->send(200, "text/plain","Erasing WiFi data ...");
- });
-
-#ifdef USE_AUTH_STAT
- server.serveStatic("/", SPIFFS, "/").setDefaultFile("index.htm").setAuthentication(http_username,http_password);
+#ifdef USE_AUTH COOKIE
+ }).setFilter(myHandshake);
#else
- server.serveStatic("/", SPIFFS, "/").setDefaultFile("index.htm");
+ });
+#endif
+
+// above paths need individual auth ////////////////////////////////////////////////
+
+#ifdef USE_AUTH_COOKIE
+ server.serveStatic("/", SPIFFS, "/").setDefaultFile("index.htm").setFilter(myHandshake);
+ server.serveStatic("/", SPIFFS, "/login/").setDefaultFile("index.htm").setFilter(!myHandshake);
+#else
+ #ifdef USE_AUTH_STAT
+ server.serveStatic("/", SPIFFS, "/").setDefaultFile("index.htm").setAuthentication(http_username,http_password);
+ #else
+ server.serveStatic("/", SPIFFS, "/").setDefaultFile("index.htm");
+ #endif
#endif
server.onNotFound([](AsyncWebServerRequest *request){ // nothing known
diff --git a/examples/SmartSwitch/data/acefull.js.gz b/examples/SmartSwitch/data/acefull.js.gz
index e08df27..00cdd5f 100644
Binary files a/examples/SmartSwitch/data/acefull.js.gz and b/examples/SmartSwitch/data/acefull.js.gz differ
diff --git a/examples/SmartSwitch/data/login/favicon.ico.gz b/examples/SmartSwitch/data/login/favicon.ico.gz
new file mode 100644
index 0000000..9e60546
Binary files /dev/null and b/examples/SmartSwitch/data/login/favicon.ico.gz differ
diff --git a/examples/SmartSwitch/data/login/index.htm b/examples/SmartSwitch/data/login/index.htm
new file mode 100644
index 0000000..c522ea4
--- /dev/null
+++ b/examples/SmartSwitch/data/login/index.htm
@@ -0,0 +1,20 @@
+
+
+
+
+ Login
+
+
+
+
+
+
+
+Password
+
+
+
\ No newline at end of file
diff --git a/examples/SmartSwitch/data/worker-css.js.gz b/examples/SmartSwitch/data/worker-css.js.gz
index 6e826b6..3684985 100644
Binary files a/examples/SmartSwitch/data/worker-css.js.gz and b/examples/SmartSwitch/data/worker-css.js.gz differ
diff --git a/examples/SmartSwitch/data/worker-html.js.gz b/examples/SmartSwitch/data/worker-html.js.gz
index 5245c5e..f9a03ea 100644
Binary files a/examples/SmartSwitch/data/worker-html.js.gz and b/examples/SmartSwitch/data/worker-html.js.gz differ
diff --git a/examples/SmartSwitch/data/worker-javascript.js.gz b/examples/SmartSwitch/data/worker-javascript.js.gz
index a5fce7e..c5ca200 100644
Binary files a/examples/SmartSwitch/data/worker-javascript.js.gz and b/examples/SmartSwitch/data/worker-javascript.js.gz differ
diff --git a/src/AsyncEventSource.cpp b/src/AsyncEventSource.cpp
index 8230f0e..1325310 100644
--- a/src/AsyncEventSource.cpp
+++ b/src/AsyncEventSource.cpp
@@ -263,6 +263,10 @@ void AsyncEventSource::onConnect(ArEventHandlerFunction cb){
_connectcb = cb;
}
+void AsyncEventSource::authorizeConnect(ArAuthorizeConnectHandler cb){
+ _authorizeConnectHandler = cb;
+}
+
void AsyncEventSource::_addClient(AsyncEventSourceClient * client){
/*char * temp = (char *)malloc(2054);
if(temp != NULL){
@@ -333,6 +337,7 @@ bool AsyncEventSource::canHandle(AsyncWebServerRequest *request){
return false;
}
request->addInterestingHeader(F("Last-Event-ID"));
+ request->addInterestingHeader("Cookie");
return true;
}
@@ -340,6 +345,11 @@ void AsyncEventSource::handleRequest(AsyncWebServerRequest *request){
if((_username.length() && _password.length()) && !request->authenticate(_username.c_str(), _password.c_str())) {
return request->requestAuthentication();
}
+ if(_authorizeConnectHandler != NULL){
+ if(!_authorizeConnectHandler(request)){
+ return request->send(401);
+ }
+ }
request->send(new AsyncEventSourceResponse(this));
}
diff --git a/src/AsyncEventSource.h b/src/AsyncEventSource.h
index b097fa6..a350e7f 100644
--- a/src/AsyncEventSource.h
+++ b/src/AsyncEventSource.h
@@ -49,6 +49,7 @@ class AsyncEventSource;
class AsyncEventSourceResponse;
class AsyncEventSourceClient;
typedef std::function ArEventHandlerFunction;
+typedef std::function ArAuthorizeConnectHandler;
class AsyncEventSourceMessage {
private:
@@ -100,6 +101,7 @@ class AsyncEventSource: public AsyncWebHandler {
String _url;
LinkedList _clients;
ArEventHandlerFunction _connectcb;
+ ArAuthorizeConnectHandler _authorizeConnectHandler;
public:
AsyncEventSource(const String& url);
~AsyncEventSource();
@@ -107,6 +109,7 @@ class AsyncEventSource: public AsyncWebHandler {
const char * url() const { return _url.c_str(); }
void close();
void onConnect(ArEventHandlerFunction cb);
+ void authorizeConnect(ArAuthorizeConnectHandler cb);
void send(const char *message, const char *event=NULL, uint32_t id=0, uint32_t reconnect=0);
size_t count() const; //number clinets connected
size_t avgPacketsWaiting() const;
diff --git a/src/AsyncWebSocket.cpp b/src/AsyncWebSocket.cpp
index 04e1a6f..9ce9385 100644
--- a/src/AsyncWebSocket.cpp
+++ b/src/AsyncWebSocket.cpp
@@ -1146,6 +1146,7 @@ void AsyncWebSocket::binaryAll(const __FlashStringHelper *message, size_t len){
const char __WS_STR_CONNECTION[] PROGMEM = { "Connection" };
const char __WS_STR_UPGRADE[] PROGMEM = { "Upgrade" };
const char __WS_STR_ORIGIN[] PROGMEM = { "Origin" };
+const char __WS_STR_COOKIE[] PROGMEM = { "Cookie" };
const char __WS_STR_VERSION[] PROGMEM = { "Sec-WebSocket-Version" };
const char __WS_STR_KEY[] PROGMEM = { "Sec-WebSocket-Key" };
const char __WS_STR_PROTOCOL[] PROGMEM = { "Sec-WebSocket-Protocol" };
@@ -1155,6 +1156,7 @@ const char __WS_STR_UUID[] PROGMEM = { "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" };
#define WS_STR_CONNECTION FPSTR(__WS_STR_CONNECTION)
#define WS_STR_UPGRADE FPSTR(__WS_STR_UPGRADE)
#define WS_STR_ORIGIN FPSTR(__WS_STR_ORIGIN)
+#define WS_STR_COOKIE FPSTR(__WS_STR_COOKIE)
#define WS_STR_VERSION FPSTR(__WS_STR_VERSION)
#define WS_STR_KEY FPSTR(__WS_STR_KEY)
#define WS_STR_PROTOCOL FPSTR(__WS_STR_PROTOCOL)
@@ -1171,6 +1173,7 @@ bool AsyncWebSocket::canHandle(AsyncWebServerRequest *request){
request->addInterestingHeader(WS_STR_CONNECTION);
request->addInterestingHeader(WS_STR_UPGRADE);
request->addInterestingHeader(WS_STR_ORIGIN);
+ request->addInterestingHeader(WS_STR_COOKIE);
request->addInterestingHeader(WS_STR_VERSION);
request->addInterestingHeader(WS_STR_KEY);
request->addInterestingHeader(WS_STR_PROTOCOL);
@@ -1185,6 +1188,14 @@ void AsyncWebSocket::handleRequest(AsyncWebServerRequest *request){
if((_username.length() && _password.length()) && !request->authenticate(_username.c_str(), _password.c_str())){
return request->requestAuthentication();
}
+//////////////////////////////////////////
+ if(_handshakeHandler != nullptr){
+ if(!_handshakeHandler(request)){
+ request->send(401);
+ return;
+ }
+ }
+//////////////////////////////////////////
AsyncWebHeader* version = request->getHeader(WS_STR_VERSION);
if(version->value().toInt() != 13){
AsyncWebServerResponse *response = request->beginResponse(400);
diff --git a/src/AsyncWebSocket.h b/src/AsyncWebSocket.h
index 5b03ace..f06af2c 100644
--- a/src/AsyncWebSocket.h
+++ b/src/AsyncWebSocket.h
@@ -237,6 +237,7 @@ class AsyncWebSocketClient {
void _onData(void *pbuf, size_t plen);
};
+typedef std::function AwsHandshakeHandler;
typedef std::function AwsEventHandler;
//WebServer Handler implementation that plays the role of a socket server
@@ -248,6 +249,7 @@ class AsyncWebSocket: public AsyncWebHandler {
AsyncWebSocketClientLinkedList _clients;
uint32_t _cNextId;
AwsEventHandler _eventHandler;
+ AwsHandshakeHandler _handshakeHandler;
bool _enabled;
AsyncWebLock _lock;
@@ -316,6 +318,11 @@ class AsyncWebSocket: public AsyncWebHandler {
_eventHandler = handler;
}
+ // Handshake Handler
+ void handleHandshake(AwsHandshakeHandler handler){
+ _handshakeHandler = handler;
+ }
+
//system callbacks (do not call)
uint32_t _getNextId(){ return _cNextId++; }
void _addClient(AsyncWebSocketClient * client);
diff --git a/src/edit.htm.gz.h b/src/edit.htm.gz.h
index c2d7f53..3f352e0 100644
--- a/src/edit.htm.gz.h
+++ b/src/edit.htm.gz.h
@@ -2,7 +2,7 @@
//File: edit.htm.gz, Size: 4408
#define edit_htm_gz_len 4408
const uint8_t edit_htm_gz[] PROGMEM = {
-0x1F,0x8B,0x08,0x08,0x52,0x4A,0xB0,0x5E,0x02,0x00,0x65,0x64,0x69,0x74,0x2E,0x68,0x74,0x6D,0x00,0xB5,
+0x1F,0x8B,0x08,0x08,0x42,0x13,0xB7,0x5E,0x02,0x00,0x65,0x64,0x69,0x74,0x2E,0x68,0x74,0x6D,0x00,0xB5,
0x1A,0x0B,0x5B,0xDB,0x36,0xF0,0xAF,0x18,0x6F,0x63,0xF6,0xE2,0x38,0x0E,0xA5,0xAC,0x73,0x30,0x2C,0x50,
0x56,0xFA,0x02,0x4A,0x42,0x3B,0xCA,0xD8,0x3E,0xC5,0x56,0x62,0x15,0x5B,0xF6,0x2C,0x99,0x40,0xB3,0xFC,
0xF7,0x9D,0x24,0x3F,0x43,0xE8,0x1E,0xDF,0xD6,0x6E,0x8D,0xA4,0xD3,0x9D,0xEE,0x4E,0xF7,0x54,0xB2,0xBB,