Skip to content

Commit

Permalink
Cookie Authentication including WS part taken from ayushsharma82 ideas
Browse files Browse the repository at this point in the history
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
  • Loading branch information
lorol committed May 9, 2020
1 parent 7dbe115 commit 2aa029b
Show file tree
Hide file tree
Showing 13 changed files with 182 additions and 34 deletions.
8 changes: 5 additions & 3 deletions examples/SmartSwitch/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 <b>smart:switch</b>
* Use latest ESP8266 or ESP32 core(from GitHub)
* Use latest ESP8266 ESP32 cores from GitHub

155 changes: 125 additions & 30 deletions examples/SmartSwitch/SmartSwitch.ino
Original file line number Diff line number Diff line change
Expand Up @@ -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 <ArduinoOTA.h>
#ifdef ESP32
Expand Down Expand Up @@ -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);

Expand All @@ -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;
Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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()
Expand All @@ -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;
Expand All @@ -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){
Expand All @@ -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);
Expand All @@ -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
Expand Down Expand Up @@ -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


#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("/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){
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();
Expand All @@ -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
Expand All @@ -489,12 +573,23 @@ void setup(){
#endif
});
request->send(200, "text/plain","Erasing WiFi data ...");
#ifdef USE_AUTH COOKIE
}).setFilter(myHandshake);
#else
});
#endif

// above paths need individual auth ////////////////////////////////////////////////

#ifdef USE_AUTH_STAT
server.serveStatic("/", SPIFFS, "/").setDefaultFile("index.htm").setAuthentication(http_username,http_password);
#ifdef USE_AUTH_COOKIE
server.serveStatic("/", SPIFFS, "/").setDefaultFile("index.htm").setFilter(myHandshake);
server.serveStatic("/", SPIFFS, "/login/").setDefaultFile("index.htm").setFilter(!myHandshake);
#else
server.serveStatic("/", SPIFFS, "/").setDefaultFile("index.htm");
#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
Expand Down
Binary file modified examples/SmartSwitch/data/acefull.js.gz
Binary file not shown.
Binary file added examples/SmartSwitch/data/login/favicon.ico.gz
Binary file not shown.
20 changes: 20 additions & 0 deletions examples/SmartSwitch/data/login/index.htm
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
<title>Login</title>
<meta name="viewport" content="width=device-width">
<link rel="apple-touch-icon" href="/favicon.ico" type="image/x-icon" />
<link rel="shortcut icon" href="/favicon.ico" type="image/x-icon" />
<link rel="icon" href="/favicon.ico" type="image/x-icon" />
</head>
<body style="background-color:#bbb;font-family:arial;"><center>
<br><br>
<h4>Password</h4>
<form action="/lg2n" method="post">
<input type="password" name="pa2w">
<input type="checkbox" name="lg0f"><label for="lg0f">Logoff</label><br><br>
<input type="submit" value="Submit">
</form></center>
</body>
</html>
Binary file modified examples/SmartSwitch/data/worker-css.js.gz
Binary file not shown.
Binary file modified examples/SmartSwitch/data/worker-html.js.gz
Binary file not shown.
Binary file modified examples/SmartSwitch/data/worker-javascript.js.gz
Binary file not shown.
10 changes: 10 additions & 0 deletions src/AsyncEventSource.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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){
Expand Down Expand Up @@ -333,13 +337,19 @@ bool AsyncEventSource::canHandle(AsyncWebServerRequest *request){
return false;
}
request->addInterestingHeader(F("Last-Event-ID"));
request->addInterestingHeader("Cookie");
return true;
}

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));
}

Expand Down
3 changes: 3 additions & 0 deletions src/AsyncEventSource.h
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ class AsyncEventSource;
class AsyncEventSourceResponse;
class AsyncEventSourceClient;
typedef std::function<void(AsyncEventSourceClient *client)> ArEventHandlerFunction;
typedef std::function<bool(AsyncWebServerRequest *request)> ArAuthorizeConnectHandler;

class AsyncEventSourceMessage {
private:
Expand Down Expand Up @@ -100,13 +101,15 @@ class AsyncEventSource: public AsyncWebHandler {
String _url;
LinkedList<AsyncEventSourceClient *> _clients;
ArEventHandlerFunction _connectcb;
ArAuthorizeConnectHandler _authorizeConnectHandler;
public:
AsyncEventSource(const String& url);
~AsyncEventSource();

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;
Expand Down
Loading

0 comments on commit 2aa029b

Please sign in to comment.