mirror of https://github.com/buster-so/buster.git
6.9 KiB
6.9 KiB
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_file',
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_file', 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_file', 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.