Skip to content

fix(login): handle existing-user poll response without api_key#22

Open
govindkavaturi-art wants to merge 1 commit intomainfrom
fix/existing-user-login-session-exchange
Open

fix(login): handle existing-user poll response without api_key#22
govindkavaturi-art wants to merge 1 commit intomainfrom
fix/existing-user-login-session-exchange

Conversation

@govindkavaturi-art
Copy link
Copy Markdown
Member

🚨 Production bug

`cueapi login` has been crashing with KeyError on every existing-user login since backend commit `adbfe77`. The backend's `POST /v1/auth/device-code/poll` was changed to omit `api_key` when the user's `api_key_encrypted` can't be decrypted (or was never populated). The CLI never got updated — it blindly did `poll_data["api_key"]` at `auth.py:71`.

New-user signups still worked (the inline `api_key` is always present for them). Only existing users hit the crash — which is exactly the people whose work depends on logging back in.

New flow (for existing users only)

  1. Poll returns approved without `api_key` but with `session_token`
  2. Exchange the one-time `session_token` for a JWT: `POST /v1/auth/session`
  3. Reveal the stored plaintext: `GET /v1/auth/key` with `Authorization: Bearer `
  4. Save it the same way the new-user path does

New users keep the inline-`api_key` path, unchanged.

Edge cases now handled with actionable messages

State Before After
Poll has neither `api_key` nor `session_token` `KeyError` stacktrace "try again or contact support"
`/auth/session` returns non-200 stacktrace status + "try again"
`/auth/key` returns 410 `plaintext_unavailable` stacktrace "Run `cueapi key regenerate`" — the only actual remedy
`/auth/key` returns other non-200 stacktrace status + "try again"

Subtle UX tweak

Existing users' api_key is no longer reprinted to the terminal on login — they already have it from first signup; reprinting leaks it to shell scrollback with no benefit. New users still see it once (the ONE time they'll ever see it).

Tests

New file `tests/test_login_existing_user.py` — 6 tests covering every branch of the decision tree:

  • new user → saves api_key from poll, skips session exchange
  • existing user with inline key → saves it, skips session exchange
  • existing user without api_key → full session → JWT → reveal flow
  • 410 `plaintext_unavailable` → regenerate guidance, no partial credential
  • `/auth/session` 500 → clear error, no partial credential
  • malformed poll (no api_key AND no session_token) → actionable error

25 tests pass (19 existing + 6 new).

Test plan

  • CI green
  • Manual: existing user on staging runs `cueapi login` → logs in cleanly, sees "Welcome back, email"
  • Manual: new user on staging runs `cueapi login` → sees their api_key once (unchanged behavior)

`cueapi login` crashed with KeyError on every existing-user login
since backend commit adbfe77 changed POST /v1/auth/device-code/poll
to omit `api_key` when the user's api_key_encrypted couldn't be
decrypted. The CLI blindly did `poll_data["api_key"]` at line 71
and blew up.

New flow
--------
When poll approves without an inline api_key, resolve it via the
session-token → JWT → reveal-key handshake:

1. Exchange the one-time session_token for a JWT via
   POST /v1/auth/session.
2. Call GET /v1/auth/key with the JWT as a Bearer token — the
   server decrypts and returns the plaintext api_key.
3. Save it as the new-user path would.

Edge cases the CLI now handles with actionable messages (instead
of traces):

- Poll has neither api_key nor session_token → "try again or
  contact support"
- /auth/session returns non-200 → surface status + "try again"
- /auth/key returns 410 plaintext_unavailable → tell user to run
  `cueapi key regenerate` (the only remedy; the stored plaintext
  is gone and the only path forward is a fresh key)
- /auth/key returns any other non-200 → surface status + "try
  again"

Behavior unchanged for new users — the inline api_key path runs
exactly as before.

Subtle UX tweak
---------------
For existing users we no longer reprint their full api_key to
the terminal. They already have it from first signup; printing
it again leaks it to their scrollback for no reason. New users
still see the plaintext once because that's literally the only
time they'll ever see it.

Tests (tests/test_login_existing_user.py — 6 new)
-------------------------------------------------
- new user → saves api_key from poll, skips session exchange
- existing user with inline key → saves it, skips session exchange
  (session_token can be present; we just don't need to use it)
- existing user without api_key → exchanges session_token, calls
  /auth/key with Bearer jwt, saves revealed api_key
- /auth/key returns 410 → "cueapi key regenerate" guidance + no
  partial credential written
- /auth/session fails → clear error + no partial credential
- poll approves with neither api_key nor session_token →
  actionable error, no crash

All 25 tests pass (19 pre-existing + 6 new).
@govindkavaturi-art govindkavaturi-art enabled auto-merge (squash) April 20, 2026 05:19
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.

1 participant