Skip to content

fix(auth): resolve active account from external token in token/gh-cli mode#25

Open
DutchmanNL wants to merge 1 commit into
debba:mainfrom
DutchmanNL:fix/token-mode-active-account
Open

fix(auth): resolve active account from external token in token/gh-cli mode#25
DutchmanNL wants to merge 1 commit into
debba:mainfrom
DutchmanNL:fix/token-mode-active-account

Conversation

@DutchmanNL

Copy link
Copy Markdown

Problem

In GH_AUTH_MODE=token and gh-cli modes, /api/auth/status reports the token as authenticated, but every data endpoint (/api/repos, /api/issues, /api/pulls, notifications, CI health) returns 401 {"ok":false,"error":"authentication required","needsAuth":true}, and the UI shows "Connect an account". Fixes #24.

Root cause

The status endpoint and the data layer resolve auth through two different paths:

  • /api/auth/statusgetProviderStatus() understands env / gh-cli tokens (loadEnvToken() / loadGhCliToken()), so it authenticates. ✅
  • Data endpoints → dashboardData.resolveActive()accountStore.getActive(), which only returns accounts from the on-disk store (accounts.json / legacy auth.json). Nothing ever seeds that store from GITHUB_TOKEN or the gh CLI, so in pure token/gh-cli mode it stays empty → getActive() returns nullauthFail()401 needsAuth. ❌

accountStore already prefers an ephemeral env-based account and add() already supports ephemeral:true, but no code path ever created that account.

Fix

Seed an ephemeral account from the external credential, at the single choke point every data consumer already uses:

  • authProvider.ts — add loadExternalAccountDraft(), exposing the env / gh-cli token (with login + scope) for the current auth mode, or null in device mode / when no token is available.
  • accountStore.tsensureExternalAccount() seeds/refreshes that as an ephemeral external account inside getActive() / list(). It is never persisted to disk and is dropped if the credential disappears at runtime.

Because the seed happens in accountStore.getActive()/list(), no call sites changedashboardData, notifications, the accounts list and status endpoints all pick it up. Both token and gh-cli modes are covered.

Testing

  • npm test — 80 pass (added 2 accountStore regression tests: the ephemeral external account is seeded and resolved as active, is not persisted, and is dropped when the credential goes away).

  • Manual, empty account store:

    /api/auth/status /api/repos
    before 200 authenticated 401 needsAuth
    after 200 authenticated 200, repos listed
    GH_AUTH_MODE=token GITHUB_TOKEN=<PAT> npm run api
    curl -s localhost:8765/api/repos   # now 200

Notes

  • No new dependencies; ephemeral account is in-memory only (never touches accounts.json).
  • A separate, GitHub-side quirk is called out in the issue: fine-grained PATs return [] from /user/orgs, so a classic read:org PAT is needed to list org repos. Not addressed here.

… mode

In `token` and `gh-cli` auth modes the credential lives outside the on-disk
account store (env var / gh CLI). `/api/auth/status` authenticated fine via
`getProviderStatus()`, but the data layer resolves the active account through
`accountStore.getActive()`, which was only ever populated from `accounts.json`
or a legacy `auth.json`. With neither present the store stayed empty, so every
data endpoint (`/api/repos`, `/api/issues`, `/api/pulls`, notifications, …)
returned `401 {"needsAuth":true}` and the UI showed "Connect an account",
despite the status endpoint reporting the token as authenticated.

Seed an ephemeral account synthesised from the external credential:
- authProvider: add `loadExternalAccountDraft()` exposing the env / gh-cli
  token (with login + scope) for the current auth mode.
- accountStore: seed/refresh that as an ephemeral `external` account inside
  `getActive()`/`list()`, reusing the pre-existing ephemeral-account support.
  It is never written to disk and is dropped when the credential goes away.

Covers both `token` and `gh-cli` modes at the single choke point every data
consumer already goes through, so no call sites change.

Verified: `GH_AUTH_MODE=token GITHUB_TOKEN=<PAT>` with an empty store now
returns 200 from `/api/repos` (was 401). Added accountStore regression tests.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Token / gh-cli auth mode: dashboard data endpoints return 401 (needsAuth) despite /api/auth/status being authenticated

1 participant