Take a token, never a password, outside DAV #122

Merged
jcoffey-dev merged 1 commits from feature/http-basic-dav-only into main 2026-09-29 17:04:01 +00:00
Owner

Contract C-23. JMAP, /api, /auth/introspect, /auth/userinfo and authenticated /auth/register refuse Authorization: Basic before checking the password, with a Bearer-only 401. A wrong password gets the same answer as the right one. CalDAV/CardDAV keep Basic.

Why: a copy of a front end hosted elsewhere could collect a password and replay it as Basic from its own server. CORS (C-14) and client registration (C-5) don't cover that path.

  • Bootstrap and recovery mode accept Basic everywhere (like C-16).
  • INBUXA_HTTP_BASIC_AUTH=all restores it everywhere; dav is the default; any other value logs a warning.
  • Test builds (test_mode) accept Basic everywhere, since the integration suites sign in with passwords; legacy_protocols.py sets the variable.

Merge order: land ihasmail-inbuxa fix/confirm-password-without-basic and deploy it first. Until then, the webmail's app-password creation checks the password over Basic and would fail.

Tested: unit tests for the paths; tests/e2e/http_basic_auth.py against the debug build, 26/26: refusals and challenges, DAV unaffected, both front ends' sign-in path, token on JMAP/API, the operator switch, recovery mode, and an unregistered redirect refused. cargo check -p tests --tests clean; fork notice/name/privacy checks clean. Not run: the full integration suites, and legacy_protocols.py with the new variable.

Contract C-23. JMAP, `/api`, `/auth/introspect`, `/auth/userinfo` and authenticated `/auth/register` refuse `Authorization: Basic` before checking the password, with a Bearer-only 401. A wrong password gets the same answer as the right one. CalDAV/CardDAV keep Basic. **Why:** a copy of a front end hosted elsewhere could collect a password and replay it as Basic from its own server. CORS (C-14) and client registration (C-5) don't cover that path. - Bootstrap and recovery mode accept Basic everywhere (like C-16). - `INBUXA_HTTP_BASIC_AUTH=all` restores it everywhere; `dav` is the default; any other value logs a warning. - Test builds (`test_mode`) accept Basic everywhere, since the integration suites sign in with passwords; `legacy_protocols.py` sets the variable. **Merge order:** land ihasmail-inbuxa `fix/confirm-password-without-basic` and deploy it first. Until then, the webmail's app-password creation checks the password over Basic and would fail. **Tested:** unit tests for the paths; `tests/e2e/http_basic_auth.py` against the debug build, 26/26: refusals and challenges, DAV unaffected, both front ends' sign-in path, token on JMAP/API, the operator switch, recovery mode, and an unregistered redirect refused. `cargo check -p tests --tests` clean; fork notice/name/privacy checks clean. Not run: the full integration suites, and `legacy_protocols.py` with the new variable.
jcoffey-dev added 1 commit 2026-09-29 14:02:10 +00:00
Take a token, never a password, outside DAV
ci / fork-checks (pull_request) Successful in 17s
ci / build (pull_request) Successful in 7m41s
faf3d1e056
Anyone could host a copy of a front end on a server of their own,
collect a person's password there, and replay it as HTTP Basic against
JMAP or the API. Cross-origin rules don't stop that, since a server
isn't a browser, and neither does client registration, since Basic
never goes through OAuth (contract C-23).

JMAP (session, API, upload, download, event source, WebSocket), /api,
/auth/introspect, /auth/userinfo and authenticated /auth/register now
refuse an Authorization: Basic header before looking at the password,
with a 401 whose only challenge is Bearer. A wrong password gets the
same answer as the right one. CalDAV and CardDAV keep Basic, and their
401s still offer it. The sign-in page's /api/auth takes the password in
its body and is unaffected, as is the token endpoint's client
authentication.

Bootstrap and recovery mode accept Basic everywhere, as they keep
permissive CORS. INBUXA_HTTP_BASIC_AUTH=all puts it back everywhere;
dav is the default, and any other value logs a warning and keeps it.
Test builds accept Basic everywhere, since the integration suites sign
in with passwords, and legacy_protocols.py sets the variable.

Tested: unit tests for the paths, and tests/e2e/http_basic_auth.py
against the debug build, 26 checks, including both front ends' sign-in
path and a refused unregistered redirect.
jcoffey-dev force-pushed feature/http-basic-dav-only from a742d0cd87 to faf3d1e056 2026-09-29 14:02:10 +00:00 Compare
jcoffey-dev merged commit a5c8927dbc into main 2026-09-29 17:04:01 +00:00
jcoffey-dev deleted branch feature/http-basic-dav-only 2026-09-29 17:04:01 +00:00
Sign in to join this conversation.
No Reviewers
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: inbuxa/inbuxa-server#122