Skip to content

feat: add POST /v1/admin/api-keys and supporting admin routes - #21

Merged
pitah23 merged 1 commit into
Astraguard:mainfrom
gadst12:feature/admin-api-keys-endpoint
Aug 8, 2026
Merged

feat: add POST /v1/admin/api-keys and supporting admin routes#21
pitah23 merged 1 commit into
Astraguard:mainfrom
gadst12:feature/admin-api-keys-endpoint

Conversation

@gadst12

@gadst12 gadst12 commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

Summary

Closes #4.

The only previous way to create an API key was npm run seed — a one-time local bootstrap script. This PR adds a full admin API key management surface gated behind the admin:api-keys scope so registry analysts and partner integrations can be provisioned without direct DB/script access.


Changes

New route file: src/api/routes/admin.ts

Method Path Scope required Description
POST /v1/admin/api-keys admin:api-keys Issue a new API key
GET /v1/admin/api-keys admin:api-keys List all keys (admin-wide)
DELETE /v1/admin/api-keys/:id admin:api-keys Revoke a key

POST /v1/admin/api-keys

  • Body: ownerEmail, label, scopes (one or more of admin:api-keys, registry:review, certification:decide, claims:review), rateLimitTier (standard / partner / internal), optional role
  • Creates the user if they don't exist (via existing ensureUser)
  • Returns the rawKey exactly once — not stored, not retrievable again
  • 201 on success, 400 on validation errors, 401 on auth/scope failure

GET /v1/admin/api-keys

  • Query params: includeRevoked (default false), limit / offset (pagination), ownerId (filter to one user)
  • Joins with users to surface ownerEmail alongside each key record
  • key_hash is never returned

DELETE /v1/admin/api-keys/:id

  • Soft-revokes via revoked_at — the auth middleware already filters WHERE revoked_at IS NULL
  • 404 if key doesn't exist
  • 409 if already revoked (distinguishes idempotent call from "was active, now revoked")

src/shared/api-keys.ts

  • Added revokeApiKey(keyId) — soft-delete helper returning a boolean indicating whether the key was active and is now revoked
  • Added listAllApiKeys(opts) — admin-wide key listing helper (not used directly by admin.ts which does a richer join, but exported for use in tests or future CLI tooling)

src/api/app.ts

  • Imports and calls registerAdminRoutes(app)

src/api/openapi.yaml

Added:

  • /v1/admin/api-keys (GET + POST)
  • /v1/admin/api-keys/{id} (DELETE)
  • ApiKeyRecord schema
  • IssueApiKeyRequest schema
  • IssuedApiKeyResponse schema

db/seed.ts

  • Adds admin:api-keys scope to the bootstrap seed key so it can immediately provision further keys via the new endpoint
  • Updates the JSDoc comment to reflect the intended bootstrap flow

README.md

  • Adds a note under Getting started: the seeded key has admin:api-keys and can provision additional keys via POST /v1/admin/api-keys

Behaviour details

Raw key visibility — consistent with the existing issueApiKey contract and the webhooks endpoint: the key is generated with randomBytes(24), hashed with SHA-256 + salt, and only the hash is persisted. The raw key appears once in the 201 response body.

RevocationDELETE sets revoked_at. The existing resolveApiKey in auth.ts already queries WHERE revoked_at IS NULL, so revoked keys are immediately rejected on the next request without any cache invalidation step.

Scope gating — the admin:api-keys scope is not granted by the public key-issuance path; only an existing admin:api-keys-scoped key (or the seed key) can issue new admin-scoped keys.


What was tested

  • tsc --noEmit passes clean — only the four pre-existing ssrf-guard.ts errors remain (unrelated to this change)

Closes Astraguard#4.

- Add POST /v1/admin/api-keys: issues a new API key for a user (by email,
  creating the user if they don't exist). Scopes, rate-limit tier, and
  role are configurable. The raw key is returned exactly once.
- Add GET /v1/admin/api-keys: admin-wide list of all keys with owner email
  joined. Supports includeRevoked, limit/offset pagination, and ownerId
  filtering.
- Add DELETE /v1/admin/api-keys/:id: soft-revokes a key (sets revoked_at).
  Returns 404 if the key doesn't exist, 409 if already revoked.
- All three routes are gated behind the admin:api-keys scope.
- Add revokeApiKey() and listAllApiKeys() helpers to shared/api-keys.ts.
- Add admin:api-keys scope to the seed key so the bootstrap key can
  immediately provision further keys via the new endpoint.
- Update db/seed.ts comment to reflect that POST /v1/admin/api-keys is
  the intended path after initial bootstrap.
- Document all three routes + ApiKeyRecord / IssueApiKeyRequest /
  IssuedApiKeyResponse schemas in openapi.yaml.
- Update README Getting started section to call out the admin endpoint.
@pitah23
pitah23 merged commit a72be67 into Astraguard:main Aug 8, 2026
1 of 2 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.

No admin/self-serve API key issuance endpoint

2 participants