From b2388b6fe73d986e614172946ea565ab66997e9b Mon Sep 17 00:00:00 2001 From: Nikolay Vasilchuk Date: Sun, 13 Oct 2019 15:27:44 +0300 Subject: [PATCH] Basic Auth for web_server component (#674) * Basic auth * Test * Linter fix * Make username/password strict strings Reason: passwords only consisting of digits (012345) will be silently converted (to "12345") Co-authored-by: Otto Winter --- esphome/components/web_server/__init__.py | 11 ++++++++++- esphome/components/web_server/web_server.cpp | 7 +++++++ esphome/components/web_server/web_server.h | 9 +++++++++ esphome/const.py | 1 + tests/test2.yaml | 3 +++ 5 files changed, 30 insertions(+), 1 deletion(-) diff --git a/esphome/components/web_server/__init__.py b/esphome/components/web_server/__init__.py index ea7b179d1e..04f3cc5c04 100644 --- a/esphome/components/web_server/__init__.py +++ b/esphome/components/web_server/__init__.py @@ -2,7 +2,9 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import web_server_base from esphome.components.web_server_base import CONF_WEB_SERVER_BASE_ID -from esphome.const import CONF_CSS_URL, CONF_ID, CONF_JS_URL, CONF_PORT +from esphome.const import ( + CONF_CSS_URL, CONF_ID, CONF_JS_URL, CONF_PORT, + CONF_AUTH, CONF_USERNAME, CONF_PASSWORD) from esphome.core import coroutine_with_priority AUTO_LOAD = ['json', 'web_server_base'] @@ -15,6 +17,10 @@ CONFIG_SCHEMA = cv.Schema({ cv.Optional(CONF_PORT, default=80): cv.port, cv.Optional(CONF_CSS_URL, default="https://esphome.io/_static/webserver-v1.min.css"): cv.string, cv.Optional(CONF_JS_URL, default="https://esphome.io/_static/webserver-v1.min.js"): cv.string, + cv.Optional(CONF_AUTH): cv.Schema({ + cv.Required(CONF_USERNAME): cv.string_strict, + cv.Required(CONF_PASSWORD): cv.string_strict, + }), cv.GenerateID(CONF_WEB_SERVER_BASE_ID): cv.use_id(web_server_base.WebServerBase), }).extend(cv.COMPONENT_SCHEMA) @@ -30,3 +36,6 @@ def to_code(config): cg.add(paren.set_port(config[CONF_PORT])) cg.add(var.set_css_url(config[CONF_CSS_URL])) cg.add(var.set_js_url(config[CONF_JS_URL])) + if CONF_AUTH in config: + cg.add(var.set_username(config[CONF_AUTH][CONF_USERNAME])) + cg.add(var.set_password(config[CONF_AUTH][CONF_PASSWORD])) diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index 3c244e9666..b51ad2cf51 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -119,6 +119,9 @@ void WebServer::setup() { void WebServer::dump_config() { ESP_LOGCONFIG(TAG, "Web Server:"); ESP_LOGCONFIG(TAG, " Address: %s:%u", network_get_address().c_str(), this->base_->get_port()); + if (this->using_auth()) { + ESP_LOGCONFIG(TAG, " Basic authentication enabled"); + } } float WebServer::get_setup_priority() const { return setup_priority::WIFI - 1.0f; } @@ -490,6 +493,10 @@ bool WebServer::canHandle(AsyncWebServerRequest *request) { return false; } void WebServer::handleRequest(AsyncWebServerRequest *request) { + if (this->using_auth() && !request->authenticate(this->username_, this->password_)) { + return request->requestAuthentication(); + } + if (request->url() == "/") { this->handle_index_request(request); return; diff --git a/esphome/components/web_server/web_server.h b/esphome/components/web_server/web_server.h index 7ecfbb3f3d..4dca8200cc 100644 --- a/esphome/components/web_server/web_server.h +++ b/esphome/components/web_server/web_server.h @@ -29,6 +29,11 @@ struct UrlMatch { class WebServer : public Controller, public Component, public AsyncWebHandler { public: WebServer(web_server_base::WebServerBase *base) : base_(base) {} + + void set_username(const char *username) { username_ = username; } + + void set_password(const char *password) { password_ = password; } + /** Set the URL to the CSS that's sent to each client. Defaults to * https://esphome.io/_static/webserver-v1.min.css * @@ -56,6 +61,8 @@ class WebServer : public Controller, public Component, public AsyncWebHandler { /// Handle an index request under '/'. void handle_index_request(AsyncWebServerRequest *request); + bool using_auth() { return username_ != nullptr && password_ != nullptr; } + #ifdef USE_SENSOR void on_sensor_update(sensor::Sensor *obj, float state) override; /// Handle a sensor request under '/sensor/'. @@ -125,6 +132,8 @@ class WebServer : public Controller, public Component, public AsyncWebHandler { protected: web_server_base::WebServerBase *base_; AsyncEventSource events_{"/events"}; + const char *username_{nullptr}; + const char *password_{nullptr}; const char *css_url_{nullptr}; const char *js_url_{nullptr}; }; diff --git a/esphome/const.py b/esphome/const.py index 8ea20773bc..463c82bbc6 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -38,6 +38,7 @@ CONF_ARGS = 'args' CONF_ASSUMED_STATE = 'assumed_state' CONF_AT = 'at' CONF_ATTENUATION = 'attenuation' +CONF_AUTH = 'auth' CONF_AUTOMATION_ID = 'automation_id' CONF_AVAILABILITY = 'availability' CONF_AWAY = 'away' diff --git a/tests/test2.yaml b/tests/test2.yaml index fc72655056..b0dfc27e96 100644 --- a/tests/test2.yaml +++ b/tests/test2.yaml @@ -45,6 +45,9 @@ logger: level: DEBUG web_server: + auth: + username: admin + password: admin deep_sleep: run_duration: 20s