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.
117 lines
3.3 KiB
Go
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
|
|
} |