merge: 合入本地 Ops 扩展与 origin/main(ECR-009–016)
保留远程用户侧 ECR-009–016 与本地 Ops 目录/RBAC/CMS/危机等能力;文档标注分叉期间 ECR 编号冲突。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,42 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/uuid"
|
||||
|
||||
"github.com/yuxingu/digital-psychology/apps/api/pkg/response"
|
||||
)
|
||||
|
||||
// AdminPermissionChecker validates admin permission codes.
|
||||
type AdminPermissionChecker interface {
|
||||
HasPermission(ctx context.Context, adminID uuid.UUID, code string) (bool, error)
|
||||
DenyPermission(ctx context.Context, adminID uuid.UUID, code, path string)
|
||||
}
|
||||
|
||||
// RequireAdminPermission aborts with 403 when the admin lacks code.
|
||||
func RequireAdminPermission(checker AdminPermissionChecker, code string) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
adminID, ok := AdminIDFromContext(c)
|
||||
if !ok {
|
||||
response.Fail(c, http.StatusUnauthorized, 40102, "admin session invalid")
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
okPerm, err := checker.HasPermission(c.Request.Context(), adminID, code)
|
||||
if err != nil {
|
||||
response.Fail(c, http.StatusInternalServerError, 50000, "permission check failed")
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
if !okPerm {
|
||||
checker.DenyPermission(c.Request.Context(), adminID, code, c.FullPath())
|
||||
response.Fail(c, http.StatusForbidden, 40301, "forbidden")
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
@@ -47,6 +47,9 @@ func DeviceAuth(pool *pgxpool.Pool) gin.HandlerFunc {
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
if !ensureActiveUser(c, pool, uid) {
|
||||
return
|
||||
}
|
||||
_, _ = pool.Exec(c.Request.Context(), `
|
||||
INSERT INTO device_identities(device_key, user_id)
|
||||
VALUES ($1,$2)
|
||||
@@ -74,12 +77,29 @@ func DeviceAuth(pool *pgxpool.Pool) gin.HandlerFunc {
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
if !ensureActiveUser(c, pool, userID) {
|
||||
return
|
||||
}
|
||||
c.Set(string(UserIDKey), userID.String())
|
||||
c.Header(DeviceKeyHeader, key)
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
// ensureActiveUser aborts with 401 when UserStatus is not active.
|
||||
func ensureActiveUser(c *gin.Context, pool *pgxpool.Pool, userID uuid.UUID) bool {
|
||||
var status string
|
||||
err := pool.QueryRow(c.Request.Context(), `
|
||||
SELECT status FROM users WHERE id=$1 AND deleted_at IS NULL`, userID,
|
||||
).Scan(&status)
|
||||
if err != nil || status != "active" {
|
||||
response.Fail(c, http.StatusUnauthorized, 40113, "账户已受限")
|
||||
c.Abort()
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func bearerFromHeader(h string) string {
|
||||
if len(h) < 8 {
|
||||
return ""
|
||||
|
||||
Reference in New Issue
Block a user