diff --git a/esphome/components/web_server/__init__.py b/esphome/components/web_server/__init__.py index ea7b179d1e..8225b3b8af 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, + cv.Required(CONF_PASSWORD): cv.string, + }), 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 fe36d6c2ce..3b3473a384 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -116,6 +116,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; } @@ -487,6 +490,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..ea0b8ab4bf 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,10 @@ 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 +134,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 3811d5b9cf..9e61ef3909 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'