/** * 服务端路由权限检查工具 * 用于在 action 中检查用户是否有权限访问目标路由 */ import { getUserRoutesByRole, type MenuItem } from './user-routes'; /** * 从 MenuItem 数组中提取所有路径(包括子路由) */ function extractAllPaths(menuItems: MenuItem[]): string[] { const paths: string[] = []; function traverse(items: MenuItem[]) { for (const item of items) { paths.push(item.path); if (item.children && item.children.length > 0) { traverse(item.children); } } } traverse(menuItems); return paths; } /** * 检查路径段是否看起来像动态ID */ function isDynamicIdSegment(segment: string): boolean { // 纯数字 if (/^\d+$/.test(segment)) { return true; } // UUID格式 if (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(segment)) { return true; } // 包含数字的混合ID if (/\d/.test(segment) && !/^[a-z]+(-[a-z]+)*$/i.test(segment)) { return true; } return false; } /** * 检查路径是否在允许列表中 */ function isPathAllowed(pathname: string, allowedPaths: string[]): boolean { // 精确匹配 if (allowedPaths.includes(pathname)) { return true; } // 动态路由匹配 for (const allowedPath of allowedPaths) { if (pathname.startsWith(allowedPath + '/')) { const subPath = pathname.substring(allowedPath.length + 1); const segments = subPath.split('/'); const firstSegment = segments[0]; if (isDynamicIdSegment(firstSegment)) { return true; } } } // 根路径 if (pathname === '/') { return true; } return false; } export interface CheckRoutePermissionResult { /** 是否有权限访问 */ allowed: boolean; /** 错误信息(当 allowed 为 false 时) */ error?: string; /** 用户允许访问的所有路由 */ allowedPaths?: string[]; } /** * 检查用户是否有权限访问指定路由 * * @param targetPath 目标路由路径(如 '/contract-draft/1') * @param userRole 用户角色 * @param jwt JWT token * @returns 权限检查结果 * * @example * ```ts * // 在 action 中使用 * export async function action({ request }: ActionFunctionArgs) { * const { userInfo, frontendJWT } = await getUserSession(request); * * // 检查用户是否有权限访问目标路由 * const permissionCheck = await checkRoutePermission( * '/contract-draft/1', * userInfo.role, * frontendJWT * ); * * if (!permissionCheck.allowed) { * return Response.json({ error: permissionCheck.error }, { status: 403 }); * } * * // 继续执行 action 逻辑... * } * ``` */ export async function checkRoutePermission( targetPath: string, userRole: string, jwt?: string ): Promise { if (!jwt) { return { allowed: false, error: '未提供认证信息' }; } try { // 获取用户的路由权限(包含隐藏路由,用于权限校验) const routesResult = await getUserRoutesByRole(userRole, jwt, true); if (!routesResult.success || !routesResult.data) { return { allowed: false, error: routesResult.error || '获取用户权限失败' }; } // 提取所有允许的路径 const allowedPaths = extractAllPaths(routesResult.data); // 检查目标路径是否在允许列表中 const allowed = isPathAllowed(targetPath, allowedPaths); if (!allowed) { console.warn(`[checkRoutePermission] 用户无权访问路由: ${targetPath}`); console.warn(`[checkRoutePermission] 用户允许的路由: ${allowedPaths.join(', ')}`); } return { allowed, error: allowed ? undefined : '您没有权限访问目标页面', allowedPaths }; } catch (error) { console.error('[checkRoutePermission] 权限检查失败:', error); return { allowed: false, error: '权限检查失败,请稍后重试' }; } } /** * 简化版:直接检查是否有权限,抛出 403 Response * * @example * ```ts * export async function action({ request }: ActionFunctionArgs) { * const { userInfo, frontendJWT } = await getUserSession(request); * * // 如果没有权限,会自动抛出 403 Response * await requireRoutePermission('/contract-draft/1', userInfo.role, frontendJWT); * * // 继续执行 action 逻辑... * } * ``` */ export async function requireRoutePermission( targetPath: string, userRole: string, jwt?: string ): Promise { const result = await checkRoutePermission(targetPath, userRole, jwt); if (!result.allowed) { throw new Response(result.error || '无权访问', { status: 403 }); } }