fix(auth): derive identity from sm:id(), not the persistent-login attribute#813
fix(auth): derive identity from sm:id(), not the persistent-login attribute#813joewiz wants to merge 1 commit into
Conversation
…ribute
GET /api/auth/whoami and POST /api/auth/session resolved the current user
by reading request:get-attribute("org.exist.login.user"). That attribute
is populated only by the persistent-login flow (login-form params or the
remember-me cookie), so a request authenticated with the HTTP Basic
header reported as "guest" even though it executed as the real user:
before: curl -u admin: .../api/auth/whoami -> user=guest, isAdmin=false
after: curl -u admin: .../api/auth/whoami -> user=admin, isAdmin=true
Replace the attribute-based auth:get-user() with auth:current-user(),
which reads the actual eXist subject via sm:id() (sm:effective preferred,
matching the cookie/token case). This works because controller.xq calls
login:set-user before forwarding to the Roaster handlers, so by handler
time the subject already reflects whatever authenticated the request --
Basic header, the shared org.exist.login cookie (Path=/exist), or a
freshly minted login session.
This puts eXide's notion of "who is logged in" on the same sm:id() basis
as Roaster's rutil:getDBUser() and existdb-openapi -- the first step of
converging the stock apps onto a single identity model.
Verified: auth_spec 16/16; identity correct under Basic, cookie, and
unauthenticated, including with a temporary non-empty-password user
(confirming the fix is not an artifact of the default empty admin
password) and correctly distinguishing dba from non-dba accounts.
Beachhead only -- deliberate follow-ups (each its own verified step):
removing the security:[] overrides on the two /api/auth routes so they
read $request?user via Roaster's middleware; retiring the legacy
controller /login branch; the same sm:id() fix in documentation-next.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
@joewiz I would like to take a completely different route forward:
|
|
[This response was co-authored with Claude Code. -Joe] Thanks — I like this direction, and I think it's the right one. Pushing identity resolution into Roaster's middleware (and letting eXide just consume To turn it into something actionable, a few specifics would help:
Thanks for steering this toward the more durable design. |
[This PR was co-authored with Claude Code. -Joe]
Summary
GET /api/auth/whoamiandPOST /api/auth/sessionnow derive identity from the actual eXist subject (sm:id()) instead of the persistent-login request attribute (org.exist.login.user).The bug
The attribute is populated only by the persistent-login flow (login-form params or the remember-me cookie). A request authenticated with the HTTP Basic header therefore reported as
guesteven though it executed as the real user:This also made eXide's notion of "who is logged in" diverge from existdb-openapi's and from Roaster's own
rutil:getDBUser()— both of which readsm:id(). Aligning onsm:id()puts eXide on the same identity basis as the rest of the stack.Why
sm:id()is correct hereeXide's
controller.xqcallslogin:set-useron every request before forwarding to the Roaster handlers, so by the time a handler runs the eXist subject already reflects whatever authenticated the request — Basic header, the sharedorg.exist.logincookie (which isPath=/exist, so it reaches every app under/exist), or a freshly minted login session.sm:id()reads that subject uniformly; the attribute does not.sm:effectiveis preferred oversm:real(cookie/token logins set the effective user — same reasoning as Roaster'sgetDBUser).Changes
modules/api/auth.xqm— replaced the attribute-basedauth:get-user()withauth:current-user()(readssm:id(), effective-preferred);whoami/loginnow use it. Removed the now-unusedloginmodule import. Response shapes unchanged.cypress/e2e/auth_spec.cy.js— newwhoami identity sourceblock: asserts the real user is reported under Basic auth (the regression), guest when unauthenticated, and the real user via the persistent-login cookie.Verification
auth_spec.cy.js: 16/16 (was 13).betatester): correct identity under Basic and cookie, correctly reportedisAdmin:falsefor the non-dba account, and a wrong password did not authenticate — confirming the fix is not an artifact of the default empty admin password.Cross-repo dependency note
Two eXide specs (
query_error_display,query_error_structured) assert that a failed query surfaces a real error. They pass only when the deployed existdb-openapi includes the fix in eXist-db/existdb-openapi#46 (the 0.9.7/api/queryregression that returned HTTP 200 + "Invalid context-item" for genuine query errors). Against a stock 0.9.7 those two specs fail. So eXide's green suite currently depends on eXist-db/existdb-openapi#46 landing and a 0.9.8 release — worth prioritizing.Scope note (deliberate, not in this PR)
This is the beachhead of a larger consolidation (existdb-openapi as the single backend; stock apps as thin clients on a common
sm:id()identity baseline). Natural follow-ups, each its own verified step:security: []overrides on the two/api/auth/*routes so they participate in Roaster'sstandard-authorizationmiddleware and read$request?userdirectly (full convergence; deferred because it brings the login POST under CSRF and wants its own validation).controller.xq/loginbranch in favour of/api/auth/session.sm:id()identity fix todocumentation-next(also reads the attribute);dashboard-nextalready reads$request?user.