Server API: OpenAPI OAuth scopes and endpoints don't match what the server uses; a disallowed client id returns 500 instead of 401

Open
#1,416 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Assessment

Difficulty
4/5
Estimated time
3-5 days
Newbie friendliness
46/100
Issue type
Bug
Clarity
Clearly specified
Activity status
Active
Tech stack
aws, clojure, openapi

Research direction

Start with deps/db-sync/src/logseq/db_sync/worker/routes/semantic.cljs for the OpenAPI scopes and OAuth URLs, then compare them with the pool discovery document and the existing OpenAPI tests. For the 500, read auth.cljs, authorization.cljs, and dispatch.cljs, and run the db-sync test suite, including the described auth-claims case. Done means scopes and endpoints match the pool and operations, and disallowed client IDs return 401 without debug fields.

Written by the indexing model from the issue text.

Description

Search first
  • I searched and no similar issues were found

Searched logseq/db-test and logseq/logseq (open and closed; issue search plus a title scan of the latest 1000 issues in each) for: openapi, oauth, scopes, api-docs, api.logseq.io, dispatch error, aud not found. Not the same as #1055 (the CLI derives its OAuth endpoints from a custom sync origin) or #1023 (a graph-specific Durable Object dispatch error 500).

What Happened?

Three problems with the Logseq Server API (https://api.logseq.io, added in logseq/logseq#12896), found while working out how to test it with a real OAuth token:

1. The OpenAPI security scheme declares scope names the operations don't use. components.securitySchemes.oauth declares the scopes read and write. Every operation requires logseq/read or logseq/write, and so does /.well-known/oauth-protected-resource. A client that takes its scopes from the security scheme requests names the server never checks for.

2. The OpenAPI security scheme points at OAuth endpoints that the user pool does not publish. authorizationUrl and tokenUrl are the pool issuer plus /oauth2/authorize and /oauth2/token, on cognito-idp.us-east-1.amazonaws.com. The pool's own OpenID discovery document lists the endpoints on its hosted-UI domain (logseq-prod.auth.us-east-1.amazoncognito.com). A plain request to each URL from the spec returned HTTP 400, while the hosted-UI authorize URL returned 302. I did not attempt a full authorization flow against either.

3. A well-formed token whose client id is not allowed gets HTTP 500 dispatch error, not 401. With the correct issuer and a client id that is not on the allowlist, GET /api/v1/graphs returns 500 with debug-message: "aud not found" and the internal debug-* fields from the dispatch error handler. A token with the wrong issuer, an unparseable token, and an allowed client id with a bad signature all return 401. The source lists "aud not found" as a recoverable auth error that should become a 401.

Reproduce the Bug

All requests are unauthenticated or use an unsigned, made-up token; none needs an account.

  1. Scope names (problem 1):
    curl -s https://api.logseq.io/openapi.json | jq '.components.securitySchemes.oauth.flows.authorizationCode.scopes | keys'
    curl -s https://api.logseq.io/openapi.json | jq '.paths["/api/v1/graphs"].get.security'
    
    First prints ["read","write"], second prints [{"oauth":["logseq/read"]}].
  2. Endpoints (problem 2):
    curl -s https://api.logseq.io/openapi.json | jq '.components.securitySchemes.oauth.flows.authorizationCode | .authorizationUrl, .tokenUrl'
    curl -s https://cognito-idp.us-east-1.amazonaws.com/us-east-1_dtagLnju8/.well-known/openid-configuration | jq '.authorization_endpoint, .token_endpoint'
    
    The first pair is on cognito-idp.us-east-1.amazonaws.com/.../oauth2/..., the second pair on logseq-prod.auth.us-east-1.amazoncognito.com/oauth2/.... Then curl -s -o /dev/null -w '%{http_code}\n' <each URL from the first pair> returns 400.
  3. The 500 (problem 3). Build an unsigned token with the pool issuer and a client id that is not allowed:
    TOKEN=$(node -e 'const b=o=>Buffer.from(JSON.stringify(o)).toString("base64url");console.log(b({alg:"RS256",kid:"x"})+"."+b({iss:"https://cognito-idp.us-east-1.amazonaws.com/us-east-1_dtagLnju8",client_id:"not-allowed",exp:Math.floor(Date.now()/1000)+3600})+".c2ln")')
    curl -s -w '\n%{http_code}\n' -H "Authorization: Bearer $TOKEN" https://api.logseq.io/api/v1/graphs | head -c 300
    
    Returns {"error":"dispatch error",...,"debug-message":"aud not found",...} and 500. Repeat with iss set to https://x, or with Authorization: Bearer garbage: both return {"error":"unauthorized"} and 401.
Expected Behavior
  1. The scopes in the security scheme are logseq/read and logseq/write, the same strings the operations and the protected-resource metadata use.
  2. authorizationUrl and tokenUrl are endpoints the user pool actually serves, as listed in its discovery document.
  3. A token whose client id is not allowed returns 401 unauthorized, like the other invalid-token cases, and not a 500 with debug fields.
Screenshots
Files
Browser, Desktop or Mobile Platform Information

Server side, no client involved. Probed https://api.logseq.io on 2026-10-08. Source read at logseq/logseq master ed0b0404dc (the OpenAPI document is generated from it; the live document matched). The deployed worker revision is not known.

Additional Context

Investigation (source at logseq/logseq master ed0b0404dc, checked 2026-10-08)

  1. Scope names — confirmed.
    • deps/db-sync/src/logseq/db_sync/worker/routes/semantic.cljs:395 declares the scopes as {:logseq/read "…" :logseq/write "…"}, namespaced keywords used as map keys. Operations use the strings "logseq/read" and "logseq/write" (same file, :scope entries near the top, and :security [{:oauth [scope]}]). The live document shows read and write, so the logseq/ part is dropped when the document is serialised (suspected mechanism: keyword keys lose their namespace).
    • Using string keys fixes it. I ran the db-sync suite with a test that compares the scheme's scopes with the operations' scopes: it fails before the change (#{"read" "write"} vs #{"logseq/read" "logseq/write"}) and passes after it (192 tests, 0 failures).
  2. Endpoints — confirmed for the values, suspected for impact.
    • semantic.cljs:393-394 builds both URLs as (str issuer "/oauth2/authorize") and (str issuer "/oauth2/token"), where issuer is COGNITO_ISSUER (deps/db-sync/worker/wrangler.toml, the cognito-idp URL). Nothing reads the discovery document. I did not run a client against the spec URLs, so the impact on a real OAuth client is not tested.
    • No test covers components.securitySchemes; the existing OpenAPI test only checks per-operation scopes.
  3. The 500 — confirmed live and in a unit test; cause unknown.
    • deps/db-sync/src/logseq/db_sync/worker/auth.cljs:34-35 lists "aud not found" as recoverable, and auth-claims should return nil for it. The throw is at deps/common/src/logseq/common/authorization.cljs:128.
    • In the db-sync test build (base 533ac4382a, same lines in auth.cljs), calling auth/auth-claims with an unsigned token with a valid issuer and an unknown client_id rejects with aud not found instead of resolving nil. Adding that test to the suite also made many later tests in the same run fail; I did not isolate why.
    • I did not find why the wrong-issuer case behaves differently live (401). A probe showed verify-jwt returns a promise rather than throwing synchronously for both cases.
    • The 500 body comes from deps/db-sync/src/logseq/db_sync/worker/dispatch.cljs:233-238, which returns :debug-type, :debug-message, :debug-data, :debug-json and :debug-stack for any uncaught error.
Are you willing to submit a PR?
  • I'm willing to submit a PR (Thank you!)
Dominant language
No language data
Stars
29
Forks
2
PR merge metrics
No merged PRs in 30d

Getting set up

This project ships no dev container, Dockerfile or contributing guide, so setting up is up to you: start from its README, and see our first-contribution guide for the general steps.

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from logseq/db-test

All issues in logseq/db-test

Similar issues

More Backend & API Design issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.