![](./src/static/images/logo.svg) # Lagomare Gates A web-based gate access management and control system. Authorized users can remotely open physical gates via a mobile-friendly PWA. An admin dashboard provides full management of gates, access codes, and users. ## Features - **Keypass authentication** — users authenticate with an access code; each keypass can have a per-gate allowlist, an optional expiration date, and an optional time/day-of-week schedule - **Keypass schedules** — restrict a keypass to specific days of the week and/or a time window (e.g. Monday–Friday 08:00–18:00, server local time) - **Remote gate control** — integrates with [AVConnect](https://www.avconnect.it) and [Shelly Cloud](https://shelly.cloud) to trigger gate macros/relays - **Gate icons** — each gate can be assigned any UTF-8 character or emoji as its icon, displayed on the user app button and keypass selector - **Role-based admin panel** — two roles (`admin`, `manager`) with different permission levels - **Two-factor authentication (TOTP)** — admins can enable app-based 2FA (Google Authenticator, Authy, etc.) on their account - **Access audit log** — every open attempt is logged with timestamp, keypass, gate, IP, and result; filterable and paginated - **Keypass QR codes** — generate a scannable QR code for each keypass; scanning opens the PWA and logs in automatically - **Keypass code options** — choose character set (alphanumeric, alpha, numeric, or a 4-word passphrase) and length when auto-generating codes - **Telegram notifications** — optional push notifications to a Telegram group whenever a gate is opened - **Progressive Web App** — installable on mobile devices with offline caching ## Tech Stack | Layer | Technology | |---|---| | Backend | FastAPI + Uvicorn | | ORM | SQLAlchemy | | Database | SQLite | | Auth | JWT (HS256) + bcrypt | | 2FA | TOTP (RFC 6238) via pyotp | | Credential storage | Fernet symmetric encryption | | Gate integration | AVConnect HTTP API / Shelly Cloud API | | Notifications | Telegram Bot API | | QR generation | qrcode + Pillow | | Frontend | Vanilla JS PWA | ## Project Structure ``` src/ ├── main.py # App entry point, startup, static file serving ├── core/ │ ├── auth.py # JWT creation/verification, password hashing │ ├── config.py # Settings loaded from environment variables │ ├── database.py # SQLAlchemy models and DB initialization │ ├── dependencies.py # FastAPI dependency injection (auth guards) │ └── schemas.py # Pydantic request/response schemas ├── routers/ │ ├── auth.py # POST /api/auth/admin, POST /api/auth/keypass │ ├── gates.py # User-facing gate list and open endpoints │ ├── keypasses.py # Admin keypass CRUD + QR code generation │ ├── admins.py # Admin user management │ ├── credentials.py # AVConnect credential management │ ├── stats.py # Access log / statistics (paginated, filtered) │ └── telegram.py # Telegram notification configuration ├── services/ │ ├── avconnect.py # AVConnect API client │ ├── gates.py # Gate open orchestration | |── shelly.py # Shelly Cloud API client │ └── telegram.py # Telegram Bot API client └── static/ # Frontend PWA (index.html, admin.html, JS, CSS) data/ └── gates.db # SQLite database (auto-created on first run) ``` ## API Endpoints ### Authentication | Method | Endpoint | Description | |---|---|---| | POST | `/api/auth/admin` | Admin login — returns JWT | | POST | `/api/auth/keypass` | Keypass login — returns JWT | ### User (keypass token required) | Method | Endpoint | Description | |---|---|---| | GET | `/api/gates` | List gates accessible to the authenticated keypass | | POST | `/api/gates/{gate_id}/open` | Open a gate | ### Admin — Gates (manager+) | Method | Endpoint | Description | |---|---|---| | GET | `/api/admin/gates` | List all gates | | POST | `/api/admin/gates` | Create a gate *(admin only)* | | PUT | `/api/admin/gates/{gate_id}` | Update a gate *(admin only)* | | DELETE | `/api/admin/gates/{gate_id}` | Delete a gate *(admin only)* | | POST | `/api/admin/gates/{gate_id}/open` | Manually open a gate | ### Admin — Keypasses (manager+) | Method | Endpoint | Description | |---|---|---| | GET | `/api/admin/keypasses` | List all keypasses | | POST | `/api/admin/keypasses` | Create a keypass | | PATCH | `/api/admin/keypasses/{kp_id}` | Update a keypass (description, expiry, gates, schedule) | | DELETE | `/api/admin/keypasses/{kp_id}` | Revoke a keypass | | GET | `/api/admin/keypasses/{kp_id}/qr` | Download QR code PNG for a keypass | ### Admin — Users (admin only) | Method | Endpoint | Description | |---|---|---| | GET | `/api/admin/admins` | List admin users | | POST | `/api/admin/admins` | Create an admin user | | DELETE | `/api/admin/admins/{username}` | Delete an admin user | | PATCH | `/api/admin/admins/{username}/password` | Change password | | POST | `/api/admin/admins/{username}/totp/setup` | Generate a new TOTP secret and return provisioning URI + QR | | POST | `/api/admin/admins/{username}/totp/enable` | Verify a TOTP code and activate 2FA | | DELETE | `/api/admin/admins/{username}/totp` | Disable 2FA and discard the secret | ### Admin — AVConnect Credentials (admin only) | Method | Endpoint | Description | |---|---|---| | GET | `/api/admin/credentials/avconnect` | View stored AVConnect credentials | | PUT | `/api/admin/credentials/avconnect` | Create or update AVConnect credentials | | GET | `/api/admin/credentials/shelly` | View stored Shelly Cloud credentials | | PUT | `/api/admin/credentials/shelly` | Create or update Shelly Cloud credentials | | GET | `/api/admin/credentials/mock` | Get mock mode status | | PUT | `/api/admin/credentials/mock` | Enable or disable mock mode | ### Admin — Statistics (manager+) | Method | Endpoint | Description | |---|---|---| | GET | `/api/admin/stats` | Paginated, filtered access log | Query parameters: `gate_id`, `keypass_code` (partial match), `success` (bool), `date_from`, `date_to`, `page` (default 1), `page_size` (default 50, max 200). ### Admin — Telegram Notifications (admin only) | Method | Endpoint | Description | |---|---|---| | GET | `/api/admin/telegram` | Get current Telegram configuration | | PUT | `/api/admin/telegram` | Save bot token and chat ID | | POST | `/api/admin/telegram/test` | Send a test message | ## Configuration All settings are read from environment variables (centralised in `src/core/config.py`). ### Security | Variable | Default | Description | |---|---|---| | `SECRET_KEY` | *(required)* | JWT signing key and Fernet encryption key. The application will refuse to start if this is not set. Use a long random string (`openssl rand -hex 32`). | ### Admin seed account | Variable | Default | Description | |---|---|---| | `ADMIN_USERNAME` | `admin` | Username for the initial admin account created on first run. | | `ADMIN_PASSWORD` | *(none)* | Password for the initial admin account. If unset, no seed account is created. Minimum 12 characters. | ### Server | Variable | Default | Description | |---|---|---| | `APP_PORT` | `8000` | HTTP port the server listens on. | ### Database | Variable | Default | Description | |---|---|---| | `DATABASE_URL` | `sqlite:///data/gates.db` | SQLAlchemy database URL. | ### Map (home location) | Variable | Default | Description | |---|---|---| | `HOME_LAT` | *(none)* | WGS-84 latitude of the home/property marker shown on the frontend map. | | `HOME_LON` | *(none)* | WGS-84 longitude of the home/property marker. | | `HOME_NAME` | `Home` | Display name for the home marker popup. | If `HOME_LAT` and `HOME_LON` are both set, the map is always visible. If only gates have coordinates (set per-gate in the admin panel), the map is shown only when at least one gate has a location. ### Network / reverse proxy | Variable | Default | Description | |---|---|---| | `CORS_ORIGINS` | *(empty — no cross-origin requests)* | Comma-separated list of allowed CORS origins, e.g. `https://gates.example.com`. | | `TRUSTED_PROXY_IPS` | `127.0.0.1` | Comma-separated list of reverse-proxy IPs whose `X-Forwarded-For` header is trusted for client IP resolution. | ### Logging | Variable | Default | Description | |---|---|---| | `LOG_LEVEL` | `INFO` | Logging verbosity. One of `DEBUG`, `INFO`, `WARNING`, `ERROR`, `CRITICAL`. | | `LOG_FILE` | `/var/log/lagomaregates.log` | Path to the rotating log file (10 MB, 5 backups). Set to an empty string to disable file logging. | ## Running with Docker Compose ```bash # Copy and edit the compose file to set your SECRET_KEY docker compose up -d ``` The default `docker-compose.yml` starts the service on port `8000`. Set a strong `SECRET_KEY` and, optionally, `ADMIN_USERNAME` / `ADMIN_PASSWORD` before deploying. The `./data` directory is mounted into the container so the SQLite database persists across restarts. ## Running Locally ```bash python -m venv venv source venv/bin/activate pip install -r requirements.txt export SECRET_KEY="$(openssl rand -hex 32)" export ADMIN_USERNAME="admin" export ADMIN_PASSWORD="changeme-at-least-12" uvicorn src.main:app --port 8000 ``` The application is then available at: - **User interface** — `http://localhost:8000/` - **Admin dashboard** — `http://localhost:8000/admin` ## AVConnect Integration Gates are controlled through one of two supported API providers. ### AVConnect Each gate is mapped to an AVConnect *macro ID*. When a gate open request is received, the service: 1. Authenticates with AVConnect using the stored credentials (session is cached in the database) 2. Executes the configured macro for the gate Credentials (password) are stored encrypted in the database using Fernet symmetric encryption derived from `SECRET_KEY`. ### Shelly Cloud Each gate is mapped to a Shelly *device ID*. The service calls the Shelly Cloud API with the stored auth key to activate the device's relay. The Shelly server URI and auth key are stored encrypted. Configure them under **Admin → Credentials → Shelly Cloud**. **Mock mode** — when enabled via the admin dashboard, gate open requests always succeed without contacting any external API. Useful for testing. ## Keypass Schedules In addition to an expiry date and a per-gate allowlist, each keypass can be restricted to a configurable time window: - **Days of the week** — select any combination of Mon–Sun. If no day is checked the restriction applies on any day. - **Time window** — a `From` / `To` time in 24-hour format (server local time). Both values must be provided together. When a keypass with a schedule is used outside its allowed window the API returns **HTTP 403** (`Keypass is not valid today` / `Keypass is not valid at this time`) and the gate will not open. The restriction is enforced in `require_keypass` in `src/core/dependencies.py`. The schedule is stored as a JSON object in the `schedule` column of the `keypasses` table: ```json { "days": [0, 1, 2, 3, 4], "time_start": "08:00", "time_end": "18:00" } ``` `days` uses Python's `weekday()` convention: 0 = Monday, 6 = Sunday. Any absent key is treated as unrestricted (e.g. omitting `days` means any day is allowed). ## Keypass QR Codes Each active keypass has a **QR** button in the admin panel. Clicking it generates a PNG QR code that encodes the URL: ``` https:///?k= ``` Scanning the code with a phone opens the web app and logs in automatically. If the keypass is expired or revoked, the user is shown an error on the login screen. ## Telegram Notifications Configure a Telegram bot to receive a message in a group or chat every time a gate is opened: 1. Create a bot via [@BotFather](https://t.me/BotFather) and copy the token 2. Add the bot to your group and obtain the chat ID (e.g. using [@userinfobot](https://t.me/userinfobot)) 3. Open **Admin → Notifications**, enter the token and chat ID, and click **Save** 4. Use **Send test message** to verify the setup Notifications are sent in a background thread and never block the gate open response. Failures are logged as warnings and do not affect gate operation. ## Two-Factor Authentication (TOTP) Each admin account can independently enable TOTP-based two-factor authentication: 1. Open **Admin → Admin Users** and click **Enable 2FA** on your own row 2. Scan the QR code with an authenticator app (Google Authenticator, Authy, 1Password, etc.) 3. Enter the 6-digit code to confirm — 2FA is only activated after a successful verification 4. On subsequent logins, after entering your password you will be prompted for the current TOTP code To disable, click **Disable 2FA** on your row and confirm. > Only the account owner can enable or disable their own 2FA. TOTP secrets are stored Fernet-encrypted in the database. ## Roles | Role | Permissions | |---|---| | `admin` | Full access — all endpoints including gate/user/credential/notification management | | `manager` | Gate open, keypass management, statistics — cannot manage admin users, AVConnect credentials, gate configuration, or Telegram settings |