Skip to content

feat(web-ui): admin UI for public API keys (issues #438/#439) - #551

Merged
Weegy merged 2 commits into
feat/issue-438-439-public-apifrom
feat/issue-438-api-keys-admin-ui
Jul 30, 2026
Merged

feat(web-ui): admin UI for public API keys (issues #438/#439)#551
Weegy merged 2 commits into
feat/issue-438-439-public-apifrom
feat/issue-438-api-keys-admin-ui

Conversation

@Weegy

@Weegy Weegy commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds the admin UI for the public API keys introduced by #438/#439: create,
list, and revoke server-to-server bearer credentials for
POST /api/public/v1/chat at /admin/api-keys — create/list/revoke against
/api/public/v1/admin/keys, gated by the same operator-session cookie every
other admin page uses.

Stacked on #549 (feat/issue-438-439-public-api) since that hasn't merged
yet — this PR targets that branch, not main.

What's in this page

  • Create: label (optional), rate limit (optional, 1–6000 req/min,
    integers only), and the single chat:write scope checkbox (hardcoded —
    it's the only scope that exists today).
  • Reveal-once token: a created key's plaintext token is shown exactly
    once, right after creation, and is never re-derived from a list reload
    (the list endpoint never returns a token field). The create form —
    button and every field — is disabled while that reveal is still on
    screen, so a second create can never silently overwrite the first key's
    only-ever-shown token before it's copied.
  • Revoke: two-step arm-then-confirm per row. Busy/confirm state is
    tracked per key id (not a single shared value), so concurrent revokes on
    different rows don't clobber each other's UI state.
  • List reload sequencing: a monotonic counter guards against an
    out-of-order response (a slower in-flight fetch) overwriting a newer
    one's result.
  • Errors: known backend error shapes (not_found,
    operator_auth.unavailable, auth.missing/auth.invalid,
    invalid_request) map to translated catalog messages under
    adminApiKeys.errors.*; nothing renders the raw backend response body.
  • Dates use useFormatter().dateTime(...), not toLocaleString().

Review history

A codex adversarial review round found 5 blocking findings against the
initial version of this page (token-overwrite bug, two race conditions,
a Lume state-color violation, and a hard i18n-rule violation) plus one
lower-severity note (decimal rate-limit input silently truncated). All 5
are fixed in this PR — see the commit for the fix-by-fix breakdown — and
a re-review round confirmed the fixes.

Test plan

  • npm run typecheck — clean
  • npm run lint (api-keys dir) — clean
  • npm run i18n:check — 0 errors, no new "identical to en" warnings
  • npx vitest run — 409/409 tests, 54/54 files (under the repo's
    pinned Node 22.22.3 — a system Node version mismatch in the sandbox
    breaks 5 unrelated, pre-existing tests in toolTemplates.test.ts on
    newer Node; confirmed unrelated to this diff)
  • Two new interaction-level tests exercise the token-overwrite fix and
    the concurrent-revoke race-condition fix directly via React Testing
    Library fireEvent
  • Live-browser (Interceptor) visual check — attempted, blocked by an
    unrelated pre-existing dev-server bug (Turbopack/Tailwind 500s on
    every page due to a literal text-[color:var(...)] example string in
    doc comments in two untouched test files); not something this PR
    introduced or is in scope to fix

View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.

Weegy added 2 commits July 30, 2026 10:16
Adds /admin/api-keys — create/list/revoke for the server-to-server bearer
credentials exposed by @omadia/channel-api's admin router
(/api/public/v1/admin/keys). Modeled on admin/webhooks (the closest
existing 'secret shown once' pattern) and admin/mcp for list/create/
loading/empty/error state conventions.

- app/_lib/api.ts: ApiKeyPublicView type + listApiKeys/createApiKey/
  revokeApiKey client functions.
- app/admin/api-keys/: page.tsx + ApiKeysPanel.tsx + shared.tsx
  (status badge, chip, card/input classnames — feature-scoped, same
  pattern as admin/webhooks/_components/shared.tsx).
- Reveal-once token UI: shown only in create-response state, dismissed
  explicitly (never auto-hidden), never re-fetched — the list endpoint
  never returns a token field.
- scopes:[] footgun avoided: the only real scope (chat:write) is a
  checkbox that must stay checked to submit; unchecking blocks the
  create button with an explanation rather than silently sending [] or
  omitting the field against the operator's intent.
- Revoke requires an explicit two-step confirm (danger-variant Button,
  text/edge only, no filled red) before calling the API.
- admin/page.tsx: unconditional static card under the Access group,
  matching the convention already used for webhooks/mcp (plugin-backed
  but not gated behind requiresNavFrom/registerNav).
- messages/{en,de}.json: adminApiKeys namespace + admin.index.cards.apiKeys.
- 11 new vitest cases covering empty/loaded/revoked states, the create
  scope-checkbox footgun (asserts payload.scopes is never []), the
  reveal-once flow, and the two-step revoke confirmation.
Fixes 5 blocking findings + 1 lower-severity note from a codex adversarial
review of the /admin/api-keys page (issues #438/#439):

1. Token-overwrite bug: creating a second key while a first key's one-time
   token reveal was still showing silently overwrote it in React state.
   `canSubmit` now requires `!revealed`, and the create form (button +
   all fields) stays disabled until the reveal is explicitly dismissed.

2. Race conditions: a single global `pendingId`/`confirmingId` let
   concurrent per-row revokes clobber each other's busy/confirm UI state,
   and unsequenced list reloads could resolve out of order. Both are now
   `ReadonlySet<string>` keyed per key id, and `reload()` uses a monotonic
   sequence ref so only the most recently issued reload's result applies.

3. Lume state-color violation: several error texts rendered in danger
   color without the mandatory 1px edge. Added `errorTextCls`/
   `errorInlineCls` (border + tint + text) to shared.tsx and applied them
   at all 6 flagged spots.

4. Hard i18n rule violation: the error helper returned raw backend
   response bodies, and the created-date display used `toLocaleString()`.
   `toFriendlyError(err, t)` now maps known backend error shapes to
   translated `adminApiKeys.errors.*` catalog keys (mirrors
   `admin/registries/page.tsx`), never rendering the raw body. Dates now
   use `useFormatter().dateTime(...)`.

5. Missing documentation: added a CHANGELOG bullet and a
   middleware-agent-handoff.md section describing the new admin UI page.

Lower severity: decimal rate-limit input ("60.7") is now rejected via
Number.isInteger instead of being silently truncated.

Two new interaction-level tests exercise findings 1 and 2 directly via
React Testing Library. Re-reviewed by codex (prReady: true).
@Weegy
Weegy merged commit bc2b26c into feat/issue-438-439-public-api Jul 30, 2026
6 checks passed
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