0022. OpenR doors and the parking gate: one Pixel account, kept alive by Pixel
- Status: accepted
- Date: 2026-10-26
- Issue: #154 (part of #150)
Context
The building's doors and the parking gate are run by OpenR. It has no API for bots: its web dashboard (dashboard.openr.nl) calls a JSON API (/api/v4), and rensr showed that a script can make the same calls. Logging in takes an email and password, then a one-time code that OpenR emails to the account. The session is two cookies: an access cookie that opens doors and lasts about 15 minutes, and a refresh cookie that lasts about 6 months from the login. POST /me/current_session/refresh with only the refresh cookie sets both again and gives their expiry as Unix times (confirmed by hand; rensr never got as far as refreshing). A door opens with POST /buildings/<building>/doors/<door>/open.
rensr logged each Discord user in with their own OpenR account and kept their sessions in a JSON file. Pixel already decides who may do what itself (tiers, capabilities, the dispatcher), and only members with an OpenR account could use per-person logins.
Decision
- One OpenR account for Pixel. Pixel logs in as its own account (
OPENR_EMAIL,OPENR_PASSWORD, app-group secretsopenr-emailandopenr-password, ADR 0017). OpenR only sees that account. Who may open which door is decided in Pixel, like everything else:/openr openneeds a member with theopenrcapability, and the gate opens only for someone/parkhas just given a spot. Pixel never sends OpenR who asked. - An admin does the login.
/admin openr loginstarts it and/admin openr codepasses on the emailed code. Whoever reads that account's mail completes the login, so control of the mailbox is part of the security of the doors. - Pixel keeps the session alive by itself, refreshing it shortly before its access expires, and ends it when OpenR refuses a refresh; Sentry then says an admin must log in again.
/admin statusshows the session. - The session is saved in
PIXEL_DATA_DIR/openr-session.json(atomic, deleted when it ends, owner-only where the filesystem allows; on Azure the share's access control is the fence), so a deploy or a crash doesn't need a person. It holds the cookies, never the email or password, and the share mirrors (just pull) never copy it. - The doors come from config, not from OpenR:
OPENR_DOORSlists the names and IDs/openr openoffers, andOPENR_GATE_DOORis the gate, which is never among them. An account that can open more doors still only opens those. - Fail closed: one call per opening, under one deadline that covers a refresh too. No retries, no queueing, one opening at a time per door.
/admin doors offstops OpenR's doors and the gate along with Home Assistant's.
Consequences
- Anyone who can read the data share can open the doors until the session ends. That share already holds
members.yaml, where a capability can be granted, so it doesn't widen who can open them through Pixel; but the file must not be copied anywhere else. Log out (/admin openr logout) to end the session at once. - OpenR's API is undocumented and may change. Requests copy the dashboard's headers, including its version (
APP_VERSIONinadapters/openr/client.ts); a dashboard update is the first thing to check when OpenR starts refusing Pixel. - Refreshing doesn't extend the refresh cookie, so an admin logs in again about every 6 months.
/admin openr statusshows when the session ends and warns in the last two weeks. - OpenR's own logs show Pixel's account for every opening; Pixel's logs (
openr.door_opened,parking.gate_opened) say who it was for. - Per-person OpenR logins, if they're ever wanted, would be a separate decision: they'd move the "who may" decision to OpenR and need account linking.