The Problem
Managing physical door access for multiple people means either issuing keys or handing out credentials to the building's core system. DoorOpener replaces that with a single web page where each user enters a personal PIN to trigger a door strike, and the building owner controls who has access without touching the physical lock.
What This Does
DoorOpener is a Flask web app that presents a glass-morphism keypad (templates/index.html) and an admin panel (templates/admin.html) for managing users. Each user has a stored PIN; entering it calls Home Assistant to toggle a switch, lock, or input_boolean entity. The app also supports OIDC SSO, rate limiting, IP blocking, and battery monitoring for Zigbee devices.
The core logic lives in app.py (1,493 lines) with user persistence in users_store.py. Configuration comes from config.ini and environment variables via .env.example. Deployment is Docker-first: Dockerfile, docker-compose.yml, and an entrypoint.sh handle the container startup.
How It Is Wired
Execution starts at app.py, which is the hub of the system: 8 modules import it, and it imports only 1, giving it an instability score of 0.11 — it's the stable center everything routes through. users_store.py (Ca 3, Ce 0) is fully stable and owns the user database. The 10 test files all import app or users_store, which is why a change to app.py ripples across the entire test suite.
A request flows: browser → app.py route handler → PIN validation against users_store.py → outbound call to the Home Assistant REST API. The blast radius is concentrated in app.py: it owns routing, authentication, rate limiting, and the Home Assistant integration, so any change touches most of the system's behavior.
The import graph shows no circular dependencies, which keeps the module structure clean. The cost is that app.py is oversized, so navigating a single file requires holding a lot of context.
How To Use It
The README documents Docker Compose as the recommended path:
git clone https://github.com/moses-y/DoorOpener.git && cd DoorOpener
cp config.ini.example config.ini # edit with your HA URL and entity
cp .env.example .env # set FLASK_SECRET_KEY, ports
docker compose up -d
Configuration lives in config.ini (Home Assistant endpoint, entity ID) and .env (port, secret key, session cookie settings). The app serves on port 6532 by default.
Real-World Use
An apartment building with 40 units: the manager creates a PIN per tenant in the admin UI. A tenant opens the web page on their phone, enters their PIN, and the app calls Home Assistant to pulse the door strike for the lobby door. The manager revokes a PIN when a tenant moves out. The battery monitoring catches a failing Zigbee door lock before it strands someone.
Code Health & Issues
Static analysis (measured, not opinion) found 8 findings across 4 kinds, all in app.py, users_store.py, and the test files: deep nesting (max indentation depth 12), the 1,493-line app.py, broad except clauses that swallow errors, and 13 duplicated code blocks across 7 test files.
The security audit adds a critical item: .github/workflows/docker-build.yml references GHCR_PAT, and a fork-triggered workflow holding a publish token is a credential exfiltration vector. High: unpinned GitHub Actions tags in .github/workflows, no LICENSE file, and continue-on-error on a CI correctness step. Medium: no dependency update bot, unpinned Docker base image, no dependency vulnerability scan, persist-credentials not disabled on checkout, and no non-root user in the Dockerfile. Low: no job timeouts in CI.
The Bottom Line
DoorOpener is a functional, self-contained access portal with a clean module structure and real test coverage. The main risks are operational — the CI workflow secret exposure is the one that needs fixing before any public use — and the concentrated logic in app.py will make larger changes slow. Suitable for small-to-medium access control deployments where the team can own the security posture.