Organizations & teams
IdentSphere's identity model is org-shaped. Every user belongs to exactly one organization (v1 limitation; many-to-many lands in v2). Permissions are evaluated within an organization.
Data model
organizations
│
│ 1:many
▼
organization_memberships ◄────── (user, organization, role)
│
│ many:1
▼
users
A user's organization_id column is their "home" org. Their role in that
org is read from organization_memberships, NOT from users.role — the
membership row is authoritative.
Roles
The default RBAC matrix ships with these roles:
| Role | Indicative permissions |
|---|---|
owner | Everything (cannot be granted via invitation; only via ownership transfer) |
admin | Org settings, member management, billing |
billing | Billing only |
member | Read org, contribute to projects |
viewer | Read-only |
These are configurable via RbacConfig::default_matrix() — fork the matrix
and pass your own when constructing Authorizer.
Owners
- An org always has at least one owner. The SDK enforces this:
DELETE /v1/orgs/:org_id/members/:user_idrefuses to remove the last owner;PATCH /v1/orgs/:org_id/members/:user_idrefuses to demote them. - The
ownerrole cannot be granted via invitations — only via the ownership-transfer flow (not yet shipped in v0.1; planned).
Org-scoped routes
Every endpoint under /v1/orgs/:org_id/* is guarded by require_org_match:
the path's :org_id must equal the caller's auth.organization_id. Cross-org
access returns 403.
Authorization
The Authorizer evaluates a Permission against the caller's role:
state
.authorizer
.authorize(&auth, &Permission::MembersInvite)
.await
.map_err(RouteError::from)?;
The permission set is defined in identsphere_core::rbac::Permission. Map your
own host permissions onto the same machinery by registering them in
RbacConfig.
Slug
organizations.slug is a globally unique URL-safe identifier. Used in:
- Email invite copy ("you've been invited to join Acme Co.")
- Optionally, your own URL routing (
/o/{slug}/projects)
The SDK enforces [a-z0-9-]{1,100} and global uniqueness. Slugs can be
provided at registration time or auto-derived from the name.
Soft delete
organizations.deleted_at is for host-implemented soft-delete; the SDK
itself never deletes orgs. Build your own teardown flow if you need one.
Settings
organizations.settings is a free-form JSONB column the SDK never reads.
Use it for host-app settings (theme, default project, etc.).
Audit
auth.registercreates an org as a side effect; the audit entry's resource is the new org id.members.invited,members.role_changed,members.removed,members.invitation_acceptedtrack membership lifecycle.