2026-05-02 13:02:38 +08:00

162 lines
3.7 KiB
Go

package middleware
import (
"net/http"
"strings"
"hyapp-admin-server/internal/appctx"
"hyapp-admin-server/internal/config"
"hyapp-admin-server/internal/response"
"hyapp-admin-server/internal/service"
"github.com/gin-gonic/gin"
)
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 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 {
for _, permission := range CurrentPermissions(c) {
if permission == code {
return true
}
}
return false
}
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
}