Known Limitations & Security Notes
Version 2 · Added the temporary CMS-New TOTP test-code bypass (open, needs removal), the resolved group member-count bug, and updated the one-role-column limitation to reference the new site_roles table.
Known Limitations & Security Notes
Open — temporary CMS-New TOTP test-code bypass left in production code
Status: open as of 2026-09-06, added deliberately for QA testing,
not yet removed.
views.py's login_view/verify_2fa_view (and the equivalent
_verify_2fa_client path for Site Clients) accept QDB_TOTP_TEST_CODE for
any login — staff or Site Client — where the resolved site name is
exactly "CMS-New", regardless of account type. This was added on request,
explicitly marked TEMPORARY in code, for QA testing convenience while
verifying the 2026-09-06/07 role and Site Client work. Fix: remove the
site_name == "CMS-New" clause (two call sites) once CMS-New testing is
confirmed complete. This is inert unless QDB_TOTP_TEST_CODE is actually
set in that environment's variables, but the code path itself should not
stay in a production codebase indefinitely. See
Authentication, MFA & Token Internals.
Open — production DEBUG mode
Status: open as of 2026-07-31, not confirmed resolved since.
start_auth.bat does not set QDBAUTH_DEBUG, and settings.py defaults it
to "1" (True) — meaning the production deployment was running with
Django's DEBUG mode on, which leaks full stack traces, file paths, and server
internals to anyone who hits a broken page. Fix: set QDBAUTH_DEBUG=0 in
the production environment.
Open — unrotated shared secret and an exposed credential in the launch script
Status: open as of 2026-07-31, not confirmed resolved since.
start_auth.bat (tracked by this repo) sets QDB_SSO_SHARED_SECRET to the
unchanged default placeholder value from settings.py — never rotated to a
real secret — and separately contains a real third-party mail-account
credential written in plaintext. This secret gates every /api/* endpoint
except /api/whoami/, so an unrotated, publicly-known default value
undermines that entire gate. Fix: rotate QDB_SSO_SHARED_SECRET to a
real, non-default value not committed to version control, and move the mail
credential out of the tracked file into an untracked env source — coordinate
the secret rotation with every consuming app's matching value at the same
time (it's a breaking change for whoever holds the old value).
Resolved — auth failures returned HTTP 200
Status: resolved 2026-07-28.
/api/login/ and /api/verify-2fa/ used to return HTTP 200 with
success:false for wrong password/code, making it easy for a caller to
mishandle the failure as success. Now returns 400. See
Changelog.
Resolved — stale group member count
Status: resolved 2026-09-06.
user_groups.list_groups() counted membership rows directly
(COUNT(user_group_members.id)), which kept counting a member after their
user account was deleted (memberships have no FK, by design, so the row
survives a user deletion). The Groups list page could show a member count
that didn't match what the group's own member list actually displayed
(which already excluded deleted users via an inner join). Fixed by joining
through users in the count query too, and — separately — delete_user_api
now proactively removes a deleted user's group memberships so the orphan
row doesn't get created in the first place. See Changelog.
Limitation — no self-service MFA recovery
If a user loses their authenticator device, there is no self-service "lost my phone" flow, for any account type (staff, site-admin, or Site Client). An admin must use the force-re-auth action to issue a fresh QR code. See Authentication, MFA & Token Internals.
Limitation — one role column, no multi-group resolution
A user granted access to two sites that each define a different custom
role list can't hold two independent role values — role is one column on
users. Mechanism updated 2026-09-07: role lists moved from the
hardcoded ROLE_GROUPS Python dict to the database-backed site_roles
table, but this specific limitation is unchanged — it's structural (one
column), not a hardcoding artifact. Previously only TD customized roles;
now any site can via the dashboard, which makes this limitation more likely
to actually arise. See
Roles, Permissions & Multi-Tenancy.
Limitation — no tenant isolation
QDBAuth is not multi-tenant in the Keycloak sense — one global user pool shared by every consuming app. Site Clients (added 2026-09-07) narrow a client's own scope to one site, but a Super Admin still sees every site's clients from the same dashboard — this is not realm-style isolation. See Roles, Permissions & Multi-Tenancy for the full explanation and the closest equivalent concept ("sites").
Limitation — single point of failure
QDBAuth is presently a single server (no redundancy) — if it's down, no consuming site can authenticate anyone. A hosting/reliability plan (cloud auto-recovery + longer-lived, locally-verified tokens) was proposed 2026-07-31; not yet implemented. See Overview's Reliability section.
Open — pending database migrations
Status: as of 2026-09-07, three separate CREATE TABLE scripts under
db/ must each be run once against the live qdb database before the
feature they back will work — none confirmed run against production as of
this writing:
db/create_user_site_credentials_table.sql(per-site credentials — confirmed open as of 2026-07-28, a liveProgrammingErrorwas observed; current status unconfirmed).db/create_site_clients_table.sql(Site Clients, 2026-09-07).db/create_site_roles_table.sql(per-site role management, 2026-09-07 — also seeds the previous default + TD role data, so nothing changes behaviorally until it's run and an admin adds roles for another site).
Symptom for any of these: Table 'qdb.<table_name>' doesn't exist. See
Administrator User Guide's troubleshooting section.