feat: update audit platform workspace
This commit is contained in:
@@ -0,0 +1,171 @@
|
||||
# Route Permission Guard 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:** Prevent users from opening hidden/unauthorized frontend pages by manually entering URLs.
|
||||
|
||||
**Architecture:** Keep backend API permission checks unchanged, and add a server-side Next.js page-route guard inside the authenticated `(audit)` layout. The guard uses backend user route authorization, normalizes feature subpages to their controlled route, and redirects unauthorized page requests before rendering content.
|
||||
|
||||
**Tech Stack:** Next.js 15 App Router, TypeScript, Node test runner, existing RBAC `/api/rbac/user/routes` data.
|
||||
|
||||
---
|
||||
|
||||
### Task 1: Add Route Access Unit Tests
|
||||
|
||||
**Files:**
|
||||
- Create: `legal-platform-frontend/lib/auth/route-access.ts`
|
||||
- Test: `legal-platform-frontend/tests/govdoc-audit/route-access.test.mts`
|
||||
- Modify: `legal-platform-frontend/lib/utils/route-alias.shared.js`
|
||||
|
||||
- [ ] **Step 1: Write failing tests for direct URL authorization**
|
||||
|
||||
Create tests that assert route guard behavior:
|
||||
|
||||
```ts
|
||||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
|
||||
import { isRoutePathAllowed, flattenMenuPaths } from "../../lib/auth/route-access.ts";
|
||||
import { normalizeRoutePathForPermission } from "../../lib/utils/route-alias.shared.js";
|
||||
|
||||
const allowedPaths = flattenMenuPaths([
|
||||
{ id: "home", title: "系统概览", path: "/home", icon: "", order: 1 },
|
||||
{ id: "rules", title: "规则管理", path: "/rules", icon: "", order: 2 },
|
||||
{ id: "contract", title: "合同管理", path: "/contract-template", icon: "", order: 3, children: [
|
||||
{ id: "contract-list", title: "模板列表", path: "/contract-template/list", icon: "", order: 1 },
|
||||
] },
|
||||
]);
|
||||
|
||||
test("route guard allows exact authorized route", () => {
|
||||
assert.equal(isRoutePathAllowed("/rules", allowedPaths), true);
|
||||
});
|
||||
|
||||
test("route guard allows feature detail page through alias", () => {
|
||||
assert.equal(isRoutePathAllowed("/rules-test/detail?packId=3&ruleId=MM-ENT-001", allowedPaths), true);
|
||||
});
|
||||
|
||||
test("route guard rejects direct URL when route is hidden from role", () => {
|
||||
assert.equal(isRoutePathAllowed("/tenants", allowedPaths), false);
|
||||
});
|
||||
|
||||
test("route guard maps current govdoc pages to govdoc root permission", () => {
|
||||
assert.equal(normalizeRoutePathForPermission("/govdoc/audits"), "/govdoc");
|
||||
assert.equal(normalizeRoutePathForPermission("/govdoc/detail/A-108bce03"), "/govdoc");
|
||||
});
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Run tests and verify failure**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
cd legal-platform-frontend
|
||||
node --experimental-strip-types --test tests/govdoc-audit/route-access.test.mts
|
||||
```
|
||||
|
||||
Expected: fail because `route-access.ts` does not exist yet and `/govdoc/*` is not normalized.
|
||||
|
||||
### Task 2: Implement Pure Route Access Helper
|
||||
|
||||
**Files:**
|
||||
- Create: `legal-platform-frontend/lib/auth/route-access.ts`
|
||||
- Modify: `legal-platform-frontend/lib/utils/route-alias.shared.js`
|
||||
|
||||
- [ ] **Step 1: Implement helper**
|
||||
|
||||
Add functions:
|
||||
- `flattenMenuPaths(menuItems)` to extract authorized paths from route tree.
|
||||
- `normalizeRequestPath(pathname)` to strip query/hash/trailing slash and apply aliases.
|
||||
- `isRoutePathAllowed(pathname, allowedPaths)` to allow exact authorized routes, authorized route subtrees, and `/home`.
|
||||
|
||||
- [ ] **Step 2: Add `/govdoc/*` route alias**
|
||||
|
||||
Map current internal document pages to `/govdoc`, matching the existing legacy `/govdoc-audit/*` behavior.
|
||||
|
||||
- [ ] **Step 3: Run focused tests**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
cd legal-platform-frontend
|
||||
node --experimental-strip-types --test tests/govdoc-audit/route-access.test.mts tests/govdoc-audit/home-routing.test.mts
|
||||
```
|
||||
|
||||
Expected: all tests pass.
|
||||
|
||||
### Task 3: Wire Server-Side Guard Into Authenticated Layout
|
||||
|
||||
**Files:**
|
||||
- Modify: `legal-platform-frontend/app/(audit)/layout.tsx`
|
||||
|
||||
- [ ] **Step 1: Read current pathname**
|
||||
|
||||
Use `headers().get("x-pathname")`, which is already set by `middleware.ts`.
|
||||
|
||||
- [ ] **Step 2: Fetch user authorized routes**
|
||||
|
||||
Call `getUserRoutesByRole(userRole, frontendJWT, true)` from server layout.
|
||||
|
||||
- [ ] **Step 3: Redirect unauthorized pages**
|
||||
|
||||
If routes load successfully and `isRoutePathAllowed(pathname, flattenMenuPaths(routes))` is false, redirect to:
|
||||
|
||||
```ts
|
||||
/home?error=insufficient_permissions
|
||||
```
|
||||
|
||||
Do not block `/home`.
|
||||
|
||||
- [ ] **Step 4: Fail closed when routes cannot load**
|
||||
|
||||
If route loading fails because the session is expired, redirect to login. For non-auth failures, redirect to `/home?error=permission_check_failed` except when already on `/home`.
|
||||
|
||||
### Task 4: Verify Build and Regression
|
||||
|
||||
**Files:**
|
||||
- No new production files unless tests expose type issues.
|
||||
|
||||
- [ ] **Step 1: Run focused tests**
|
||||
|
||||
```bash
|
||||
cd legal-platform-frontend
|
||||
node --experimental-strip-types --test tests/govdoc-audit/route-access.test.mts tests/govdoc-audit/home-routing.test.mts
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Run full existing frontend node tests**
|
||||
|
||||
```bash
|
||||
cd legal-platform-frontend
|
||||
node --experimental-strip-types --test tests/govdoc-audit/*.test.mts
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Run lint**
|
||||
|
||||
```bash
|
||||
cd legal-platform-frontend
|
||||
npm run lint -- --quiet
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Run production build**
|
||||
|
||||
```bash
|
||||
cd legal-platform-frontend
|
||||
npm run build
|
||||
```
|
||||
|
||||
### Task 5: Manual Acceptance Checklist
|
||||
|
||||
- [ ] A role without `/tenants` cannot open `/tenants` by URL.
|
||||
- [ ] A role without `/rules` cannot open `/rules-test/list` or `/rules-test/detail?...` by URL.
|
||||
- [ ] A role with `/rules` can still open `/rules-test/list` and `/rules-test/detail?...`.
|
||||
- [ ] `/home` remains reachable for logged-in users.
|
||||
- [ ] Sidebar menu hiding remains unchanged.
|
||||
- [ ] Backend API 403 behavior remains independent.
|
||||
|
||||
---
|
||||
|
||||
### Self-Review
|
||||
|
||||
- Spec coverage: Direct URL access is blocked at the server layout before page render.
|
||||
- Placeholder scan: No TBD/TODO remains in implementation steps.
|
||||
- Type consistency: Helper consumes existing `MenuItem` shape from `lib/auth/user-routes.ts`.
|
||||
Reference in New Issue
Block a user