fix: protect admin routes and ignore local docs

This commit is contained in:
Simo committed 2026-07-11 21:35:37 +02:00
1 parent 6a08db8dd0
commit 17264dfc06
6 files changed
+37 -413

No files matched your search

+3
View File
@@ -13,3 +13,6 @@ prod.log
# Database backups # Database backups
db_backup_*.sql db_backup_*.sql
# Local project documentation
/docs/
@@ -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<Array<{ importer: string; specifier: string }>>`
- 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:[email protected]: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<TArgs, TResult>(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:[email protected]: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`.
@@ -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.
+14
View File
@@ -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);
});
});
+9
View File
@@ -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;
}
+11 -1
View File
@@ -1,10 +1,20 @@
import { type NextRequest, NextResponse } from "next/server"; 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 // 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 // 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 // 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. // 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); const headers = new Headers(req.headers);
headers.set("x-pathname", req.nextUrl.pathname); headers.set("x-pathname", req.nextUrl.pathname);
const ip = const ip =