Skip to main content

User Permissions & Module Gate

Overview

Access control in cms-api-go is made up of four layers that behave quite differently. This is one of the easiest parts of the system to misread, so it is worth separating clearly.

  1. CASL policy (@CheckPolicies) — metadata is present on every route, but enforcement is turned off: CheckPolicies is a no-op that lets every request through, matching the original NestJS PoliciesGuard.canActivate, which always returned true. As a result, the permissions in system_module and system_role_module serve only to show or hide menu items in cms-web, via GET /api/user/:id/permission.
  2. SuperAdminGuard — actually enforced: roleId must equal 1.
  3. ModuleGate — actually enforced. When a platform admin disables a module for an organization, a customer calling the API directly gets a 403.
  4. AppEnabledGuard — actually enforced. Apps such as appointment, loyalty, and bulletin must be enabled for the organization before they can be used.

Business Flow

Reading your own permissions (for menu rendering)

  1. cms-web calls GET /api/user/:id/permission, where :id must be the caller's own id. Anything else returns 400 with the message Cannot access other userId.
  2. The service loads the user together with SystemRoleModule (the role baseline) and the full list of module names from system_module.
  3. If the user has type = customer and their organization has rows in organization_module, that organization's override replaces the baseline. Platform admins control these overrides.
  4. If the user has type = onemoby — which includes role 1, the platform operator — the role baseline always applies and is never overridden.
  5. The response is a list shaped as {subject: module name, action: [...]}, which cms-web uses to decide which menu items to display.

ModuleGate (server-side enforcement)

  1. The middleware reads the caller's user.type from CLS via selfId.
  2. If the caller is onemoby, or cannot be identified, the request passes straight through (governors bypass).
  3. If the caller is customer, the gate asks orgmodulesetting.IsModuleEnabledForOrg(orgId, moduleName) using the same baseline-versus-override logic described in step 3 above.
  4. A disabled module returns 403 with ModuleDisabled. However, an error during the lookup fails OPEN — the request is allowed through and the error is logged — so that a hiccuping query cannot take the system down or lock an organization out of its modules.
  5. PlatformOnly does the opposite and fails CLOSED, because the risk there is privilege escalation. It protects PUT /api/apps/:appId, which enables or disables an app for an organization and must only ever be driven from the platform admin console.

Key Files & Functions

FileResponsibility
internal/modules/auth/policy.goThe PolicyAction and PolicyModule enums, plus Can() and the no-op CheckPolicies()
internal/modules/auth/guards.goSuperAdmin() and InternalApiKey()
internal/modules/modulegate/modulegate.goModuleGate(d, moduleName), PlatformOnly(d), gate(), and dbCallerType()
internal/modules/apps/guard.goAppEnabledGuard(d) — matches the regex /apps/(\w+) and then checks IsAppEnabled
internal/modules/user/service.goFindPermissionByUserID (around line 405)
internal/modules/orgmodulesetting/service.goIsModuleEnabledForOrg — the resolver shared by both the gate and the permission endpoint

Related endpoint:

MethodRouteHandlerNote
GET/api/user/:id/permissionct.findPermissionByUserIdNo @CheckPolicies; the service restricts reads to the caller's own record

Modules wrapped by ModuleGate (named as they appear in system_module): api-key, attribute-master, audiences, auto-response, campaign, customer-database, form-builder, friend-track, import-mapping, report-all-friends, rich-menu, rich-message, template-message, trigger-rule, and workflow.

The PolicyModule enum values present in the code: line-oa, campaign, rich-message, rich-menu, auto-response, organization, dashboard, user, system_role, system_module, line-user, audiences, report-all-friends, template-message, api-key, form-builder, customer-database, import-mapping, tracking-log, attribute-master, trigger-rule, workflow, friend-track, system-attribute, quick-reply, and otp-config.

Connections to Other Services

  • Tablessystem_module, system_role, system_role_module, organization_module, user, and line_oa_app.
  • CLS — uses selfId, organizationId, roleId, and isSuperAdmin, all populated by the JWT guard.
  • Who sets the overrides — platform admins, through the Organization Module Setting module.
  • App-level gating — tied directly to the Apps Platform module.
  • Frontend — cms-web consumes these permissions to render its menus.