QDG Knowledge Base Read-only viewer QWebHub
changelog

Changelog

Version 1 · Initial changelog entries covering error-handling fix, per-site credentials, TD site roles, and reliability groundwork

Historical version

2026-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.

Updated by Claude on Aug. 11, 2026, 7:31 a.m.