2026-07-14 17:12:39 +08:00

198 lines
4.7 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package middleware
import (
"errors"
"net/http"
"slices"
"strings"
"hyapp-admin-server/internal/appctx"
"hyapp-admin-server/internal/config"
"hyapp-admin-server/internal/repository"
"hyapp-admin-server/internal/response"
"hyapp-admin-server/internal/service"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
)
type AppAccessStore interface {
AppAccessForUser(userID uint) (repository.AppAccess, error)
}
const (
ContextUserID = "userID"
ContextUsername = "username"
ContextPermissions = "permissions"
)
func CORS(cfg config.Config) gin.HandlerFunc {
allowed := map[string]struct{}{}
for _, origin := range cfg.CORSAllowedOrigins {
allowed[origin] = struct{}{}
}
return func(c *gin.Context) {
origin := c.Request.Header.Get("Origin")
if _, ok := allowed[origin]; ok {
c.Header("Access-Control-Allow-Origin", origin)
c.Header("Vary", "Origin")
c.Header("Access-Control-Allow-Credentials", "true")
c.Header("Access-Control-Allow-Headers", "Authorization, Content-Type, X-Request-ID, "+appctx.HeaderAppCode)
c.Header("Access-Control-Expose-Headers", "X-Request-ID")
c.Header("Access-Control-Allow-Methods", "GET, POST, PATCH, PUT, DELETE, OPTIONS")
}
if c.Request.Method == http.MethodOptions {
c.AbortWithStatus(http.StatusNoContent)
return
}
c.Next()
}
}
func AppCode() gin.HandlerFunc {
return func(c *gin.Context) {
appCode := appctx.Normalize(c.GetHeader(appctx.HeaderAppCode))
c.Request = c.Request.WithContext(appctx.WithContext(c.Request.Context(), appCode))
c.Header(appctx.HeaderAppCode, appCode)
c.Next()
}
}
func AuthRequired(auth *service.AuthService) gin.HandlerFunc {
return func(c *gin.Context) {
header := c.GetHeader("Authorization")
if !strings.HasPrefix(header, "Bearer ") {
response.Unauthorized(c, "缺少访问凭证")
c.Abort()
return
}
claims, err := auth.ParseAccessToken(strings.TrimPrefix(header, "Bearer "))
if err != nil {
response.Unauthorized(c, "访问凭证已失效")
c.Abort()
return
}
c.Set(ContextUserID, claims.UserID)
c.Set(ContextUsername, claims.Username)
c.Set(ContextPermissions, claims.Permissions)
c.Next()
}
}
func RequireAppScope(store AppAccessStore) gin.HandlerFunc {
return func(c *gin.Context) {
// App 级路由必须显式携带 header不能复用 appctx 的 lalu 默认值;否则无 App 用户省略 header 仍会落入 Lalu 数据域。
appCode := strings.ToLower(strings.TrimSpace(c.GetHeader(appctx.HeaderAppCode)))
if appCode == "" {
response.Forbidden(c, "当前用户没有可访问的 App")
c.Abort()
return
}
if store == nil {
response.ServerError(c, "App 权限服务不可用")
c.Abort()
return
}
access, err := store.AppAccessForUser(CurrentUserID(c))
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
response.Unauthorized(c, "用户不存在")
} else {
response.ServerError(c, "校验 App 权限失败")
}
c.Abort()
return
}
if !access.Allows(appCode) {
response.Forbidden(c, "当前用户无权访问该 App")
c.Abort()
return
}
c.Next()
}
}
func RequirePermission(code string) gin.HandlerFunc {
return func(c *gin.Context) {
if HasPermission(c, code) {
c.Next()
return
}
response.Forbidden(c, "没有操作权限")
c.Abort()
}
}
// RequireAnyPermission is used by transitional RBAC routes where the new
// button-level permission and the old broad permission must both work.
func RequireAnyPermission(codes ...string) gin.HandlerFunc {
return func(c *gin.Context) {
if HasAnyPermission(c, codes...) {
c.Next()
return
}
response.Forbidden(c, "没有操作权限")
c.Abort()
}
}
// HasAnyPermission keeps privilege checks in handlers readable when one action
// is allowed by multiple compatible permission codes.
func HasAnyPermission(c *gin.Context, codes ...string) bool {
for _, code := range codes {
if HasPermission(c, code) {
return true
}
}
return false
}
// HasPermission reads only the JWT-derived permission list. Database state is
// refreshed by login/refresh, so every request uses one consistent permission snapshot.
func HasPermission(c *gin.Context, code string) bool {
return slices.Contains(CurrentPermissions(c), code)
}
func CurrentUserID(c *gin.Context) uint {
value, ok := c.Get(ContextUserID)
if !ok {
return 0
}
id, ok := value.(uint)
if !ok {
return 0
}
return id
}
func CurrentUsername(c *gin.Context) string {
value, ok := c.Get(ContextUsername)
if !ok {
return ""
}
username, ok := value.(string)
if !ok {
return ""
}
return username
}
func CurrentPermissions(c *gin.Context) []string {
value, ok := c.Get(ContextPermissions)
if !ok {
return nil
}
permissions, ok := value.([]string)
if !ok {
return nil
}
return permissions
}