Skip to content

Suds-Lab/Home-Assistant-Frontend

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

118 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Control Center: a simple Home Assistant companion app

A small web app with a basic login that shows each person only their own lights and air conditioning, and lets them control them. It talks to your Home Assistant instance through a small Python backend that keeps your Home Assistant token private (the browser never sees it).

This repo is a Home Assistant add-on repository; the add-on lives in the control_center/ subfolder:

repository.yaml      Marks this repo as an HA add-on repository
control_center/             The add-on
  config.yaml          Add-on manifest
  Dockerfile           Builds the add-on image
  app.py               Flask backend (login + talks to Home Assistant)
  requirements.txt     Python dependencies
  users.json           Seed accounts + which entities each may control
  static/              The web UI (React vendored locally, no build step)

The frontend is React, vendored locally and transformed in the browser by Babel, so there's no npm and no build step.

Install it (add-on repository)

On Home Assistant OS / Supervised:

  1. Settings → Add-ons → Add-on Store → ⋮ (top-right) → Repositories.
  2. Paste https://github.com/Suds-Lab/Home-Assistant-Frontend and Add.
  3. The Control Center add-on appears in the store. Open it → Install.
  4. Configuration tab: optionally set jwt_secret (leave it blank to auto-generate one). Start the add-on. (Users are not configured here; see step 5.)
  5. Open Control Center in the sidebar. A default admin alice / changeme is created on first run; log in, change its password, and add everyone else from the Manage users screen. Point household members at http://<your-home-assistant>:8099 for their dashboard.

Updates then arrive as a normal Update button when you push new commits.

1. One-time setup

a) Create a Home Assistant token

  1. Open Home Assistant in your browser.
  2. Click your profile (bottom-left), open the Security tab.
  3. Under Long-lived access tokens, click Create Token, name it (e.g. "Control Center App"), and copy the token.

b) Configure the backend

Copy .env.example to .env and fill in:

  • HA_URL: your Home Assistant address, e.g. http://homeassistant.local:8123 or http://192.168.1.50:8123 (no trailing slash).
  • HA_TOKEN: the token you just created.
  • JWT_SECRET: optional; leave blank to auto-generate and persist a random one.

c) Set up users and their devices

The easiest way is the built-in Manage users screen (see below), but the first admin account has to be seeded in a file. Edit users.json: each user has a username, password, display name, an optional admin flag, and the list of entity IDs they're allowed to control:

{
  "username": "alice",
  "password": "changeme",
  "displayName": "Alice",
  "admin": true,
  "entities": ["light.bedroom", "climate.bedroom_ac"]
}

"admin": true lets that user open the Manage users screen. Entity IDs are found in HA under Settings → Devices & Services → Entities or Developer Tools → States.

Supported device types

Assign any entity to a user; the dashboard groups devices by type and shows controls tailored to each:

Type Controls
light on/off toggle + brightness slider
switch, input_boolean on/off toggle
fan on/off + speed slider
climate mode buttons + target-temperature stepper
cover open / stop / close + position slider
lock lock / unlock
media_player prev / play-pause / next + volume
scene, script activate / run
automation trigger + enable toggle
button, input_button press
vacuum start / pause / dock
anything else (e.g. sensor) read-only state display

Every card has an button that opens a detail panel showing the entity's full state, last-changed time, and every attribute.

Control is enforced server-side: the backend only calls a fixed allow-list of services (ALLOWED_SERVICES in app.py), always scoped to the entity's own domain and only for entities the user owns.

Two interfaces (two ports)

The app serves two different experiences, decided by which port a request arrives on:

Management User dashboard
Who You, the admin/owner Each household member
Where HA sidebar tab (Ingress, port 4000) http://<home-assistant>:8099
Auth HA's own login (admin-only, like Terminal) the app account you created
Does add/edit/delete users, assign devices control only their devices

The management port (4000) is never published; it's reachable only through Home Assistant's Ingress, so a request there is trusted as an authenticated HA admin (no separate password). The user port (8099) is published for household members and is protected by the app's per-user login; the admin endpoints are not available on it at all.

Managing users (no file editing)

Open the Control Center tab in the HA sidebar, which goes straight to the management screen. There you add, edit, and delete users and tick each person's devices from a searchable, grouped list of all your real Home Assistant entities (no typing entity IDs). Accounts are saved to a persistent store (/data/users.json in the add-on, users.json standalone) and survive restarts. Passwords left blank on edit are kept, and the system won't let you remove the last admin.

users.json is only the initial seed (it bootstraps a default admin on first run). Once the store exists, the Manage users screen is the single source of truth; users are not set in the add-on's Configuration tab.

Sign-in with Google / OAuth

The user dashboard can authenticate household members with Google (or any OpenID Connect provider) instead of, or alongside, a local password. The OAuth credentials live in the add-on Configuration; you then choose the sign-in methods in Settings → Sign-in methods (Local / Google / Both).

The dashboard must be reachable over HTTPS at a public URL (e.g. a reverse proxy or Cloudflare Tunnel). Your redirect URI is that URL plus /api/oauth/callback, e.g. https://home.example.com/api/oauth/callback.

1. Create Google credentials in the Google Cloud Console:

  • APIs & Services → OAuth consent screen: pick Internal (Workspace) or External, set the app name + support email, add the scopes openid, .../auth/userinfo.email, .../auth/userinfo.profile. For External, add test users or Publish.
  • Credentials → Create credentials → OAuth client ID → Web application: under Authorised redirect URIs add your redirect URI exactly (the provider only redirects to URLs you register here). Copy the Client ID and secret.

2. Configure the add-on (Configuration tab):

oauth_client_id: "1234….apps.googleusercontent.com"
oauth_client_secret: "GOCSPX-…"
oauth_redirect_url: "https://home.example.com"   # public base URL (no trailing slash needed)
oauth_allowed_domains: ["my.domain"]             # restrict to these domains (optional)

oauth_allowed_domains restricts sign-in to those email domains (only *@my.domain). For personal Gmail (or anyone without a domain of their own), list the individual addresses instead: oauth_allowed_emails: ["alice@gmail.com", "bob@gmail.com"]. If you set neither, sign-in is refused (fail-closed). Set oauth_allow_any: true only if you deliberately want anyone with a verified email to sign in (they still land on the onboarding screen with no devices until an admin assigns some). Restart the add-on, then enable Google or Both in Settings → Sign-in methods. (For non-Google providers, oauth_logo_url sets the button's logo.)

Behind Cloudflare Tunnel / a reverse proxy: point the tunnel's public hostname at the add-on's user-dashboard port 8099; the /api/oauth/* routes ride along on that same app. The add-on builds the redirect from oauth_redirect_url (not the proxied request), so Google sees the real HTTPS URL even though Cloudflare reaches the add-on over plain HTTP. If you also run Cloudflare Access on that hostname, drop it here or bypass /api/oauth/*, or its login page will intercept the callback.

First sign-in & onboarding: a Google user who signs in for the first time is auto-created with no devices and sees a "reach out to your administrator" screen until an admin assigns them devices in Manage users. Only users with at least one device see the dashboard. (You can also pre-create a user whose username is their email and assign devices first; an OAuth login for that email then adopts that account, so the same person can use a password or OAuth.)

Other providers: override oauth_authorize_url, oauth_token_url, oauth_userinfo_url, oauth_scopes and oauth_provider_name for any OIDC provider. The provider must return a verified email (email_verified: true). (The full walkthrough is also in the add-on's Documentation tab / control_center/DOCS.md.)

2. Run it (standalone / dev)

From the repo root:

python -m venv .venv
.\.venv\Scripts\Activate.ps1
pip install -r control_center/requirements.txt
copy control_center\.env.example .env   # then fill in HA_URL + HA_TOKEN
python control_center/app.py

This serves both ports from one process:

The management port (4000) binds to 127.0.0.1 by default, so the no-login admin screen isn't exposed on your LAN. To reach it from another machine, set INGRESS_BIND=0.0.0.0, but firewall it, since anything that reaches port 4000 is trusted as admin.

3. Install locally without the repository (alternative)

Instead of adding the repository URL (see Install it above), you can drop the add-on onto the machine directly:

  1. Copy the control_center/ folder into /addons/control_center/ on your HA machine. Easiest ways to get files there: the Samba share, Studio Code Server, or Advanced SSH & Web Terminal add-ons.
  2. In Home Assistant go to Settings → Add-ons → Add-on Store, open the menu (top-right) → Check for updates. "Control Center" appears under Local add-ons.
  3. Click it → Install (this builds the image; takes a few minutes).
  4. Open the Configuration tab (optionally set jwt_secret; blank auto-generates one). Start the add-on.
  5. Thanks to Ingress, Control Center appears as its own admin tab in the Home Assistant sidebar (like the Terminal / Mosquitto add-ons); click it to open the management screen right inside HA. No separate password: HA already authenticated you as an admin.
  6. A default admin (alice / changeme) is seeded on first run. Use Manage users to change it and add household members + their devices.
  7. Tell each member to open http://<your-home-assistant>:8099 and log in with the account you created; that's their personal device dashboard. (Make sure port 8099 is enabled in the add-on's Network section.)

The add-on also gets a proper Documentation tab (from DOCS.md) and an icon/logo in the store and sidebar (icon.png / logo.png).

Notes:

  • Requires an install with the Supervisor (HA OS or Supervised). Plain Docker (HA Container) or pip (Core) installs don't have add-ons; use the standalone run above and a panel_iframe: sidebar link instead.
  • homeassistant_api: true in config.yaml grants the proxied API access; the app talks to http://supervisor/core/api with the injected SUPERVISOR_TOKEN.
  • HA's own login gates the page (Ingress); the app's per-user login then scopes each person to their own entities.

Installable app (PWA)

The user dashboard is a Progressive Web App. On a first visit it shows an Install banner; installing adds a "Control Center" icon to the phone's home screen and runs full-screen with no browser chrome; it feels like a native app, and the app shell is cached so it loads instantly (and offline).

  • Android / desktop Chrome: tap Install in the banner (or the browser's install button).
  • iOS Safari: the banner says to use Share → Add to Home Screen.
  • Taps give subtle haptic feedback on phones that support it (iOS and Android).
  • Every screen has a theme toggle (top corner) for System / Light / Dark, remembered per device.
  • ⚠️ Installing (and the service worker / offline cache) needs a secure context, i.e. https:// or localhost. Over plain http://<ip>:8099 on a LAN, Android won't offer install and offline won't work, though iOS "Add to Home Screen" still gives a full-screen shortcut. To get the full experience, serve it over HTTPS (e.g. a reverse proxy, or Home Assistant's own TLS / Nabu Casa in front of it).

How it works / security notes

  • Real-time updates. The backend holds one WebSocket to Home Assistant and relays state changes to each browser over a WebSocket (/api/ws), so the UI reflects any change (the app, a physical switch, an automation) within a fraction of a second, with no polling. The browser reconnects and re-syncs over REST automatically, so it can't go stale; if the link drops it shows a "Connection lost. Reconnecting…" notice with a Retry now button. When a new version is deployed, open dashboards reload themselves to pick it up.
    • Behind a reverse proxy, allow the WebSocket upgrade on /api/ws (nginx: proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection upgrade;). Cloudflare passes WebSockets through automatically. If you can't proxy WebSockets, set STREAM=0 to fall back to polling.
  • The browser only ever talks to this backend. Your Home Assistant token lives only in .env (standalone) or never exists at all (add-on, via Supervisor).
  • Login issues a 7-day session token (JWT). Each request is checked against the user's allowed entity list, so users can't control devices that aren't theirs.
  • Passwords are stored hashed (PBKDF2-HMAC-SHA256 with a per-password salt); the store never holds plaintext. Any legacy plaintext from an older version is upgraded to a hash automatically (on upgrade and on first login). Keep users.json private regardless, and put the dashboard behind HTTPS for any internet exposure.
  • Login is rate-limited per username to slow brute-force attempts (it ignores the client X-Forwarded-For, which can be spoofed), with constant-time password checks that don't reveal whether a username exists.
  • The session-signing secret (jwt_secret) is auto-generated and persisted on first run when you don't set one, so sessions are never signed with a guessable default. Rotate it anytime from Settings → Session security (which signs everyone out).
  • Device IDs and time ranges are validated before any call to Home Assistant, so a logged-in user can't smuggle a path into the HA API.
  • Responses send X-Content-Type-Options: nosniff. Set block_iframe_embedding: true to also send X-Frame-Options: DENY on the user dashboard (anti-clickjacking); it's off by default so it never breaks a panel_iframe embed, and the management UI is always exempt.

About

Create & Manage users and entities without exposing ur whole Home Assistant instance

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors