From 17264dfc06542ddfaab0c34e148b29ac95fa6049 Mon Sep 17 00:00:00 2001 From: simoleo89 Date: Sat, 11 Jul 2026 21:35:37 +0200 Subject: [PATCH] fix: protect admin routes and ignore local docs --- .gitignore | 3 + .../2026-07-11-admin-feature-recovery.md | 345 ------------------ ...026-07-11-admin-feature-recovery-design.md | 67 ---- src/lib/proxy-access.test.ts | 14 + src/lib/proxy-access.ts | 9 + src/proxy.ts | 12 +- 6 files changed, 37 insertions(+), 413 deletions(-) delete mode 100644 docs/superpowers/plans/2026-07-11-admin-feature-recovery.md delete mode 100644 docs/superpowers/specs/2026-07-11-admin-feature-recovery-design.md create mode 100644 src/lib/proxy-access.test.ts create mode 100644 src/lib/proxy-access.ts diff --git a/.gitignore b/.gitignore index 7d3edaee5b..61bcb0f7b5 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,6 @@ prod.log # Database backups db_backup_*.sql + +# Local project documentation +/docs/ diff --git a/docs/superpowers/plans/2026-07-11-admin-feature-recovery.md b/docs/superpowers/plans/2026-07-11-admin-feature-recovery.md deleted file mode 100644 index 491402d456..0000000000 --- a/docs/superpowers/plans/2026-07-11-admin-feature-recovery.md +++ /dev/null @@ -1,345 +0,0 @@ -# EpicNext CMS Admin Feature Recovery Implementation Plan - -> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. - -**Goal:** Restore all admin functionality removed by `4d515bc` and make the complete EpicNext CMS application pass tests, typecheck, and production build. - -**Architecture:** Revert the destructive revert, inventory unresolved imports, then port the smallest compatible shared and domain layers from `E:\Users\simol\Desktop\habbo-next`. EpicNext CMS remains authoritative for routing, authentication, Prisma models, and error handling. - -**Tech Stack:** Next.js 16, React 19, TypeScript, Prisma 7/MariaDB, NextAuth 5, Vitest, Tailwind CSS 4, Radix UI/shadcn-style primitives. - -## Global Constraints - -- Never delete a feature merely to make compilation succeed. -- Treat `E:\Users\simol\Desktop\habbo-next` as read-only reference material. -- Preserve all security, PayPal, pagination, and public contrast changes from `4a1e111`. -- Adapt every database operation to EpicNext CMS's local Prisma schema. -- Every server mutation must use the existing staff authorization path and validated input. -- Do not push until `pnpm test`, `pnpm typecheck`, and `pnpm build` all exit zero. - ---- - -### Task 1: Restore the Removed Feature Set and Capture the Failure Inventory - -**Files:** -- Restore: files deleted or rewound by commit `4d515bc` -- Create: `scripts/check-local-imports.mjs` -- Test: `src/lib/local-imports.test.ts` - -**Interfaces:** -- Produces: `findMissingLocalImports(root: string): Promise>` -- Produces: a deterministic unresolved-import report used by every later task. - -- [ ] **Step 1: Write a failing local-import test** - -Create a fixture containing `import { Button } from "@/components/ui/button"` and assert that `findMissingLocalImports()` reports it when no matching file exists. - -- [ ] **Step 2: Verify RED** - -Run: `pnpm vitest run src/lib/local-imports.test.ts` - -Expected: failure because the scanner does not exist. - -- [ ] **Step 3: Implement the scanner** - -Scan `.ts` and `.tsx` files, resolve `@/` against `src`, and test `.ts`, `.tsx`, `.js`, `.jsx`, and `/index` candidates. Return sorted importer/specifier pairs and expose the same function through `scripts/check-local-imports.mjs` for CI output. - -- [ ] **Step 4: Verify GREEN** - -Run: `pnpm vitest run src/lib/local-imports.test.ts` - -Expected: fixture test passes. - -- [ ] **Step 5: Restore functionality** - -Run: `git revert 4d515bc` - -Resolve overlaps by preserving current security/contrast implementations while restoring every admin consumer and action from `41be683`. - -- [ ] **Step 6: Capture the build and import failures** - -Run: - -```powershell -node scripts/check-local-imports.mjs -$env:DATABASE_URL='mysql://user:password@127.0.0.1:3306/atomcms' -pnpm prisma:generate -pnpm build -``` - -Expected: build fails for unresolved modules, and the scanner lists the complete local dependency inventory. - -- [ ] **Step 7: Commit** - -```powershell -git add scripts/check-local-imports.mjs src/lib/local-imports.test.ts src -git commit -m "fix: restore incomplete admin feature set" -``` - -### Task 2: Restore Shared UI, Hooks, Utilities, Types, and Packages - -**Files:** -- Create/modify: `src/components/ui/*.tsx` -- Create: `src/hooks/use-server-action.ts` -- Create/modify: `src/lib/utils.ts` -- Create/modify: `src/types/index.ts` -- Modify: `package.json` -- Modify: `pnpm-lock.yaml` -- Test: `src/hooks/use-server-action.test.tsx` -- Test: `src/lib/utils.test.ts` - -**Interfaces:** -- Produces: `cn(...inputs: ClassValue[]): string` -- Produces: `useServerAction(action, options)` with pending, result, and error state. -- Produces: shadcn-compatible exports consumed by restored admin pages, including Button, Badge, Card, Dialog, Input, Label, Select, Table, Tabs, Textarea, Checkbox, Command, Popover, Switch, Tooltip, Skeleton, and AlertDialog. - -- [ ] **Step 1: Add failing utility and hook tests** - -Assert that `cn("a", false && "b", "c")` returns `"a c"`, conflicting Tailwind classes merge correctly, successful actions expose results, and thrown actions expose a safe error while clearing pending state. - -- [ ] **Step 2: Verify RED** - -Run: `pnpm vitest run src/lib/utils.test.ts src/hooks/use-server-action.test.tsx` - -Expected: missing-module failures. - -- [ ] **Step 3: Port the minimal shared layer** - -Use the corresponding Habbo Next files as reference. Remove locale routing and unrelated providers. Add only packages directly imported by restored consumers, including Radix primitives, `class-variance-authority`, `clsx`, `tailwind-merge`, `cmdk`, `sonner`, and the three `@dnd-kit` packages. - -- [ ] **Step 4: Verify GREEN and rescan** - -Run: - -```powershell -pnpm vitest run src/lib/utils.test.ts src/hooks/use-server-action.test.tsx -node scripts/check-local-imports.mjs -pnpm typecheck -``` - -Expected: shared-layer tests pass; remaining missing imports are domain-specific. - -- [ ] **Step 5: Commit** - -```powershell -git add package.json pnpm-lock.yaml src/components/ui src/hooks src/lib/utils.ts src/lib/utils.test.ts src/types -git commit -m "fix: restore admin shared component layer" -``` - -### Task 3: Restore Catalog and Furni Import Domains - -**Files:** -- Create/modify: `src/components/admin/catalog/**` -- Create/modify: `src/components/admin/catalog-manager/**` -- Create/modify: `src/lib/furni/**` -- Create/modify: `src/lib/client-cache/**` -- Create/modify: `src/lib/catalog-layouts.ts` -- Create/modify: `src/lib/move-suggestions.ts` -- Modify: `src/actions/catalog.ts` -- Modify: `src/actions/catalog-bc.ts` -- Modify: `src/actions/catalog-items.ts` -- Modify: `src/actions/import-badges.ts` -- Modify: `src/actions/import-furni.ts` -- Test: focused tests under `src/lib/furni/*.test.ts` and `src/actions/catalog-items.test.ts` - -**Interfaces:** -- Produces: catalog page/item CRUD actions returning `{ ok: true; data } | { ok: false; error }`. -- Produces: safe furni classname/path helpers and import validation. - -- [ ] **Step 1: Add failing tests** - -Cover invalid catalog identifiers, unauthorized mutation, safe furni classnames, duplicate imported items, and transactional bulk updates. - -- [ ] **Step 2: Verify RED** - -Run: `pnpm vitest run src/lib/furni src/actions/catalog-items.test.ts` - -- [ ] **Step 3: Port and adapt implementations** - -Use Habbo Next catalog/import helpers as reference. Replace `[locale]` paths with EpicNext routes, use `requireStaffRateLimited()`, and map every Prisma field against `prisma/schema.prisma` before writing queries. - -- [ ] **Step 4: Verify GREEN and checkpoint build** - -Run: - -```powershell -pnpm vitest run src/lib/furni src/actions/catalog-items.test.ts -node scripts/check-local-imports.mjs -pnpm typecheck -``` - -- [ ] **Step 5: Commit** - -```powershell -git add src/components/admin/catalog src/components/admin/catalog-manager src/lib/furni src/lib/client-cache src/lib/catalog-layouts.ts src/lib/move-suggestions.ts src/actions/catalog*.ts src/actions/import-*.ts -git commit -m "fix: restore catalog and furni administration" -``` - -### Task 4: Restore Permissions, Users, and Rooms - -**Files:** -- Create/modify: `src/lib/permissions.ts` -- Create/modify: `src/lib/admin-list.ts` -- Create/modify: `src/lib/imager.ts` -- Create/modify: `src/lib/services/watch.ts` -- Modify: `src/actions/permissions.ts` -- Modify: `src/actions/bulk-users.ts` -- Modify: `src/actions/rooms.ts` -- Modify: `src/actions/multi-account-detect.ts` -- Test: `src/lib/permissions.test.ts`, `src/actions/permissions.test.ts`, `src/actions/rooms.test.ts` - -**Interfaces:** -- Produces: permission reads and mutations based on EpicNext rank tables. -- Produces: room and bulk-user actions with staff checks and validated numeric IDs. - -- [ ] **Step 1: Add failing authorization and schema tests** - -Test denial without a staff session, denial without the named permission, preservation of immutable permission fields, positive integer IDs, and atomic room updates. - -- [ ] **Step 2: Verify RED** - -Run: `pnpm vitest run src/lib/permissions.test.ts src/actions/permissions.test.ts src/actions/rooms.test.ts` - -- [ ] **Step 3: Implement against the EpicNext schema** - -Use Habbo Next only for workflow structure. Preserve EpicNext's `resolveStaffUser` path and adapt permission/rank queries to actual local Prisma models. - -- [ ] **Step 4: Verify GREEN and typecheck** - -Run: - -```powershell -pnpm vitest run src/lib/permissions.test.ts src/actions/permissions.test.ts src/actions/rooms.test.ts -node scripts/check-local-imports.mjs -pnpm typecheck -``` - -- [ ] **Step 5: Commit** - -```powershell -git add src/lib/permissions.ts src/lib/admin-list.ts src/lib/imager.ts src/lib/services/watch.ts src/actions/permissions.ts src/actions/bulk-users.ts src/actions/rooms.ts src/actions/multi-account-detect.ts src/app/admin/permissions src/app/admin/users src/app/admin/rooms -git commit -m "fix: restore permissions users and room administration" -``` - -### Task 5: Restore Tickets, Translations, and Sounds - -**Files:** -- Create/modify: `src/actions/tickets.ts` -- Create/modify: `src/actions/ticket-templates.ts` -- Create/modify: `src/actions/translations.ts` -- Create/modify: `src/lib/services/ticket-replies.ts` -- Create/modify: sound and translation helpers identified by the import scanner. -- Test: `src/actions/tickets.test.ts`, `src/actions/translations.test.ts`, existing `src/lib/services/ticket-replies.test.ts` - -**Interfaces:** -- Produces: ticket assignment/status/priority/reply actions. -- Produces: template CRUD actions. -- Produces: validated CMS/client translation writes. -- Produces: sound metadata operations required by admin pages. - -- [ ] **Step 1: Add failing domain tests** - -Cover cross-ticket reply rejection, invalid status transitions, template ownership/permission, translation key validation, path traversal rejection, and unsupported sound metadata. - -- [ ] **Step 2: Verify RED** - -Run: `pnpm vitest run src/actions/tickets.test.ts src/actions/translations.test.ts src/lib/services/ticket-replies.test.ts` - -- [ ] **Step 3: Port compatible implementations** - -Use Habbo Next actions as behavioral reference, route errors through `logServerError`, validate all identifiers and paths, and retain EpicNext's existing ticket-reply service protections. - -- [ ] **Step 4: Verify GREEN and clear imports** - -Run: - -```powershell -pnpm vitest run src/actions/tickets.test.ts src/actions/translations.test.ts src/lib/services/ticket-replies.test.ts -node scripts/check-local-imports.mjs -pnpm typecheck -``` - -Expected: zero unresolved local imports. - -- [ ] **Step 5: Commit** - -```powershell -git add src/actions/tickets.ts src/actions/ticket-templates.ts src/actions/translations.ts src/lib/services src/app/admin/tickets src/app/admin/translations src/app/admin/sounds -git commit -m "fix: restore ticket translation and sound administration" -``` - -### Task 6: Integration Hardening and Navigation - -**Files:** -- Modify: restored admin navigation and messages. -- Modify: action files with inconsistent result or authorization contracts. -- Test: `src/lib/admin-action-contract.test.ts` - -**Interfaces:** -- Produces: consistent admin action contract and reachable navigation for every restored page. - -- [ ] **Step 1: Add failing contract checks** - -Assert every restored action module begins with `"use server"`, imports an approved staff guard for mutations, and every restored route has a navigation label in all supported message files. - -- [ ] **Step 2: Verify RED** - -Run: `pnpm vitest run src/lib/admin-action-contract.test.ts` - -- [ ] **Step 3: Correct integration gaps** - -Add missing guards, stable result shapes, narrow `revalidatePath()` calls, navigation entries, and translations without changing unrelated public behavior. - -- [ ] **Step 4: Verify GREEN** - -Run: `pnpm vitest run src/lib/admin-action-contract.test.ts` - -- [ ] **Step 5: Commit** - -```powershell -git add src/actions src/components/admin src/app/admin src/messages src/lib/admin-action-contract.test.ts -git commit -m "fix: harden restored admin integrations" -``` - -### Task 7: Full Verification and Publication - -**Files:** -- Modify only files required by fresh verification failures. - -**Interfaces:** -- Produces: a complete, buildable `main` with restored admin functionality. - -- [ ] **Step 1: Clean generated state and generate Prisma** - -```powershell -Remove-Item -Recurse -Force .next -ErrorAction SilentlyContinue -$env:DATABASE_URL='mysql://user:password@127.0.0.1:3306/atomcms' -pnpm prisma:generate -``` - -- [ ] **Step 2: Run complete gates** - -```powershell -node scripts/check-local-imports.mjs -pnpm test -pnpm typecheck -pnpm build -git diff --check nextjs/main..HEAD -``` - -Expected: zero missing imports, zero failed tests, typecheck exit 0, build exit 0, and clean diff check. - -- [ ] **Step 3: Audit retained fixes** - -Confirm PayPal capture ownership/idempotency, staff session normalization, safe pagination, and readable theme variables remain present and covered by their existing tests. - -- [ ] **Step 4: Fetch and publish only by fast-forward** - -```powershell -git fetch nextjs main -git rev-list --left-right --count nextjs/main...HEAD -git push nextjs HEAD:main -git ls-remote nextjs refs/heads/main -``` - -Expected before push: `0 N`. Expected after push: remote SHA equals local `HEAD`. diff --git a/docs/superpowers/specs/2026-07-11-admin-feature-recovery-design.md b/docs/superpowers/specs/2026-07-11-admin-feature-recovery-design.md deleted file mode 100644 index faceb6c557..0000000000 --- a/docs/superpowers/specs/2026-07-11-admin-feature-recovery-design.md +++ /dev/null @@ -1,67 +0,0 @@ -# EpicNext CMS Admin Feature Recovery Design - -## Objective - -Restore every admin page and server action introduced by commit `41be683` without reintroducing unresolved imports or sacrificing the security, PayPal, pagination, and public-theme contrast fixes already on `main`. - -`E:\Users\simol\Desktop\habbo-next` is a read-only architectural reference. Its files may guide implementation, but EpicNext CMS keeps its own routing, authentication, Prisma schema, conventions, and public behavior. - -## Constraints - -- No feature may be removed merely to make compilation succeed. -- Do not modify `E:\Users\simol\Desktop\habbo-next`. -- Do not overwrite or depend on uncommitted changes in the user's original AtomCMS checkout. -- Preserve the existing security, PayPal, pagination, and contrast changes. -- Adapt referenced code to EpicNext CMS rather than copying it blindly. -- Publish to `main` only after tests, typecheck, and production build pass. - -## Recovery Architecture - -Recovery proceeds in dependency order. First restore the shared foundation used by the imported admin pages: package dependencies, UI primitives, hooks, utilities, common types, permissions helpers, cache helpers, and focused domain services. Restore feature groups only after that foundation compiles. - -Feature groups are: - -1. Catalog and furni import. -2. Permissions and users. -3. Rooms and room furni. -4. Tickets and ticket templates. -5. Translations. -6. Sounds and remaining admin navigation. - -Each group must expose explicit server-action interfaces and use the existing EpicNext CMS staff authorization guard. Data access must match the local Prisma schema; Habbo Next models and field names are references, not assumptions. - -## Data and Authorization Flow - -Client admin components invoke typed server actions. Each mutation validates input, resolves the signed-in staff member through the existing guard, checks the relevant permission or minimum rank, performs a Prisma transaction where multiple writes must be atomic, and returns a stable result shape suitable for the existing `useServerAction` hook. Successful mutations invalidate the narrowest affected route or cache key. - -User-controlled paths, URLs, identifiers, JSON, and imported archive content are validated before filesystem or database access. Errors returned to clients remain safe and actionable; detailed failures go through the existing server logger. - -## Source-Recovery Method - -Restore the feature consumers from `41be683`, then generate a complete unresolved-import inventory. For every missing module: - -- Prefer an existing EpicNext CMS implementation when available. -- Otherwise use the corresponding Habbo Next file as a behavioral reference. -- Remove locale-specific routing assumptions and unsupported integrations. -- Port only the API needed by current consumers. -- Add the smallest compatible dependency set to `package.json`. - -This avoids both extremes: deleting consumers to hide failures and copying the entire Habbo Next application into EpicNext CMS. - -## Testing Strategy - -The previously failing production build is the top-level regression test. Lower-level tests cover permission checks, validation, transactional behavior, error results, and domain helpers. Recovery follows red-green cycles: expose one missing dependency or failing behavior, add the minimal compatible implementation, then rerun its focused test. - -Every feature-group checkpoint requires: - -- Focused Vitest tests for new helpers and action behavior. -- `pnpm typecheck` after regenerating Next route metadata where necessary. -- `pnpm test` with zero failures. -- `pnpm build` with a valid build-time database configuration. -- An unresolved-import scan with zero missing local modules. - -The final build may log database connection warnings when run against a deliberately unreachable validation URL, but it must exit successfully and produce the expected route manifest. - -## Delivery - -Work occurs in the existing isolated publication worktree on a dedicated recovery branch. Commits are grouped by independently verifiable dependency or feature boundaries. Nothing is pushed to `main` until the complete restored application passes all final gates and the remote branch is confirmed unchanged before a fast-forward push. diff --git a/src/lib/proxy-access.test.ts b/src/lib/proxy-access.test.ts new file mode 100644 index 0000000000..310b3ac9af --- /dev/null +++ b/src/lib/proxy-access.test.ts @@ -0,0 +1,14 @@ +import { describe, expect, it } from "vitest"; +import { shouldRedirectAdminRequest } from "./proxy-access"; + +describe("shouldRedirectAdminRequest", () => { + it("redirects anonymous and non-staff admin requests before rendering", () => { + expect(shouldRedirectAdminRequest("/admin", null)).toBe(true); + expect(shouldRedirectAdminRequest("/admin/tickets", { rank: 1 })).toBe(true); + }); + + it("allows staff admin requests and never affects public routes", () => { + expect(shouldRedirectAdminRequest("/admin/permissions", { rank: 7 })).toBe(false); + expect(shouldRedirectAdminRequest("/news", null)).toBe(false); + }); +}); diff --git a/src/lib/proxy-access.ts b/src/lib/proxy-access.ts new file mode 100644 index 0000000000..0a8cbecbfb --- /dev/null +++ b/src/lib/proxy-access.ts @@ -0,0 +1,9 @@ +export interface ProxyToken { + rank?: unknown; +} + +export function shouldRedirectAdminRequest(pathname: string, token: ProxyToken | null): boolean { + if (pathname !== "/admin" && !pathname.startsWith("/admin/")) return false; + const rank = typeof token?.rank === "number" ? token.rank : Number(token?.rank); + return !Number.isInteger(rank) || rank < 7; +} diff --git a/src/proxy.ts b/src/proxy.ts index 35f467db2b..4f1aa10e30 100644 --- a/src/proxy.ts +++ b/src/proxy.ts @@ -1,10 +1,20 @@ import { type NextRequest, NextResponse } from "next/server"; +import { getToken } from "next-auth/jwt"; +import { shouldRedirectAdminRequest } from "@/lib/proxy-access"; // Edge proxy (formerly "middleware"): Prisma can't run here, so we only forward // the request path (so server components / the access guard can read it via // headers()) and normalize the real client IP. The DB-backed banned/maintenance // checks happen in src/lib/access-guard.ts (Node runtime) from the root layout. -export function proxy(req: NextRequest) { +export async function proxy(req: NextRequest) { + if (req.nextUrl.pathname === "/admin" || req.nextUrl.pathname.startsWith("/admin/")) { + const secret = process.env.AUTH_SECRET; + const token = secret ? await getToken({ req, secret }) : null; + if (shouldRedirectAdminRequest(req.nextUrl.pathname, token)) { + return NextResponse.redirect(new URL("/login", req.url)); + } + } + const headers = new Headers(req.headers); headers.set("x-pathname", req.nextUrl.pathname); const ip =