Skip to content

Commit d0efc37

Browse files
committed
Cookie Authentication including WS part taken from ayushsharma82 ideas
me-no-dev#684 For Websocket added: void handleHandshake(AwsHandshakeHandler handler) For EventSource added: void authorizeConnect(ArAuthorizeConnectHandler cb); Auth example and modifications. Tested on ESP8266 and ESP32 platforms See SmartSwitch.ino
1 parent 1d47224 commit d0efc37

13 files changed

+182
-34
lines changed

examples/SmartSwitch/README.md

+5-3
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,16 @@
44

55
## SmartSwitch
66
* Remote Temperature Control application with schedule (example car block heater or battery charger)
7-
* Based on ESP_AsyncFSBrowser example
7+
* Based on ESP_AsyncFSBrowser example with ACE editor
88
* Wide browser compatibility, no extra server-side needed
99
* HTTP server and WebSocket, single port
1010
* Standalone, no JS dependencies for the browser from Internet (I hope), ace editor included
1111
* Added ESPAsyncWiFiManager
12-
* Fallback to an own WIFI_AP, no Internet to sync but by a browser the clock can be set once
1312
* Real Time (NTP) w/ Time Zones
1413
* Memorized settings to EEPROM
1514
* Multiple clients can be connected at same time, they see each other' requests
15+
* Base Authentication of the editor, static content, WS
16+
* Or Cookie Authentication including WS part, need lib src changes taken from https://github.com/me-no-dev/ESPAsyncWebServer/pull/684
1617
* Default credentials <b>smart:switch</b>
17-
* Use latest ESP8266 or ESP32 core(from GitHub)
18+
* Use latest ESP8266 ESP32 cores from GitHub
19+

examples/SmartSwitch/SmartSwitch.ino

+125-30
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,21 @@ Use latest ESP core lib (from Github)
1616
#define USE_WFM // to use ESPAsyncWiFiManager
1717
//#define DEL_WFM // delete Wifi credentials stored
1818
//(use once then comment and flash again), also HTTP /erase-wifi can do the same live
19-
20-
#define USE_AUTH_STAT // .setAuthentication also for static (editor always requires auth)
21-
//#define USE_AUTH_WS // .setAuthentication also for ws, broken for Safari iOS
19+
20+
// AUTH COOKIE uses only the password, Base uses both
21+
#define http_username "smart"
22+
#define http_password "switch"
23+
24+
//See https://github.com/me-no-dev/ESPAsyncWebServer/pull/684
25+
#define USE_AUTH_COOKIE
26+
#define MY_COOKIE_FULL "LLKQ=7;max-age=31536000;"
27+
#define MY_COOKIE_DEL "LLKQ="
28+
#define MY_COOKIE "LLKQ=7"
29+
30+
#ifndef USE_AUTH_COOKIE
31+
#define USE_AUTH_STAT //Base Auth for stat, /commands and SPIFFSEditor
32+
//#define USE_AUTH_WS //Base Auth also for WS, not very supported
33+
#endif
2234

2335
#include <ArduinoOTA.h>
2436
#ifdef ESP32
@@ -55,7 +67,10 @@ Use latest ESP core lib (from Github)
5567

5668
// DHT
5769
#define DHTTYPE DHT22 // DHT 11 // DHT 22, AM2302, AM2321 // DHT 21, AM2301
58-
#define DHTPIN 4 //D2
70+
#define DHTPIN 4 //D2
71+
72+
#define DHT_T_CORR -0.5 //Temperature offset compensation of the sensor (can be -)
73+
#define DHT_H_CORR 1.5 //Humidity offset compensation of the sensor
5974

6075
DHT dht(DHTPIN, DHTTYPE);
6176

@@ -80,9 +95,7 @@ AsyncWebSocket ws("/ws");
8095
const char* ssid = "MYROUTERSSD";
8196
const char* password = "MYROUTERPASSWD";
8297
#endif
83-
const char* hostName = "smartsw";
84-
const char* http_username = "smart";
85-
const char* http_password = "switch";
98+
const char* hostName = "smartsw32";
8699

87100
// RTC
88101
static timeval tv;
@@ -91,6 +104,15 @@ static time_t now;
91104
// HW I/O
92105
const int btnPin = 0; //D3
93106
const int ledPin = 2; //D4
107+
108+
#ifdef ESP32
109+
#define LED_ON 0x1
110+
#define LED_OFF 0x0
111+
#elif defined(ESP8266)
112+
#define LED_ON 0x0
113+
#define LED_OFF 0x1
114+
#endif
115+
94116
int btnState = HIGH;
95117

96118
// Globals
@@ -101,7 +123,7 @@ float t = 0;
101123
float h = 0;
102124
bool udht = false;
103125
bool heat_enabled_prev = false;
104-
int ledState;
126+
int ledState = LED_OFF;
105127

106128
struct EE_bl {
107129
byte memid; //here goes the EEMARK stamp
@@ -178,30 +200,32 @@ void showTime()
178200
}
179201

180202
if (heat_enabled_prev) { // smart control (delayed one cycle)
181-
if (((t - HYST) < ee.tempe)&&(ledState == HIGH)) { // OFF->ON once
182-
ledState = LOW;
203+
if (((t - HYST) < ee.tempe)&&(ledState == LED_OFF)) { // OFF->ON once
204+
ledState = LED_ON;
183205
digitalWrite(ledPin, ledState); // apply change
184206
ws.textAll("led,ledon");
185207
}
186-
if ((((t + HYST) > ee.tempe)&&(ledState == LOW))||(!heat_enabled)) { // ON->OFF once, also turn off at end of period.
187-
ledState = HIGH;
208+
if ((((t + HYST) > ee.tempe)&&(ledState == LED_ON))||(!heat_enabled)) { // ON->OFF once, also turn off at end of period.
209+
ledState = LED_OFF;
188210
digitalWrite(ledPin, ledState); // apply change
189211
ws.textAll("led,ledoff");
190212
}
191-
Serial.printf(ledState ? "LED OFF" : "LED ON");
213+
214+
Serial.printf(ledState == LED_ON ? "LED ON" : "LED OFF");
192215
Serial.print(F(", Smart enabled\n"));
193216
}
194217
heat_enabled_prev = heat_enabled; //update
195218
}
196219

197220
void updateDHT(){
198-
h = dht.readHumidity();
199-
t = dht.readTemperature(); //Celsius or dht.readTemperature(true) for Fahrenheit
200-
if (isnan(h) || isnan(t)) {
221+
float h1 = dht.readHumidity();
222+
float t1 = dht.readTemperature(); //Celsius or dht.readTemperature(true) for Fahrenheit
223+
if (isnan(h1) || isnan(t1)) {
201224
Serial.print(F("Failed to read from DHT sensor!"));
202-
h = 0; // debug w/o sensor
203-
t = 0;
204-
}
225+
} else {
226+
h = h1 + DHT_H_CORR;
227+
t = t1 + DHT_T_CORR;
228+
}
205229
}
206230

207231
void analogSample()
@@ -216,7 +240,7 @@ void checkPhysicalButton()
216240
if (btnState != LOW) { // btnState is used to avoid sequential toggles
217241
ledState = !ledState;
218242
digitalWrite(ledPin, ledState);
219-
if (ledState) ws.textAll("led,ledoff");
243+
if (ledState == LED_OFF) ws.textAll("led,ledoff");
220244
else ws.textAll("led,ledon");
221245
}
222246
btnState = LOW;
@@ -241,6 +265,16 @@ void mytimer() {
241265
}
242266
}
243267

268+
#ifdef USE_AUTH_COOKIE
269+
bool myHandshake(AsyncWebServerRequest *request){ // false will 401
270+
if (request->hasHeader("Cookie")){
271+
String cookie = request->header("Cookie");
272+
if (cookie.indexOf(MY_COOKIE) != -1) return true;
273+
else return false;
274+
} else return false;
275+
}
276+
#endif
277+
244278
// server
245279
void onWsEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len){
246280
if(type == WS_EVT_CONNECT){
@@ -252,7 +286,7 @@ void onWsEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventT
252286
Serial.printf("[%u] Connected from %d.%d.%d.%d\n", client->id(), ip[0], ip[1], ip[2], ip[3]);
253287
showTime();
254288
analogSample();
255-
if (ledState) ws.textAll("led,ledoff");
289+
if (ledState == LED_OFF) ws.textAll("led,ledoff");
256290
else ws.textAll("led,ledon");
257291

258292
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
279313
}
280314
if(data[0] == 'L') { // LED
281315
if(data[1] == '1') {
282-
ledState = LOW;
316+
ledState = LED_ON;
283317
ws.textAll("led,ledon"); // for others
284318
}
285319
else if(data[1] == '0') {
286-
ledState = HIGH;
320+
ledState = LED_OFF;
287321
ws.textAll("led,ledoff");
288322
}
289323
digitalWrite(ledPin, ledState); // apply change
@@ -444,31 +478,74 @@ void setup(){
444478
#ifdef USE_AUTH_WS
445479
ws.setAuthentication(http_username,http_password);
446480
#endif
481+
482+
#ifdef USE_AUTH_COOKIE
483+
ws.handleHandshake(myHandshake);
484+
#endif
485+
447486
ws.onEvent(onWsEvent);
448487
server.addHandler(&ws);
449488

450489
#ifdef ESP32
490+
#ifdef USE_AUTH_STAT
451491
server.addHandler(new SPIFFSEditor(SPIFFS, http_username,http_password));
492+
#elif defined(USE_AUTH_COOKIE)
493+
server.addHandler(new SPIFFSEditor(SPIFFS)).setFilter(myHandshake);
494+
#endif
452495
#elif defined(ESP8266)
496+
#ifdef USE_AUTH_STAT
453497
server.addHandler(new SPIFFSEditor(http_username,http_password));
498+
#elif defined(USE_AUTH_COOKIE)
499+
server.addHandler(new SPIFFSEditor()).setFilter(myHandshake);
500+
#endif
454501
#endif
455-
502+
503+
#ifdef USE_AUTH_COOKIE
504+
server.on("/lg2n", HTTP_POST, [](AsyncWebServerRequest *request){ // cookie test
505+
if((request->hasParam("pa2w",true) && (String(request->getParam("pa2w",true)->value().c_str()) == String(http_password)))||(request->hasParam("lg0f",true))){
506+
AsyncWebServerResponse *response = request->beginResponse(301);
507+
response->addHeader("Location", "/");
508+
response->addHeader("Cache-Control", "no-cache");
509+
if(request->hasParam("lg0f",true)) response->addHeader("Set-Cookie", MY_COOKIE_DEL);
510+
else response->addHeader("Set-Cookie", MY_COOKIE_FULL);
511+
request->send(response);
512+
} else request->send(200, "text/plain","Wrong Password!");
513+
});
514+
#endif
515+
516+
// below paths need individual auth ////////////////////////////////////////////////
517+
456518
server.on("/free-ram", HTTP_GET, [](AsyncWebServerRequest *request){ // direct request->answer
519+
#ifdef USE_AUTH_STAT
520+
if(!request->authenticate(http_username, http_password)) return request->requestAuthentication();
521+
#endif
457522
request->send(200, "text/plain", String(ESP.getFreeHeap()));
523+
#ifdef USE_AUTH COOKIE
524+
}).setFilter(myHandshake);
525+
#else
458526
});
527+
#endif
459528

460-
461-
server.on("/get-time", HTTP_GET, [](AsyncWebServerRequest *request){
529+
server.on("/get-time", HTTP_GET, [](AsyncWebServerRequest *request){
530+
#ifdef USE_AUTH_STAT
531+
if(!request->authenticate(http_username, http_password)) return request->requestAuthentication();
532+
#endif
462533
if(request->hasParam("btime")){
463534
time_t rtc = (request->getParam("btime")->value()).toInt();
464535
timeval tv = { rtc, 0 };
465536
settimeofday(&tv, nullptr);
466537
}
467538
request->send(200, "text/plain","Got browser time ...");
539+
#ifdef USE_AUTH COOKIE
540+
}).setFilter(myHandshake);
541+
#else
468542
});
469-
543+
#endif
470544

471545
server.on("/hw-reset", HTTP_GET, [](AsyncWebServerRequest *request){
546+
#ifdef USE_AUTH_STAT
547+
if(!request->authenticate(http_username, http_password)) return request->requestAuthentication();
548+
#endif
472549
request->onDisconnect([]() {
473550
#ifdef ESP32
474551
ESP.restart();
@@ -477,9 +554,16 @@ void setup(){
477554
#endif
478555
});
479556
request->send(200, "text/plain","Restarting ...");
557+
#ifdef USE_AUTH COOKIE
558+
}).setFilter(myHandshake);
559+
#else
480560
});
561+
#endif
481562

482563
server.on("/erase-wifi", HTTP_GET, [](AsyncWebServerRequest *request){
564+
#ifdef USE_AUTH_STAT
565+
if(!request->authenticate(http_username, http_password)) return request->requestAuthentication();
566+
#endif
483567
request->onDisconnect([]() {
484568
WiFi.disconnect(true);
485569
#ifdef ESP32
@@ -489,12 +573,23 @@ void setup(){
489573
#endif
490574
});
491575
request->send(200, "text/plain","Erasing WiFi data ...");
576+
#ifdef USE_AUTH COOKIE
577+
}).setFilter(myHandshake);
578+
#else
492579
});
580+
#endif
581+
582+
// above paths need individual auth ////////////////////////////////////////////////
493583

494-
#ifdef USE_AUTH_STAT
495-
server.serveStatic("/", SPIFFS, "/").setDefaultFile("index.htm").setAuthentication(http_username,http_password);
584+
#ifdef USE_AUTH_COOKIE
585+
server.serveStatic("/", SPIFFS, "/").setDefaultFile("index.htm").setFilter(myHandshake);
586+
server.serveStatic("/", SPIFFS, "/login/").setDefaultFile("index.htm").setFilter(!myHandshake);
496587
#else
497-
server.serveStatic("/", SPIFFS, "/").setDefaultFile("index.htm");
588+
#ifdef USE_AUTH_STAT
589+
server.serveStatic("/", SPIFFS, "/").setDefaultFile("index.htm").setAuthentication(http_username,http_password);
590+
#else
591+
server.serveStatic("/", SPIFFS, "/").setDefaultFile("index.htm");
592+
#endif
498593
#endif
499594

500595
server.onNotFound([](AsyncWebServerRequest *request){ // nothing known
0 Bytes
Binary file not shown.
5.67 KB
Binary file not shown.
+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
5+
<title>Login</title>
6+
<meta name="viewport" content="width=device-width">
7+
<link rel="apple-touch-icon" href="/favicon.ico" type="image/x-icon" />
8+
<link rel="shortcut icon" href="/favicon.ico" type="image/x-icon" />
9+
<link rel="icon" href="/favicon.ico" type="image/x-icon" />
10+
</head>
11+
<body style="background-color:#bbb;font-family:arial;"><center>
12+
<br><br>
13+
<h4>Password</h4>
14+
<form action="/lg2n" method="post">
15+
<input type="password" name="pa2w">
16+
<input type="checkbox" name="lg0f"><label for="lg0f">Logoff</label><br><br>
17+
<input type="submit" value="Submit">
18+
</form></center>
19+
</body>
20+
</html>
0 Bytes
Binary file not shown.
0 Bytes
Binary file not shown.
Binary file not shown.

src/AsyncEventSource.cpp

+10
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,10 @@ void AsyncEventSource::onConnect(ArEventHandlerFunction cb){
263263
_connectcb = cb;
264264
}
265265

266+
void AsyncEventSource::authorizeConnect(ArAuthorizeConnectHandler cb){
267+
_authorizeConnectHandler = cb;
268+
}
269+
266270
void AsyncEventSource::_addClient(AsyncEventSourceClient * client){
267271
/*char * temp = (char *)malloc(2054);
268272
if(temp != NULL){
@@ -333,13 +337,19 @@ bool AsyncEventSource::canHandle(AsyncWebServerRequest *request){
333337
return false;
334338
}
335339
request->addInterestingHeader(F("Last-Event-ID"));
340+
request->addInterestingHeader("Cookie");
336341
return true;
337342
}
338343

339344
void AsyncEventSource::handleRequest(AsyncWebServerRequest *request){
340345
if((_username.length() && _password.length()) && !request->authenticate(_username.c_str(), _password.c_str())) {
341346
return request->requestAuthentication();
342347
}
348+
if(_authorizeConnectHandler != NULL){
349+
if(!_authorizeConnectHandler(request)){
350+
return request->send(401);
351+
}
352+
}
343353
request->send(new AsyncEventSourceResponse(this));
344354
}
345355

src/AsyncEventSource.h

+3
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ class AsyncEventSource;
4949
class AsyncEventSourceResponse;
5050
class AsyncEventSourceClient;
5151
typedef std::function<void(AsyncEventSourceClient *client)> ArEventHandlerFunction;
52+
typedef std::function<bool(AsyncWebServerRequest *request)> ArAuthorizeConnectHandler;
5253

5354
class AsyncEventSourceMessage {
5455
private:
@@ -100,13 +101,15 @@ class AsyncEventSource: public AsyncWebHandler {
100101
String _url;
101102
LinkedList<AsyncEventSourceClient *> _clients;
102103
ArEventHandlerFunction _connectcb;
104+
ArAuthorizeConnectHandler _authorizeConnectHandler;
103105
public:
104106
AsyncEventSource(const String& url);
105107
~AsyncEventSource();
106108

107109
const char * url() const { return _url.c_str(); }
108110
void close();
109111
void onConnect(ArEventHandlerFunction cb);
112+
void authorizeConnect(ArAuthorizeConnectHandler cb);
110113
void send(const char *message, const char *event=NULL, uint32_t id=0, uint32_t reconnect=0);
111114
size_t count() const; //number clinets connected
112115
size_t avgPacketsWaiting() const;

0 commit comments

Comments
 (0)