fix: protect admin routes and ignore local docs
This commit is contained in:
1 parent
6a08db8dd0
commit
17264dfc06
6 files changed
+37
-413
No files matched your search
@@ -13,3 +13,6 @@ prod.log
|
||||
|
||||
# Database backups
|
||||
db_backup_*.sql
|
||||
|
||||
# Local project documentation
|
||||
/docs/
|
||||
File diff suppressed because it is too large.
Load diff
@@ -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.
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
@@ -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
@@ -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 =
|
||||
|
||||
Reference in new issue
Block a user