From 7e767ecb4f9455db57bb7d550e516b1c756f2477 Mon Sep 17 00:00:00 2001 From: mrhid6 Date: Thu, 13 Aug 2026 09:26:34 +0000 Subject: [PATCH] fix: Move the API keys page off the /api prefix MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit /api-keys shares a raw string prefix with /api, and the proxies in front of this app do not all match by path segment. Nginx Proxy Manager routes /api straight to the Go server with a prefix location, so /api-keys never reached Next at all — it reached a control plane with no such route and came back as a JSON 404. Traefik's PathPrefix has the same shape of matcher, which puts the Helm ingress at risk whenever ingress.api.enabled is on. The page is /tokens now, which cannot collide with anything, and which matches the /api/tokens the REST API already publishes. The sidebar still says API Keys — the label is for the reader, the path is for the router. A permanent redirect covers anyone who bookmarked the old path today. Fixing the proxy config instead would have left the trap set for the next deployment, and for whatever sits in front of it. --- CLAUDE.md | 4 ++-- web/app/(app)/settings/page.tsx | 4 ++-- web/app/(app)/{api-keys => tokens}/page.tsx | 0 web/components/Sidebar.tsx | 2 +- web/next.config.ts | 14 ++++++++++++++ 5 files changed, 19 insertions(+), 5 deletions(-) rename web/app/(app)/{api-keys => tokens}/page.tsx (100%) diff --git a/CLAUDE.md b/CLAUDE.md index b2b8bd2..ffc090e 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -467,7 +467,7 @@ window, answering 429 with `Retry-After`; cookie sessions are untouched; it exists so a runaway script cannot take an instance down, not as the general API rate-limiting project some future ticket might build. -**The UI calls them API keys and lives at `/api-keys`, not on `/settings`.** +**The UI calls them API keys and lives at `/tokens`, not on `/settings`.** The page is reachable at **every** role, which is the whole reason it is a page: `/settings` is owner|admin throughout, so a card there hid a capability every member has. `settings.api_token_max_days` stays on `/settings` because it is @@ -1022,7 +1022,7 @@ Customer nav is three destinations — Overview, People, Billing. Settings is in | `/steps` | Reusable step library | | `/monitors`, `/monitors/new`, `/monitors/[id][/edit]` | Checks, uptime, incidents | | `/secrets`, `/secrets/[group]` | Vault | -| `/api-keys` | Personal API keys — reachable at **every** role, unlike `/settings` | +| `/tokens` | Personal API keys — reachable at **every** role, unlike `/settings` | | `/audit` | Audit log | | `/settings`, `/settings/notifications`, `/settings/license` | Members, OIDC, alerts, retention, ESO token · channels · licence | diff --git a/web/app/(app)/settings/page.tsx b/web/app/(app)/settings/page.tsx index f1688de..78d2166 100644 --- a/web/app/(app)/settings/page.tsx +++ b/web/app/(app)/settings/page.tsx @@ -297,7 +297,7 @@ export default function SettingsPage() { - {/* The cap lives here rather than on /api-keys because it is + {/* The cap lives here rather than on /tokens because it is instance policy, not one person's credentials — which is also what lets that page be reachable at every role. */}

Keys themselves are managed on{" "} - + API Keys , which every member can reach. diff --git a/web/app/(app)/api-keys/page.tsx b/web/app/(app)/tokens/page.tsx similarity index 100% rename from web/app/(app)/api-keys/page.tsx rename to web/app/(app)/tokens/page.tsx diff --git a/web/components/Sidebar.tsx b/web/components/Sidebar.tsx index 806cfab..ef316a6 100644 --- a/web/components/Sidebar.tsx +++ b/web/components/Sidebar.tsx @@ -185,7 +185,7 @@ const navGroups: NavGroup[] = [ // Not adminOnly: the API lets any member mint and revoke their own // keys, capped at their own role, so gating the page would hide a // capability they have. - { href: "/api-keys", label: "API Keys", icon: }, + { href: "/tokens", label: "API Keys", icon: }, ], }, { diff --git a/web/next.config.ts b/web/next.config.ts index 8c42919..30a10ba 100644 --- a/web/next.config.ts +++ b/web/next.config.ts @@ -8,6 +8,20 @@ const apiUrl = process.env.API_URL ?? process.env.NEXT_PUBLIC_API_URL ?? "http:/ const nextConfig: NextConfig = { output: "standalone", + async redirects() { + return [ + { + // The API keys page briefly lived at /api-keys, which every + // raw-prefix proxy in front of this app captures with its /api + // rule — nginx's `location /api` matches /api-keys, so the + // request reached the Go server and 404'd. The page is at + // /tokens now precisely because that cannot happen to it. + source: "/api-keys", + destination: "/tokens", + permanent: true, + }, + ]; + }, async rewrites() { return [ {