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.
Core Concepts
Section titled “Core Concepts”- 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
Rolewithin 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 grantedpermissions.
Middleware Enforcement
Section titled “Middleware Enforcement”API route protection is primarily handled by two middleware functions: AuthMiddleware and RequirePermission.
1. AuthMiddleware
Section titled “1. AuthMiddleware”Located in internal/middleware/auth.go, this is the first line of defense.
- It validates the JWT provided in the
Authorizationheader. - It unpacks the claims from the token.
- It injects the user’s
workspace_id,user_id,role_id, and the string ofpermissionsinto the Gin context for use in subsequent handlers and middleware.
// ... (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"// ...2. RequirePermission Middleware
Section titled “2. RequirePermission Middleware”This middleware, found in internal/handler/rbac_handler.go (as part of the RBACHandler), provides fine-grained control over individual routes or route groups.
- It accepts a required permission
key(e.g.,outlets), aplatform(WeborMobile), and anaccessLevel(Create,Read,Update,Delete). - It retrieves the user’s permissions string from the Gin context.
- 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.
Example Usage
Section titled “Example Usage”// 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)}Business Logic & Management
Section titled “Business Logic & Management”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
Permissionentities are restricted to users operating within therootworkspace. This prevents regular workspace admins from creating new permissions and escalating their privileges. The service explicitly checks for therootworkspace before proceeding with any write operations on thepermissionstable.
This centralized control ensures that the set of available permissions is consistent and managed by super administrators only.