Skip to main content

Roles and permissions

Fovea's RBAC is a three-scope role system on top of a single RolePermission matrix. Users hold roles at one or more scopes; the matrix says what each role can do; CASL composes the union.

Scopes

system    every authenticated user has a system role
group users in a group hold a group role
project users in a project hold a project role

User.systemRole is the system role. GroupMembership.role is the group role; one row per (user, group) pair. ProjectMembership.role is the project role; one row per (user, project) pair.

System roles

system_admin    can('manage', 'all') — full access to every resource
user default; permissions come from group + project roles
plus baseline ownership rules

User.systemRole defaults to 'user'. system_admin is set explicitly during seed or via an admin endpoint; in v0.1.x the flag was the boolean User.isAdmin. Both columns exist on v0.2.x; new code uses systemRole.

Group roles

group_owner     update / delete group, manage members, create projects
group_admin update group, manage members, create projects
group_member read group info

Project roles

project_owner     full content access + project admin (update / delete /
manage members)
project_manager full content access + project admin minus delete project
annotator create / read / update / delete own content; read all
content within the project
reviewer read all content; review action on
annotation / summary / claim
viewer read-only on every project content type

annotator write actions are ownOnly: true in the seed, so CASL adds a createdByUserId = userId (or equivalent) condition; read is unconditional within the project.

Baseline ownership

Independent of the role matrix, every authenticated user can always read / update / delete resources they own:

own Annotation  by createdByUserId
own VideoSummary by createdBy
own Claim by createdBy
own Persona by userId
own WorldState by userId

These rules are added unconditionally in defineAbilitiesFor. A user without any project or group role still owns their personal content.

Seeding a custom role

server/prisma/seed-permissions.ts inserts the production matrix. Adding a new role is a pair of writes plus a cache flush:

await prisma.rolePermission.create({
data: { scope: 'project', role: 'curator', resourceType: 'claim',
action: 'update', ownOnly: false }
})
invalidatePermissionCache()

invalidatePermissionCache() flushes the global matrix cache and every per-user ability cache. Without it, the new rule does not take effect until the matrix TTL elapses (5 minutes).

See also