Skip to content

Commit

Permalink
dashboard: fix supervisor auth doing I/O in the event loop (esphome#5807
Browse files Browse the repository at this point in the history
)
  • Loading branch information
bdraco authored Nov 28, 2023
1 parent 2f888ff commit ad5f6b5
Showing 1 changed file with 37 additions and 25 deletions.
62 changes: 37 additions & 25 deletions esphome/dashboard/web_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@
import shutil
import subprocess
import threading
from pathlib import Path
from typing import Any, Callable, TypeVar
from collections.abc import Iterable
from pathlib import Path
from typing import TYPE_CHECKING, Any, Callable, TypeVar

import tornado
import tornado.concurrent
Expand Down Expand Up @@ -45,12 +45,17 @@
from .util.subprocess import async_run_system_command
from .util.text import friendly_name_slugify

if TYPE_CHECKING:
from requests import Response


_LOGGER = logging.getLogger(__name__)

ENV_DEV = "ESPHOME_DASHBOARD_DEV"

COOKIE_AUTHENTICATED_YES = b"yes"

cookie_authenticated_yes = b"yes"
AUTH_COOKIE_NAME = "authenticated"


settings = DASHBOARD.settings
Expand Down Expand Up @@ -89,18 +94,18 @@ def decorator(self, *args: Any, **kwargs: Any):
return decorator


def is_authenticated(request_handler):
def is_authenticated(handler: BaseHandler) -> bool:
"""Check if the request is authenticated."""
if settings.on_ha_addon:
# Handle ingress - disable auth on ingress port
# X-HA-Ingress is automatically stripped on the non-ingress server in nginx
header = request_handler.request.headers.get("X-HA-Ingress", "NO")
header = handler.request.headers.get("X-HA-Ingress", "NO")
if str(header) == "YES":
return True

if settings.using_auth:
return (
request_handler.get_secure_cookie("authenticated")
== cookie_authenticated_yes
)
return handler.get_secure_cookie(AUTH_COOKIE_NAME) == COOKIE_AUTHENTICATED_YES

return True


Expand Down Expand Up @@ -860,38 +865,45 @@ def render_login_page(self, error: str | None = None) -> None:
**template_args(),
)

def post_ha_addon_login(self) -> None:
def _make_supervisor_auth_request(self) -> Response:
"""Make a request to the supervisor auth endpoint."""
import requests

headers = {
"X-Supervisor-Token": os.getenv("SUPERVISOR_TOKEN"),
}

headers = {"X-Supervisor-Token": os.getenv("SUPERVISOR_TOKEN")}
data = {
"username": self.get_argument("username", ""),
"password": self.get_argument("password", ""),
}
return requests.post(
"http://supervisor/auth", headers=headers, json=data, timeout=30
)

async def post_ha_addon_login(self) -> None:
loop = asyncio.get_running_loop()
try:
req = requests.post(
"http://supervisor/auth", headers=headers, json=data, timeout=30
)
if req.status_code == 200:
self.set_secure_cookie("authenticated", cookie_authenticated_yes)
self.redirect("/")
return
req = await loop.run_in_executor(None, self._make_supervisor_auth_request)
except Exception as err: # pylint: disable=broad-except
_LOGGER.warning("Error during Hass.io auth request: %s", err)
self.set_status(500)
self.render_login_page(error="Internal server error")
return

if req.status_code == 200:
self._set_authenticated()
self.redirect("/")
return
self.set_status(401)
self.render_login_page(error="Invalid username or password")

def _set_authenticated(self) -> None:
"""Set the authenticated cookie."""
self.set_secure_cookie(AUTH_COOKIE_NAME, COOKIE_AUTHENTICATED_YES)

def post_native_login(self) -> None:
username = self.get_argument("username", "")
password = self.get_argument("password", "")
if settings.check_password(username, password):
self.set_secure_cookie("authenticated", cookie_authenticated_yes)
self._set_authenticated()
self.redirect("./")
return
error_str = (
Expand All @@ -900,17 +912,17 @@ def post_native_login(self) -> None:
self.set_status(401)
self.render_login_page(error=error_str)

def post(self) -> None:
async def post(self):
if settings.using_ha_addon_auth:
self.post_ha_addon_login()
await self.post_ha_addon_login()
else:
self.post_native_login()


class LogoutHandler(BaseHandler):
@authenticated
def get(self) -> None:
self.clear_cookie("authenticated")
self.clear_cookie(AUTH_COOKIE_NAME)
self.redirect("./login")


Expand Down

0 comments on commit ad5f6b5

Please sign in to comment.