Add a development-only login bypass #15
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Context
The README states plainly: "Without OAuth credentials configured the app starts but the
login button won't complete (for development you can directly create a User in the DB
or add a dev-login bypass later)." Hand-editing the database to get a session is a poor
developer experience and blocks automated end-to-end testing entirely.
Scope
An explicit, development-only login path that issues a normal session for a chosen
user and role.
Implementation notes
CIRCA_DEV_LOGIN_ENABLED) that defaults to off and isrejected outright when
CIRCA_ENV=production— the fail-fast config issue in thismilestone should treat it as a hard startup error, not a warning. A dev bypass that can
be switched on in production is a full authentication bypass.
role, and issues the same signed session cookie the OAuth callback would. Reusing the
real session path is the point — a parallel session mechanism would drift from the
code that matters.
backend reports the bypass is active.
"create a User in the DB" advice.
Done when
CIRCA_ENV=productionReferences
README.md(current workaround)backend/app/auth/,frontend/src/pages/LoginRelated: the fail-fast configuration issue. Blocks the end-to-end reviewer flow test.
Done in
afd5e5c.backend/tests/test_dev_login.py(24) andfrontend/src/pages/LoginPage.test.tsx(8).Three guards, each asserted separately
POST /api/auth/dev-loginissues a real session for a chosen email and role. Said plainly that is an authentication bypass, so the tests that matter are not the ones proving it works — they are the ones proving it cannot be reached. Each guard is checked on its own, because a defence that holds only while the other two do is one refactor from being no defence at all:validate_settingsrefuses to start production with it on (#14).validate_settingsruns when the application is built. An app built in development whose settings are later changed — a reload, a long-lived process, a test — would otherwise keep serving a route that mints admin sessions. "Unreachable" is a property of today's wiring; this isn't.Disabled, it answers 404, not 403: a deployment without this feature should not advertise that the feature exists.
Reusing the real session path
As the issue asks, and it is the point. Same
SessionRepository.create, samecreate_session_cookie, same signer — so revocation (#61), the sliding idle window and the absolute ceiling all apply, and #12's journey will exercise what a reviewer actually holds rather than a parallel mechanism that drifts from it. Asserted directly: the cookie decodes to a realuser_sessionrow, logout ends it, a viewer session is refused a reviewer route the same way, and what it writes is attributed to it.Two details worth naming:
dev-login:<email>inprovider_sub. That column is the join key to the identity provider; a bypass account sharing the namespace could collide with a real one, or be mistaken for one later by someone auditing who had access.Every use logs at warning level with the identity and role.
Frontend
The sign-in page grows a loudly labelled panel when the backend reports the bypass is on. A failed probe is treated as off — defaulting the other way would put an authentication-bypass form on the one page an unauthenticated stranger can always reach. The status endpoint discloses exactly one boolean, and that is asserted so it does not grow into a description of how the deployment is configured.
A real defect fell out
Writing the "it logs its use" assertion found that
alembic/env.pywas switching off the whole application's logging. It calledfileConfigwith the generated template's defaultdisable_existing_loggers=True, which sets.disabledon every logger that already exists — permanently, with no error and nothing to notice.The symptom was a test that passed alone and failed in a full run: alone, the route's module hadn't been imported when the migration ran, so there was nothing to disable. It reaches production through
app/cli/, whose tools migrate and then work in the same process, and it would have quietly undermined #89.test_harness_isolation.pynow asserts both the flag and that a record actually reaches a handler afterwards.Done when
1020 passed, 8 skipped backend; 58 frontend; ruff and
tscclean.Note on naming: the issue says
CIRCA_ENV, but the existing setting isCIRCA_ENVIRONMENTand that is what both this and #14 use.