fix(auth): fold /api/auth routes into Roaster authorization ($request?user) - #814
fix(auth): fold /api/auth routes into Roaster authorization ($request?user)#814joewiz wants to merge 9 commits into
Conversation
|
We need to start fresh:
|
…r.xq Addresses @line-o's review on eXist-db#814. eXide stops authenticating in controller.xq and delegates login/logout entirely to Roaster's auth module, matching the roasted reference app. - controller.xq: drop the persistent-login import, login:set-user, the index-vs-login auth routing, the AJAX login route, and the unauthorized redirect/401 handling. The app page always loads; Roaster enforces access on the API. Identity for the one privileged non-Roaster route (/execute) is now derived from sm:id() rather than the persistent-login attribute, and the gate returns a clean 403 (it previously fell through to 404). - modules/api/auth.xqm: auth:login calls roaster-auth:login-user(...) to validate credentials and mint the session; auth:logout calls roaster-auth:logout-user. login still rejects accounts the config bars (guest when restrictions/@guest = "no") with 401. - modules/api.json: /api/auth/session login bypasses authorization (security: []) and accepts credentials as application/json (XHR) or application/x-www-form-urlencoded (login.html form). - expath-pkg.xml(.tmpl): bump the Roaster dependency to >= 1.12.1. - login.html: submit via fetch and redirect on success (the route returns JSON); redirect already-authenticated visitors to index.html. The SPA login modal already POSTs form-encoded credentials to /api/auth/session, so the frontend needed no change. auth_spec updated: the app no longer gates the page server-side, so the former page-redirect assertions become "the page loads; the API enforces access." Verified against a Roaster 1.12.1 instance: login (form + JSON), whoami, logout, bad-credential 401, guest-barred-under-guest=no 401, and the /execute gate (dba 200, guest 403 under restrictions). auth_spec 16/16; file_save, dbmanager, run_as_test_*, query_execution green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
| "properties": { | ||
| "user": { "type": "string" }, | ||
| "password": { "type": "string", "format": "password" }, | ||
| "duration": { "type": "string" } |
There was a problem hiding this comment.
Duration was added but is never read. I think this parameter should be dropped entirely. A boolean "remember-me" might be what we are after here.
| rauth:login-user( | ||
| string($request?body?user), | ||
| string($request?body?password), | ||
| rauth:add-cookie-name($request, map {}) |
There was a problem hiding this comment.
the empty map that is passed to rauth:add-cookie-name is the options map where we might want to set the duration for the session based if the user selected "remember-me" in the login form.
|
[This response was co-authored with Claude Code. -Joe] Thanks @line-o — done, modeled on the roasted reference app. Pushed in
One thing I'd like your read on: |
|
[This response was co-authored with Claude Code. -Joe] Pushed the Guest gate — the hard refusal ( The app shell is now served by a Roaster route, Retired the legacy The editor runs queries through existdb-openapi's Verified on a Roaster 1.12.1 instance: guest under Still open (not eXide): for Merge ordering: this branch is behind |
|
I think this needs a rebase |
…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>
…?user) Removes the `security: []` overrides on POST /api/auth/session and GET /api/auth/whoami so they participate in Roaster's standard-authorization middleware like every other route, and reads identity from `$request?user` (populated via rutil:getDBUser() -> sm:id()) instead of the app-local auth:current-user() helper from the previous commit. Net effect is identical identity resolution (both are sm:id()-based), but the auth routes are no longer special-cased out of the framework: one identity path for all routes, and the login POST is now a cookie-auth route that Roaster's csrf:enforce can protect once x-csrf is enabled (currently a no-op in both apps — neither declares x-csrf; tracked separately). No CSRF behaviour change here: with no x-csrf config, csrf:enforce is inert, so removing security:[] from the login POST is safe. cookieAuth declares its cookie name (org.exist.login), so use-cookie-auth resolves correctly on these routes. Verified: identity correct under Basic, cookie, unauthenticated, and fresh login POST; full eXide suite 263 passing / 0 failing / 2 skipped. Stacked on fix/auth-identity-from-request-user (PR eXist-db#813); rebase onto develop once that merges. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
$request?user is populated by Roaster's standard-authorization for every route (= the guest account, name "guest", when unauthenticated), as the module doc states — so $user?name is always present and the ($user?name, "guest")[1] fallback is dead code. Folding these routes back into the auth middleware (this PR's change) is exactly what makes the fallback unnecessary; removing it also avoids masking a genuinely empty $request?user behind a silent "guest". Per @line-o's review. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…r.xq Addresses @line-o's review on eXist-db#814. eXide stops authenticating in controller.xq and delegates login/logout entirely to Roaster's auth module, matching the roasted reference app. - controller.xq: drop the persistent-login import, login:set-user, the index-vs-login auth routing, the AJAX login route, and the unauthorized redirect/401 handling. The app page always loads; Roaster enforces access on the API. Identity for the one privileged non-Roaster route (/execute) is now derived from sm:id() rather than the persistent-login attribute, and the gate returns a clean 403 (it previously fell through to 404). - modules/api/auth.xqm: auth:login calls roaster-auth:login-user(...) to validate credentials and mint the session; auth:logout calls roaster-auth:logout-user. login still rejects accounts the config bars (guest when restrictions/@guest = "no") with 401. - modules/api.json: /api/auth/session login bypasses authorization (security: []) and accepts credentials as application/json (XHR) or application/x-www-form-urlencoded (login.html form). - expath-pkg.xml(.tmpl): bump the Roaster dependency to >= 1.12.1. - login.html: submit via fetch and redirect on success (the route returns JSON); redirect already-authenticated visitors to index.html. The SPA login modal already POSTs form-encoded credentials to /api/auth/session, so the frontend needed no change. auth_spec updated: the app no longer gates the page server-side, so the former page-redirect assertions become "the page loads; the API enforces access." Verified against a Roaster 1.12.1 instance: login (form + JSON), whoami, logout, bad-credential 401, guest-barred-under-guest=no 401, and the /execute gate (dba 200, guest 403 under restrictions). auth_spec 16/16; file_save, dbmanager, run_as_test_*, query_execution green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds auth_spec coverage for the privileged /execute route's authorization: under guest=no a guest is denied (403) and a dba is still allowed (200) — the API-level enforcement that replaced the old controller page-redirect. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…st gate Adds view:index — a Roaster handler that serves index.html (with the runtime config injection) gated by auth:is-allowed. A disallowed guest (e.g. while restrictions/@guest = "no") is refused and redirected to login.html, so the app never loads for an anonymous visitor. Authorization moves into Roaster, off the controller. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Query execution and result paging go through existdb-openapi's /api/query cursor
(runQueryCursor/fetchCursorPage). Drop the legacy session path: remove
app.retrieveNext() (which fetched the controller's /results/{n}) and the dead
else-branches in browseNext/Previous/First/Last. Move dbmanager's setup query off
the /eXide/execute route (retired next) to the eXist REST query envelope. Tidy the
stale /execute-fallback note in ws-eval.js (WebSocket eval is in eXist core 7.0.0).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The editor runs queries through /api/query, so the legacy /execute (XQueryServlet)
and /results (session paging) controller routes are dead. Removing them deletes
the controller's last authorization logic (the sm:id-derived query-execution
gate): it no longer imports config, reads sm:id(), or makes any auth decision —
it routes /api/* and the app shell to Roaster and otherwise serves static files.
Delete the now-unused modules/{session,view}.xq and the already-retired (410)
modules/{debuger,git}.xq (debuger imported session.xq). auth_spec gains the guest
hard-refusal tests and drops the /execute-gate test (query-execution authorization
now belongs to /api/query).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…cute This PR retires eXide's /execute route, but develop's cy.execXQuery helper (added by eXist-db#825) and dbmanager_spec's inline setup call both still POST to it, so after the rebase every spec that seeds or cleans up state 404s. Point the helper at eXist-db's REST query envelope instead, and switch dbmanager_spec's inline call to the helper so there is one transport. The helper now asserts a 2xx: posting *raw* XQuery to /exist/rest does not execute it (eXist parses the body as XML and 400s), a failure mode that previously passed silently because the caller ignored the status. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
afea6db to
e73d059
Compare
|
[This response was prompted by Joe, drafted by Claude Code, and reviewed by Joe.] @duncdrum rebased onto current One extra commit was needed to make the rebase actually work. This PR retires the Verified on the stack the eXist-db image bundles (7.0.0-SNAPSHOT, existdb-openapi 0.10.0, Roaster 1.12.2): 278 tests, with the only failure being The failing CI test here is not this PR. Against the current image, the pinned Every spec calls |
|
the quick fix is to allign the version in the workflow with the one specified in expath. See my response in the other PR |
[This PR was co-authored with Claude Code. -Joe]
Stacked on #813 (
fix/auth-identity-from-request-user). Targetsdevelop; rebase once #813 merges. Review #813 first.Summary
Removes the
security: []overrides onPOST /api/auth/sessionandGET /api/auth/whoamiso they participate in Roaster'sstandard-authorizationmiddleware like every other route, and reads identity from$request?userinstead of the app-localauth:current-user()helper that #813 introduced.Why
#813 fixed the identity source (sm:id() not the persistent-login attribute) but left the two auth routes opted out of the Roaster auth middleware via
security: [], with a local helper doing thesm:id()read. That override is itself the last bit of auth-route special-casing. Removing it means one identity path for every route —$request?user, populated byrutil:getDBUser()→sm:id()— matching existdb-openapi and the rest of the stack.Net identity resolution is unchanged (both are
sm:id()-based); this is convergence/cleanup, plus it makes the login POST a genuine cookie-auth route that Roaster'scsrf:enforcecan protect once CSRF is enabled.Safety
x-csrf, so Roaster'scsrf:enforceis inert — removingsecurity: []from the login POST triggers nothing. (Enabling CSRF protection is tracked as a separate, deliberate hardening step.)cookieAuthdeclares its cookie name (org.exist.login), souse-cookie-authresolves correctly on these routes.controller.xq'slogin:set-user(form params) before the forward; the handler only reports.Verification
$request?user.)Follow-ups (separate, tracked)
x-csrf: {same-origin: true}on cookie-authed writes in both apps (real CSRF gap today —csrf:enforceis a no-op without it). Needs a cy.request-Origin check first.controller.xq/loginbranch in favour of/api/auth/session.