Organization Permissions v3

Overview

Organization Permissions v3 extends the stable v2 foundation (custom roles + JWT integration) with two major features: immutable audit logging for compliance and debugging, and team management for simplified bulk role assignment. This guide covers both features and how they work together to provide enhanced permission management at scale.

Key Features

1. Audit Logging

Track every permission change with immutable audit logs that capture:
  • Who changed it (actor ID)
  • What changed (role/team changes with before/after values)
  • When it happened (timestamp)
  • Where it came from (IP address, session context)
  • Why it was approved (optional approval metadata in future versions)
Audit logs are:
  • Immutable: Never modified or deleted (except by retention policy)
  • Write-only: No direct updates after creation
  • Queryable: Filter by actor, resource type, action, and date range
  • Retentionable: Automatic cleanup based on organization policy

2. Teams

Simplify permission management at scale by:
  • Creating organization-specific teams
  • Assigning multiple custom roles to teams
  • Adding/removing members from teams
  • Members get the union of their personal roles + team roles
Teams enable:
  • Bulk role assignment: One team can represent many members with the same permissions
  • Scalability: Manage 100+ members more efficiently
  • Flexibility: Members can belong to multiple teams
  • Gradual adoption: Both personal and team-based role assignment work simultaneously

Architecture

Permission Check Flow

When checking if a user has a permission:
User.can(orgId, "permission:action")?

Is user an owner or admin? → YES: ✅ Allow
    ↓ NO
Check cache for computed permissions

If miss: Compute union of:
  • Personal custom roles → permissions
  • Team role assignments → permissions

Cache result (5 min TTL)

Check if permission in union

Permission Union Model

A member’s effective permissions are the union of:
  1. Personal Roles: Custom roles assigned directly to the member
  2. Team Roles: Custom roles assigned to teams the member belongs to
  3. Built-in Roles: owner/admin bypass checks entirely
Example:
Member "Alice" has:
  • Personal roles: [Editor]
  • Team memberships: [Marketing, Sales]
  • Marketing team roles: [Viewer, Content Approver]
  • Sales team roles: [Lead Manager]

Effective permissions:
  = Editor permissions ∪ Viewer permissions ∪ Content Approver permissions ∪ Lead Manager permissions

Caching Strategy

Permission unions are cached for performance:
  • Primary: Redis (distributed, shared across processes)
  • Fallback: In-memory Map (single process, when Redis unavailable)
  • TTL: 5 minutes
  • Invalidation: Eager, on any role/team change
Cache key: member:{memberId}:org:{orgId}:permissions

Database Schema

New Tables

audit_logs — Immutable append-only audit trail
  • id (UUID, PK)
  • org_id (UUID, FK)
  • actor_id (UUID) — Who made the change
  • action (VARCHAR) — Free-form action string (e.g., “member_role_assigned”)
  • resource_type (VARCHAR) — What was changed (e.g., “role”, “team”, “member”)
  • resource_id (UUID) — ID of the resource changed
  • changes_json (JSONB) — Before/after values
  • ip_address (INET) — Optional IP address for audit trail
  • session_context (JSONB) — Optional session data for compliance
  • approval_status (VARCHAR) — Optional approval workflow status (future)
  • approval_metadata (JSONB) — Optional approval details (future)
  • org_retention_days (INT) — Retention policy at time of log
  • created_at (TIMESTAMP)
teams — Organization teams
  • id (UUID, PK)
  • org_id (UUID, FK)
  • name (VARCHAR)
  • description (TEXT)
  • created_at (TIMESTAMP)
  • updated_at (TIMESTAMP)
team_role_assignment — Map custom roles to teams
  • id (UUID, PK)
  • team_id (UUID, FK)
  • role_id (UUID, FK)

Schema Extensions

members table:
  • team_ids (UUID[]) — Fast lookup of member’s teams (JSONB array)

API Endpoints

Audit Logging

Read Audit Logs

GET /api/v1/organizations/:orgId/audit List audit logs with optional filtering. Query Parameters:
ParameterTypeDescription
actor_idUUIDFilter by actor who made the change
resource_typestringFilter by resource type (role, team, member)
resource_idUUIDFilter by specific resource
actionstringFilter by action name
start_dateISO 8601Filter logs after this date
end_dateISO 8601Filter logs before this date
pagenumberPagination (default: 1)
pageSizenumberPage size (default: 20, max: 100)
Response (200 OK):
{
  "logs": [
    {
      "id": "audit_abc123",
      "orgId": "org_xyz789",
      "actorId": "user_def456",
      "action": "member_role_assigned",
      "resourceType": "member",
      "resourceId": "member_xyz789",
      "changes": {
        "before": { "customRoleIds": ["role_a"] },
        "after": { "customRoleIds": ["role_a", "role_b"] }
      },
      "ipAddress": "192.168.1.1",
      "createdAt": "2026-04-24T10:30:00Z"
    }
  ],
  "total": 1,
  "page": 1,
  "pageSize": 20,
  "retentionDays": 90
}

Cleanup Audit Logs

POST /api/v1/organizations/:orgId/audit/cleanup Delete audit logs older than the organization’s retention policy. Typically called by a scheduled job. Request Body:
{
  "dry_run": false
}
Response (200 OK):
{
  "deleted": 150,
  "retainedFrom": "2026-01-25T00:00:00Z"
}

Teams Management

Create Team

POST /api/v1/organizations/:orgId/teams Create a new team in the organization. Requires owner or admin role. Request Body:
{
  "name": "Engineering",
  "description": "Engineering team members"
}
Response (201 Created):
{
  "id": "team_abc123",
  "orgId": "org_xyz789",
  "name": "Engineering",
  "description": "Engineering team members",
  "createdAt": "2026-04-24T10:30:00Z"
}

List Teams

GET /api/v1/organizations/:orgId/teams List all teams in the organization. Query Parameters:
ParameterTypeDescription
pagenumberPagination (default: 1)
pageSizenumberPage size (default: 20, max: 100)
Response (200 OK):
{
  "teams": [
    {
      "id": "team_abc123",
      "orgId": "org_xyz789",
      "name": "Engineering",
      "description": "Engineering team members",
      "memberCount": 5,
      "createdAt": "2026-04-24T10:30:00Z"
    }
  ],
  "total": 1,
  "page": 1,
  "pageSize": 20
}

Get Team

GET /api/v1/organizations/:orgId/teams/:teamId Retrieve a specific team by ID. Response (200 OK):
{
  "id": "team_abc123",
  "orgId": "org_xyz789",
  "name": "Engineering",
  "description": "Engineering team members",
  "roles": [
    {
      "id": "role_abc123",
      "name": "Senior Engineer",
      "permissions": ["code:review", "code:merge"]
    }
  ],
  "members": [
    {
      "id": "member_abc123",
      "userId": "user_abc123",
      "email": "alice@example.com",
      "joinedAt": "2026-04-20T09:00:00Z"
    }
  ],
  "createdAt": "2026-04-24T10:30:00Z"
}

Update Team

PATCH /api/v1/organizations/:orgId/teams/:teamId Update team name or description. Requires owner or admin role. Request Body:
{
  "name": "Engineering Team",
  "description": "Backend and frontend engineers"
}
Response (200 OK):
{
  "id": "team_abc123",
  "orgId": "org_xyz789",
  "name": "Engineering Team",
  "description": "Backend and frontend engineers",
  "updatedAt": "2026-04-24T12:00:00Z"
}

Delete Team

DELETE /api/v1/organizations/:orgId/teams/:teamId Delete a team. Members remain in the organization but lose team role assignments. Response (204 No Content)

Assign Role to Team

POST /api/v1/organizations/:orgId/teams/:teamId/roles Assign a custom role to the team. Members of this team gain the role’s permissions. Request Body:
{
  "roleId": "role_abc123"
}
Response (201 Created):
{
  "roleId": "role_abc123",
  "teamId": "team_abc123",
  "assignedAt": "2026-04-24T10:30:00Z"
}

Remove Role from Team

DELETE /api/v1/organizations/:orgId/teams/:teamId/roles/:roleId Remove a custom role from the team. Members lose this role’s permissions unless they have it through another team or personal assignment. Response (204 No Content)

Add Member to Team

POST /api/v1/organizations/:orgId/teams/:teamId/members Add an existing organization member to the team. Request Body:
{
  "memberId": "member_abc123"
}
Response (201 Created):
{
  "memberId": "member_abc123",
  "teamId": "team_abc123",
  "joinedAt": "2026-04-24T10:30:00Z"
}

Remove Member from Team

DELETE /api/v1/organizations/:orgId/teams/:teamId/members/:memberId Remove a member from the team. Response (204 No Content)

Common Actions

Track Permission Changes

# Get all changes made by a specific admin
curl "https://api.example.com/api/v1/organizations/org_xyz/audit?actor_id=user_abc&pageSize=50" \
  -H "Authorization: Bearer <token>"

Create a Team with Multiple Roles

# 1. Create team
curl -X POST "https://api.example.com/api/v1/organizations/org_xyz/teams" \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Marketing",
    "description": "Marketing team"
  }'

# 2. Assign roles to the team
curl -X POST "https://api.example.com/api/v1/organizations/org_xyz/teams/team_123/roles" \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{"roleId": "role_content_writer"}'

# 3. Add members to the team
curl -X POST "https://api.example.com/api/v1/organizations/org_xyz/teams/team_123/members" \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{"memberId": "member_abc"}'

Query Audit Trail for Compliance

# Get all role changes in the last 30 days
curl "https://api.example.com/api/v1/organizations/org_xyz/audit?start_date=2026-03-25&end_date=2026-04-25&pageSize=100" \
  -H "Authorization: Bearer <token>"

Access Control

ActionRequired RoleScope
List/View Audit LogsMemberOrganization
Create TeamOwner, AdminOrganization
Update/Delete TeamOwner, AdminOrganization
Assign/Remove Team RolesOwner, AdminOrganization
Add/Remove Team MembersOwner, AdminOrganization