Files
gochat/backend/internal/middleware/role_check.go
T
rogee aeddedf2a3 Reorganize repo: backend/, deploy/, docs/ layout + AGENTS.md
Restructure the monorepo into clear top-level directories:
- backend/: Go module root (cmd, internal, pkg, configs, migrations,
  docs/swagger, scripts, tests, go.mod, Makefile, .air.toml)
- deploy/: Docker (Dockerfile, docker-compose*), quickstart, fluentd
- docs/: project documentation + reports/ (moved from repo root)
- AGENTS.md: new AI coding-agent guide at repo root

Update all references to the new layout:
- Dockerfile: COPY backend/go.mod, COPY backend/ (context = repo root)
- docker-compose files: context ../.., dockerfile deploy/docker/Dockerfile,
  env_file ../../.env, volume mounts ../../backend:/app
- deploy/quickstart/compose.yaml: dockerfile deploy/docker/Dockerfile
- CI: working-directory: backend for go commands, file deploy/docker/Dockerfile,
  coverage path backend/coverage.out, health_check backend/scripts/
- backend/Makefile: docker target uses -f ../deploy/docker/Dockerfile ../
- README: architecture tree, quickstart, config paths updated

Move root stray scripts (rename_models.*, run_m11_tests.sh, verify_build.sh,
gorm_bool_main.go) to backend/scripts/legacy/. All moves via git mv to
preserve history. Build, vet, SQLite tests, and docker compose config verified.
2026-07-07 14:44:12 +08:00

117 lines
3.3 KiB
Go

package middleware
// Reference: P2E §2.7 — RoleCheck middleware
// Simple middleware that verifies the user has the specified role in the current account.
// Used for endpoints that require a specific role regardless of permission dimensions.
import (
"net/http"
"github.com/gin-gonic/gin"
"github.com/gochat/gochat/internal/auth"
"github.com/gochat/gochat/pkg/response"
)
// RoleCheck creates a middleware that verifies the current user has the specified role.
// This is a simple role gate — for more granular permission checks, use PolicyMiddleware.
//
// Usage:
// router.POST("/accounts/:id/users", RoleCheck("administrator"), inviteUser)
// router.GET("/reports", RoleCheck("administrator"), viewReports)
//
// Valid role values: "agent", "administrator", "custom_role"
func RoleCheck(role string) gin.HandlerFunc {
return func(c *gin.Context) {
pc, exists := c.Get("policy_context")
if !exists {
response.AbortWithStatusError(c, http.StatusForbidden, response.ErrForbidden,
"Policy context not found — ensure AccountScope middleware is applied first")
return
}
policyCtx, ok := pc.(*auth.PolicyContext)
if !ok {
response.AbortWithStatusError(c, http.StatusForbidden, response.ErrForbidden,
"Invalid policy context type")
return
}
switch role {
case "administrator":
if !policyCtx.IsAdministrator() {
response.AbortWithStatusError(c, http.StatusForbidden, response.ErrForbidden,
"Administrator role required")
return
}
case "agent":
if !policyCtx.IsAgent() {
response.AbortWithStatusError(c, http.StatusForbidden, response.ErrForbidden,
"Agent role required")
return
}
case "custom_role":
if !policyCtx.IsCustomRole() {
response.AbortWithStatusError(c, http.StatusForbidden, response.ErrForbidden,
"Custom role required")
return
}
default:
// Check if the user's role matches exactly
if policyCtx.Role != role {
response.AbortWithStatusError(c, http.StatusForbidden, response.ErrForbidden,
"Role '"+role+"' required, but user has role '"+policyCtx.Role+"'")
return
}
}
c.Next()
}
}
// RoleCheckAny creates a middleware that verifies the current user has ANY of the specified roles.
// Useful for endpoints accessible to multiple roles.
//
// Usage:
// router.GET("/dashboard", RoleCheckAny("administrator", "custom_role"), viewDashboard)
func RoleCheckAny(roles ...string) gin.HandlerFunc {
roleSet := make(map[string]bool, len(roles))
for _, r := range roles {
roleSet[r] = true
}
return func(c *gin.Context) {
pc, exists := c.Get("policy_context")
if !exists {
response.AbortWithStatusError(c, http.StatusForbidden, response.ErrForbidden,
"Policy context not found — ensure AccountScope middleware is applied first")
return
}
policyCtx, ok := pc.(*auth.PolicyContext)
if !ok {
response.AbortWithStatusError(c, http.StatusForbidden, response.ErrForbidden,
"Invalid policy context type")
return
}
if !roleSet[policyCtx.Role] {
response.AbortWithStatusError(c, http.StatusForbidden, response.ErrForbidden,
"One of the following roles required: "+formatRoles(roles))
return
}
c.Next()
}
}
func formatRoles(roles []string) string {
result := ""
for i, r := range roles {
if i > 0 {
result += ", "
}
result += r
}
return result
}