Skip to content

Folders and projects cannot be shared with a specific person (no collaborators API) #112

Description

@Jason-jo17

Summary

A folder or a project can't be shared with a specific person. The permission engine already supports it — only the API surface is missing:

  • doc_acl already models resource_type doc | folder | project × principal_type user | team | org_role × permission read | write | admin | none, with expires_at and a unique (resource_type, resource_id, principal_type, principal_id) grant.
  • app_effective_permission() already resolves precedence (explicit deny wins, then best positive across principals).
  • canAccess() (apps/api/src/lib/iam.ts) already implements inheritance: doc → folder → project.

But doc_acl rows are only ever written from routes/docs.ts and routes/org.ts, and routes/folders.ts has no sharing endpoints at all — nor does routes/projects.ts. So there is no way for an owner to add a collaborator to a folder or project and pick their access level.

Proposal

Add the missing CRUD, writing the same doc_acl rows the org access matrix already writes:

GET    /api/{folders|projects}/:id/collaborators
POST   /api/{folders|projects}/:id/collaborators
PATCH  /api/{folders|projects}/:id/collaborators/:userId
DELETE /api/{folders|projects}/:id/collaborators/:userId

Because inheritance already exists, a grant on a folder automatically covers every doc inside it — no new resolution logic, no schema change.

  • Managing shares requires admin on the resource via canAccess() (which falls through to the workspace role, so owners/admins don't need an explicit grant on every resource).
  • Unknown ids and ids from another tenant both 404 — existence is checked through RLS so this can't be used to probe for foreign ids.
  • Every grant / update / revoke is written to iam_audit_log.

Scope: the grantee must already be a workspace member. Inviting an outside email needs a pending grant that materialises on signup plus a restricted-visibility guest role — deliberately out of scope here, since that changes a security default and should be decided separately.

Question for maintainers (core vs enterprise)

CONTRIBUTING lists org-IAM-SSO as an enterprise module, but doc_acl, lib/iam.ts, routes/folders.ts and routes/projects.ts are all present in this repo — i.e. resource-level ACL looks like core, while org-wide IAM (roles/teams/SSO) is the enterprise part.

Is resource-level sharing core, or should it sit behind the enterprise seam? Opening this first per CONTRIBUTING ("if you're unsure which side a change belongs on, open an issue first"). Happy to relocate — the implementation is self-contained (one route file, one lib file, one registration line).

Acceptance criteria

  • An owner/admin of a folder or project can list, grant, update and revoke a collaborator's access.
  • Grants inherit to contained docs via the existing canAccess() chain.
  • Non-admins get 403; unknown/foreign resources 404.
  • Actions are audit-logged; no schema change; core continues to build with enterprise modules absent.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions