features
RBAC & Permissions

RBAC & Permissions

Role-Based Access Control system in HiveForge.

Overview

HiveForge uses a flexible RBAC system with:

  • Predefined roles (Owner, Admin, Member, Viewer)
  • Granular permissions
  • Resource-level access control
  • Easy customization

Default Roles

Owner

Full control of organization.

Permissions:

  • All organization management
  • Billing and subscription
  • Member management
  • Delete organization

Limitations:

  • Only one owner per organization
  • Cannot be removed (must transfer ownership)

Admin

Administrative access without billing.

Permissions:

  • Manage members (except owner)
  • Update organization settings
  • Manage API keys
  • Access all resources

Cannot:

  • Manage billing
  • Delete organization
  • Change owner

Member

Standard user access.

Permissions:

  • View organization resources
  • Create and edit own resources
  • Collaborate with team

Cannot:

  • Manage members
  • Change settings
  • Access billing

Viewer

Read-only access.

Permissions:

  • View organization
  • View resources

Cannot:

  • Edit anything
  • Create resources

Permission System

Permission Format

Permissions use format: resource.action

Examples:

  • organizations.update
  • members.invite
  • billing.manage
  • api_keys.create

Check Permissions

Frontend

import { usePermissions } from '@/hooks/usePermissions'
 
function EditButton() {
  const { hasPermission } = usePermissions()
 
  if (!hasPermission('organizations.update')) {
    return null
  }
 
  return <button>Edit Organization</button>
}

Backend

from app.services.permissions import check_permission
 
@router.put("/organizations/{org_id}")
async def update_organization(
    org_id: str,
    user = Depends(get_current_user)
):
    if not await check_permission(user.id, org_id, "organizations.update"):
        raise HTTPException(status_code=403, detail="Permission denied")
 
    # Update organization

Custom Permissions

Add custom permissions:

INSERT INTO permissions (name, resource, action, description)
VALUES (
  'projects.archive',
  'projects',
  'archive',
  'Archive projects'
);
 
-- Assign to role
INSERT INTO role_permissions (role_id, permission_id)
SELECT
  (SELECT id FROM roles WHERE name = 'admin'),
  (SELECT id FROM permissions WHERE name = 'projects.archive');

Role Management

Assign Roles

await updateMemberRole({
  organizationId: org.id,
  userId: user.id,
  role: 'admin',
})

Custom Roles

Create custom roles:

const role = await createRole({
  name: 'Project Manager',
  permissions: [
    'projects.create',
    'projects.update',
    'projects.delete',
    'members.view',
  ],
})

Permission Scopes

Organization-Level

Permissions apply to entire organization:

hasPermission('organizations.update')

Resource-Level

Permissions for specific resources:

hasPermission('projects.delete', projectId)

User Permissions

Individual user overrides:

-- Grant specific permission to user
INSERT INTO user_permissions (user_id, organization_id, permission_id, granted)
VALUES (user_id, org_id, permission_id, true);
 
-- Revoke permission
UPDATE user_permissions
SET granted = false
WHERE user_id = ? AND permission_id = ?;

Best Practices

1. Principle of Least Privilege

Give users minimum permissions needed:

// Good: Member role for standard users
await inviteMember(email, 'member')
 
// Bad: Admin role when not needed
await inviteMember(email, 'admin')

2. Check Permissions Early

Verify permissions before operations:

async function deleteProject(projectId: string) {
  // Check permission first
  if (!hasPermission('projects.delete')) {
    throw new PermissionError()
  }
 
  // Then perform operation
  await api.delete(`/projects/${projectId}`)
}

3. Audit Permission Changes

Log permission changes:

await audit_log(
    user_id=admin_id,
    action="permission.granted",
    resource_type="user",
    resource_id=user_id,
    metadata={
        "permission": "organizations.update",
        "granted_by": admin_id
    }
)

Examples

Protected Component

'use client'
 
import { usePermissions } from '@/hooks/usePermissions'
import { Button } from '@/components/ui/Button'
 
export function OrganizationSettings() {
  const { hasPermission } = usePermissions()
 
  const canUpdate = hasPermission('organizations.update')
  const canDelete = hasPermission('organizations.delete')
  const canManageBilling = hasPermission('billing.manage')
 
  return (
    <div>
      <h1>Settings</h1>
 
      {canUpdate && (
        <section>
          <h2>General Settings</h2>
          <EditForm />
        </section>
      )}
 
      {canManageBilling && (
        <section>
          <h2>Billing</h2>
          <BillingSettings />
        </section>
      )}
 
      {canDelete && (
        <section>
          <h2>Danger Zone</h2>
          <Button variant="danger" onClick={handleDelete}>
            Delete Organization
          </Button>
        </section>
      )}
    </div>
  )
}

Protected API Route

from app.core.permissions import require_permission
 
@router.delete("/organizations/{org_id}")
@require_permission("organizations.delete")
async def delete_organization(
    org_id: str,
    user = Depends(get_current_user)
):
    """Delete organization (requires organizations.delete permission)."""
    await OrganizationService.delete(org_id, user.id)
    return {"status": "deleted"}

Permission Decorator

from functools import wraps
from fastapi import HTTPException
 
def require_permission(permission: str):
    """Decorator to require specific permission."""
    def decorator(func):
        @wraps(func)
        async def wrapper(*args, **kwargs):
            # Get user from dependency
            user = kwargs.get('user')
            org_id = kwargs.get('org_id')
 
            # Check permission
            has_perm = await check_permission(
                user.id, org_id, permission
            )
 
            if not has_perm:
                raise HTTPException(
                    status_code=403,
                    detail=f"Missing permission: {permission}"
                )
 
            return await func(*args, **kwargs)
        return wrapper
    return decorator

Next Steps