buster/packages/access-controls
dal e92b610ed2
Refactor BigQuery adapter to include schema metadata in query results and improve type mappings. Update tests to validate new behavior and ensure compatibility with existing functionality.
2025-09-16 16:27:28 -06:00
..
scripts Use tsx and .ts files for validation 2025-07-21 16:07:14 -06:00
src Refactor BigQuery adapter to include schema metadata in query results and improve type mappings. Update tests to validate new behavior and ensure compatibility with existing functionality. 2025-09-16 16:27:28 -06:00
.env.example Mastra braintrust (#391) 2025-07-02 14:33:40 -07:00
.gitignore Mastra braintrust (#391) 2025-07-02 14:33:40 -07:00
CLAUDE.md CLAUDE.md and README.md updates... 2025-09-15 15:06:41 -06:00
README.md CLAUDE.md and README.md updates... 2025-09-15 15:06:41 -06:00
biome.json Mastra braintrust (#391) 2025-07-02 14:33:40 -07:00
env.d.ts Mastra braintrust (#391) 2025-07-02 14:33:40 -07:00
package.json Integrate SQL functionality by adding SQL routes and updating dependencies. Refactor access control imports to use centralized access-controls package. Update pnpm-lock and package.json for new dependencies including node-sql-parser and yaml. 2025-09-15 15:07:43 -06:00
tsconfig.json Update inlcude 2025-07-12 23:46:09 -06:00
turbo.json update database dev 2025-07-15 22:26:13 -06:00
vitest.config.ts Mastra braintrust (#391) 2025-07-02 14:33:40 -07:00

README.md

Access Controls Package

Permission and security logic for the Buster platform. This package enforces access control policies across the entire application.

Installation

pnpm add @buster/access-controls

Overview

@buster/access-controls provides:

  • User authentication and authorization
  • Role-based access control (RBAC)
  • Resource-level permissions
  • Security policy enforcement
  • Permission validation and checking

Security Model

User → Roles → Permissions → Resources
         ↓
    Organization
    (Multi-tenant)

Usage

Basic Permission Check

import { checkPermission } from '@buster/access-controls';

const canAccess = await checkPermission({
  user: currentUser,
  action: 'read',
  resource: {
    type: 'dashboard',
    id: 'dash-123',
    organizationId: 'org-456'
  }
});

if (!canAccess) {
  throw new ForbiddenError('Access denied');
}

Role Definitions

import { Roles } from '@buster/access-controls';

// Available roles
const roles = {
  OWNER: 'owner',    // Full access
  ADMIN: 'admin',    // Admin access
  MEMBER: 'member',  // Read/write access
  VIEWER: 'viewer'   // Read-only access
};

Resource Access Control

import { canAccessResource } from '@buster/access-controls';

const hasAccess = await canAccessResource({
  user: currentUser,
  resource: {
    type: 'datasource',
    id: 'ds-123'
  }
});

Permission System

Granular Permissions

import { Permissions } from '@buster/access-controls';

// Available permissions
const permissions = {
  // Dashboard permissions
  DASHBOARD_CREATE: 'dashboard:create',
  DASHBOARD_READ: 'dashboard:read',
  DASHBOARD_UPDATE: 'dashboard:update',
  DASHBOARD_DELETE: 'dashboard:delete',
  DASHBOARD_SHARE: 'dashboard:share',
  
  // Data source permissions
  DATASOURCE_CREATE: 'datasource:create',
  DATASOURCE_READ: 'datasource:read',
  DATASOURCE_UPDATE: 'datasource:update',
  DATASOURCE_DELETE: 'datasource:delete',
  DATASOURCE_QUERY: 'datasource:query',
  
  // Organization permissions
  ORG_MANAGE_MEMBERS: 'org:manage_members',
  ORG_MANAGE_BILLING: 'org:manage_billing',
  ORG_MANAGE_SETTINGS: 'org:manage_settings'
};

Checking Specific Permissions

import { checkUserPermission } from '@buster/access-controls';

const canCreateDashboard = await checkUserPermission(
  userId,
  Permissions.DASHBOARD_CREATE
);

Multi-Tenant Security

Organization Boundaries

import { enforceOrganizationBoundary } from '@buster/access-controls';

// Ensure user and resource belong to same organization
await enforceOrganizationBoundary(
  userId,
  organizationId,
  resourceId
);

Cross-Organization Access

import { checkCrossOrgAccess } from '@buster/access-controls';

const canShare = await checkCrossOrgAccess({
  sourceOrg: org1,
  targetOrg: org2,
  user: currentUser
});

Authentication

Session Validation

import { validateSession } from '@buster/access-controls';

const user = await validateSession(sessionToken);
if (!user) {
  throw new UnauthorizedError('Invalid session');
}

API Key Authentication

import { validateApiKey } from '@buster/access-controls';

const validation = await validateApiKey(apiKey);
if (!validation.valid) {
  throw new UnauthorizedError('Invalid API key');
}

// Use validated context
const { userId, organizationId, scopes } = validation;

Middleware Integration

Hono Middleware

import { requirePermission } from '@buster/access-controls';

// Protect routes with permission checks
app.get('/api/dashboards/:id', 
  requireAuth(),
  requirePermission(Permissions.DASHBOARD_READ),
  getDashboardHandler
);

app.post('/api/dashboards',
  requireAuth(),
  requirePermission(Permissions.DASHBOARD_CREATE),
  createDashboardHandler
);

Custom Middleware

export function requireRole(role: Role) {
  return async (c: Context, next: Next) => {
    const user = c.get('user');
    const hasRole = await checkUserRole(user.id, role);
    
    if (!hasRole) {
      return c.json({ error: 'Insufficient privileges' }, 403);
    }
    
    await next();
  };
}

Security Policies

Rate Limiting

import { checkRateLimit } from '@buster/access-controls';

const allowed = await checkRateLimit(userId, 'api_call');
if (!allowed) {
  throw new RateLimitError('Too many requests');
}

IP Allowlisting

import { checkIpAllowlist } from '@buster/access-controls';

const allowed = await checkIpAllowlist(
  organizationId,
  request.ip
);

if (!allowed) {
  throw new ForbiddenError('IP not allowed');
}

Audit Logging

import { logSecurityEvent } from '@buster/access-controls';

// Log security-relevant events
await logSecurityEvent({
  type: 'permission_denied',
  userId: user.id,
  resource: resource.id,
  action: 'delete',
  ipAddress: request.ip,
  timestamp: new Date()
});

Error Handling

import { 
  SecurityError,
  ForbiddenError,
  UnauthorizedError 
} from '@buster/access-controls';

try {
  await checkPermission(params);
} catch (error) {
  if (error instanceof ForbiddenError) {
    // User doesn't have permission
    return res.status(403).json({ error: 'Access denied' });
  }
  
  if (error instanceof UnauthorizedError) {
    // User not authenticated
    return res.status(401).json({ error: 'Authentication required' });
  }
  
  // Other security error
  throw error;
}

Testing

Unit Tests

describe('checkPermission', () => {
  it('should allow owner all actions', async () => {
    const result = await checkPermission({
      user: { id: '1', role: 'owner' },
      action: 'delete',
      resource: { type: 'dashboard', id: '123', organizationId: 'org1' }
    });
    
    expect(result).toBe(true);
  });
  
  it('should deny viewer write access', async () => {
    const result = await checkPermission({
      user: { id: '2', role: 'viewer' },
      action: 'write',
      resource: { type: 'dashboard', id: '123', organizationId: 'org1' }
    });
    
    expect(result).toBe(false);
  });
});

Best Practices

DO:

  • Use functional permission checks
  • Validate all inputs with Zod
  • Implement defense in depth
  • Log security events
  • Use principle of least privilege
  • Check permissions at every layer
  • Implement rate limiting
  • Use secure session management

DON'T:

  • Hardcode permissions
  • Trust client-side checks
  • Skip permission validation
  • Store plain text passwords
  • Log sensitive data
  • Use predictable tokens
  • Allow unlimited access
  • Bypass security for convenience

Development

# Build
turbo build --filter=@buster/access-controls

# Test
turbo test:unit --filter=@buster/access-controls
turbo test:integration --filter=@buster/access-controls

# Lint
turbo lint --filter=@buster/access-controls

This package is critical for platform security. Always err on the side of denying access when in doubt.