Skip to content

fix: route API key writes through backend#2197

Closed
jihadMo wants to merge 2 commits into
Cap-go:mainfrom
jihadMo:codex/route-apikey-ui-writes
Closed

fix: route API key writes through backend#2197
jihadMo wants to merge 2 commits into
Cap-go:mainfrom
jihadMo:codex/route-apikey-ui-writes

Conversation

@jihadMo
Copy link
Copy Markdown

@jihadMo jihadMo commented May 11, 2026

/claim #1667

Summary

  • add shared frontend helpers for the existing PUT /apikey/:id and DELETE /apikey/:id backend routes
  • route API-key rename, scope save, regenerate, and delete UI writes through those backend routes instead of direct browser-side apikeys table mutations
  • align GET /apikey/:id and DELETE /apikey/:id lookup behavior with PUT /apikey/:id so numeric ids and UUID key values are queried separately
  • add regression coverage for fetching and deleting a plain API key by UUID key value

Why

The backend API-key routes already centralize ownership checks, limited-key guards, validation, and policy enforcement. The UI should use that path for API-key mutations instead of writing the table directly.

The GET/DELETE lookup change also avoids mixing UUID key values into the numeric id filter, matching the safer lookup pattern already used by the PUT handler.

Related: #1667

Testing

  • git diff --check
  • npx.cmd --yes vitest run tests/apikeys.test.ts (blocked locally: this checkout has no installed dependencies; Vitest cannot resolve vite from vitest.config.ts)
  • npx.cmd --yes eslint src/services/apikeys.ts src/pages/ApiKeys.vue src/components/organization/ApiKeyRbacManager.vue src/pages/settings/organization/ApiKeys.[id].vue supabase/functions/_backend/public/apikey/get.ts supabase/functions/_backend/public/apikey/delete.ts tests/apikeys.test.ts (blocked locally: ESLint cannot resolve @antfu/eslint-config without installed dependencies)

Summary by CodeRabbit

  • New Features

    • UI now uses centralized API-key service for create/rename/regenerate/delete flows.
  • Bug Fixes

    • Tighter validation and scoping for API key deletion to prevent unintended access.
    • More reliable lookup logic for identifying keys across legacy and numeric formats.
  • Tests

    • Added end-to-end tests covering legacy plain (UUID) API key creation, retrieval, and deletion.
  • Chores

    • Introduced a helper to standardize API-key lookup and service routes.

Review Change Stack

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 11, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: aa279693-5428-4c99-834b-e92d883732f1

📥 Commits

Reviewing files that changed from the base of the PR and between 11a91c0 and c04472b.

📒 Files selected for processing (8)
  • src/components/organization/ApiKeyRbacManager.vue
  • src/pages/ApiKeys.vue
  • src/pages/settings/organization/ApiKeys.[id].vue
  • src/services/apikeys.ts
  • supabase/functions/_backend/public/apikey/delete.ts
  • supabase/functions/_backend/public/apikey/get.ts
  • supabase/functions/_backend/public/apikey/lookup.ts
  • tests/apikeys.test.ts
✅ Files skipped from review due to trivial changes (2)
  • supabase/functions/_backend/public/apikey/lookup.ts
  • tests/apikeys.test.ts
🚧 Files skipped from review as they are similar to previous changes (5)
  • src/pages/settings/organization/ApiKeys.[id].vue
  • src/services/apikeys.ts
  • supabase/functions/_backend/public/apikey/delete.ts
  • src/components/organization/ApiKeyRbacManager.vue
  • src/pages/ApiKeys.vue

📝 Walkthrough

Walkthrough

This PR centralizes API key CRUD into new service helpers (updateApiKey, deleteApiKey), updates frontend components to use them, and changes backend GET/DELETE handlers to resolve either numeric id or legacy UUID key via a new lookup helper. Tests exercise legacy-key fetch and delete flows.

Changes

API Key Operations Refactoring

Layer / File(s) Summary
Service Helper Contracts and Types
src/services/apikeys.ts
Introduces typed update payload with optional regenerate, getApiKeyRoute URL-encoding, and exports updateApiKey (PUT) and deleteApiKey (DELETE) that call per-key edge function routes.
Backend Route Handlers for Dual-Format Lookup
supabase/functions/_backend/public/apikey/lookup.ts, supabase/functions/_backend/public/apikey/get.ts, supabase/functions/_backend/public/apikey/delete.ts
Adds getApiKeyLookupFilter(id) and refactors GET/DELETE to use the helper to match either numeric id or legacy key UUID, then operate on the resolved apikey.id scoped to the authenticated user.
RBAC Manager Component Migration
src/components/organization/ApiKeyRbacManager.vue
deleteKey and regenerateKey now call deleteApiKey / updateApiKey instead of inline Supabase calls; UI flows and toasts unchanged.
API Keys Page Component Migration
src/pages/ApiKeys.vue
Handlers regenrateKey, deleteKey, and changeName now use updateApiKey / deleteApiKey; preserves key extraction, modal, and local state updates.
Settings Page API Key Edit Migration
src/pages/settings/organization/ApiKeys.[id].vue
saveKey uses updateApiKey for persisting name, limited_to_orgs, and limited_to_apps instead of inline table update.
Integration Tests for Dual-Format Operations
tests/apikeys.test.ts
Adds tests creating plain (non-hashed) keys, fetching by key UUID, deleting by key UUID, and asserting subsequent GET by id returns 404.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Possibly related PRs

Suggested reviewers

  • riderx

🐰 A key by any route resolves the same,
Whether numeric ID or UUID's name —
Helpers hop in, tidy each call,
Frontend and backend answer the hall,
Hop, click, deploy — one small carrot for all!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title concisely and clearly describes the main change—routing API key writes through the backend instead of direct table mutations.
Description check ✅ Passed The PR description includes a comprehensive summary and rationale but lacks explicit test plan steps and screenshots (not critical for backend changes).
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@codspeed-hq
Copy link
Copy Markdown
Contributor

codspeed-hq Bot commented May 11, 2026

Merging this PR will not alter performance

✅ 43 untouched benchmarks
⏩ 2 skipped benchmarks1


Comparing jihadMo:codex/route-apikey-ui-writes (c04472b) with main (a4f7c93)

Open in CodSpeed

Footnotes

  1. 2 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tests/apikeys.test.ts`:
- Around line 42-57: Replace direct uses of BASE_URL for the new API calls with
the test helper getEndpointUrl(path): call fetch(getEndpointUrl('/apikey'), ...)
when creating the key and fetch(getEndpointUrl(`/apikey/${createData.key}`),
...) when retrieving it; ensure getEndpointUrl is imported/available in
tests/apikeys.test.ts and apply the same replacement to the other occurrences
noted (around the other block at lines 549-552) so requests route correctly
between Supabase and Cloudflare Workers.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 938c7137-6028-4db0-afff-471c55ee831b

📥 Commits

Reviewing files that changed from the base of the PR and between e85eca6 and 11a91c0.

📒 Files selected for processing (7)
  • src/components/organization/ApiKeyRbacManager.vue
  • src/pages/ApiKeys.vue
  • src/pages/settings/organization/ApiKeys.[id].vue
  • src/services/apikeys.ts
  • supabase/functions/_backend/public/apikey/delete.ts
  • supabase/functions/_backend/public/apikey/get.ts
  • tests/apikeys.test.ts

Comment thread tests/apikeys.test.ts Outdated
@jihadMo jihadMo force-pushed the codex/route-apikey-ui-writes branch 2 times, most recently from 925e3b9 to 6d5fe12 Compare May 11, 2026 10:30
Copy link
Copy Markdown

@Flob365 Flob365 left a comment

Choose a reason for hiding this comment

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

Thanks for moving the API key mutations behind the backend route. One blocker I found from the latest CI run: bun typecheck fails in supabase/functions/_backend/public/apikey/delete.ts after the new getApiKeyLookupFilter() helper is used.

The failing job reports:

supabase/functions/_backend/public/apikey/delete.ts(...): error TS2339: Property 'id' does not exist on type 'GenericStringError'.

This looks like a Supabase type inference issue caused by passing the dynamic lookup.column union into .eq(...); the query builder can no longer prove that the selected row is an apikeys row, so data becomes GenericStringError and apikey.id is rejected.

A small fix would be to keep the runtime behavior but branch explicitly, similar to put.ts:

const baseQuery = supabase
  .from('apikeys')
  .select('id')
  .eq('user_id', auth.userId)

const apikeyQuery = /^\d+$/.test(id)
  ? baseQuery.eq('id', Number(id))
  : baseQuery.eq('key', id)

const { data: apikey, error: apikeyError } = await apikeyQuery.single()

That should preserve the cast-error avoidance while restoring the typed row shape. The same pattern may also be worth using in get.ts if the typecheck reaches that file after delete.ts is fixed.

@jihadMo jihadMo force-pushed the codex/route-apikey-ui-writes branch from 6d5fe12 to 49bccd0 Compare May 11, 2026 10:35
Copy link
Copy Markdown

@mingisrookie mingisrookie left a comment

Choose a reason for hiding this comment

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

One security tradeoff to call out: the UI changes now pass numeric IDs, which is good, but the backend/tests still deliberately support using a plaintext API key value as the :id path segment for GET/DELETE. That preserves compatibility with the old .or(key.eq...,id.eq...) behavior, but it also means raw API key material can appear in URLs, reverse-proxy logs, request traces, browser history, and test output.

If that compatibility path is required, I would document it as legacy/deprecated and keep the new UI/helper examples anchored on numeric id only. The new regression tests currently exercise /apikey/${createData.key} directly; a short comment there (or an additional test asserting the UI path uses id) would make it clearer that secret-in-path is only for backward compatibility rather than the preferred contract going forward.

@Flob365
Copy link
Copy Markdown

Flob365 commented May 11, 2026

Good point. I agree the numeric id path should be the preferred contract, and the key-as-path-segment behavior should be treated as legacy compatibility only. The UI helpers added here already call the route with numeric ids, so documenting/deprecating the plaintext-key route and making the regression tests explicitly label it as legacy would make the intent clearer.

@jihadMo
Copy link
Copy Markdown
Author

jihadMo commented May 11, 2026

Thanks, good point. I pushed c04472b to make the intent explicit: the frontend API-key helpers now accept numeric IDs only, the backend lookup helper labels plaintext-key path usage as legacy compatibility, and the regression tests are renamed/commented as legacy coverage while using getEndpointUrl().

@sonarqubecloud
Copy link
Copy Markdown

Copy link
Copy Markdown

@mingisrookie mingisrookie left a comment

Choose a reason for hiding this comment

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

Verified the follow-up at c04472b. The new frontend service helpers now take a numeric ApiKeyId and build routes from that id, the UI callers pass apiKey.id, and the remaining key-as-URL tests/helper path are explicitly marked as legacy compatibility. I also re-ran the focused API-key regression suite successfully:

bun x vitest run tests/apikeys.test.ts → 36 passed.

The remaining full vue-tsc run is still blocked locally by unrelated CLI test/module errors (@capgo/cli/sdk resolution and an implicit-any in tests/cli-channel.test.ts), not by the touched API-key route files. From the reviewed scope, the legacy-path intent is now clear.

@WcaleNieWolny
Copy link
Copy Markdown
Contributor

Closing as AI-generated spam. Part of a 50+ PR wave of duplicate redact logs PRs from disposable accounts. If you're human, open an issue first.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants