Fix issue with QR code login and caching. Update README

This commit is contained in:
Ettore
2026-05-10 16:22:31 +02:00
parent a5470544a1
commit c4355eb371
3 changed files with 60 additions and 10 deletions

View File

@@ -9,7 +9,10 @@ A web-based gate access management and control system. Authorized users can remo
- **Keypass authentication** — users authenticate with an access code; each keypass can have a per-gate allowlist and an optional expiration date - **Keypass authentication** — users authenticate with an access code; each keypass can have a per-gate allowlist and an optional expiration date
- **Remote gate control** — integrates with [AVConnect](https://www.avconnect.it) to trigger gate macros - **Remote gate control** — integrates with [AVConnect](https://www.avconnect.it) to trigger gate macros
- **Role-based admin panel** — two roles (`admin`, `manager`) with different permission levels - **Role-based admin panel** — two roles (`admin`, `manager`) with different permission levels
- **Access audit log** — every open attempt is logged with timestamp, keypass, gate, IP, and result - **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 - **Progressive Web App** — installable on mobile devices with offline caching
## Tech Stack ## Tech Stack
@@ -22,6 +25,8 @@ A web-based gate access management and control system. Authorized users can remo
| Auth | JWT (HS256) + bcrypt | | Auth | JWT (HS256) + bcrypt |
| Credential storage | Fernet symmetric encryption | | Credential storage | Fernet symmetric encryption |
| Gate integration | AVConnect HTTP API | | Gate integration | AVConnect HTTP API |
| Notifications | Telegram Bot API |
| QR generation | qrcode + Pillow |
| Frontend | Vanilla JS PWA | | Frontend | Vanilla JS PWA |
## Project Structure ## Project Structure
@@ -38,13 +43,15 @@ src/
├── routers/ ├── routers/
│ ├── auth.py # POST /api/auth/admin, POST /api/auth/keypass │ ├── auth.py # POST /api/auth/admin, POST /api/auth/keypass
│ ├── gates.py # User-facing gate list and open endpoints │ ├── gates.py # User-facing gate list and open endpoints
│ ├── keypasses.py # Admin keypass CRUD │ ├── keypasses.py # Admin keypass CRUD + QR code generation
│ ├── admins.py # Admin user management │ ├── admins.py # Admin user management
│ ├── credentials.py # AVConnect credential management │ ├── credentials.py # AVConnect credential management
── stats.py # Access log / statistics ── stats.py # Access log / statistics (paginated, filtered)
│ └── telegram.py # Telegram notification configuration
├── services/ ├── services/
│ ├── avconnect.py # AVConnect session management and macro execution │ ├── avconnect.py # AVConnect session management and macro execution
── gates.py # Gate open orchestration ── gates.py # Gate open orchestration
│ └── telegram.py # Telegram Bot API client
└── static/ # Frontend PWA (index.html, admin.html, JS, CSS) └── static/ # Frontend PWA (index.html, admin.html, JS, CSS)
data/ data/
└── gates.db # SQLite database (auto-created on first run) └── gates.db # SQLite database (auto-created on first run)
@@ -84,6 +91,7 @@ data/
| POST | `/api/admin/keypasses` | Create a keypass | | POST | `/api/admin/keypasses` | Create a keypass |
| PATCH | `/api/admin/keypasses/{kp_id}` | Update a keypass | | PATCH | `/api/admin/keypasses/{kp_id}` | Update a keypass |
| DELETE | `/api/admin/keypasses/{kp_id}` | Revoke a keypass | | 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) ### Admin — Users (admin only)
@@ -100,12 +108,24 @@ data/
|---|---|---| |---|---|---|
| GET | `/api/admin/credentials` | View stored credentials | | GET | `/api/admin/credentials` | View stored credentials |
| PUT | `/api/admin/credentials` | Create or update credentials | | PUT | `/api/admin/credentials` | Create or update credentials |
| GET | `/api/admin/credentials/mock` | Get mock mode status |
| PUT | `/api/admin/credentials/mock` | Enable or disable mock mode |
### Admin — Statistics (manager+) ### Admin — Statistics (manager+)
| Method | Endpoint | Description | | Method | Endpoint | Description |
|---|---|---| |---|---|---|
| GET | `/api/admin/stats` | Retrieve the last 500 access log entries | | 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 ## Configuration
@@ -189,9 +209,32 @@ Gates are controlled through the AVConnect platform. Each gate is mapped to an A
Credentials (password) are stored encrypted in the database using Fernet symmetric encryption derived from `SECRET_KEY`. Credentials (password) are stored encrypted in the database using Fernet symmetric encryption derived from `SECRET_KEY`.
**Mock mode** — when enabled via the admin dashboard, gate open requests always succeed without contacting AVConnect. Useful for testing.
## 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://<your-domain>/?k=<KEYPASS_CODE>
```
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.
## Roles ## Roles
| Role | Permissions | | Role | Permissions |
|---|---| |---|---|
| `admin` | Full access — all endpoints including gate/user/credential management | | `admin` | Full access — all endpoints including gate/user/credential/notification management |
| `manager` | Gate open, keypass management, statistics — cannot manage admin users, AVConnect credentials, or create/delete gates | | `manager` | Gate open, keypass management, statistics — cannot manage admin users, AVConnect credentials, gate configuration, or Telegram settings |

View File

@@ -215,15 +215,18 @@ document.getElementById("logout-btn").addEventListener("click", () => {
headers: { "Content-Type": "application/json" }, headers: { "Content-Type": "application/json" },
body: JSON.stringify({ code: k.toUpperCase() }), body: JSON.stringify({ code: k.toUpperCase() }),
}) })
.then(res => res.ok ? res.json() : Promise.reject()) .then(res => res.ok ? res.json() : res.json().then(j => Promise.reject(j.detail || "Invalid keypass")))
.then(data => { .then(data => {
saveToken(data.token); saveToken(data.token);
showGatesView(); showGatesView();
loadGates(); loadGates();
}) })
.catch(() => { .catch(msg => {
clearToken(); clearToken();
showLogin(); showLogin();
const errEl = document.getElementById("login-error");
errEl.textContent = typeof msg === "string" ? msg : "QR code login failed";
errEl.classList.remove("hidden");
}); });
return; return;
} }

View File

@@ -1,6 +1,6 @@
/* Service worker - Lagomare Gates */ /* Service worker - Lagomare Gates */
const CACHE = "lagomare-gates-v1"; const CACHE = "lagomare-gates-v1";
const PRECACHE = ["/", "/static/style.css", "/static/app.js", "/static/logo.svg", "/static/mobile_icon.png", "/manifest.json"]; const PRECACHE = ["/static/style.css", "/static/app.js", "/static/logo.svg", "/static/mobile_icon.png", "/manifest.json"];
self.addEventListener("install", event => { self.addEventListener("install", event => {
event.waitUntil( event.waitUntil(
@@ -20,6 +20,10 @@ self.addEventListener("fetch", event => {
// Let API calls always go to the network // Let API calls always go to the network
if (event.request.url.includes("/api/")) return; if (event.request.url.includes("/api/")) return;
// Navigation requests (page loads, QR code opens) must always hit the network
// so query parameters like ?k=CODE are preserved for app.js
if (event.request.mode === "navigate") return;
event.respondWith( event.respondWith(
caches.match(event.request).then(cached => cached || fetch(event.request)) caches.match(event.request).then(cached => cached || fetch(event.request))
); );