656 lines
23 KiB
Markdown
656 lines
23 KiB
Markdown
# Codebase Concerns
|
|
|
|
**Analysis Date:** 2026-05-07
|
|
|
|
## Tech Debt
|
|
|
|
### Multiple Lock Files Conflict Risk
|
|
|
|
**Issue:** Three conflicting lock files coexist: `package-lock.json`, `pnpm-lock.yaml`, and `yarn.lock`.
|
|
|
|
**Files:**
|
|
- `package-lock.json` (177KB)
|
|
- `pnpm-lock.yaml` (2.3KB)
|
|
- `yarn.lock` (91KB)
|
|
|
|
**Impact:**
|
|
- Risk of installing different dependency versions across environments
|
|
- CI/CD, Docker builds, and local development may pull incompatible versions
|
|
- Lock file merge conflicts during multi-developer collaboration
|
|
- Dockerfile uses yarn exclusively, but npm/pnpm may be used locally, creating version drift
|
|
|
|
**Current State:**
|
|
- `Dockerfile` (line 12, 34) explicitly uses `yarn.lock*`
|
|
- `.gitignore` likely checks in all three files
|
|
- CLAUDE.md explicitly warns "不要混用" but provides no automation to enforce it
|
|
|
|
**Fix Approach:**
|
|
1. Choose single package manager (recommend yarn, per Dockerfile)
|
|
2. Delete `package-lock.json` and `pnpm-lock.yaml` from git history
|
|
3. Add enforcement to `.gitignore` and CI/CD (fail if multiple lock files modified)
|
|
4. Document in CLAUDE.md that ONLY yarn should be used
|
|
|
|
**Priority:** High - impacts build reproducibility and team workflow
|
|
|
|
---
|
|
|
|
### Production Build Ignores TypeScript & ESLint Errors
|
|
|
|
**Issue:** Build succeeds despite type/lint errors; production may contain undetected bugs.
|
|
|
|
**Files:**
|
|
- `next.config.mjs:16-21` — Both `ignoreDuringBuilds: true`
|
|
|
|
**Current Configuration:**
|
|
```javascript
|
|
eslint: {
|
|
ignoreDuringBuilds: true,
|
|
},
|
|
typescript: {
|
|
ignoreBuildErrors: true,
|
|
},
|
|
```
|
|
|
|
**Impact:**
|
|
- TypeScript errors (type mismatches, missing nullchecks) not caught at build time
|
|
- ESLint warnings (unused imports, unreachable code) ignored
|
|
- QA burden shifts to runtime discovery
|
|
- Breaking changes in dependencies not detected early
|
|
|
|
**Evidence:** README.md (line 121) acknowledges this: "务必在本地执行 `next lint` 与 `tsc --noEmit` 保证质量" — manual step, not enforced
|
|
|
|
**Fix Approach:**
|
|
1. Set both to `false` in production config
|
|
2. Fix all current type/lint errors (likely small count given ~27K LOC)
|
|
3. Add pre-commit hooks to `husky` to block commits with errors
|
|
4. Make lint/type checks mandatory in CI/CD
|
|
|
|
**Priority:** High - gates correctness
|
|
|
|
---
|
|
|
|
### Console Logging in Client Code (Debug Artifact)
|
|
|
|
**Issue:** Production code contains console.log/warn/error calls, exposing debug info and token prefixes.
|
|
|
|
**Files & Line Numbers:**
|
|
- `lib/api/client.ts:23` — `console.log('🔍 Token检查:', ...)`
|
|
- `lib/api/client.ts:29` — `console.warn('⚠️ 未找到auth_token...')`
|
|
- `lib/api/client.ts:35` — `console.log('📡 发送请求:', ...)`
|
|
- `lib/api/client.ts:36` — `console.log('📋 请求头:', ...)`
|
|
- `lib/api/client.ts:47` — `console.log('✅ 响应成功:', ...)`
|
|
- `lib/api/client.ts:51` — `console.error('❌ 响应错误:', ...)`
|
|
- `lib/api/client.ts:55` — `console.warn('🚫 认证失败...')`
|
|
- `lib/api/client.ts:56` — `console.log('响应详情:', ...)`
|
|
- `lib/api/upload.ts:33` — `console.log('📤 开始上传文件:', ...)`
|
|
- `lib/api/upload.ts:43` — `console.log(...上传进度...)`
|
|
- `lib/api/upload.ts:59` — `console.log('✅ 文件上传成功:', ...)`
|
|
- `lib/api/upload.ts:62` — `console.error("文件上传失败:", error)`
|
|
- Similar issues in upload multifile (lines 83, 93)
|
|
- `components/sidebar.tsx:97` — `console.error("退出登录失败:", error)`
|
|
- `app/outfits/page.tsx:72, 117, 135, 154` — console.error calls
|
|
- Many other pages: `app/*/page.tsx` contain error logging
|
|
|
|
**Impact:**
|
|
- Token prefixes exposed in browser console → XSS/developer tools leak risk
|
|
- Error details (e.g., API paths, status codes) visible to end users
|
|
- Production debugging should use structured error handling, not console
|
|
- Could be scraped by malicious actors with browser console access
|
|
|
|
**Evidence:** `lib/api/token-debug.ts` is entire debug utility with `console.log` throughout (lines 7-54), showing token prefix extraction logic
|
|
|
|
**Fix Approach:**
|
|
1. Remove all console.log/warn/error in client code, replace with structured error handling
|
|
2. Create error boundary/toast notification pattern for user-facing errors
|
|
3. Optional: Create client-side error tracking service (Sentry) for production
|
|
4. Add ESLint rule: `no-console` (already available in Next.js config, just not enabled)
|
|
|
|
**Priority:** High - security and code cleanliness
|
|
|
|
---
|
|
|
|
## Security Concerns
|
|
|
|
### Client-Side Permission Enforcement Only (Trust Boundary Violation)
|
|
|
|
**Issue:** Permissions are enforced ONLY on client; backend may not re-validate on API calls.
|
|
|
|
**Files:**
|
|
- `lib/permissions.ts:65-87` — `getUserRole()`, `hasPermission()` read from `localStorage.user_role`
|
|
- `components/sidebar.tsx:102-104` — Filter menu items client-side only
|
|
- `middleware.ts:17-44` — Token validation in middleware, but NO permission check against module being accessed
|
|
|
|
**Risk Model:**
|
|
```
|
|
User (attacker) → Modify localStorage.user_role="超级管理员" →
|
|
Sidebar shows all menus (useless) BUT
|
|
Direct API call to DELETE /api/v1/admin/users/123 → Backend accepts if no re-check
|
|
```
|
|
|
|
**Current Evidence:**
|
|
- `lib/api/users.ts:24-43` — `getUsers()` makes raw API call; no permission guard in client code
|
|
- No evidence of backend role check in Django API (not inspectable from frontend code, but CRITICAL to verify)
|
|
|
|
**Impact:**
|
|
- **CRITICAL:** If backend doesn't re-validate role on each endpoint, attackers can:
|
|
- Modify localStorage role → make admin API calls
|
|
- Bypass middleware token check (SSR can't access localStorage) → direct API calls with stolen token
|
|
- Perform unauthorized operations (delete users, change settings, etc.)
|
|
|
|
**Trust Boundary Rule:** Backend MUST re-check permission for EVERY admin API call, regardless of client claims.
|
|
|
|
**Fix Approach:**
|
|
1. **URGENT:** Audit Django backend API (`qy_lty/lib/api/v1/admin/`) to confirm all endpoints validate user role/permissions
|
|
2. Document in CLAUDE.md: "Client-side permission filters are UI courtesy only; backend MUST validate on all admin/ endpoints"
|
|
3. Add explicit checks in frontend error handling for 403 Forbidden responses
|
|
4. Consider adding request logging/monitoring to catch unauthorized attempts
|
|
|
|
**Priority:** CRITICAL - security vulnerability if backend doesn't re-check
|
|
|
|
---
|
|
|
|
### Token Storage in localStorage (XSS Vulnerability)
|
|
|
|
**Issue:** Auth token stored in `localStorage` instead of secure HttpOnly cookie; vulnerable to XSS.
|
|
|
|
**Files:**
|
|
- `lib/api/auth.ts:48-59` — Stores token in localStorage AND sets js-cookie
|
|
- `lib/api/client.ts:21-22` — Reads token from localStorage in request interceptor
|
|
- `middleware.ts:32` — Token read from Cookie (correct), but localStorage is fallback
|
|
|
|
**Current Implementation:**
|
|
```typescript
|
|
// Stored in BOTH localStorage and cookie
|
|
localStorage.setItem("auth_token", token); // XSS risk
|
|
Cookies.set("auth_token", token, { expires: 7, path: '/' }); // No HttpOnly flag
|
|
```
|
|
|
|
**Impact:**
|
|
- XSS attack can steal token from localStorage
|
|
- js-cookie (js-cookie, no HttpOnly flag) also vulnerable to XSS
|
|
- 7-day expiry without rotation = long XSS window
|
|
- `localStorage.user_role` also stored unencrypted (attackable)
|
|
|
|
**Proper Secure Pattern:**
|
|
- HttpOnly, Secure, SameSite cookies (set by backend during login)
|
|
- Token never exposed to JavaScript
|
|
- Client reads from Bearer token response, never stores in accessible storage
|
|
|
|
**Fix Approach:**
|
|
1. Backend should set HttpOnly cookie during login response
|
|
2. Remove localStorage token storage entirely
|
|
3. Keep only sealed HTTP-only cookie for auth
|
|
4. If SSR requires token access, use secure session storage (not localStorage)
|
|
5. Add CSP (Content-Security-Policy) headers to mitigate XSS
|
|
|
|
**Priority:** High - significant security risk
|
|
|
|
---
|
|
|
|
### No Token Rotation / Refresh Strategy
|
|
|
|
**Issue:** Token expires in 7 days, no refresh token or rotation mechanism visible.
|
|
|
|
**Files:**
|
|
- `lib/api/auth.ts:63` — `Cookies.set("auth_token", token, { expires: 7, path: '/' })`
|
|
- `lib/api/client.ts:54-60` — 401 handler just redirects to /login, doesn't attempt refresh
|
|
|
|
**Impact:**
|
|
- User activity > 7 days → auto-logout (UX degradation)
|
|
- No short-lived access token + long-lived refresh token pattern
|
|
- Stolen token valid for full 7 days
|
|
- No token revocation (logout doesn't invalidate server-side if stateless JWT)
|
|
|
|
**Fix Approach:**
|
|
1. Implement short-lived access token (15-30 min) + refresh token (7-14 days)
|
|
2. Refresh interceptor: if 401, attempt token refresh before redirecting to /login
|
|
3. Backend session should blacklist tokens on logout
|
|
4. Document token lifecycle in CLAUDE.md
|
|
|
|
**Priority:** Medium - acceptable for internal admin tool, but should improve for production
|
|
|
|
---
|
|
|
|
### No Input Validation on File Upload
|
|
|
|
**Issue:** File uploads accept any mimetype/size without validation.
|
|
|
|
**Files:**
|
|
- `lib/api/upload.ts:25-65` — `uploadFile()` accepts File object, no size/type checks in client
|
|
- `components/ui/file-upload.tsx` — Likely has upload UI, needs validation
|
|
|
|
**Impact:**
|
|
- Malicious files (executables, oversized) may reach backend
|
|
- Backend must validate, but client-side check is first defense
|
|
- No maximum file size enforced client-side
|
|
|
|
**Fix Approach:**
|
|
1. Add file size limits (e.g., max 50MB)
|
|
2. Whitelist MIME types (images: image/jpeg, image/png, image/webp only)
|
|
3. Show user error before upload attempt
|
|
4. Verify backend enforces same limits
|
|
|
|
**Priority:** Medium - backend should catch it, but client should be first defense
|
|
|
|
---
|
|
|
|
## Fragile Areas
|
|
|
|
### Large Pages with Mixed Concerns (1000+ line files)
|
|
|
|
**Issue:** Several pages handle data fetching, UI rendering, and business logic in single component.
|
|
|
|
**Large Files:**
|
|
- `app/affinity/page.tsx` (1005 lines) — Affinity rules, levels, interactions all in one page
|
|
- `components/ui/sidebar.tsx` (763 lines) — Navigation logic, permission filtering, logout in one component
|
|
- `app/songs/[id]/page.tsx` (651 lines) — Song detail page, editing, deletion mixed
|
|
- `app/songs/page.tsx` (644 lines) — Song list, search, pagination, CRUD operations
|
|
|
|
**Fragility Risks:**
|
|
- Hard to test individual business logic
|
|
- Changes to one feature (e.g., affinity rules) risk breaking another (levels)
|
|
- Difficult to reuse UI logic across pages
|
|
- High cognitive load for maintenance
|
|
|
|
**Safe Modification Pattern:**
|
|
1. Extract data-fetching logic to `lib/api/` hooks or services
|
|
2. Create smaller sub-components (e.g., `<AffinityRuleSection>`, `<AffinityLevelSection>`)
|
|
3. Use custom hooks for state management per feature
|
|
4. Keep pages as composition only
|
|
|
|
**Example Refactor:**
|
|
```typescript
|
|
// Current: 1005-line page
|
|
// After:
|
|
export default function AffinityPage() {
|
|
return (
|
|
<DashboardShell>
|
|
<DashboardHeader />
|
|
<AffinityRulesSection />
|
|
<AffinityLevelsSection />
|
|
<InteractionLogsSection />
|
|
</DashboardShell>
|
|
)
|
|
}
|
|
```
|
|
|
|
**Priority:** Medium - refactor as part of feature changes, not urgent
|
|
|
|
---
|
|
|
|
### No Error Boundaries (Next.js error.tsx Files Missing)
|
|
|
|
**Issue:** No error boundary components for graceful error handling in App Router.
|
|
|
|
**Files:**
|
|
- `app/` has NO `error.tsx` files
|
|
- Only some leaf routes have `loading.tsx` (e.g., `app/outfits/loading.tsx`)
|
|
|
|
**Current Error Handling:**
|
|
- Pages catch errors in `try/catch`, show toast notifications
|
|
- Uncaught errors propagate, may show raw error UI
|
|
- No fallback UI for API failures
|
|
|
|
**Impact:**
|
|
- User sees blank page or browser error on network failure
|
|
- No recovery mechanism (retry button, fallback content)
|
|
- Log data lost if error page crashes
|
|
|
|
**Next.js Error Boundary Pattern:**
|
|
```typescript
|
|
// app/error.tsx (catches errors in all children)
|
|
'use client'
|
|
export default function Error({ error, reset }) {
|
|
return (
|
|
<div>
|
|
<h1>Something went wrong</h1>
|
|
<button onClick={() => reset()}>Try again</button>
|
|
</div>
|
|
)
|
|
}
|
|
```
|
|
|
|
**Fix Approach:**
|
|
1. Add `app/error.tsx` for app-level errors
|
|
2. Add segment-level `error.tsx` for feature-specific handling (e.g., `app/outfits/error.tsx`)
|
|
3. Keep try/catch for API calls, render fallback UI
|
|
4. Add `app/not-found.tsx` for 404 routes
|
|
|
|
**Priority:** Low - pages have basic error handling via toasts, but UX could improve
|
|
|
|
---
|
|
|
|
### Hardcoded API Base URL Fallback
|
|
|
|
**Issue:** API client falls back to hardcoded URL if env var not set.
|
|
|
|
**Files:**
|
|
- `lib/api/client.ts:9` — `API_BASE_URL = process.env.NEXT_PUBLIC_API_BASE_URL || "http://localhost:8000/api"`
|
|
|
|
**Risk:**
|
|
- If `NEXT_PUBLIC_API_BASE_URL` not set in production, silently uses localhost
|
|
- Requests fail silently in production, confusing to debug
|
|
- No warning log when fallback is used
|
|
|
|
**Fix Approach:**
|
|
1. Remove fallback; throw error if env var not set at build time
|
|
2. Use `if (!process.env.NEXT_PUBLIC_API_BASE_URL) throw new Error("...")`
|
|
3. Document required env vars in deployment guide
|
|
|
|
**Priority:** Low - environmental issue, unlikely in production if properly configured
|
|
|
|
---
|
|
|
|
## Performance Bottlenecks
|
|
|
|
### No Memoization on Permission Checks
|
|
|
|
**Issue:** `hasPermission()`, `getUserRole()` called on every render without memoization.
|
|
|
|
**Files:**
|
|
- `components/sidebar.tsx:102-104` — Calls `hasPermission(item.module)` in `.filter()` on every render
|
|
- `lib/permissions.ts:65-87` — No caching; reads localStorage every time
|
|
|
|
**Current Pattern:**
|
|
```typescript
|
|
const visibleAiItems = mounted ? aiMenuItems.filter((item) => hasPermission(item.module)) : []
|
|
```
|
|
|
|
**Impact:**
|
|
- Sidebar re-renders, re-filters items, re-reads localStorage unnecessarily
|
|
- Minimal impact on small arrays (3-15 items), but scales poorly
|
|
|
|
**Fix Approach:**
|
|
1. Memoize `getUserRole()` result in sidebar:
|
|
```typescript
|
|
const userRole = useMemo(() => getUserRole(), [mounted])
|
|
const visibleItems = useMemo(() =>
|
|
aiMenuItems.filter(item => hasPermission(item.module)),
|
|
[userRole]
|
|
)
|
|
```
|
|
2. Or move to custom hook: `const { role, allowedModules } = usePermissions()`
|
|
|
|
**Priority:** Low - not a visible performance issue, but good practice
|
|
|
|
---
|
|
|
|
## Cross-Repo Coupling Risks
|
|
|
|
### Strong Runtime Dependency on qy_lty Backend
|
|
|
|
**Issue:** Front-end depends on specific API contract from `qy_lty/` Django backend; contract drift risk.
|
|
|
|
**Files:**
|
|
- `lib/api/users.ts:6-21` — Maps backend `ParadiseUser` to `User` type (field mapping)
|
|
- `lib/api/outfits.ts` (not read, but expected) — Maps backend outfit schema
|
|
- ALL API modules expect specific response structure from `/api/v1/admin/*`
|
|
|
|
**Coupling Points:**
|
|
- Backend field names: `username`, `email`, `phone_number`, `date_joined`, `last_login`, `is_superuser`, `is_staff`
|
|
- Response structure: `{ results: [...], count: number }` (Django REST Framework default)
|
|
- Endpoint paths: `/v1/admin/login/`, `/user/`, `/common/upload/`
|
|
- Token storage key in Redis: `admin_token:{token}` (per CLAUDE.md)
|
|
|
|
**Risk Scenario:**
|
|
1. Backend dev changes `/v1/admin/users/` response format
|
|
2. Frontend still sends old field names or expects old response shape
|
|
3. Silent failures or type mismatches
|
|
4. Detected late in testing or production
|
|
|
|
**Mitigation in Place:**
|
|
- CLAUDE.md (line 34) requires both repos update `docs/修改记录.md` when contract changes
|
|
- But no automated contract testing (OpenAPI, TypeScript codegen)
|
|
|
|
**Fix Approach:**
|
|
1. **Shared types/API contract** (OpenAPI/GraphQL schema) versioned in git
|
|
2. Generate TypeScript types from backend OpenAPI spec (e.g., with Swagger-Codegen)
|
|
3. Add pre-commit hook to detect schema changes
|
|
4. Document in CLAUDE.md: "Any API response structure change requires updating both repos' docs"
|
|
|
|
**Priority:** Medium - mitigated by manual process, but automatable
|
|
|
|
---
|
|
|
|
## Dependencies at Risk
|
|
|
|
### No Enforcement of Dependency Updates
|
|
|
|
**Issue:** Many dependencies pinned to `latest`, creating unpredictable updates.
|
|
|
|
**Files:**
|
|
- `package.json:12, 56, 63` — `"latest"` version specifiers
|
|
|
|
**Current Versions Using "latest":**
|
|
```json
|
|
"@hookform/resolvers": "latest",
|
|
"react-hook-form": "latest",
|
|
"recharts": "latest",
|
|
"zod": "latest"
|
|
```
|
|
|
|
**Impact:**
|
|
- `npm install` at different times installs different major versions
|
|
- Breaking changes in `react-hook-form` or `recharts` not caught by lock file
|
|
- Tests pass locally, fail in CI if new major version published
|
|
|
|
**Fix Approach:**
|
|
1. Replace all `"latest"` with specific versions (e.g., `"^7.0.0"` for caret range)
|
|
2. Use Dependabot or renovate to automate safe updates
|
|
3. Pin devDependencies to exact versions
|
|
|
|
**Example:**
|
|
```json
|
|
"@hookform/resolvers": "^3.3.4",
|
|
"react-hook-form": "^7.51.0",
|
|
"recharts": "^2.12.0",
|
|
"zod": "^3.22.0"
|
|
```
|
|
|
|
**Priority:** Medium - not urgent, but good practice
|
|
|
|
---
|
|
|
|
### Unused Dependencies or Overlapping Packages
|
|
|
|
**Issue:** Unclear if all installed packages are actively used.
|
|
|
|
**Potential Issues:**
|
|
- Multiple notification libraries: `sonner` + `@radix-ui/react-toast` (which is used?)
|
|
- Figure out which is preferred and remove duplication
|
|
|
|
**Files:**
|
|
- `package.json:36, 59` — Both toast packages listed
|
|
|
|
**Fix Approach:**
|
|
1. Audit each dependency in `package.json`
|
|
2. Remove unused packages
|
|
3. Consolidate duplicate functionality
|
|
|
|
**Priority:** Low - cleanup task
|
|
|
|
---
|
|
|
|
## Test Coverage Gaps
|
|
|
|
### No Tests Found
|
|
|
|
**Issue:** No Jest/Vitest configuration or test files detected in codebase.
|
|
|
|
**Expected Test Files:**
|
|
- `*.test.ts`, `*.spec.ts`, `*.test.tsx`, `*.spec.tsx` not found
|
|
- No `jest.config.*`, `vitest.config.*`
|
|
|
|
**Impact:**
|
|
- Complex logic (permission matrix, API adapters) not tested
|
|
- Refactoring risky (no regression protection)
|
|
- Onboarding new developers without safety net
|
|
- No confidence in critical paths (auth, user management)
|
|
|
|
**Critical Areas Needing Tests:**
|
|
- `lib/permissions.ts` — Permission matrix logic (many branches)
|
|
- `lib/api/client.ts` — Request/response interceptors (error handling paths)
|
|
- `lib/api/users.ts` — User mapping logic (backend schema mismatches)
|
|
- Custom hooks in pages (data fetching, state management)
|
|
|
|
**Fix Approach:**
|
|
1. Set up Jest or Vitest (Vitest recommended for ESM + modern tooling)
|
|
2. Create basic test for `lib/permissions.ts`:
|
|
```typescript
|
|
// lib/permissions.test.ts
|
|
describe('Permission checks', () => {
|
|
it('should allow super admin all modules', () => {
|
|
// Mock localStorage with 超级管理员
|
|
expect(hasPermission('users')).toBe(true)
|
|
expect(hasPermission('ai-model')).toBe(true)
|
|
})
|
|
|
|
it('should restrict content admin to content modules', () => {
|
|
// Mock localStorage with 内容管理员
|
|
expect(hasPermission('users')).toBe(false)
|
|
expect(hasPermission('outfits')).toBe(true)
|
|
})
|
|
})
|
|
```
|
|
3. Aim for 80%+ coverage of critical API functions
|
|
|
|
**Priority:** Medium - important for long-term maintainability
|
|
|
|
---
|
|
|
|
## Missing Critical Features
|
|
|
|
### No Logout Token Revocation
|
|
|
|
**Issue:** `logout()` clears client-side storage but doesn't invalidate server-side token.
|
|
|
|
**Files:**
|
|
- `lib/api/auth.ts:108-131` — `logout()` calls API endpoint, then clears localStorage
|
|
|
|
**Problem:**
|
|
- If using stateless JWT, backend has no record of logout
|
|
- Token remains valid until expiry (7 days)
|
|
- Attacker with stolen token can still use it after target user "logs out"
|
|
|
|
**Expected Behavior:**
|
|
- Server should blacklist token on logout (Redis blacklist or DB flag)
|
|
- Verify endpoint `/v1/admin/logout/` actually invalidates token
|
|
|
|
**Fix Approach:**
|
|
1. Audit backend logout endpoint to confirm token is invalidated
|
|
2. Add token blacklist to backend (Redis: token → blacklisted, TTL=7 days)
|
|
3. Check token against blacklist on every request
|
|
4. Document in CLAUDE.md
|
|
|
|
**Priority:** High - security issue if not handled on backend
|
|
|
|
---
|
|
|
|
### No Rate Limiting on Client (Brute Force Risk)
|
|
|
|
**Issue:** Login page may be vulnerable to brute force attacks.
|
|
|
|
**Files:**
|
|
- `app/login/page.tsx` — Likely has login form
|
|
- `lib/api/auth.ts:25-40` — No rate limiting before API call
|
|
|
|
**Impact:**
|
|
- Attacker can spam login attempts to guess password
|
|
- Backend must implement rate limiting, but client can't spam if it has throttling
|
|
|
|
**Fix Approach:**
|
|
1. Client-side: Disable login button for 30 seconds after failed attempt
|
|
2. Backend: Implement account lockout (3 failed attempts = 10 min lockout)
|
|
3. Monitor login failures in logs
|
|
|
|
**Priority:** Medium - backend responsibility, but client should assist
|
|
|
|
---
|
|
|
|
## Build Configuration Issues
|
|
|
|
### Next.js Standalone Output May Have Deployment Pitfalls
|
|
|
|
**Issue:** Standalone mode used, but edge case behaviors not fully documented.
|
|
|
|
**Files:**
|
|
- `next.config.mjs:30` — `output: 'standalone'`
|
|
|
|
**What Standalone Does:**
|
|
- Bundles all dependencies into `.next/standalone` folder
|
|
- Reduces Docker image size (~100MB vs ~500MB)
|
|
- Requires explicit `COPY .next .next` in Dockerfile (correctly done)
|
|
|
|
**Potential Issues:**
|
|
- Public assets not automatically copied; must COPY separately (done in Dockerfile:44-45)
|
|
- `next.config.mjs` must be manually copied (done in Dockerfile:46)
|
|
- Environment variables must be passed at runtime, not build time
|
|
- Debugging bundle issues harder (bundled deps not in node_modules)
|
|
|
|
**Current Dockerfile (lines 44-46) handles this correctly:**
|
|
```dockerfile
|
|
COPY --from=builder /app/.next ./.next
|
|
COPY --from=builder /app/public ./public
|
|
COPY --from=builder /app/next.config.mjs ./
|
|
```
|
|
|
|
**Risk:**
|
|
- If dependencies add new output files in future versions, they won't be copied
|
|
- Node version mismatch between builder (22.10.0) and runner (22.10.0) is correct but fragile
|
|
|
|
**Fix Approach:**
|
|
1. Document standalone output caveats in deployment guide
|
|
2. Test Docker image monthly to ensure no drift in builder/runner
|
|
3. Consider base image pinning: `FROM node:22.10.0-alpine@sha256:...`
|
|
|
|
**Priority:** Low - current setup is correct, just needs documentation
|
|
|
|
---
|
|
|
|
### No Pre-commit Hooks or Husky Setup
|
|
|
|
**Issue:** No automated checks before commits; developers can commit broken code.
|
|
|
|
**Files:**
|
|
- No `.husky/` directory
|
|
- No `pre-commit` config
|
|
- CLAUDE.md doesn't mention hook setup
|
|
|
|
**Risk:**
|
|
- Developer commits console.log, broken types, or lint errors
|
|
- CI catches them later, but feedback loop is slow
|
|
- No enforcement of modification record updates (CLAUDE.md requirement)
|
|
|
|
**Fix Approach:**
|
|
1. Install husky: `npm install husky --save-dev`
|
|
2. Setup hook: `npx husky install`
|
|
3. Create `.husky/pre-commit`:
|
|
```bash
|
|
#!/bin/sh
|
|
npm run lint && npm run type-check
|
|
```
|
|
4. Create `.husky/prepare-commit-msg` to remind user about docs/修改记录.md
|
|
|
|
**Priority:** Low - improvement for team workflow
|
|
|
|
---
|
|
|
|
## Summary Table
|
|
|
|
| Category | Issue | Severity | Fix Effort |
|
|
|----------|-------|----------|-----------|
|
|
| Security | Client-only permissions (backend trust) | CRITICAL | High |
|
|
| Security | Token in localStorage (XSS) | High | Medium |
|
|
| Security | No token revocation on logout | High | High |
|
|
| Tech Debt | Lock file conflicts | High | Low |
|
|
| Tech Debt | Build ignores TS/ESLint errors | High | Low |
|
|
| Tech Debt | Console logging in production | High | Low |
|
|
| Fragility | Large 1000+ line pages | Medium | Medium |
|
|
| Performance | No memoization on permissions | Low | Low |
|
|
| Testing | Zero test coverage | Medium | High |
|
|
| Build | Standalone output not fully documented | Low | Low |
|
|
|
|
---
|
|
|
|
*Concerns audit: 2026-05-07*
|