Skip to content

Commit

Permalink
[WebUI] Added Basic Authentication (#1618)
Browse files Browse the repository at this point in the history
* [WebUI] Added Basic Authentication

* Make the security option a MACRO to avoid typos in future updates
  • Loading branch information
NorthernMan54 authored Apr 27, 2023
1 parent abeef87 commit acc0d91
Show file tree
Hide file tree
Showing 5 changed files with 148 additions and 29 deletions.
3 changes: 2 additions & 1 deletion docs/.vuepress/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,8 @@ module.exports = {
'use/actuators',
'use/boards',
'use/displays',
'use/gateway'
'use/gateway',
'use/webui'
]
},
{
Expand Down
49 changes: 49 additions & 0 deletions docs/use/webui.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# WebUI

For ESP32 based environments a WebUI is available to provide basic configuration and monitoring of your OpenMQTTGateway Device. Functions included are:

* Configuration
* Information
* Firmware Upgrade
* Console
* Resart

# Login Authentication

By default access to the WebUI uses basic authentication to control access to your OpenMQTTGateway Device. The login is `admin` and the password is your ota_password.

# Configuration Options

## Wifi

Abiltity to change the SSID and password for your Wifi, if the change is unsuccessful it will revert back to the previous wifi settings.

## MQTT

Abiltity to change the mqtt settings, if the change is unsuccessful it will revert back to the previous mqtt settings.

## WebUI

Ability to change the display of sensor to Metric or Imperial, and disable the WebUI Authentication

## Logging

Ability to temporarily change the logging level.

# Information

Details of OpenMQTTGateway Device status

# Firmware Upgrade

Ability to upgrade firmware by URL or to latest version.

# Console

Ability to view messages from the OpenMQTTGateway console. The scope of messages visible in the UI is limited to just the OpenMQTTGatewy codebase, messages from the ESP hardware or other libraries are not visible,

Ability to inject commands to OpenMQTTGateway for processing. The commands accepted are of the form mqtt topic then json message. And as you are already on the target device, you do not need to include the device name ie

`commands/MQTTtoSYS/config {"cmd":"restart"}`

This works for all modules in your environment.
102 changes: 78 additions & 24 deletions main/ZwebUI.ino
Original file line number Diff line number Diff line change
Expand Up @@ -45,22 +45,6 @@ QueueHandle_t webUIQueue;

WebServer server(80);

boolean displayMetric = DISPLAY_METRIC;

/*------------------- Local functions ----------------------*/

void notFound();
void handleRoot(); // "/"

void handleCS(); // Console
void handleCN(); // Configuration

void handleIN(); // Information

void handleRT(); // Restart
void handleCL(); // Configure Cloud
void handleTK(); // Return Cloud token

/*------------------- External functions ----------------------*/

esp_err_t nvs_flash_erase(void);
Expand All @@ -81,7 +65,11 @@ const uint16_t TOPSZ = 151; // Max number of characters in topic string
uint8_t masterlog_level; // Master log level used to override set log level
bool reset_web_log_flag = false; // Reset web console log

# ifdef ESP32
const char* www_username = WEBUI_LOGIN;
String authFailResponse = "Authentication Failed";
bool webUISecure = WEBUI_AUTH;
boolean displayMetric = DISPLAY_METRIC;

/*********************************************************************************************\
* ESP32 AutoMutex
\*********************************************************************************************/
Expand Down Expand Up @@ -241,8 +229,6 @@ String HtmlEscape(const String unescaped) {
return result;
}

# endif // ESP32

void AddLogData(uint32_t loglevel, const char* log_data, const char* log_data_payload = nullptr, const char* log_data_retained = nullptr) {
// Store log_data in buffer
// To lower heap usage log_data_payload may contain the payload data from MqttPublishPayload()
Expand Down Expand Up @@ -414,6 +400,7 @@ bool exists(String path) {
*/
void handleRoot() {
WEBUI_TRACE_LOG(F("handleRoot: uri: %s, args: %d, method: %d" CR), server.uri(), server.args(), server.method());
WEBUI_SECURE
if (server.args()) {
for (uint8_t i = 0; i < server.args(); i++) {
WEBUI_TRACE_LOG(F("Arg: %d, %s=%s" CR), i, server.argName(i).c_str(), server.arg(i).c_str());
Expand Down Expand Up @@ -476,6 +463,7 @@ void handleRoot() {
*
*/
void handleCN() {
WEBUI_SECURE
WEBUI_TRACE_LOG(F("handleCN: uri: %s, args: %d, method: %d" CR), server.uri(), server.args(), server.method());
if (server.args()) {
for (uint8_t i = 0; i < server.args(); i++) {
Expand All @@ -499,6 +487,54 @@ void handleCN() {
}
}

/**
* @brief /WU - Configuration Page
* T: handleWU: uri: /wu, args: 3, method: 1
* T: handleWU Arg: 0, dm=on - displayMetric
* T: handleWU Arg: 1, sw=on - webUISecure
* T: handleWU Arg: 2, save=
*/
void handleWU() {
WEBUI_TRACE_LOG(F("handleWU: uri: %s, args: %d, method: %d" CR), server.uri(), server.args(), server.method());
WEBUI_SECURE
if (server.args()) {
for (uint8_t i = 0; i < server.args(); i++) {
WEBUI_TRACE_LOG(F("handleWU Arg: %d, %s=%s" CR), i, server.argName(i).c_str(), server.arg(i).c_str());
}
bool update = false;

if (displayMetric != server.hasArg("dm")) {
update = true;
}
displayMetric = server.hasArg("dm");

if (webUISecure != server.hasArg("sw")) {
update = true;
}
webUISecure = server.hasArg("sw");

if (server.hasArg("save") && update) {
WebUIConfig_save();
}
}

char jsonChar[100];
serializeJson(modules, jsonChar, measureJson(modules) + 1);

char buffer[WEB_TEMPLATE_BUFFER_MAX_SIZE];

snprintf(buffer, WEB_TEMPLATE_BUFFER_MAX_SIZE, header_html, (String(gateway_name) + " - Configure WebUI").c_str());
String response = String(buffer);
response += String(script);
response += String(style);
int logLevel = Log.getLevel();
snprintf(buffer, WEB_TEMPLATE_BUFFER_MAX_SIZE, config_webui_body, jsonChar, gateway_name, (displayMetric ? "checked" : ""), (webUISecure ? "checked" : ""));
response += String(buffer);
snprintf(buffer, WEB_TEMPLATE_BUFFER_MAX_SIZE, footer, OMG_VERSION);
response += String(buffer);
server.send(200, "text/html", response);
}

/**
* @brief /WI - Configure WiFi Page
* T: handleWI: uri: /wi, args: 4, method: 1
Expand All @@ -508,6 +544,7 @@ void handleCN() {
*/
void handleWI() {
WEBUI_TRACE_LOG(F("handleWI: uri: %s, args: %d, method: %d" CR), server.uri(), server.args(), server.method());
WEBUI_SECURE
String WiFiScan = "";
if (server.args()) {
for (uint8_t i = 0; i < server.args(); i++) {
Expand Down Expand Up @@ -670,6 +707,7 @@ void handleWI() {
*/
void handleMQ() {
WEBUI_TRACE_LOG(F("handleMQ: uri: %s, args: %d, method: %d" CR), server.uri(), server.args(), server.method());
WEBUI_SECURE
if (server.args()) {
for (uint8_t i = 0; i < server.args(); i++) {
WEBUI_TRACE_LOG(F("handleMQ Arg: %d, %s=%s" CR), i, server.argName(i).c_str(), server.arg(i).c_str());
Expand Down Expand Up @@ -708,10 +746,10 @@ void handleMQ() {
}

// SC - Secure Connection argument is only present when true
WEBtoSYS["mqtt_secure"] = server.hasArg("sc");
if (mqtt_secure != server.hasArg("sc")) {
update = true;
}
WEBtoSYS["mqtt_secure"] = server.hasArg("sc");

if (!update) {
Log.warning(F("[WebUI] clearing" CR));
Expand Down Expand Up @@ -792,6 +830,7 @@ void handleMQ() {
*/
void handleLO() {
WEBUI_TRACE_LOG(F("handleLO: uri: %s, args: %d, method: %d" CR), server.uri(), server.args(), server.method());
WEBUI_SECURE
if (server.args()) {
for (uint8_t i = 0; i < server.args(); i++) {
WEBUI_TRACE_LOG(F("handleLO Arg: %d, %s=%s" CR), i, server.argName(i).c_str(), server.arg(i).c_str());
Expand Down Expand Up @@ -825,6 +864,7 @@ void handleLO() {
*/
void handleRT() {
WEBUI_TRACE_LOG(F("handleRT: uri: %s, args: %d, method: %d" CR), server.uri(), server.args(), server.method());
WEBUI_SECURE
if (server.args()) {
for (uint8_t i = 0; i < server.args(); i++) {
WEBUI_TRACE_LOG(F("handleRT Arg: %d, %s=%s" CR), i, server.argName(i).c_str(), server.arg(i).c_str());
Expand Down Expand Up @@ -862,6 +902,7 @@ void handleRT() {
*/
void handleCL() {
WEBUI_TRACE_LOG(F("handleCL: uri: %s, args: %d, method: %d" CR), server.uri(), server.args(), server.method());
WEBUI_SECURE
if (server.args()) {
for (uint8_t i = 0; i < server.args(); i++) {
WEBUI_TRACE_LOG(F("handleCL Arg: %d, %s=%s" CR), i, server.argName(i).c_str(), server.arg(i).c_str());
Expand Down Expand Up @@ -915,6 +956,7 @@ void handleCL() {
*/
void handleTK() {
WEBUI_TRACE_LOG(F("handleTK: uri: %s, args: %d, method: %d" CR), server.uri(), server.args(), server.method());
WEBUI_SECURE
if (server.args()) {
for (uint8_t i = 0; i < server.args(); i++) {
WEBUI_TRACE_LOG(F("handleTK Arg: %d, %s=%s" CR), i, server.argName(i).c_str(), server.arg(i).c_str());
Expand Down Expand Up @@ -957,6 +999,7 @@ void handleTK() {
*/
void handleIN() {
WEBUI_TRACE_LOG(F("handleCN: uri: %s, args: %d, method: %d" CR), server.uri(), server.args(), server.method());
WEBUI_SECURE
if (server.args()) {
for (uint8_t i = 0; i < server.args(); i++) {
WEBUI_TRACE_LOG(F("handleIN Arg: %d, %s=%s" CR), i, server.argName(i).c_str(), server.arg(i).c_str());
Expand Down Expand Up @@ -1026,6 +1069,7 @@ void handleIN() {
*/
void handleUP() {
WEBUI_TRACE_LOG(F("handleUP: uri: %s, args: %d, method: %d" CR), server.uri(), server.args(), server.method());
WEBUI_SECURE
if (server.args()) {
for (uint8_t i = 0; i < server.args(); i++) {
WEBUI_TRACE_LOG(F("handleUP Arg: %d, %s=%s" CR), i, server.argName(i).c_str(), server.arg(i).c_str());
Expand Down Expand Up @@ -1110,6 +1154,7 @@ void sendRestartPage() {
*/
void handleCS() {
WEBUI_TRACE_LOG(F("handleCS: uri: %s, args: %d, method: %d" CR), server.uri(), server.args(), server.method());
WEBUI_SECURE
if (server.args() && server.hasArg("c2")) {
for (uint8_t i = 0; i < server.args(); i++) {
WEBUI_TRACE_LOG(F("handleCS Arg: %d, %s=%s" CR), i, server.argName(i).c_str(), server.arg(i).c_str());
Expand Down Expand Up @@ -1173,6 +1218,7 @@ void handleCS() {
*
*/
void notFound() {
WEBUI_SECURE
# ifdef WEBUI_DEVELOPMENT
String path = server.uri();
if (!exists(path)) {
Expand All @@ -1196,6 +1242,7 @@ void notFound() {
void WebUISetup() {
WEBUI_TRACE_LOG(F("ZwebUI setup start" CR));

WebUIConfig_load();
webUIQueue = xQueueCreate(5, sizeof(webUIQueueMessage*));

# ifdef WEBUI_DEVELOPMENT
Expand All @@ -1222,6 +1269,7 @@ void WebUISetup() {
server.on("/cn", handleCN); // Configuration
server.on("/wi", handleWI); // Configure Wifi
server.on("/mq", handleMQ); // Configure MQTT
server.on("/wu", handleWU); // Configure WebUI
# if defined(ZgatewayCloud)
server.on("/cl", handleCL); // Configure Cloud
server.on("/tk", handleTK); // Store Device Token
Expand All @@ -1233,6 +1281,8 @@ void WebUISetup() {

Log.begin(LOG_LEVEL, &WebLog);

Log.trace(F("[WebUI] displayMetric %T" CR), displayMetric);
Log.trace(F("[WebUI] WebUI Secure %T" CR), webUISecure);
Log.notice(F("OpenMQTTGateway URL: http://%s/" CR), WiFi.localIP().toString().c_str());
displayPrint("URL: http://", (char*)WiFi.localIP().toString().c_str());
Log.notice(F("ZwebUI setup done" CR));
Expand Down Expand Up @@ -1306,6 +1356,7 @@ String stateWebUIStatus() {
StaticJsonDocument<JSON_MSG_BUFFER> jsonBuffer;
JsonObject WebUIdata = jsonBuffer.to<JsonObject>();
WebUIdata["displayMetric"] = (bool)displayMetric;
WebUIdata["webUISecure"] = (bool)webUISecure;
WebUIdata["displayQueue"] = uxQueueMessagesWaiting(webUIQueue);
;

Expand All @@ -1321,25 +1372,27 @@ bool WebUIConfig_save() {
StaticJsonDocument<JSON_MSG_BUFFER> jsonBuffer;
JsonObject jo = jsonBuffer.to<JsonObject>();
jo["displayMetric"] = (bool)displayMetric;
jo["webUISecure"] = (bool)webUISecure;
// Save config into NVS (non-volatile storage)
String conf = "";
serializeJson(jsonBuffer, conf);
preferences.begin(Gateway_Short_Name, false);
preferences.putString("WebUIConfig", conf);
int result = preferences.putString("WebUIConfig", conf);
preferences.end();
Log.trace(F("[WebUI] WebUIConfig_save: %s, result: %d" CR), conf.c_str(), result);
return true;
}

void WebUIConfig_init() {
boolean displayMetric = DISPLAY_METRIC;
displayMetric = DISPLAY_METRIC;
webUISecure = WEBUI_AUTH;
Log.notice(F("WebUI config initialised" CR));
}

bool WebUIConfig_load() {
StaticJsonDocument<JSON_MSG_BUFFER> jsonBuffer;
preferences.begin(Gateway_Short_Name, true);
String exists = preferences.getString("WebUIConfig", "{}");
if (exists != "{}") {
if (preferences.isKey("WebUIConfig")) {
auto error = deserializeJson(jsonBuffer, preferences.getString("WebUIConfig", "{}"));
preferences.end();
if (error) {
Expand All @@ -1352,6 +1405,7 @@ bool WebUIConfig_load() {
}
JsonObject jo = jsonBuffer.as<JsonObject>();
displayMetric = jo["displayMetric"].as<bool>();
webUISecure = jo["webUISecure"].as<bool>();
return true;
} else {
preferences.end();
Expand Down
8 changes: 4 additions & 4 deletions main/config_WebContent.h
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,8 @@
#else
# define configure_3
#endif
#define configure_4 "<p><form action='lo' method='get'><button>Configure Logging</button></form></p>"
#define configure_5
#define configure_4 "<p><form action='wu' method='get'><button>Configure WebUI</button></form></p>"
#define configure_5 "<p><form action='lo' method='get'><button>Configure Logging</button></form></p>"
#define configure_6
#define configure_7
#define configure_8
Expand Down Expand Up @@ -83,8 +83,6 @@ const char console_body[] = body_header "<br><textarea readonly id='t1' cols='34

const char information_body[] = body_header "<style>td {padding: 0px 5px;}</style><div id='i' name='i'></div>" body_footer_main_menu;

// <fieldset class=\"set1\"><legend><span><b>&nbsp;Upgrade by file upload&nbsp;</b></span></legend><form method='post' action='up' enctype='multipart/form-data'><br><input type='file' name='u2'><br><br><button type='submit' onclick='eb(\"f1\").style.display=\"none\";eb(\"f2\").style.display=\"block\";this.form.submit();'>Start upgrade</button></form></fieldset>

const char upgrade_body[] = body_header "<div id='f1' style='display:block;'><fieldset class=\"set1\"><legend><span><b>Upgrade by Web Server</b></span></legend><form method='get' action='up'><br><b>OTA URL</b><br><input id='o' placeholder=\"OTA_URL\" value=\"%s\"><br><br><button type='submit' class='button bgrn'>Start upgrade</button></form></fieldset><br><br><fieldset class=\"set1\"><legend><span><b>Upgrade to Level</b></span></legend><form method='get' action='up'><p><b>Level</b><br><select id='le'><option value='1'>Latest Release</option><option value='2'>Development</option></select></p><br><button type='submit' class='button bgrn'>Start upgrade</button></form></fieldset></div><div id='f2' style='display:none;text-align:center;'><b>Upload started ...</b></div><div id=but2d style=\"display: block;\"></div><p>" body_footer_main_menu;

const char config_wifi_body[] = body_header "%s<br><div><a href='/wi?scan='><b>Scan for all WiFi Networks</b></a></div><br><fieldset class=\"set1\"><legend><span><b>WiFi Parameters</b></span></legend><form method='get' action='wi'><p><b>WiFi Network</b> () <br><input id='s1' placeholder=\"Type or Select your WiFi Network\" value=\"%s\"></p><p><label><b>WiFi Password</b><input type='checkbox' onclick='sp(\"p1\")'></label><br><input id='p1' type='password' placeholder=\"Enter your WiFi Password\" value=\"%s\"></p><br><button name='save' type='submit' class='button bgrn'>Save</button></form></fieldset>" body_footer_config_menu;
Expand All @@ -95,6 +93,8 @@ const char config_mqtt_body[] = body_header "<fieldset class=\"set1\"><legend><s

const char config_logging_body[] = body_header "<fieldset class=\"set1\"><legend><span><b>OpenMQTTGateway Logging</b></span></legend><form method='get' action='lo'><p><b>Log Level</b><br><select id='lo'><option %s value='0'>Silent</option><option %s value='1'>Fatal</option><option %s value='2'>Error</option><option %s value='3'>Warning</option><option %s value='4'>Notice</option><option %s value='5'>Trace</option><option %s value='6'>Verbose</option></select></p><br><button name='save' type='submit' class='button bgrn'>Save</button></form></fieldset>" body_footer_config_menu;

const char config_webui_body[] = body_header "<fieldset class=\"set1\"><legend><span><b>Configure WebUI</b></span></legend><form method='get' action='wu'><p><b>Display Metric</b><br><input id='dm' type='checkbox' %s></p><p><b>Secure WebUI</b><br><input id='sw' type='checkbox' %s></p><br><button name='save' type='submit' class='button bgrn'>Save</button></form></fieldset>" body_footer_config_menu;

const char footer[] = "<div style='text-align:right;font-size:11px;'><hr/><a href='https://community.openmqttgateway.com' target='_blank' style='color:#aaa;'>%s</a></div></div></body></html>";

#endif
15 changes: 15 additions & 0 deletions main/config_WebUI.h
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,23 @@
# define WEBUI_TRACE_LOG(...)
#endif

#ifndef WEBUI_AUTH
# define WEBUI_AUTH true // Default to WebUI authentication
#endif

#ifndef WEBUI_LOGIN
# define WEBUI_LOGIN "admin"
#endif

/*------------------- End of Compiler Directives ----------------------*/

#define WEBUI_SECURE \
if (webUISecure) { \
if (!server.authenticate(www_username, ota_pass)) { \
return server.requestAuthentication(DIGEST_AUTH, gateway_name, authFailResponse); \
} \
}

#define MAX_WIFI_NETWORKS_TO_SHOW 10

/*
Expand Down

0 comments on commit acc0d91

Please sign in to comment.