mirror of
https://github.com/gl-inet/glkvm-cloud.git
synced 2026-09-20 17:43:29 +00:00
55854afba8
- support device group sorting - auto-create local users for LDAP and OIDC login Signed-off-by: GL.iNet-Yongping.Xie <yongping.xie@gl-inet.com>
138 lines
3.7 KiB
Go
Executable File
138 lines
3.7 KiB
Go
Executable File
package middleware
|
|
|
|
import (
|
|
"net/http"
|
|
"rttys/internal/domain/identity"
|
|
"strings"
|
|
|
|
"rttys/internal/domain/permission"
|
|
"rttys/internal/domain/user"
|
|
"rttys/internal/http/dto"
|
|
"rttys/internal/store/memory"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
)
|
|
|
|
const PrincipalKey = "principal"
|
|
|
|
type Principal struct {
|
|
UserID int64 `json:"userId"`
|
|
Username string `json:"username"`
|
|
DisplayName string `json:"displayName"`
|
|
Role identity.Role `json:"role"`
|
|
AuthProvider string `json:"authProvider"`
|
|
PermissionKeys []string `json:"permissions"`
|
|
}
|
|
|
|
func MustPrincipal(c *gin.Context) Principal {
|
|
v, ok := c.Get(PrincipalKey)
|
|
if !ok {
|
|
panic("principal missing")
|
|
}
|
|
return v.(Principal)
|
|
}
|
|
|
|
func Auth(sessionStore *memory.SessionStore, userSvc *user.Service, permSvc *permission.Service) gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
traceID := GetTraceID(c)
|
|
|
|
// 1) Prefer Bearer token
|
|
token := parseBearer(c.GetHeader("Authorization"))
|
|
|
|
// 2) Fallback to cookie sid
|
|
if token == "" {
|
|
sid, err := c.Cookie("sid")
|
|
if err == nil {
|
|
token = strings.TrimSpace(sid)
|
|
}
|
|
}
|
|
|
|
// 3) Fallback to Token header (compat with API docs)
|
|
if token == "" {
|
|
token = strings.TrimSpace(c.GetHeader("Token"))
|
|
}
|
|
|
|
if token == "" {
|
|
dto.Write(c, dto.Err(traceID, dto.CodeAuthRequired, "Please login", nil))
|
|
c.Abort()
|
|
return
|
|
}
|
|
|
|
sess, ok := sessionStore.Get(token)
|
|
if !ok {
|
|
dto.Write(c, dto.Err(traceID, dto.CodeAuthExpired, "Session expired", nil))
|
|
c.Abort()
|
|
return
|
|
}
|
|
|
|
u, err := userSvc.GetByID(c.Request.Context(), sess.UserID)
|
|
if err != nil || u == nil {
|
|
dto.Write(c, dto.Err(traceID, dto.CodeForbidden, "Permission denied", nil))
|
|
c.Abort()
|
|
return
|
|
}
|
|
|
|
keys, _ := permSvc.ListByRole(c.Request.Context(), u.Role)
|
|
perms := make([]string, 0, len(keys))
|
|
for _, k := range keys {
|
|
perms = append(perms, string(k))
|
|
}
|
|
|
|
displayName := u.Description
|
|
if strings.TrimSpace(displayName) == "" {
|
|
displayName = u.Username
|
|
}
|
|
|
|
authProvider := u.AuthProvider
|
|
if authProvider == "" {
|
|
authProvider = "local"
|
|
}
|
|
|
|
c.Set(PrincipalKey, Principal{
|
|
UserID: u.ID,
|
|
Username: u.Username,
|
|
DisplayName: displayName,
|
|
Role: u.Role,
|
|
AuthProvider: authProvider,
|
|
PermissionKeys: perms,
|
|
})
|
|
|
|
c.Next()
|
|
}
|
|
}
|
|
|
|
// Require checks capability keys (frontend/back-end single source of truth).
|
|
func Require(required permission.Key) gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
traceID := GetTraceID(c)
|
|
p := MustPrincipal(c)
|
|
for _, k := range p.PermissionKeys {
|
|
if k == string(required) {
|
|
c.Next()
|
|
return
|
|
}
|
|
}
|
|
dto.Write(c, dto.Err(traceID, dto.CodeForbidden, "Permission denied", map[string]any{
|
|
"required": string(required),
|
|
}))
|
|
c.Abort()
|
|
}
|
|
}
|
|
|
|
func parseBearer(v string) string {
|
|
v = strings.TrimSpace(v)
|
|
if v == "" {
|
|
return ""
|
|
}
|
|
parts := strings.SplitN(v, " ", 2)
|
|
if len(parts) == 2 && strings.EqualFold(parts[0], "Bearer") {
|
|
return strings.TrimSpace(parts[1])
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// Write wrapper for gin to keep consistent HTTP 200.
|
|
func Write(c *gin.Context, payload any) {
|
|
c.JSON(http.StatusOK, payload)
|
|
}
|