Changelog
Version 1 · Initial changelog entries covering error-handling fix, per-site credentials, TD site roles, and reliability groundwork
Historical version2026-07-31 — Production hardening groundwork
Added /health/ endpoint (apps/auth/views.py health_view, checks app + DB
connectivity) and serve_waitress.py (production WSGI launcher, replacing
manage.py runserver). Groundwork for a broader server-reliability plan —
recommended direction is cloud hosting (AWS/Azure) with provider-level
auto-recovery for a single instance, plus a managed database with automatic
failover, rather than the team manually operating two servers with MySQL
replication.
2026-07-30 — TD site-specific roles
Added ROLE_GROUPS in apps/auth/users.py — the TD (Troyen Data) site gets its
own 5 roles (Developer, Support, Dev Lead, Support Lead, Super Admin) instead of
the global admin/developer/user list. role_group_for_sites() resolves which
group applies; create_user_direct, update_user, and
bulk_users.create_users_from_rows all validate/default against the correct
group. The Create/Edit User dashboard forms swap the Role dropdown's options live
via JS based on which site checkbox is checked (role_groups passed to the
template via json_script).
2026-07-28 — Per-site credentials
Added apps/auth/site_credentials.py and
db/create_user_site_credentials_table.sql (new user_site_credentials table,
deliberately with no FK constraints to users/site, matching this codebase's
existing convention of not hard-linking auxiliary tables so delete_user_api and
site deletion keep working unmodified).
Accounts created going forward get one password + TOTP secret per granted site
(or one "siteless" row if granted none), instead of one shared credential across
every site. site_credentials.has_any(user_id) is the legacy/new-style switch.
Fixes: previously, granting one person 2-3 sites sent 2-3 separate verification
emails that all pointed at the same shared credential, so completing setup via
the first email made every other site's link say "already verified" immediately.
Existing accounts are explicitly NOT migrated — a deliberate decision, not an oversight, to avoid touching already-working logins.
Also touched: tokens.py (signup/pending-login tokens now carry an optional
credential_id), emailer.py, dashboard.py, bulk_users.py, views.py
(login_view, verify_2fa_view, verify_signup_view, verify_signup_qr_view,
api_login_view, api_verify_2fa_view, api_totp_qr_view), sites.py (added
site_name_for_url, get_site_id_by_name).
2026-07-28 — HTTP 400 for auth failures
/api/login/ and /api/verify-2fa/ previously returned HTTP 200 with
success:false for wrong password / wrong authenticator code — now return HTTP
400 for these business-logic failures. HTTP 401 stays reserved for a
missing/wrong X-QDB-Client-Secret. /api/verify-2fa/ and /api/token/refresh/
responses also gained refresh_token_expires_at; /api/verify-2fa/ additionally
returns a user object (id, email, first_name, last_name, role) so
consuming apps (e.g. the CMS) don't need a separate call to get basic user info
after login.