Two layers of authorization
Atlas authorization is a combination of:- Global auth: “is this request authenticated?”
- enforced by
verifyTokeninMeridian/backend/middlewares/verifyToken.js
- enforced by
- Org-scoped auth: “can this user do X inside org Y?”
- enforced by
Meridian/backend/middlewares/orgPermissions.js
- enforced by
Permission vocabulary
Permissions are defined in:Meridian/backend/constants/permissions.js
allview_rolesmanage_rolesmanage_membersmanage_eventsview_analyticsview_events
Where permissions are stored
Org roles
Roles are stored in the org document:Org.positions[]
name(string key)permissions[]- plus helper booleans (
canManageMembers, etc)
permissions[].
Member assignment
Membership records are stored in:OrgMemberdocuments
role(string key that must matchOrg.positions[].name)- overrides:
customPermissions[](force-allow)deniedPermissions[](force-deny)
How enforcement works (backend)
requireOrgPermission(permission, orgParam = 'orgId')
File: Meridian/backend/middlewares/orgPermissions.js
Admin/root bypass
Before checking org membership, the middleware checksreq.user.roles:
- If
roles.includes('admin')orroles.includes('root')→ bypass org membership - Load the org, attach
req.org = organdreq.orgMember = null - Proceed to the route handler
requireOrgPermissionrequireAnyOrgPermissionrequireOrgOwner
?adminView=true in the club dashboard URL. The frontend passes adminBypass to child components (Members, Roles, Settings) so they grant full access without fetching org membership.
Member-level override semantics
InOrgMember.hasPermissionWithOrg(permission, org):
Org-level role check semantics
InOrg.hasPermission(roleName, permission):
Role management endpoints (and how they are gated)
Most role operations are served under/org-roles:
GET /org-roles/:orgId/roles→ requiresrequireOrgPermission('view_roles')POST /org-roles/:orgId/roles→ requiresrequireRoleManagement()→ checksmanage_rolesPUT /org-roles/:orgId/roles→ requiresmanage_rolesPUT /org-roles/:orgId/roles/:roleName→ requiresmanage_rolesDELETE /org-roles/:orgId/roles/:roleName→ requiresmanage_roles
- You cannot remove
ownerormemberroles (Org.removeRole) - You must preserve
ownerin bulk updates (PUT /org-roles/:orgId/roles) - Owner role must retain
permissions: ['all']and role management capability (Org.updateRole)
Member management endpoints (and how they are gated)
DELETE /org-roles/:orgId/members/:userIdis gated byrequireMemberManagement()→ checksmanage_members.- Some other member endpoints currently do not use middleware gates (e.g. assigning roles) and may rely on higher-level UI gating. When hardening security, audit these and add
requireMemberManagementwhere appropriate.
Frontend permission checks (client-side)
The club dashboard (Meridian/frontend/src/pages/ClubDash/ClubDash.jsx) computes permissions to show/hide UI tabs:
- If
adminView=trueand user hasadminorrootrole → all capabilities (admin bypass) - Else if org owner → all capabilities
- Else:
- fetch
GET /org-roles/:orgId/members - find current user’s member row
- look up role object in
org.positions - allow if:
- role boolean is true or
- role.permissions includes the string or
- role.permissions includes
all
- fetch
Common pitfalls
Admin bypass: Site admins (
admin or root in req.user.roles) bypass org membership checks in orgPermissions.js. See Admin/root bypass above.