Skip to content

feat(accounts): find accounts by upstream API key from the account search - #239

Merged
HTExplicit merged 1 commit into
mainfrom
feat/account-key-search
Oct 4, 2026
Merged

HTExplicit merged 1 commit into
mainfrom
feat/account-key-search

Conversation

@HTExplicit

Copy link
Copy Markdown
Owner

Problem

The account page could only search by account name. To find which accounts use a given upstream API key (duplicates, a key reported as leaked or exhausted, a key about to be added) the owner had to open accounts one by one, and the key is not shown in the list.

Change

What the admin sees. The existing search box also finds accounts by upstream API key; the placeholder reads "搜索名称或 API Key" / "Search name or API key". Nothing else is added to the page.

  • The match is the whole key, exact and case-sensitive, against credentials.api_key only. That one field holds the upstream key of every API-key style account on every platform.
  • It combines with the active folder and filters like the name search, so select-all, bulk-by-filter and export act on the same set.
  • It does not depend on the "显示 API Key" switch, and there is no indicator for a key match.

The key is not sent. The page hashes the trimmed search text (SHA-256, WebCrypto) and first asks whether any account has that key, using the digest term sha256:<64 hex> as the search value of a one-row list request.

  • If an account has it, every request of the page carries the digest term instead of the text: list, auto-refresh, facets, upstream billing rates, export, select-all paging and bulk-by-filter. The term is 71 bytes, under the handler's existing 100-byte cap, and it is what audit rows record.
  • Otherwise the text is sent as before and matches names only. So a text that is no account's key still travels as a plaintext name search, as it always has.
  • Without crypto.subtle (plain HTTP) the page keeps the name-only search.

Backend. service.AccountSearchPredicate is now the one search condition of both account query builders (accountListFilteredQuery, accountConsoleQuery), so list, facets, export and bulk-by-filter agree.

  • A digest term matches accounts whose api_key is a JSON string that, trimmed of space, tab, CR and LF, hashes to it. It never matches names.
  • Any other search value is the name search it was. The five other account pickers that share the endpoint are unchanged.
  • No new route, parameter, migration or index.

Frontend state.

  • A restored search text is resolved before the first list and facets requests.
  • A lookup in flight is joined instead of restarted, so a filter change cannot cancel a refresh's lookup.
  • A failed lookup shows the error and puts the search box back to the search the list shows; the text is never sent in its place.
  • A name search is looked up again on manual refresh and after the page itself adds or changes accounts (create, job completion, import), because the text may now be an account's key.
  • One CSS rule gives the search field 6px instead of 10px right inset in the accounts toolbar: at 1280px the Chinese placeholder was 2px wider than the field's text area.

Verification

  • Backend: gofmt, go build ./..., go vet -tags=integration ./internal/repository/, and the predicate unit test, which asserts the generated PostgreSQL and its bound argument for a digest term and that nine near-miss inputs stay name searches.
  • Frontend: vue-tsc --noEmit, eslint on the touched files, and the nine AccountsView.*.spec.ts files plus the util spec (81 tests, run twice). The new cases record the search value of each list request and cover: name text, key text, restored text, failed lookup, re-lookup after create and after import, and a filter change during a refresh's lookup.
  • Owner preview on mock data (13 flows, light/dark, zh/en, 1280/1440/390): in every key flow the typed key appears in no request.
  • Two review passes, each finding verified by a second reader; all confirmed findings are fixed.

Not run locally: the two integration cases (TestListWithFilters_SearchByAPIKeyDigest, TestAccountTaxonomyConsoleSearchByAPIKeyDigestIntegration) need Docker, so the new SQL meets a real PostgreSQL for the first time in this PR's CI. No full suites, no real model calls.

🤖 Generated with Claude Code

…arch

The account page's search box now also finds the accounts that use a given
upstream API key: the whole key, exact, compared with credentials.api_key
only, combined with the active folder and filters like the name search.

The key itself is not sent. The page hashes the trimmed search text and first
asks whether any account has that key, with the digest term "sha256:<hex>" as
the search value of a one-row list request. If one does, every request of the
page (list, auto-refresh, facets, upstream billing rates, export, select-all,
bulk-by-filter) carries the digest term instead of the text. Otherwise the
text is sent as before and matches names only.

Backend: service.AccountSearchPredicate is the one search condition of both
account query builders. A digest term matches accounts whose api_key is a JSON
string that, trimmed of space, tab, CR and LF, hashes to it; any other value
is the name search it was.

Frontend: the text is resolved before the first load, a lookup in flight is
joined rather than restarted, a failed lookup puts the search box back to the
applied search instead of sending the text, and a name search is looked up
again on manual refresh and after the page adds or changes accounts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@HTExplicit
HTExplicit merged commit 0761723 into main Oct 4, 2026
18 checks passed
@HTExplicit
HTExplicit deleted the feat/account-key-search branch October 4, 2026 07:39
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