Skip to content

RBAC System

The Merq platform employs a robust Role-Based Access Control (RBAC) system to ensure that users can only access the resources and perform the actions appropriate for their assigned roles. This system is enforced at the API level through a series of middleware and database checks.

  • Workspaces: All resources and user roles are scoped to a workspace. A user’s permissions are only valid within the workspace they are currently operating in.
  • Roles: Each user is assigned a single Role within a workspace, which dictates their general level of access (e.g., “Admin”, “Team Lead”, “Field Agent”).
  • Permissions: Permissions are granular rules that define a specific action a user can take (e.g., create_project, view_outlet, delete_team_member).
  • JWT Claims: When a user authenticates, the returned JSON Web Token (JWT) contains their user_id, workspace_id, role_id, role_value, and a comma-separated string of all their granted permissions.

API route protection is primarily handled by two middleware functions: AuthMiddleware and RequirePermission.

Located in internal/middleware/auth.go, this is the first line of defense.

  1. It validates the JWT provided in the Authorization header.
  2. It unpacks the claims from the token.
  3. It injects the user’s workspace_id, user_id, role_id, and the string of permissions into the Gin context for use in subsequent handlers and middleware.
internal/middleware/auth.go
// ... (token parsing logic)
c.Set("workspace_id", claims.WorkspaceID)
c.Set("user_id", claims.UserID)
c.Set("role_id", claims.RoleID)
c.Set("permissions", claims.Permissions) // "perm1,perm2,perm3"
// ...

This middleware, found in internal/handler/rbac_handler.go (as part of the RBACHandler), provides fine-grained control over individual routes or route groups.

  1. It accepts a required permission key (e.g., outlets), a platform (Web or Mobile), and an accessLevel (Create, Read, Update, Delete).
  2. It retrieves the user’s permissions string from the Gin context.
  3. It checks if the required permission—formatted as {platform}:{key}:{accessLevel} (e.g., Web:outlets:Create)—is present in the user’s permissions string.

If the permission is not found, the middleware aborts the request and returns a 403 Forbidden error.

// internal/handler/outlet_handler.go (example)
func (h *OutletHandler) RegisterRoutes(rg *gin.RouterGroup) {
rg.POST("", h.rbac.RequirePermission("outlets", "Web", "Create"), h.CreateOutlet)
rg.GET("", h.rbac.RequirePermission("outlets", "Web", "Read"), h.FindOutlets)
rg.GET("/:id", h.rbac.RequirePermission("outlets", "Web", "Read"), h.GetOutletByID)
rg.PUT("/:id", h.rbac.RequirePermission("outlets", "Web", "Update"), h.UpdateOutlet)
rg.DELETE("/:id", h.rbac.RequirePermission("outlets", "Web", "Delete"), h.DeleteOutlet)
}

The internal/service/rbac_service.go file contains the business logic for managing roles and permissions themselves. A key security constraint is implemented here:

  • Root Workspace Restriction: The creation, updating, and deletion of core Permission entities are restricted to users operating within the root workspace. This prevents regular workspace admins from creating new permissions and escalating their privileges. The service explicitly checks for the root workspace before proceeding with any write operations on the permissions table.

This centralized control ensures that the set of available permissions is consistent and managed by super administrators only.