Users, Roles & Permissions
Overview
This feature has two halves that work together:
- User administration at
/user-management— creating, editing, viewing and deleting users, assigning roles, toggling status, deciding which LINE OAs each user can reach, and triggering password resets on their behalf. - The permission system — the machinery that turns the permissions the server returns into the rules the frontend uses to gate pages, menus and a handful of buttons.
Users are organization-level records, not tied to any single channel, so this page is treated as account-level in the same way as LINE OA Management and renders without the sidebar.
Worth knowing: this page does not appear in the sidebar. There are only two ways in — a button in the LINE OA Management header and the quick-access menu — and both are filtered by the user-view permission. The page itself is additionally protected by a route-level permission check, on both the list and the form.
Business Flow
1. User list
- On open, the app checks user-view permission, then sets the breadcrumb and active menu entry.
- The most recent filter is restored from sessionStorage and the list is fetched from
GET /user. - The toolbar holds a search box, a status filter, and a total-user counter.
- The table has five columns: row number, user details (initials avatar, full name that links to the detail view, and email), role, status, and actions.
- Role tags are coloured automatically from the role name — administrator-type roles in red, managers in orange, editors in blue.
- The actions column offers four buttons: edit, OA access, password reset and delete.
- Every change to the page, page size or filter is written back to sessionStorage.
- The "Create user" button sits in the page header.
2. Creating, editing and viewing a user
- The form page supports three modes — create, edit and read-only — distinguished by the URL parameters.
- It loads the role list for the dropdown, and the user record when an id is present.
- The form has five fields: first name, last name, email, role and status (active / inactive).
- In edit mode, email and role are locked and cannot be changed. Read-only mode disables every field and offers only a back button.
- Saving posts to
POST /userfor a new user orPUT /user/{id}for an existing one. - On success a confirmation appears and the user returns to the list. If the server identifies invalid fields, errors are shown per field; otherwise an error modal is displayed.
3. Deleting a user
The delete button in the table opens an inline confirmation. Once confirmed, the app calls DELETE /user/{id} and reloads the table.
4. Resetting a user's password
The key icon opens a confirmation prompt. On confirmation the app calls POST /user/{id}/admin-reset-password, and the server emails that user a link to set a new password. The rest of that flow is covered under Password Management.
5. Assigning LINE OA access to a user
- The access button in the table opens a modal populated from
GET /user/{id}/oa-access. - The mode is inferred automatically: users who already have specific OAs assigned start in "specific OAs" mode, everyone else in "all OAs" mode.
- In specific mode the modal lists every channel with checkboxes.
- Choosing specific mode without selecting anything produces a warning and nothing is saved.
- Saving sends the selection to
PUT /user/{id}/oa-access, where an empty list means access to every channel. - The mirror image of this setting is the channel access modal in LINE OA Management, which views the same relationship from the channel's side.
6. Translating server permissions into frontend rules
- The server returns permissions as a list of modules, each with the actions the user is allowed — read, create, update, delete, export and so on.
- The frontend converts this in three steps:
- Look up which frontend pages correspond to each server module name. Modules with no matching page are skipped.
- Convert the actions: read-type actions become a "view" permission, and export becomes an "export" permission. Create, update and delete are not translated into the frontend permission system at all.
- Fan out — a single server module may unlock several frontend pages at once.
- The resulting rules form a pure allow-list: there are no explicit denials and no record-level conditions.
- The rule set lives in global state but is not persisted, so it is rebuilt on every page load. It is compiled in exactly two places: when the application boots, and when the user selects a channel to work in.
The mapping from server modules to frontend pages:
| Server module | Pages unlocked |
|---|---|
dashboard | Dashboard |
line-oa | LINE OA Management, Menu Builder and the whole content management group |
user | User Management and the settings page |
rich-menu | All three rich menu types (default, custom, switch) |
rich-message | Rich Message Management |
template-message | Template Message Management |
campaign | Campaign Management and the Campaign Planner |
auto-response | Auto Response and Quick Reply |
report-all-friends | All-friends report |
audiences | Every audience management variant |
form-builder | Form Builder and OTP configuration |
customer-database | Member database |
import-mapping | Import mapping |
attribute-master | Attribute setup |
trigger-rule | Trigger rules |
workflow | Workflows |
friend-track | Friend tracking |
api-key | API key management |
system-attribute | Settings page |
system_module | Knowledge base and BigQuery sync |
7. How permissions filter the interface
- Page level — the page wrapper (
PermissionGuard) renders nothing while permissions are still loading, shows a "no permission" screen when the check fails, and renders the real content when it passes. - Sidebar — each menu entry is added only if the user can view that page. The rich menu group checks the combined permission of all three types first, then filters each child. The Apps and Settings groups are shown without a permission check, except for the OTP configuration child.
- Quick access — entries with no page reference always appear; entries that name one are filtered exactly as the sidebar is.
- Button level — only a handful of buttons are filtered, such as the User Management button in the LINE OA Management header.
- A limitation worth knowing — because create, update and delete actions are never translated into the frontend permission system, those buttons appear for everyone who can reach the page. The real enforcement happens server-side.
- Super administrator — per-user channel access in LINE OA Management is gated by checking the role id directly rather than going through the central permission system.
Key Screens & Components
User list screen
All list logic lives in a single container (src/components/user-management/list/user-management.container.tsx), which builds the table, toolbar and columns inline and also handles deletion, password resets and searching.
OA access modal
The modal pairs a two-option mode selector with a checkbox list, letting administrators choose between granting access to every channel and naming specific ones.
User form screen
The form splits into a container that loads role options, assembles the payload and performs the save, and a presentational form that renders the fields and enables or disables them according to the current mode.
Permission layer
The permission layer lives under src/components/ability/ in two main files: one holding the module mapping table and the conversion function, and one that compiles the rules into a usable ability set.
Main API endpoints
| Operation | Endpoint |
|---|---|
| List users | GET /user |
| Read one user | GET /user/{id} |
| Create a user | POST /user |
| Update a user | PUT /user/{id} |
| Delete a user | DELETE /user/{id} |
| A user's OA access | GET and PUT /user/{id}/oa-access |
| Administrator password reset | POST /user/{id}/admin-reset-password |
| Load permissions to build the rule set | GET /user/{id}/permission |
| Role options for the dropdown | GET /system-role/find-all-object |
Dependencies
- Login — the rule set is compiled when the application boots and again when a channel is selected, including the always-granted access to the OA selection page.
- LINE OA Management — the main way into this page, and home to the channel access modal that mirrors the OA access setting here.
- Password Management — the reset button in the table starts the flow that emails a user their password-reset link.
- Role definitions — this CMS has no screen for managing roles and permissions; which modules a role can reach is configured on the server.
- Every page in the system — the permission machinery described here determines which menus each user sees and which pages they can open.
Backend Details (CMS API)
This section matters more than most, because the backend permission system does not work the way its name suggests — and misreading it leads directly to an inaccurate security assessment.
Four permission layers, only three actually enforced
| Layer | Enforced? | What it does |
|---|---|---|
| CASL policy (action/module level) | Not enforced | Metadata exists on every route, but the checker lets every request through |
| SuperAdminGuard | Enforced | Requires role id 1 |
| ModuleGate | Enforced | If a platform administrator disables a module for an organization, its customers get 403 even when calling the API directly |
| AppEnabledGuard | Enforced | An app (appointment / loyalty / bulletin) must be enabled for that organization first |
The key takeaway: the permissions stored against roles and modules exist only to show or hide menus in cms-web — they do not block API calls. This behaviour was carried over deliberately from the previous system, whose policy guard also always returned "allowed". So when this page notes that "real control lives on the server", that means ModuleGate and SuperAdminGuard, not per-action permissions.
What GET /api/user/:id/permission actually does
- Restricts reads to yourself — the user id in the path must match the token's user, otherwise the request is rejected with a "cannot access another user id" error. The frontend simply cannot read anyone else's permissions.
- Loads the user's baseline from the permissions attached to their role, and pulls the full list of system modules.
- Branches on the user's type — for customer-type users whose organization has module settings configured, the platform administrator's settings replace the role baseline. For platform-operator users (including role id 1), the role baseline always applies and is never overridden.
- Returns a list pairing module names with their allowed actions, which the frontend compiles into its rule set.
A consequence: two users with the same role in different organizations may see different menus if their organizations have different module settings.
ModuleGate — the layer that really blocks API calls
The gate runs in order: read the caller's type from the request context, let platform operators (or callers it cannot identify) through immediately, and for customers check whether the module is enabled for their organization using the same baseline-versus-override logic described above.
Error-handling behaviour worth knowing:
- ModuleGate fails open. If the check itself errors — a stumbling query, for instance — the request is allowed through and the failure is logged, rather than denied, so a transient problem cannot lock an entire organization out.
- The platform-only route fails closed, in contrast. The route that enables or disables apps for an organization denies by default when the check cannot be completed, because the risk there is privilege escalation.
Modules wrapped by ModuleGate: 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.
Inside the user module
- Every route uses the guard that accepts tokens without an OA id, not the ordinary one, because user management is an account-level page that must be reachable before a channel is selected.
- The user list is always scoped to the caller's organization, so an administrator in one organization never sees another's users even though the policy layer is not enforced.
- User creation accepts multipart data to support avatar uploads, and hashes the password and records it in password history within the same request.
- Deletion is a soft delete that stamps a deleted timestamp. A separate hard-delete route exists, requiring super admin plus an explicit confirmation parameter — the frontend exposes no button for it.
- Per-user OA access writes the same table as the modal in LINE OA Management, so the two screens genuinely show one data set from opposite directions. This feature is new and did not exist in the previous system.
GET /api/user/find-all-object, used to populate dropdowns, carries no permission metadata at all, matching a comment left in the original system.
Inside system roles
- The backend role module exposes a single endpoint,
GET /api/system-role/find-all-object, purely to populate dropdowns. - There is no API for editing roles or their permissions. That data is master data set directly in the database and then adjusted per organization through organization module settings. This is the technical explanation for the observation that the CMS has no role management screen.
- Role id 1 is the super admin, the value the super-admin guard checks directly — consistent with the frontend checking the role id itself rather than going through the central permission system.