The remote FlutterFlow app connects to Mosquitto as "app_<firebase_uid>"
with a Firebase ID token as the password, so no per-user MQTT accounts
need to exist anywhere.
For app_ usernames, POST /mqtt/auth/user now:
- verifies the token with firebase_admin.auth.verify_id_token
(check_revoked=True),
- requires the decoded uid to equal the uid in the username,
- requires a users doc with that `uid` field (queried, not by doc id)
whose status is not "blocked" (same meaning as users.service.block_user).
It returns 200/403 and logs the deny reason - never the token.
app_ usernames never fall through to the HMAC / legacy "vesper" check.
Device and kiosk auth are unchanged. App users are still denied every
topic by the existing ACL until the app ACL lands in the next commit.
Both handlers are now plain `def` so the blocking Firestore / Firebase
calls run in FastAPI's threadpool instead of stalling the event loop.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>