Skip to content

feat(admin): add paginated API key listing - #593

Merged
Miracle656 merged 2 commits into
Divinity-Labs:mainfrom
Zuliyat:fix/issue-505
Oct 25, 2026
Merged

Miracle656 merged 2 commits into
Divinity-Labs:mainfrom
Zuliyat:fix/issue-505

Conversation

@Zuliyat

@Zuliyat Zuliyat commented Oct 21, 2026

Copy link
Copy Markdown

Summary

  • Add an admin-token-protected GET /admin/keys endpoint with bounded limit/offset pagination.
  • Hide revoked keys by default and allow requesting them with revoked=true.
  • Select and serialize only safe metadata; the stored hash and plaintext key are never returned.
  • Document the endpoint and cover admin authorization, pagination, filters and hash exclusion.

Fixes #505

Verification

  • git diff --check passed.
  • Tests were added but not run in this pass.
  • Checked the tracked source for known PolinRider indicators and suspicious artifacts; none were found.

@drips-wave

drips-wave Bot commented Oct 21, 2026

Copy link
Copy Markdown

@Zuliyat Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@Miracle656 Miracle656 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#505 is assigned to you, so this is the one I'm reviewing for it — #569 implements the same issue and I've said so there. It needs a rebase, and there's a detail worth knowing before you do.

Part of #505 already shipped

main now has a GET /admin/keys (src/api/admin.ts:123). It lists keys, so "there is no way to see what keys exist" is solved — what it does not have is pagination or a revoked filter:

const keys = await prisma.apiKey.findMany({ ... })   // no take, no skip, no revoked filter

So your PR is now a replacement of that handler rather than a new route, and that changes the merge. As it stands, both sides register GET /admin/keys:

routes on main:   2 × '/admin/keys'   (POST + GET)
routes on pr593:  2 × '/admin/keys'   (POST + GET)

Merged naively that is three registrations of /admin/keys across two handlers, and Fastify refuses duplicate routes at startup — the app would not boot.

The branch is missing two routes

main has /admin/keys/:id/rotate and /admin/webhook-delivery-failures; this branch has neither, because it predates them. The conflict in src/api/admin.ts is one 80-line hunk that git has aligned your new handler against webhook-delivery-failures, which is why it looks stranger than it is.

I did not resolve this by hand, and the reason is specific: picking which parts of main's existing handler survive — the selected columns, the response shape — against yours is a behaviour decision on an endpoint that exposes API key metadata, and you know which you intended. A rebase makes those choices yours instead of mine.

What to do

Rebase on current main and fold your pagination into the existing GET /admin/keys rather than adding a second one. Keep parsePageValue and MAX_KEY_PAGE_SIZE — a cap on page size is the right instinct for an admin listing, and bounding offset is more than most do. admin_auth_failures_total.inc() on the 401 path matches what main already does, so that lines up.

Also note #595 is open to replace the hand-rolled validation in this same file with zod, and #574 adds an admin auth hook. Whoever lands first moves src/api/admin.ts under the others; yours is the one with an assigned issue behind it, so it has first claim.

docs/admin-api-keys.md is a good addition — an admin endpoint that is only discoverable by reading the route file is one nobody uses.

@Jossy-ossy

Copy link
Copy Markdown
Contributor

Note for maintainers: this overlaps with #569 (same issue), and #595 (zod) and #574 (admin auth hook) touch the same admin file. Sequencing is for maintainers. main is merged in. The four targeted test files pass (112 tests). The full suite needs a live database and was not run.

# Conflicts:
#	src/__tests__/auth.test.ts
#	src/api/admin.ts
@Miracle656
Miracle656 merged commit b88ffba into Divinity-Labs:main Oct 25, 2026
@Miracle656

Copy link
Copy Markdown
Collaborator

Merged into main (b88ffba). You did exactly what the last round asked for, which made this quick.

The rebase is right. The pagination is folded into the existing GET /admin/keys rather than standing up a second one, and main's selected columns all survived — lastUsedAt from #587 and the quota/overage fields are still in the select. That was the specific thing I did not want to resolve by hand, because choosing which columns an API-key listing exposes is a behaviour decision, not a merge. Yours now.

parsePageValue is the detail worth keeping. It rejects rather than coerces:

if (!/^(0|[1-9]\d*)$/.test(value)) return null

so ?limit=abc and ?limit=-1 are 400s instead of quietly becoming a default. Capping limit at MAX_KEY_PAGE_SIZE is standard; bounding offset at Number.MAX_SAFE_INTEGER too is not, and it is the reason a hostile offset cannot turn into a query that silently does something odd. Keep doing that.

admin_auth_failures_total.inc() on the 401 path matches the other admin routes, so the metric still means one thing.

The conflict

src/api/admin.ts conflicted with #595, which landed zod validation in the same region. It was additive on both sides — main's CreateKeySchema and quota fields against your ListKeysQuery, MAX_KEY_PAGE_SIZE and parsePageValue — so I resolved it as a union and kept both. Nothing of either side was dropped.

docs/admin-api-keys.md is the part that makes this usable. An admin endpoint whose pagination parameters you can only learn by reading the route file is one operators work around rather than use.

tsc clean, 1,734 tests pass.

https://claude.ai/code/session_01USgemLt4Rnz4SGB1Srf3GB

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.

There is no way to see what keys exist

3 participants