UNPKG

mira-app-server

Version:

Mira Server - standalone server application using mira-app-core

116 lines 4.68 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.canAccessLibrary = canAccessLibrary; exports.createHttpPermissionMiddleware = createHttpPermissionMiddleware; // ============================================================ // 权限配置表 — 集中管理,避免逐路由修改 // ============================================================ // 无需认证的公开路由(method + path,相对于 /api mount point) const PUBLIC_ROUTES = new Set([ 'GET /health', 'POST /auth/login', 'POST /auth/register', 'GET /settings', ]); // 不需要鉴权的路由前缀(支持动态路径参数) const PUBLIC_PREFIXES = [ '/user/avatar/', // 服务端日志流(SSE):供本地控制台订阅后端运行日志,GET 只读,仅本地使用 '/logs/stream', ]; // 需要做素材库 allowedRoles 校验的路由前缀 const LIBRARY_SCOPED_PREFIXES = [ '/files/', '/tags/', '/folders/', '/database/', '/devices/', '/fs/', '/statistics/', ]; // ============================================================ // 工具函数 // ============================================================ function canAccessLibrary(config, userRole) { if (!userRole) return true; if (!config?.allowedRoles || config.allowedRoles.length === 0) return true; return config.allowedRoles.includes(userRole); } function extractLibraryId(req) { return req.params?.id || req.params?.libraryId || req.query?.libraryId || req.body?.libraryId; } function isPublicRoute(method, path) { if (PUBLIC_ROUTES.has(`${method} ${path}`)) return true; if (method === 'GET' && PUBLIC_PREFIXES.some(prefix => path.startsWith(prefix))) return true; return false; } function isLibraryScoped(path) { return LIBRARY_SCOPED_PREFIXES.some(prefix => path.startsWith(prefix)); } /** * 创建统一权限中间件 * * @param authService 认证服务(来自 AuthRouter.getAuthService()) * @param settingsManager 设置管理器 * @param getLibraryStorage 延迟获取 LibraryStorage(因为 HttpServer 构造时 libraries 尚未初始化) */ function createHttpPermissionMiddleware(authService, settingsManager, getLibraryStorage) { return async (req, res, next) => { const method = req.method.toUpperCase(); const path = req.path; // mount 在 /api 下,所以是相对路径 // 1. 公开路由直接放行 if (isPublicRoute(method, path)) { return next(); } // 2. authRequired=false 时放行 const settings = settingsManager.getSettings(); if (!settings.authRequired) { return next(); } // 3. 提取并校验 token(支持 Authorization header 和 query 参数) const token = req.headers.authorization?.replace('Bearer ', '') || req.query.token; if (!token) { return res.status(401).json({ code: 401, message: '未提供认证令牌', data: null }); } let user; try { user = await authService.validateToken(token); } catch { return res.status(500).json({ code: 500, message: '认证服务错误', data: null }); } if (!user) { return res.status(401).json({ code: 401, message: '无效或过期的认证令牌', data: null }); } req.user = authService.getUserInfo(user); // 4. 库级别权限检查 const libraryStorage = getLibraryStorage(); if (!libraryStorage) return next(); // libraries 尚未初始化,放行 if (isLibraryScoped(path)) { const libraryId = extractLibraryId(req); if (libraryId) { const libConfig = libraryStorage.getLibraryConfig(libraryId); if (!canAccessLibrary(libConfig, req.user?.role)) { return res.status(403).json({ code: 403, message: '权限不足,无法访问该素材库', data: null }); } } } // 5. PUT/DELETE /libraries/:id 也需要库级别检查 if ((method === 'PUT' || method === 'DELETE') && path.match(/^\/libraries\/[^/]+/)) { const libraryId = req.params?.id; if (libraryId) { const libConfig = libraryStorage.getLibraryConfig(libraryId); if (!canAccessLibrary(libConfig, req.user?.role)) { return res.status(403).json({ code: 403, message: '权限不足,无法访问该素材库', data: null }); } } } next(); }; } //# sourceMappingURL=permission.js.map