chore(config): update auth, env, i18n, proxy, and dependencies
- Update src/auth.ts auth configuration - Update src/env.mjs environment variable validation - Update src/i18n/request.ts locale handling - Update src/proxy.ts middleware - Update src/next-auth.d.ts type declarations - Update package.json and package-lock.json dependencies
This commit is contained in:
76
src/proxy.ts
76
src/proxy.ts
@@ -2,51 +2,15 @@ import { NextResponse } from "next/server"
|
||||
import type { NextRequest } from "next/server"
|
||||
import { getToken } from "next-auth/jwt"
|
||||
|
||||
import { Permissions } from "@/shared/types/permissions"
|
||||
|
||||
// Route prefix → minimum required permission
|
||||
// Note: /admin/announcements is covered by /admin prefix (requires school:manage)
|
||||
// Note: /announcements is accessible to all authenticated users (no permission entry needed)
|
||||
// P0 修复:原先 /teacher 和 /parent 都使用 EXAM_READ,但 student/parent 也有 EXAM_READ,
|
||||
// 导致跨角色访问漏洞(学生可访问 /teacher/*,教师可访问 /parent/*)。
|
||||
// 改为使用各角色独有的权限点,确保跨角色访问被拒绝。
|
||||
const ROUTE_PERMISSIONS: Record<string, string> = {
|
||||
"/admin": Permissions.SCHOOL_MANAGE,
|
||||
"/teacher": Permissions.EXAM_CREATE,
|
||||
"/student": Permissions.HOMEWORK_SUBMIT,
|
||||
"/parent": Permissions.DASHBOARD_PARENT_READ,
|
||||
"/management": Permissions.GRADE_MANAGE,
|
||||
}
|
||||
|
||||
// 仪表盘路由的细粒度权限(覆盖 ROUTE_PERMISSIONS 的前缀匹配)
|
||||
// 防止拥有 EXAM_READ 的学生/家长访问 /teacher/dashboard 等
|
||||
const DASHBOARD_ROUTE_PERMISSIONS: Record<string, string> = {
|
||||
"/admin/dashboard": Permissions.DASHBOARD_ADMIN_READ,
|
||||
"/teacher/dashboard": Permissions.DASHBOARD_TEACHER_READ,
|
||||
"/student/dashboard": Permissions.DASHBOARD_STUDENT_READ,
|
||||
"/parent/dashboard": Permissions.DASHBOARD_PARENT_READ,
|
||||
}
|
||||
|
||||
// 精确路由权限(优先级最高,覆盖 ROUTE_PERMISSIONS 的前缀匹配)
|
||||
// 用于将 /admin/* 下的特定页面开放给非管理员角色
|
||||
// V3.1:/admin/ai-settings 对所有 AI_CHAT 用户开放(管理自己的 private provider)
|
||||
const SPECIFIC_ROUTE_PERMISSIONS: Record<string, string> = {
|
||||
"/admin/ai-settings": Permissions.AI_CHAT,
|
||||
}
|
||||
|
||||
// API route prefix → required permission
|
||||
const API_PERMISSIONS: Record<string, string> = {
|
||||
"/api/ai/chat": Permissions.AI_CHAT,
|
||||
}
|
||||
|
||||
function resolveDefaultPath(roles: string[]): string {
|
||||
if (roles.includes("admin")) return "/admin/dashboard"
|
||||
if (roles.includes("grade_head") || roles.includes("teaching_head")) return "/teacher/dashboard"
|
||||
if (roles.includes("teacher")) return "/teacher/dashboard"
|
||||
if (roles.includes("student")) return "/student/dashboard"
|
||||
if (roles.includes("parent")) return "/parent/dashboard"
|
||||
return "/dashboard"
|
||||
}
|
||||
import { type Permission } from "@/shared/types/permissions"
|
||||
import { resolveDefaultPath } from "@/shared/lib/route-resolver"
|
||||
import { hasPermissionInBitmap } from "@/shared/lib/permission-bitmap"
|
||||
import {
|
||||
SPECIFIC_ROUTE_PERMISSIONS,
|
||||
ROUTE_PREFIX_PERMISSIONS,
|
||||
DASHBOARD_ROUTE_PERMISSIONS,
|
||||
API_ROUTE_PERMISSIONS,
|
||||
} from "@/shared/lib/route-permissions"
|
||||
|
||||
// Next.js 16 renamed `middleware` to `proxy`.
|
||||
// See: https://nextjs.org/docs/messages/middleware-to-proxy
|
||||
@@ -92,13 +56,21 @@ export async function proxy(request: NextRequest) {
|
||||
return NextResponse.redirect(new URL(defaultPath, request.url))
|
||||
}
|
||||
|
||||
const permissions: string[] = (token.permissions as string[]) ?? []
|
||||
const permissionsBitmap: string = (token.permissionsBitmap as string) ?? ""
|
||||
const roles: string[] = (token.roles as string[]) ?? []
|
||||
|
||||
/**
|
||||
* audit-P1-7:使用位图检查权限,避免每次路由检查都解码完整权限数组。
|
||||
* proxy.ts 在 edge runtime 运行,每个请求都经过这里,性能至关重要。
|
||||
*/
|
||||
function hasPermission(requiredPerm: Permission): boolean {
|
||||
return hasPermissionInBitmap(permissionsBitmap, requiredPerm)
|
||||
}
|
||||
|
||||
// Check API route permissions
|
||||
for (const [prefix, requiredPerm] of Object.entries(API_PERMISSIONS)) {
|
||||
for (const [prefix, requiredPerm] of Object.entries(API_ROUTE_PERMISSIONS)) {
|
||||
if (pathname.startsWith(prefix)) {
|
||||
if (!permissions.includes(requiredPerm)) {
|
||||
if (!hasPermission(requiredPerm)) {
|
||||
return NextResponse.json({ error: "Forbidden" }, { status: 403 })
|
||||
}
|
||||
break
|
||||
@@ -109,7 +81,7 @@ export async function proxy(request: NextRequest) {
|
||||
// 优先级 1:精确路由权限(覆盖前缀匹配,用于将 /admin/* 下特定页面开放给非管理员)
|
||||
if (Object.prototype.hasOwnProperty.call(SPECIFIC_ROUTE_PERMISSIONS, pathname)) {
|
||||
const requiredPerm = SPECIFIC_ROUTE_PERMISSIONS[pathname]
|
||||
if (!permissions.includes(requiredPerm)) {
|
||||
if (!hasPermission(requiredPerm)) {
|
||||
const defaultPath = resolveDefaultPath(roles)
|
||||
const redirectUrl = new URL(defaultPath, request.url)
|
||||
redirectUrl.searchParams.set("from", pathname)
|
||||
@@ -122,7 +94,7 @@ export async function proxy(request: NextRequest) {
|
||||
// 优先级 2:仪表盘路由的细粒度权限(防止跨角色访问仪表盘)
|
||||
if (Object.prototype.hasOwnProperty.call(DASHBOARD_ROUTE_PERMISSIONS, pathname)) {
|
||||
const requiredPerm = DASHBOARD_ROUTE_PERMISSIONS[pathname]
|
||||
if (!permissions.includes(requiredPerm)) {
|
||||
if (!hasPermission(requiredPerm)) {
|
||||
const defaultPath = resolveDefaultPath(roles)
|
||||
const redirectUrl = new URL(defaultPath, request.url)
|
||||
redirectUrl.searchParams.set("from", pathname)
|
||||
@@ -132,9 +104,9 @@ export async function proxy(request: NextRequest) {
|
||||
return NextResponse.next()
|
||||
}
|
||||
|
||||
for (const [prefix, requiredPerm] of Object.entries(ROUTE_PERMISSIONS)) {
|
||||
for (const [prefix, requiredPerm] of Object.entries(ROUTE_PREFIX_PERMISSIONS)) {
|
||||
if (pathname.startsWith(prefix)) {
|
||||
if (!permissions.includes(requiredPerm)) {
|
||||
if (!hasPermission(requiredPerm)) {
|
||||
const defaultPath = resolveDefaultPath(roles)
|
||||
// Carry original path + reason in URL so the target page can explain
|
||||
// why the user was redirected (Web Interface Guidelines: URL reflects state).
|
||||
|
||||
Reference in New Issue
Block a user