feat(portal-shell): v2.1 P0 auth + middleware + login + graphql proxy
- 新增 ARCHITECTURE.md v3.0:portal-shell 架构权威文档 涵盖 §3.4 V3-A2/A3 认证链、§4 GraphQL 联邦、§5 安全、 §6 部署、§10 P0-P3 验收清单 - 新增 middleware.ts:认证 + 路由门禁 httpOnly cookie edu_session(JWT)读取 DEV_MODE 合成 dev-user/teacher 身份(NODE_ENV!=production && NEXT_PUBLIC_DEV_MODE=true) 生产模式 jose JWKS RS256 验签(iss/aud 校验) 路由权限位图注入 x-user-id/x-user-role/x-user-permissions 头 /shell/** 强制 checkRoutePermission,拒绝跳 /shell/forbidden - 新增 instrumentation.ts:生产环境 DEV_MODE 强制 false 防止生产环境误开 DEV_MODE 合成身份 - 新增 app/api/auth/login/route.ts + logout/route.ts 登录走 api-gateway /v1/iam/login 设置 httpOnly + Secure + SameSite=Strict cookie - 新增 app/api/graphql/route.ts:同域 GraphQL 代理 转发到 apollo-router,注入 router-authorization 头 - 新增 app/login/page.tsx + login-form.tsx zod 表单校验,next 参数支持 - 新增 app/shell/forbidden/page.tsx:403 页面 - 更新 route-permissions.ts:补全 P0 路由权限映射 - 更新 permission-bitmap.ts(shared-ts):位图编码/解码 - 更新 apollo-client.ts:DEV_MODE APQ 关闭,错误处理 - 更新 config-fetcher.ts:config-service 直连降级 - 更新 ApolloProvider.tsx:SSR/RSC 兼容 - 更新 eslint.config.js:design-tokens/no-hardcoded-fonts 白名单调整
This commit is contained in:
@@ -267,14 +267,44 @@ export const DASHBOARD_ROUTE_PERMISSIONS: Record<
|
||||
*
|
||||
* 用于 /api/* 路径的权限校验。
|
||||
* 注意:API Route 通常需要更严格的权限校验,因为它们直接操作数据。
|
||||
*
|
||||
* P0-2(ARCHITECTURE.md §6.2 §3.4 V3-A3):新增 auth/graphql 公开端点。
|
||||
*/
|
||||
export const API_ROUTE_PERMISSIONS: Record<string, RoutePermissionConfig> = {
|
||||
// 错误上报端点:所有登录用户可访问
|
||||
// 错误上报端点:所有登录用户可访问(空 config 表示登录即可)
|
||||
"/api/log": {},
|
||||
// 健康检查:公开
|
||||
"/api/healthz": {},
|
||||
"/api/health": {},
|
||||
"/api/ready": {},
|
||||
// 认证端点:公开(未登录也要能调登录接口)
|
||||
"/api/auth/login": {},
|
||||
"/api/auth/logout": {},
|
||||
// GraphQL 同域代理:登录即可(细粒度由后端 resolver 把关)
|
||||
"/api/graphql": {},
|
||||
};
|
||||
|
||||
/**
|
||||
* 5. 公共路由白名单(P0-2,ARCHITECTURE.md §6.2 §3.4 V3-A3 / §11.7 红线 #5)
|
||||
*
|
||||
* 这些路由允许匿名访问(登录前/无身份也能访问)。
|
||||
* middleware 对白名单路由跳过身份校验,直接放行。
|
||||
*
|
||||
* 注意:白名单外的路由,未登录访问 → middleware 重定向到 /login。
|
||||
*/
|
||||
export const PUBLIC_ROUTES: readonly string[] = [
|
||||
"/",
|
||||
"/login",
|
||||
"/shell/forbidden",
|
||||
"/api/health",
|
||||
"/api/healthz",
|
||||
"/api/ready",
|
||||
"/api/log",
|
||||
"/api/auth/login",
|
||||
"/api/auth/logout",
|
||||
"/api/graphql",
|
||||
];
|
||||
|
||||
/**
|
||||
* 校验权限配置的合法性(开发时辅助)
|
||||
*
|
||||
@@ -343,6 +373,11 @@ export function checkRoutePermission(
|
||||
userBitmap: string,
|
||||
userRole: Role,
|
||||
): RoutePermissionResult {
|
||||
// 0. 公共路由白名单优先(含 /shell/forbidden 自身,避免循环重定向)
|
||||
if (PUBLIC_ROUTES.includes(pathname)) {
|
||||
return { allowed: true, matchedPath: "PUBLIC" };
|
||||
}
|
||||
|
||||
// 1. 匹配精确路由
|
||||
const exactConfig = EXACT_ROUTE_PERMISSIONS[pathname];
|
||||
if (exactConfig) {
|
||||
@@ -368,14 +403,23 @@ export function checkRoutePermission(
|
||||
if (apiConfig) {
|
||||
return evaluateConfig(apiConfig, userBitmap, userRole, pathname);
|
||||
}
|
||||
// 未配置的 API 路由默认拒绝
|
||||
// 未配置的 API 路由默认拒绝(fail-closed,§11.7 红线 #5)
|
||||
return {
|
||||
allowed: false,
|
||||
reason: "no_config",
|
||||
};
|
||||
}
|
||||
|
||||
// 5. 未匹配任何配置:默认放行(如 / /login /shell/forbidden 等公共路由)
|
||||
// 5. /shell/** 下未登记路由 → 默认拒绝(fail-closed,§3.4 V3-A3 / §11.7 红线 #5)
|
||||
// 防止"幽灵路由"绕过门禁;新增路由必须显式登记到 EXACT/PREFIX/DASHBOARD 表
|
||||
if (pathname.startsWith("/shell/") || pathname === "/shell") {
|
||||
return {
|
||||
allowed: false,
|
||||
reason: "no_config",
|
||||
};
|
||||
}
|
||||
|
||||
// 6. 其他未匹配路由(如 /favicon.ico / 静态资源)默认放行
|
||||
return { allowed: true };
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user