@ -18,23 +18,28 @@ Copyright (C) 2016 by Xose Pérez <xose dot perez at gmail dot com>
AsyncWebServer server ( 80 ) ;
AsyncWebServer server ( 80 ) ;
AsyncWebSocket ws ( " /ws " ) ;
AsyncWebSocket ws ( " /ws " ) ;
unsigned long _csrf [ CSRF_BUFFER_SIZE ] ;
typedef struct {
IPAddress ip ;
unsigned long timestamp = 0 ;
} ws_ticket_t ;
ws_ticket_t _ticket [ WS_BUFFER_SIZE ] ;
// -----------------------------------------------------------------------------
// -----------------------------------------------------------------------------
// WEBSOCKETS
// WEBSOCKETS
// -----------------------------------------------------------------------------
// -----------------------------------------------------------------------------
bool webSocket Send ( char * payload ) {
bool ws Send ( char * payload ) {
//DEBUG_MSG("[WEBSOCKET] Broadcasting '%s'\n", payload);
//DEBUG_MSG("[WEBSOCKET] Broadcasting '%s'\n", payload);
ws . textAll ( payload ) ;
ws . textAll ( payload ) ;
}
}
bool webSocket Send ( uint32_t client_id , char * payload ) {
bool ws Send ( uint32_t client_id , char * payload ) {
//DEBUG_MSG("[WEBSOCKET] Sending '%s' to #%ld\n", payload, client_id);
//DEBUG_MSG("[WEBSOCKET] Sending '%s' to #%ld\n", payload, client_id);
ws . text ( client_id , payload ) ;
ws . text ( client_id , payload ) ;
}
}
void webSocket Parse( uint32_t client_id , uint8_t * payload , size_t length ) {
void _ws Parse( uint32_t client_id , uint8_t * payload , size_t length ) {
// Parse JSON input
// Parse JSON input
DynamicJsonBuffer jsonBuffer ;
DynamicJsonBuffer jsonBuffer ;
@ -45,16 +50,6 @@ void webSocketParse(uint32_t client_id, uint8_t * payload, size_t length) {
return ;
return ;
}
}
// CSRF
unsigned long csrf = 0 ;
if ( root . containsKey ( " csrf " ) ) csrf = root [ " csrf " ] ;
if ( csrf ! = _csrf [ client_id % CSRF_BUFFER_SIZE ] ) {
DEBUG_MSG ( " [WEBSOCKET] CSRF check failed \n " ) ;
ws . text ( client_id , " { \" message \" : \" Session expired, please reload page... \" } " ) ;
return ;
}
// Check actions
// Check actions
if ( root . containsKey ( " action " ) ) {
if ( root . containsKey ( " action " ) ) {
@ -76,6 +71,7 @@ void webSocketParse(uint32_t client_id, uint8_t * payload, size_t length) {
bool dirty = false ;
bool dirty = false ;
bool dirtyMQTT = false ;
bool dirtyMQTT = false ;
bool apiEnabled = false ;
unsigned int network = 0 ;
unsigned int network = 0 ;
for ( unsigned int i = 0 ; i < config . size ( ) ; i + + ) {
for ( unsigned int i = 0 ; i < config . size ( ) ; i + + ) {
@ -97,6 +93,12 @@ void webSocketParse(uint32_t client_id, uint8_t * payload, size_t length) {
if ( value . length ( ) = = 0 ) continue ;
if ( value . length ( ) = = 0 ) continue ;
}
}
// Checkboxes
if ( key = = " apiEnabled " ) {
apiEnabled = true ;
continue ;
}
if ( key = = " ssid " ) {
if ( key = = " ssid " ) {
key = key + String ( network ) ;
key = key + String ( network ) ;
}
}
@ -113,6 +115,12 @@ void webSocketParse(uint32_t client_id, uint8_t * payload, size_t length) {
}
}
// Checkboxes
if ( apiEnabled ! = ( getSetting ( " apiEnabled " ) . toInt ( ) = = 1 ) ) {
setSetting ( " apiEnabled " , String ( ) + ( apiEnabled ? 1 : 0 ) ) ;
dirty = true ;
}
// Save settings
// Save settings
if ( dirty ) {
if ( dirty ) {
@ -146,7 +154,7 @@ void webSocketParse(uint32_t client_id, uint8_t * payload, size_t length) {
}
}
void webSocket Start( uint32_t client_id ) {
void _ws Start( uint32_t client_id ) {
char app [ 64 ] ;
char app [ 64 ] ;
sprintf ( app , " %s %s " , APP_NAME , APP_VERSION ) ;
sprintf ( app , " %s %s " , APP_NAME , APP_VERSION ) ;
@ -157,12 +165,6 @@ void webSocketStart(uint32_t client_id) {
DynamicJsonBuffer jsonBuffer ;
DynamicJsonBuffer jsonBuffer ;
JsonObject & root = jsonBuffer . createObject ( ) ;
JsonObject & root = jsonBuffer . createObject ( ) ;
// CSRF
if ( client_id < CSRF_BUFFER_SIZE ) {
_csrf [ client_id ] = random ( 0x7fffffff ) ;
}
root [ " csrf " ] = _csrf [ client_id % CSRF_BUFFER_SIZE ] ;
root [ " app " ] = app ;
root [ " app " ] = app ;
root [ " manufacturer " ] = String ( MANUFACTURER ) ;
root [ " manufacturer " ] = String ( MANUFACTURER ) ;
root [ " chipid " ] = chipid ;
root [ " chipid " ] = chipid ;
@ -171,7 +173,7 @@ void webSocketStart(uint32_t client_id) {
root [ " hostname " ] = getSetting ( " hostname " , HOSTNAME ) ;
root [ " hostname " ] = getSetting ( " hostname " , HOSTNAME ) ;
root [ " network " ] = getNetwork ( ) ;
root [ " network " ] = getNetwork ( ) ;
root [ " ip " ] = getIP ( ) ;
root [ " ip " ] = getIP ( ) ;
root [ " mqttStatus " ] = mqttConnected ( ) ? " 1 " : " 0 " ;
root [ " mqttStatus " ] = mqttConnected ( ) ;
root [ " mqttServer " ] = getSetting ( " mqttServer " , MQTT_SERVER ) ;
root [ " mqttServer " ] = getSetting ( " mqttServer " , MQTT_SERVER ) ;
root [ " mqttPort " ] = getSetting ( " mqttPort " , String ( MQTT_PORT ) ) ;
root [ " mqttPort " ] = getSetting ( " mqttPort " , String ( MQTT_PORT ) ) ;
root [ " mqttUser " ] = getSetting ( " mqttUser " ) ;
root [ " mqttUser " ] = getSetting ( " mqttUser " ) ;
@ -179,6 +181,8 @@ void webSocketStart(uint32_t client_id) {
root [ " mqttTopic " ] = getSetting ( " mqttTopic " , MQTT_TOPIC ) ;
root [ " mqttTopic " ] = getSetting ( " mqttTopic " , MQTT_TOPIC ) ;
root [ " relayStatus " ] = relayStatus ( 0 ) ;
root [ " relayStatus " ] = relayStatus ( 0 ) ;
root [ " relayMode " ] = getSetting ( " relayMode " , String ( RELAY_MODE ) ) ;
root [ " relayMode " ] = getSetting ( " relayMode " , String ( RELAY_MODE ) ) ;
root [ " apiEnabled " ] = getSetting ( " apiEnabled " ) . toInt ( ) = = 1 ;
root [ " apiKey " ] = getSetting ( " apiKey " ) ;
# if ENABLE_DHT
# if ENABLE_DHT
root [ " dhtVisible " ] = 1 ;
root [ " dhtVisible " ] = 1 ;
@ -217,15 +221,37 @@ void webSocketStart(uint32_t client_id) {
}
}
void webSocketEvent ( AsyncWebSocket * server , AsyncWebSocketClient * client , AwsEventType type , void * arg , uint8_t * data , size_t len ) {
bool _wsAuth ( AsyncWebSocketClient * client ) {
IPAddress ip = client - > remoteIP ( ) ;
unsigned long now = millis ( ) ;
unsigned short index = 0 ;
for ( index = 0 ; index < WS_BUFFER_SIZE ; index + + ) {
if ( ( _ticket [ index ] . ip = = ip ) & & ( now - _ticket [ index ] . timestamp < WS_TIMEOUT ) ) break ;
}
if ( index = = WS_BUFFER_SIZE ) {
DEBUG_MSG ( " [WEBSOCKET] Validation check failed \n " ) ;
ws . text ( client - > id ( ) , " { \" message \" : \" Session expired, please reload page... \" } " ) ;
return false ;
}
return true ;
}
void _wsEvent ( AsyncWebSocket * server , AsyncWebSocketClient * client , AwsEventType type , void * arg , uint8_t * data , size_t len ) {
// Authorize
# ifndef NOWSAUTH
if ( ! _wsAuth ( client ) ) return ;
# endif
if ( type = = WS_EVT_CONNECT ) {
if ( type = = WS_EVT_CONNECT ) {
# if DEBUG_PORT
{
IPAddress ip = server . remoteIP ( client - > id ( ) ) ;
DEBUG_MSG ( " [WEBSOCKET] #%u connected, ip: %d.%d.%d.%d, url: %s \n " , client - > id ( ) , ip [ 0 ] , ip [ 1 ] , ip [ 2 ] , ip [ 3 ] , server - > url ( ) ) ;
}
# endif
webSocketStart ( client - > id ( ) ) ;
IPAddress ip = client - > remoteIP ( ) ;
DEBUG_MSG ( " [WEBSOCKET] #%u connected, ip: %d.%d.%d.%d, url: %s \n " , client - > id ( ) , ip [ 0 ] , ip [ 1 ] , ip [ 2 ] , ip [ 3 ] , server - > url ( ) ) ;
_wsStart ( client - > id ( ) ) ;
} else if ( type = = WS_EVT_DISCONNECT ) {
} else if ( type = = WS_EVT_DISCONNECT ) {
DEBUG_MSG ( " [WEBSOCKET] #%u disconnected \n " , client - > id ( ) ) ;
DEBUG_MSG ( " [WEBSOCKET] #%u disconnected \n " , client - > id ( ) ) ;
} else if ( type = = WS_EVT_ERROR ) {
} else if ( type = = WS_EVT_ERROR ) {
@ -233,8 +259,9 @@ void webSocketEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsE
} else if ( type = = WS_EVT_PONG ) {
} else if ( type = = WS_EVT_PONG ) {
DEBUG_MSG ( " [WEBSOCKET] #%u pong(%u): %s \n " , client - > id ( ) , len , len ? ( char * ) data : " " ) ;
DEBUG_MSG ( " [WEBSOCKET] #%u pong(%u): %s \n " , client - > id ( ) , len , len ? ( char * ) data : " " ) ;
} else if ( type = = WS_EVT_DATA ) {
} else if ( type = = WS_EVT_DATA ) {
webSocket Parse( client - > id ( ) , data , len ) ;
_ws Parse( client - > id ( ) , data , len ) ;
}
}
}
}
// -----------------------------------------------------------------------------
// -----------------------------------------------------------------------------
@ -245,16 +272,43 @@ void _logRequest(AsyncWebServerRequest *request) {
DEBUG_MSG ( " [WEBSERVER] Request: %s %s \n " , request - > methodToString ( ) , request - > url ( ) . c_str ( ) ) ;
DEBUG_MSG ( " [WEBSERVER] Request: %s %s \n " , request - > methodToString ( ) , request - > url ( ) . c_str ( ) ) ;
}
}
void _onHome ( AsyncWebServerRequest * request ) {
_logRequest ( request ) ;
bool _authenticate ( AsyncWebServerRequest * request ) {
String password = getSetting ( " adminPass " , ADMIN_PASS ) ;
String password = getSetting ( " adminPass " , ADMIN_PASS ) ;
char httpPassword [ password . length ( ) + 1 ] ;
char httpPassword [ password . length ( ) + 1 ] ;
password . toCharArray ( httpPassword , password . length ( ) + 1 ) ;
password . toCharArray ( httpPassword , password . length ( ) + 1 ) ;
if ( ! request - > authenticate ( HTTP_USERNAME , httpPassword ) ) {
return request - > requestAuthentication ( ) ;
return request - > authenticate ( HTTP_USERNAME , httpPassword ) ;
}
void _onAuth ( AsyncWebServerRequest * request ) {
_logRequest ( request ) ;
if ( ! _authenticate ( request ) ) return request - > requestAuthentication ( ) ;
IPAddress ip = request - > client ( ) - > remoteIP ( ) ;
unsigned long now = millis ( ) ;
unsigned short index ;
for ( index = 0 ; index < WS_BUFFER_SIZE ; index + + ) {
if ( _ticket [ index ] . ip = = ip ) break ;
if ( _ticket [ index ] . timestamp = = 0 ) break ;
if ( now - _ticket [ index ] . timestamp > WS_TIMEOUT ) break ;
}
}
if ( index = = WS_BUFFER_SIZE ) {
request - > send ( 423 ) ;
} else {
_ticket [ index ] . ip = ip ;
_ticket [ index ] . timestamp = now ;
request - > send ( 204 ) ;
}
}
void _onHome ( AsyncWebServerRequest * request ) {
_logRequest ( request ) ;
if ( ! _authenticate ( request ) ) return request - > requestAuthentication ( ) ;
request - > send ( SPIFFS , " /index.html " ) ;
request - > send ( SPIFFS , " /index.html " ) ;
}
}
@ -276,16 +330,48 @@ void _onRelayOff(AsyncWebServerRequest *request) {
} ;
} ;
ArRequestHandlerFunction _onRelayStatusWrapper ( bool relayID ) {
bool _apiAuth ( AsyncWebServerRequest * request ) {
if ( getSetting ( " apiEnabled " ) . toInt ( ) = = 0 ) {
DEBUG_MSG ( " [WEBSERVER] HTTP API is not enabled \n " ) ;
request - > send ( 403 ) ;
return false ;
}
if ( ! request - > hasParam ( " apikey " , ( request - > method ( ) = = HTTP_PUT ) ) ) {
DEBUG_MSG ( " [WEBSERVER] Missing apikey parameter \n " ) ;
request - > send ( 403 ) ;
return false ;
}
AsyncWebParameter * p = request - > getParam ( " apikey " , ( request - > method ( ) = = HTTP_PUT ) ) ;
if ( ! p - > value ( ) . equals ( getSetting ( " apiKey " ) ) ) {
DEBUG_MSG ( " [WEBSERVER] Wrong apikey parameter \n " ) ;
request - > send ( 403 ) ;
return false ;
}
return true ;
}
ArRequestHandlerFunction _onRelayStatusWrapper ( unsigned int relayID ) {
return [ & ] ( AsyncWebServerRequest * request ) {
return [ & ] ( AsyncWebServerRequest * request ) {
_logRequest ( request ) ;
_logRequest ( request ) ;
if ( ! _apiAuth ( request ) ) return ;
if ( request - > method ( ) = = HTTP_PUT ) {
if ( request - > method ( ) = = HTTP_PUT ) {
if ( request - > hasParam ( " status " , true ) ) {
if ( request - > hasParam ( " status " , true ) ) {
AsyncWebParameter * p = request - > getParam ( " status " , true ) ;
AsyncWebParameter * p = request - > getParam ( " status " , true ) ;
relayStatus ( relayID , p - > value ( ) . toInt ( ) = = 1 ) ;
unsigned int value = p - > value ( ) . toInt ( ) ;
if ( value = = 2 ) {
relayToggle ( relayID ) ;
} else {
relayStatus ( relayID , value = = 1 ) ;
}
}
}
}
}
@ -296,7 +382,7 @@ ArRequestHandlerFunction _onRelayStatusWrapper(bool relayID) {
}
}
if ( asJson ) {
if ( asJson ) {
char buffer [ 4 0] ;
char buffer [ 2 0] ;
sprintf ( buffer , " { \" status \" : %d} " , relayStatus ( relayID ) ? 1 : 0 ) ;
sprintf ( buffer , " { \" status \" : %d} " , relayStatus ( relayID ) ? 1 : 0 ) ;
request - > send ( 200 , " application/json " , buffer ) ;
request - > send ( 200 , " application/json " , buffer ) ;
} else {
} else {
@ -310,12 +396,13 @@ ArRequestHandlerFunction _onRelayStatusWrapper(bool relayID) {
void webSetup ( ) {
void webSetup ( ) {
// Setup websocket plugin
// Setup websocket plugin
ws . onEvent ( webSocket Event) ;
ws . onEvent ( _ws Event) ;
server . addHandler ( & ws ) ;
server . addHandler ( & ws ) ;
// Serve home (password protected)
// Serve home (password protected)
server . on ( " / " , HTTP_GET , _onHome ) ;
server . on ( " / " , HTTP_GET , _onHome ) ;
server . on ( " /index.html " , HTTP_GET , _onHome ) ;
server . on ( " /index.html " , HTTP_GET , _onHome ) ;
server . on ( " /auth " , HTTP_GET , _onAuth ) ;
// API entry points (non protected)
// API entry points (non protected)
server . on ( " /relay/on " , HTTP_GET , _onRelayOn ) ;
server . on ( " /relay/on " , HTTP_GET , _onRelayOn ) ;