feat(copilot): finish configuration center
This commit is contained in:
@@ -53,6 +53,7 @@ func AuthMiddlewareWithService(jwtSvc *auth.JWTService) gin.HandlerFunc {
|
||||
c.Set("user_id", claims.UserID)
|
||||
c.Set("account_id", claims.AccountID)
|
||||
c.Set("role", claims.Role)
|
||||
c.Set("user_type", claims.UserType)
|
||||
c.Set("provider", claims.Provider)
|
||||
c.Set("custom_role_id", claims.CustomRoleID)
|
||||
c.Set("claims", claims) // full Claims struct for handlers that need it
|
||||
|
||||
@@ -118,6 +118,33 @@ func TestAuthMiddleware_ChatwootAccessTokenHeader(t *testing.T) {
|
||||
assert.Equal(t, 200, w.Code)
|
||||
}
|
||||
|
||||
func TestAuthMiddleware_AllowsPlatformAdminThroughSuperAdminGuard(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
cfg := makeJWTConfig()
|
||||
jwtService := auth.NewJWTService(cfg)
|
||||
user := &model.User{
|
||||
Base: model.Base{ID: 1},
|
||||
Provider: "email",
|
||||
Role: "super_admin",
|
||||
Type: "User",
|
||||
}
|
||||
pair, err := jwtService.GenerateTokenPair(user, 2, "administrator")
|
||||
assert.NoError(t, err)
|
||||
|
||||
r := gin.New()
|
||||
r.Use(AuthMiddleware(cfg), SuperAdmin())
|
||||
r.GET("/test", func(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{"ok": true})
|
||||
})
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req := httptest.NewRequest(http.MethodGet, "/test", nil)
|
||||
req.Header.Set("access-token", pair.AccessToken)
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
}
|
||||
|
||||
func TestAuthMiddleware_FallbackHeaders(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
cfg := makeJWTConfig()
|
||||
|
||||
@@ -6,6 +6,7 @@ package middleware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
@@ -17,30 +18,17 @@ import (
|
||||
// Super admins are platform-level administrators that can manage all accounts,
|
||||
// platform apps, and system configuration.
|
||||
//
|
||||
// This checks the user's type field (not the account-level role).
|
||||
// user.type = "super_admin" is set at the User model level, not AccountUser.
|
||||
// This checks the signed platform user type (not the account-level role).
|
||||
//
|
||||
// Usage:
|
||||
// router.GET("/platform/accounts", SuperAdmin(), listAllAccounts)
|
||||
// router.POST("/platform/apps", SuperAdmin(), createPlatformApp)
|
||||
// router.GET("/platform/analytics", SuperAdmin(), viewPlatformAnalytics)
|
||||
//
|
||||
// router.GET("/platform/accounts", SuperAdmin(), listAllAccounts)
|
||||
// router.POST("/platform/apps", SuperAdmin(), createPlatformApp)
|
||||
// router.GET("/platform/analytics", SuperAdmin(), viewPlatformAnalytics)
|
||||
func SuperAdmin() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
// Check for super_admin flag in context (set by AuthRequired middleware)
|
||||
userType, exists := c.Get("user_type")
|
||||
if !exists {
|
||||
// No user_type in context — check claims for super_admin indication
|
||||
_, claimsExists := c.Get("auth_claims")
|
||||
if claimsExists {
|
||||
// Claims exist but user_type not set — not super_admin
|
||||
}
|
||||
response.AbortWithStatusError(c, http.StatusForbidden, response.ErrForbidden,
|
||||
"Super admin access required")
|
||||
return
|
||||
}
|
||||
|
||||
typeStr, ok := userType.(string)
|
||||
if !ok || typeStr != "super_admin" {
|
||||
if !exists || !isSuperAdminType(userType) {
|
||||
response.AbortWithStatusError(c, http.StatusForbidden, response.ErrForbidden,
|
||||
"Super admin access required. Your account does not have platform administration privileges.")
|
||||
return
|
||||
@@ -57,14 +45,14 @@ func SuperAdmin() gin.HandlerFunc {
|
||||
// Useful for endpoints that should be accessible to account admins and platform admins.
|
||||
//
|
||||
// Usage:
|
||||
// router.DELETE("/accounts/:id", SuperAdminOrAdministrator(), deleteAccount)
|
||||
//
|
||||
// router.DELETE("/accounts/:id", SuperAdminOrAdministrator(), deleteAccount)
|
||||
func SuperAdminOrAdministrator() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
// Check super_admin first
|
||||
userType, exists := c.Get("user_type")
|
||||
if exists {
|
||||
typeStr, ok := userType.(string)
|
||||
if ok && typeStr == "super_admin" {
|
||||
if isSuperAdminType(userType) {
|
||||
c.Set("is_super_admin", true)
|
||||
c.Next()
|
||||
return
|
||||
@@ -94,4 +82,13 @@ func SuperAdminOrAdministrator() gin.HandlerFunc {
|
||||
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func isSuperAdminType(value any) bool {
|
||||
typeStr, ok := value.(string)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
normalized := strings.ToLower(strings.ReplaceAll(strings.TrimSpace(typeStr), "_", ""))
|
||||
return normalized == "superadmin"
|
||||
}
|
||||
|
||||
@@ -47,6 +47,19 @@ func TestSuperAdmin_IsSuperAdmin(t *testing.T) {
|
||||
assert.Equal(t, 200, w.Code)
|
||||
}
|
||||
|
||||
func TestSuperAdmin_AcceptsChatwootSerializedType(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
r := gin.New()
|
||||
r.Use(func(c *gin.Context) { c.Set("user_type", "SuperAdmin"); c.Next() })
|
||||
r.Use(SuperAdmin())
|
||||
r.GET("/test", func(c *gin.Context) { c.JSON(200, gin.H{"ok": true}) })
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req := httptest.NewRequest(http.MethodGet, "/test", nil)
|
||||
r.ServeHTTP(w, req)
|
||||
assert.Equal(t, 200, w.Code)
|
||||
}
|
||||
|
||||
func TestSuperAdmin_ClaimsExistButNoType(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
r := gin.New()
|
||||
@@ -58,4 +71,4 @@ func TestSuperAdmin_ClaimsExistButNoType(t *testing.T) {
|
||||
req := httptest.NewRequest(http.MethodGet, "/test", nil)
|
||||
r.ServeHTTP(w, req)
|
||||
assert.Equal(t, 403, w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user