Skip to content

fix(trustlab): enforce role-based Payload content access#1473

Merged
kilemensi merged 3 commits into
mainfrom
trustlab-rbac-access
May 13, 2026
Merged

fix(trustlab): enforce role-based Payload content access#1473
kilemensi merged 3 commits into
mainfrom
trustlab-rbac-access

Conversation

@kilemensi
Copy link
Copy Markdown
Member

Description

@/trustlab's Payload access layer had two categories of issues.

Authorization gaps (P1):

  • Reports, Playbooks, and Toolkits used Boolean(user), so any authenticated session, regardless of role, could create, update, or delete these collections.
  • Partners, Donors, Opportunities, and Organisations had no explicit create/update/delete access at all, relying on Payload's implicit behaviour.
  • SiteSettings was world-readable: loggedIn was defined as (user) => Boolean(user) but wired as a Payload access adapter, so Payload called it as loggedIn({ req }), which was always truthy.

Structural issues that made auditing difficult:

  • User-level predicates and Payload access adapters shared the same function signatures, so call sites had to inline ({ req: { user } }) => helper(user) everywhere.
  • Boolean(user) trusts any truthy req.user value; a token with an arbitrary payload would pass.

Changes

Access layer refactor (src/payload/access/)

Introduces a clear three-tier naming convention:

Tier Returns Used by
isXxx(user) boolean custom route/middleware code
canXxx(user) boolean | Payload where-filter internal policy logic
hasXxxAccess({ req }) Payload access adapter collection/global access: config
  • hasValidRole(user) added to roles.js: validates user.role against the known role constants. Replaces Boolean(user) everywhere.
  • loggedIn.js removed. isLoggedIn, isEditor, isAuthor, isAdmin, canAuthor, canManageUser are the new user-level predicates.
  • hasLoggedInAccess, hasEditorAccess, hasAuthorAccess, hasAdminAccess, hasManageUserAccess, hasCreateUserAccess are the new Payload adapters, each a thin wrapper over the corresponding predicate.

Collections and globals

Surface Before After
Reports, Playbooks, Toolkits (write) Boolean(user) hasEditorAccess
Pages (write) canManagePages(user) inline hasEditorAccess
Opportunities, Organisations, Partners, Donors (write) missing (implicit) hasEditorAccess
Posts, Media (write) canManageContent(user) inline hasAuthorAccess
Users collection inline wrappers named adapters; role-field update tightened to hasCreateUserAccess
SiteSettings read loggedIn (broken — always true) hasLoggedInAccess
SiteSettings update canManageSiteSettings inline hasAdminAccess

Posts and Media intentionally remain on hasAuthorAccess to preserve the ownership-aware author workflow.

Preview route

preview/route.ts now uses isAuthor(user), the correct boolean predicate, instead of the old canManageContent(user) whose filter-object return value was only coincidentally truthy in a boolean context.

Tests (src/payload/access/index.test.js)

Full policy matrix at the helper layer: isAuthor, canAuthor, hasAuthorAccess, hasEditorAccess, hasAdminAccess, hasLoggedInAccess, hasCreateUserAccess, hasManageUserAccess, and anyone. Cases cover admin/editor/author/anonymous and invalid-role shapes (role: "unknown", bare integer, undefined). The canAuthor suite includes the id-guard edge case (author with no id returns false rather than a filter with equals: undefined).

Type of change

Please delete options that are not relevant.

  • Bug fix (non-breaking change which fixes an issue)

Screenshots

N/A

Checklist:

  • My code follows the style guidelines of this project
  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation

@kilemensi kilemensi self-assigned this May 13, 2026
@kilemensi kilemensi added the bug Something isn't working label May 13, 2026
@kilemensi

This comment was marked as resolved.

chatgpt-codex-connector[bot]

This comment was marked as resolved.

@kilemensi

This comment was marked as resolved.

@claude

This comment was marked as resolved.

@kilemensi

This comment was marked as resolved.

@claude

This comment was marked as resolved.

@kilemensi kilemensi requested a review from a team May 13, 2026 07:31
@github-project-automation github-project-automation Bot moved this to 🚧 In Progress in COMMONS May 13, 2026
@kilemensi

This comment was marked as resolved.

@koechkevin

This comment was marked as outdated.

Comment thread apps/trustlab/src/payload/access/abilities.js
koechkevin

This comment was marked as resolved.

@kilemensi kilemensi enabled auto-merge May 13, 2026 08:39
@kilemensi kilemensi disabled auto-merge May 13, 2026 08:40
@kilemensi kilemensi merged commit b7c5d3a into main May 13, 2026
12 checks passed
@kilemensi kilemensi deleted the trustlab-rbac-access branch May 13, 2026 08:40
@github-project-automation github-project-automation Bot moved this from 🚧 In Progress to ✅ Done in COMMONS May 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

Status: ✅ Done

Development

Successfully merging this pull request may close these issues.

2 participants