Skip to content

Permissions

Hule has a layered access control model. Each layer is checked in order, and every layer must pass.

Layer 1: Authentication

Every request (except auth endpoints) requires a valid JWT access token. The token identifies the user and carries their profile information. Public endpoints — login, registration, password reset — are exempt.

Layer 2: Rate limiting

Requests are rate-limited by IP address. Auth endpoints (/auth/*) have stricter limits to prevent brute-force attacks. If you exceed the limit, you'll receive a 429 Too Many Requests response.

Layer 3: Workspace membership

To access any workspace-scoped resource, you must be an active member of that workspace. Membership is checked live on every request — caching is not used, so changes (removal, leaving) take effect immediately.

Workspace membership comes in two states:

StateMeaning
ActiveFull member — can see and be granted access to content
PendingInvite sent but not yet accepted — no access to workspace content

Workspace membership alone does not grant access to spaces, lists, or views inside the workspace. It's a prerequisite check, not an authorisation.

Roles

Every active member has a role. The owner is authoritative via Workspace.ownerId (no membership row needed); Admin/Member come from the membership row.

RoleCapabilities
OwnerFull control. Can change roles, delete the workspace. Has implicit edit access to every entity (no Share row required).
AdminCan invite/remove plain members and grant/revoke Shares. Cannot manage the owner or other admins. No implicit entity access — needs a Share (or shareEverything) like a member.
MemberNo member-management or sharing. Sees only the entities they hold a Share on.

A per-member shareEverything flag grants a member a blanket edit Share on every entity in the workspace (current and future). Toggling it off removes exactly those blanket grants.

Layer 4: Entity-level sharing (Shares)

Inside a workspace, access to spaces, lists, and views is controlled by Share rows — explicit permission records scoped to one user and one entity.

How shares are checked

When you access a space, list, or view, the system looks for a Share row matching (entityType, entityId, yourUserId). If none exists, access is denied — even if you're a workspace member.

Two exceptions bypass the Share-row lookup: the workspace owner has implicit edit on every entity, and workspace-root views are readable by any active member without a Share row.

Access levels

LevelWhat you can do
ReadView the entity and its contents. May also add comments (and the @mentions they carry) — comment creation deliberately requires only Read, not Edit. No other modifications.
EditCreate, update, and delete tasks and comments within the entity.

Cascade on share

When a space is shared, all its existing lists and views are shared at the same level. New lists or views created after the share are not auto-shared — the owner must grant access explicitly, or use the auto-grant flow in the share dialog.

Tasks and comments

Tasks and comments don't have their own Share rows. Access is derived:

  • Task: check Share on the task's parent list.
  • Comment: check Share on the comment's parent task → its parent list.

This means if you have Edit access to a list, you can create and edit tasks and comments inside it without any additional permissions.

Layer 5: Role-gated operations

Certain operations require Admin or Owner:

  • Inviting and removing members (an admin may remove plain members only, never the owner or another admin).
  • Granting and revoking Shares (an admin may manage plain members only).
  • Configuration: creating/updating folders, lists, views, templates.

A few operations remain owner-only:

  • Changing a member's role or shareEverything flag.
  • Deleting the workspace.

Self-service exceptions: any grantee may self-revoke (leave) a Share granted to them, and any member may leave the workspace.

These are enforced by @RequireRole guards and re-checked at the service layer. An unauthorised attempt gets a 403 Forbidden.

Summary

Request


JWT Auth Guard        ← Layer 1: valid token required


Rate Limiter          ← Layer 2: per-IP limits


Member Guard          ← Layer 3: active workspace member?


Share Guard           ← Layer 4: Share row exists? (owner + root-views bypass)


Role Guard + service  ← Layer 5: role-gated (admin/owner) constraints
  • Workspaces — membership management.
  • Sharing — how to share spaces, lists, and views.