Team & Group Access
Managing access for individual users can quickly become overwhelming. Instead of assigning permissions user-by-user, you can group users into teams, departments, or roles, and grant permissions to the entire group.
This guide shows you how to define a group relation, add users to groups, and check if members inherit access.
To learn more about how subjects, objects, and relationships work under the hood, read the Relations and Actions guide.
1. Configure the schema
To support groups, your schema must include at least one relation with type: "group". This relation tells polizy that a subject is a member of another subject (which acts as a group).
import { defineSchema } from "polizy";
const schema = defineSchema({
subjectTypes: ["user", "team"],
objectTypes: ["document", "team"],
relations: {
// Declares that subjects can belong to teams
member: { type: "group" },
// Standard direct relations
owner: { type: "direct" },
viewer: { type: "direct" },
},
actionToRelations: {
view: ["viewer", "owner", "member"],
edit: ["owner"],
},
});
For more options, check out the Schema Reference.
2. Add members to a group
Use addMember to add a user (or another subject) to a group.
import { AuthSystem, InMemoryStorageAdapter } from "polizy";
const authz = new AuthSystem({
storage: new InMemoryStorageAdapter(),
schema,
});
// Add Alice to the engineering team
await authz.addMember({
member: { type: "user", id: "alice" },
group: { type: "team", id: "engineering" },
});
3. Grant access to the group
In polizy, groups are subjects. You can grant access to a group using allow, just like you would for an individual user:
await authz.allow({
who: { type: "team", id: "engineering" },
toBe: "viewer",
onWhat: { type: "document", id: "design-doc" },
});
4. Check inherited access
When you run check for a user, polizy automatically traverses group memberships to see if they inherit the permission:
const canView = await authz.check({
who: { type: "user", id: "alice" },
canThey: "view",
onWhat: { type: "document", id: "design-doc" },
});
console.log(canView); // true (inherited through engineering team membership)
5. Remove members from a group
When a user leaves the group, use removeMember to revoke their membership. They will immediately lose all inherited permissions:
await authz.removeMember({
member: { type: "user", id: "alice" },
group: { type: "team", id: "engineering" },
});
const canViewAfterLeaving = await authz.check({
who: { type: "user", id: "alice" },
canThey: "view",
onWhat: { type: "document", id: "design-doc" },
});
console.log(canViewAfterLeaving); // false
Advanced Scenarios
Nested groups (teams within teams)
Polizy automatically traverses nested memberships. If the frontend team is a member of the engineering team, members of frontend will inherit anything granted to engineering:
// Add frontend team as a member of the engineering team
await authz.addMember({
member: { type: "team", id: "frontend" },
group: { type: "team", id: "engineering" },
});
// Add Bob to the frontend team
await authz.addMember({
member: { type: "user", id: "bob" },
group: { type: "team", id: "frontend" },
});
// Bob inherits viewer access on the design-doc granted to engineering
const bobCanView = await authz.check({
who: { type: "user", id: "bob" },
canThey: "view",
onWhat: { type: "document", id: "design-doc" },
});
console.log(bobCanView); // true
Multiple group relations (using the as parameter)
If your schema defines more than one group relation (for example, member and manager), you must explicitly specify which relation to use with the as parameter:
// Schema with multiple group relations
const multiGroupSchema = defineSchema({
subjectTypes: ["user", "team"],
objectTypes: ["document", "team"],
relations: {
member: { type: "group" },
manager: { type: "group" },
},
actionToRelations: {
view: ["member", "manager"],
}
});
// ... instantiate authz ...
// Specify "as" because the system cannot infer which relation to use
await authz.addMember({
member: { type: "user", id: "alice" },
group: { type: "team", id: "engineering" },
as: "manager",
});
// The same requirement applies when removing a member
await authz.removeMember({
member: { type: "user", id: "alice" },
group: { type: "team", id: "engineering" },
as: "manager",
});
The as parameter is optional and inferred automatically only when there is exactly one group relation defined in your schema.