diff --git a/CHANGELOG.md b/CHANGELOG.md index b9c95b5..7434503 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,16 @@ ## 2026-05-14 +- [新增] 权限系统重构:工单细粒度权限(`tickets:create` 手动建单、`tickets:import` 导入、`tickets:export` 导出),`tickets:write` 缩窄为编辑/删除 +- [新增] 报告三层权限:`reports:read`(查看列表+预览)、`reports:download`(下载)、`reports:create`(新建+生成文档+删除) +- [新增] `/api/auth/me` 返回 `permissions` 字段,前端根据权限数组控制按钮和导航显隐 +- [新增] 角色管理 UI 权限项从 8 个扩展到 14 个(含 `api-keys:read/write`) +- [修复] 角色管理页面:admin/operator/viewer 标记为"内置"不可删除但可编辑,admin 在 API 层禁止删除 +- [修复] 报告 API 路由全部补全权限检查(此前列表/预览/下载/删除均无权限检查) +- [修复] 导出工单 API 新增 `tickets:export` 权限检查(此前仅验证登录态) +- [修复] POST 创建报告权限从错误的 `reports:read` 修正为 `reports:create` +- [修复] `reports:write` 全面替换为 `reports:create` +- [修复] 种子数据迁移逻辑不再每次启动覆盖用户自定义权限,仅首次安装时创建默认权限 - [修复] SSO 新用户免登录失败:根页面 `getCurrentUser()` 改为直接 `redirect('/dashboard')`,由中间件统一处理共享 JWT 认证(与 assets-ai 行为一致) - [修复] `next.config.ts` 添加 `ldapts` 到 `serverExternalPackages`,确保 Next.js standalone 构建包含 LLDAP 客户端模块,避免 `ldapUserExists()` 因模块缺失失败导致 SSO 自动创建用户静默中断 - [调整] 全局证书切换:Cloudflare Origin CA → Let's Encrypt(`/etc/letsencrypt/live/www.tlyq.ai/`),覆盖全部 7 个子域名,nginx 8 个站点配置同步更新 diff --git a/src/app/(app)/settings/roles/page.tsx b/src/app/(app)/settings/roles/page.tsx index 4feb742..ea74834 100644 --- a/src/app/(app)/settings/roles/page.tsx +++ b/src/app/(app)/settings/roles/page.tsx @@ -11,6 +11,8 @@ interface Role { created_at: string } +const BUILTIN_ROLES = ['admin', 'operator', 'viewer'] + const allPermissions = [ { key: 'tickets:read', label: '查看工单' }, { key: 'tickets:create', label: '手动建单' }, @@ -96,8 +98,6 @@ export default function RolesPage() { } catch { return '无权限' } } - const builtinRoles = ['admin', 'operator', 'viewer'] - return (
@@ -115,12 +115,19 @@ export default function RolesPage() { {roles.map(r => ( {r.name} - {r.display_name} + +
+ {r.display_name} + {BUILTIN_ROLES.includes(r.name) && ( + 内置 + )} +
+ {formatPermissions(r.permissions)}
- {!builtinRoles.includes(r.name) && ( + {!BUILTIN_ROLES.includes(r.name) && ( )}
diff --git a/src/app/api/roles/[id]/route.ts b/src/app/api/roles/[id]/route.ts index ea1024e..681da32 100644 --- a/src/app/api/roles/[id]/route.ts +++ b/src/app/api/roles/[id]/route.ts @@ -15,7 +15,7 @@ export async function PUT(request: NextRequest, { params }: { params: Promise<{ const body = await request.json() const db = getDb() - const existing = db.prepare('SELECT * FROM roles WHERE id = ?').get(id) + const existing = db.prepare('SELECT * FROM roles WHERE id = ?').get(id) as any if (!existing) return NextResponse.json({ error: '角色不存在' }, { status: 404 }) const fields: string[] = [] diff --git a/src/lib/db-schema.ts b/src/lib/db-schema.ts index 890a434..abf5657 100644 --- a/src/lib/db-schema.ts +++ b/src/lib/db-schema.ts @@ -63,20 +63,7 @@ export function initDatabase(): void { { name: 'viewer', display_name: '查看者', permissions: '["tickets:read","tickets:export","reports:read","reports:download"]' }, ] for (const r of roles) { - const ex = db.prepare('SELECT id, permissions FROM roles WHERE name = ?').get(r.name) as { id: number; permissions: string } | undefined - if (!ex) { - db.prepare('INSERT INTO roles (name, display_name, permissions) VALUES (?, ?, ?)').run(r.name, r.display_name, r.permissions) - } else { - // 迁移:更新已有角色的权限(追加新权限,保留已有自定义) - const newPerms = JSON.parse(r.permissions) as string[] - let existingPerms: string[] = [] - try { existingPerms = JSON.parse(ex.permissions) } catch {} - if (!existingPerms.includes('*')) { - for (const p of newPerms) { - if (!existingPerms.includes(p)) existingPerms.push(p) - } - db.prepare('UPDATE roles SET permissions = ? WHERE id = ?').run(JSON.stringify(existingPerms), ex.id) - } - } + const ex = db.prepare('SELECT id FROM roles WHERE name = ?').get(r.name) + if (!ex) db.prepare('INSERT INTO roles (name, display_name, permissions) VALUES (?, ?, ?)').run(r.name, r.display_name, r.permissions) } }