feat: 流水线三级权限体系(手动多选+通配符授权)+ 菜单分级 + SSE 认证修复

权限模型(借鉴 Jenkins Role-Strategy 与云效,默认拒绝):
- 三级权限:查看(流水线+成功/失败日志)/ 发布(+构建、批量发布、重试)/ 管理(+配置、成员);admin 全局豁免
- 权限组(管理员配置):默认手动多选流水线(免正则,操作友好);高级模式仅 * 通配符(cex-*)
- 单流水线成员直授权作为例外补充;AI 批量发布逐 job 校验,越权明确拒绝

后端:
- 新增 PipelineRole/PipelineMember 表与 /api/pipeline-roles、/api/pipelines/:id/members CRUD
- RequirePipelineAccess/RequireRunAccess 中间件挂全流水线与运行路由;列表按可见性过滤并附 accessLevel/canRetry
- /api/me 会话接口;logs/stream SSE 支持 ?token= 认证(原先完全免鉴权)并校验查看权限
- 凭证/主机/集群/构建节点/工具/Webhook/Jenkins/GitOps/插件/设置全部 AdminOnly;触发人以会话身份落库

前端:
- useSession 会话 hook;Sidebar 菜单分级(资源/集成/系统管理 admin-only);admin 路由守卫
- 按钮级权限:执行/配置/删除/保存/重试按等级显隐;新页「流水线权限」与流水线成员弹窗(纯点选)
- 登录页记住密码;GUIDE/API 文档补权限章节
This commit is contained in:
Logan.Li committed 2026-10-27 17:34:41 +08:00
1 parent 7090c8efc2
commit 9103b13510
27 files changed
+1690 -190

No files matched your search

+20 -6
View File
@@ -68,23 +68,28 @@ func VerifyToken(token string) (string, error) {
return parts[0], nil
}
// AuthMiddleware 会话鉴权:仅保护 /api/*(豁免登录/入站 Webhook/SSE 日志流);SPA 与静态资源放行
// AuthMiddleware 会话鉴权:仅保护 /api/*(豁免登录/入站 Webhook);SPA 与静态资源放行。
// SSE 日志流(*/logs/stream)的 EventSource 无法携带 Authorization 头,额外接受 ?token= 查询参数。
func AuthMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
p := c.Request.URL.Path
if p == "/health" || !strings.HasPrefix(p, "/api/") ||
p == "/api/auth/login" ||
strings.HasPrefix(p, "/api/hooks/") ||
strings.HasSuffix(p, "/logs/stream") {
strings.HasPrefix(p, "/api/hooks/") {
c.Next()
return
}
auth := c.GetHeader("Authorization")
if !strings.HasPrefix(auth, "Bearer ") {
token := ""
if auth := c.GetHeader("Authorization"); strings.HasPrefix(auth, "Bearer ") {
token = strings.TrimPrefix(auth, "Bearer ")
} else if strings.HasSuffix(p, "/logs/stream") {
token = c.Query("token")
}
if token == "" {
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "未登录"})
return
}
username, err := VerifyToken(strings.TrimPrefix(auth, "Bearer "))
username, err := VerifyToken(token)
if err != nil {
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "登录已失效,请重新登录"})
return
@@ -100,6 +105,15 @@ func AuthMiddleware() gin.HandlerFunc {
}
}
// Me GET /api/me —— 当前会话信息(前端菜单分级与按钮权限依据)
func Me(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"username": c.GetString("username"),
"role": c.GetString("role"),
"isAdmin": c.GetString("role") == "admin",
})
}
// AdminOnly 管理员专用接口守卫
func AdminOnly() gin.HandlerFunc {
return func(c *gin.Context) {