Skip to content

add role-based UI restrictions for TinaCMS editors#586

Merged
ajolipa merged 8 commits intoRB-clerk-authfrom
fix/role-based-ui
Mar 25, 2026
Merged

add role-based UI restrictions for TinaCMS editors#586
ajolipa merged 8 commits intoRB-clerk-authfrom
fix/role-based-ui

Conversation

@jamiefolsom
Copy link
Copy Markdown
Member

@jamiefolsom jamiefolsom commented Mar 24, 2026

Role Mapping: Clerk → TinaCMS

This PR maps Clerk organization roles to TinaCMS permissions, and closes #580 and #581.

Clerk Role TinaCMS Role Access
org:admin Admin Full access to all collections and content
org:member Editor Posts and Paths only; can only edit own content

Role detection uses a shared helper (tina/utils/getUserRole.ts) that reads Clerk org membership. For local development, a dev-mode override is available via TINA_PUBLIC_DEV_ROLE and TINA_PUBLIC_DEV_USER_ID env vars (set on the Netlify site's dev context).

Non-Clerk site compatibility: Role-based UI is gated behind TINA_PUBLIC_AUTH_USE_SSO. Sites not using Clerk are completely unaffected — getUserRole defaults to admin when no Clerk user is found, and the cmsCallback skips role-ui initialization entirely.

UX for Editors

Sidebar restrictions

Admin-only collections (Settings, Branding, Internationalization, Navbar, Pages) appear grayed out with a lock icon and are not clickable. Editors can only navigate to Posts and Paths.

Non-owned content (view-only mode)

When an editor opens a post or path created by another user:

  • A banner reads: "You're not the author of this content. You can view but not edit."
  • All form fields are CSS-disabled (pointer-events: none, reduced opacity)
  • The status dot in the form header is replaced with a lock icon
  • The save button is disabled

When an editor opens their own content, none of these restrictions apply.

Published toggle

Admins see an interactive toggle. Editors see a read-only "Published/Unpublished" label.

Save guard

If an editor somehow bypasses the CSS restrictions and attempts to save non-owned content, a beforeSubmit guard throws a clear error. This is a safety net — the backend enforcement in netlify/functions/tina.ts is the real security layer.

Code Changes

File Purpose
tina/utils/getUserRole.ts Shared role detection from Clerk org membership, with dev-mode override. Defaults to admin when no Clerk user found (non-Clerk site safety).
tina/role-ui.ts CSS injection for form disabling + MutationObserver for sidebar link locking
tina/components/OwnershipNotice.tsx Pseudo-field rendered at top of post/path forms; shows banner and sets data-tina-read-only attribute via useEffect
tina/components/PublishToggle.tsx Refactored to use shared getUserRole helper
tina/config.ts Added cmsCallback to initialize role-based UI on CMS load, gated behind useSSO
tina/content/posts.ts Added OwnershipNotice field + ownership guard in beforeSubmit
tina/content/paths.ts Same as posts
docs/clerk-rbac-fixes.md TODO tracking for known limitations and follow-up work

All frontend restrictions are cosmetic — backend enforcement in netlify/functions/tina.ts (from PR #577) is unchanged and remains the security boundary.

How to Test

Local testing with dev role override

The PADP staging site has TINA_PUBLIC_DEV_ROLE and TINA_PUBLIC_DEV_USER_ID set on its dev context.

As editor:

netlify env:set TINA_PUBLIC_DEV_ROLE org:member --context dev
# restart dev server
  1. Open /admin → sidebar should show Settings/Branding/I18n/Navbar/Pages as locked
  2. Open a post with a creator.id that doesn't match TINA_PUBLIC_DEV_USER_ID → banner, disabled fields, lock icon
  3. Open a post with no creator field (e.g., Local-test-post) → fully editable (no owner = editable)

As admin:

netlify env:set TINA_PUBLIC_DEV_ROLE org:admin --context dev
# restart dev server
  1. All sidebar links normal
  2. All posts fully editable regardless of owner
  3. No banners, no disabled fields

On deployed PADP staging

Log in with a Clerk account that is an org:member of the Performant org. Verify the same restrictions apply. Log in as org:admin and verify full access.

Known Limitations

  • The _ownershipNotice pseudo-field appears as a sort option in collection list views
  • CSS selectors for form disabling target Tina's utility classes which could break if Tina updates its markup
  • Posts/paths without a creator field (pre-existing content) are treated as editable by all editors
  • See docs/clerk-rbac-fixes.md for full TODO list including migration planning

- Shared getUserRole helper with dev-mode override for local testing
- MutationObserver locks admin-only sidebar links (Settings, Branding,
  Internationalization, Navbar) with gray styling and lock icon
- Ownership notice banner on posts/paths when viewing another user's
  content
- beforeSubmit guard blocks saves for non-owners with clear error
- Refactor PublishToggle to use shared role detection
- CSS-disable all form fields (inputs, buttons, date picker, dropzone)
- Hide status dot SVG, show lock icon in form header
- Fix banner text and sizing (remove email, fix overflow)
- Use useEffect for data attribute to avoid render loops
- Add Pages to admin-only sidebar collections
- Consolidate CSS selectors to target form scroll area
@netlify
Copy link
Copy Markdown

netlify bot commented Mar 24, 2026

Deploy Preview for padp-staging ready!

Name Link
🔨 Latest commit 0c8f87b
🔍 Latest deploy log https://app.netlify.com/projects/padp-staging/deploys/69c4304083dee60008ec069d
😎 Deploy Preview https://deploy-preview-586--padp-staging.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@jamiefolsom jamiefolsom self-assigned this Mar 24, 2026
- cmsCallback only loads role-ui when TINA_PUBLIC_AUTH_USE_SSO is true
- getUserRole defaults to admin when no Clerk user is found
- Non-Clerk sites are unaffected by RBAC restrictions
@ajolipa ajolipa marked this pull request as ready for review March 25, 2026 18:58
@ajolipa ajolipa merged commit d4637df into RB-clerk-auth Mar 25, 2026
3 of 4 checks passed
@ajolipa ajolipa deleted the fix/role-based-ui branch March 25, 2026 18:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants