mirror of https://github.com/buster-so/buster.git
347 lines
6.9 KiB
Markdown
347 lines
6.9 KiB
Markdown
# Access Controls Package
|
|
|
|
Permission and security logic for the Buster platform. This package enforces access control policies across the entire application.
|
|
|
|
## Installation
|
|
|
|
```bash
|
|
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
|
|
|
|
```typescript
|
|
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
|
|
|
|
```typescript
|
|
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
|
|
|
|
```typescript
|
|
import { canAccessResource } from '@buster/access-controls';
|
|
|
|
const hasAccess = await canAccessResource({
|
|
user: currentUser,
|
|
resource: {
|
|
type: 'datasource',
|
|
id: 'ds-123'
|
|
}
|
|
});
|
|
```
|
|
|
|
## Permission System
|
|
|
|
### Granular Permissions
|
|
|
|
```typescript
|
|
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
|
|
|
|
```typescript
|
|
import { checkUserPermission } from '@buster/access-controls';
|
|
|
|
const canCreateDashboard = await checkUserPermission(
|
|
userId,
|
|
Permissions.DASHBOARD_CREATE
|
|
);
|
|
```
|
|
|
|
## Multi-Tenant Security
|
|
|
|
### Organization Boundaries
|
|
|
|
```typescript
|
|
import { enforceOrganizationBoundary } from '@buster/access-controls';
|
|
|
|
// Ensure user and resource belong to same organization
|
|
await enforceOrganizationBoundary(
|
|
userId,
|
|
organizationId,
|
|
resourceId
|
|
);
|
|
```
|
|
|
|
### Cross-Organization Access
|
|
|
|
```typescript
|
|
import { checkCrossOrgAccess } from '@buster/access-controls';
|
|
|
|
const canShare = await checkCrossOrgAccess({
|
|
sourceOrg: org1,
|
|
targetOrg: org2,
|
|
user: currentUser
|
|
});
|
|
```
|
|
|
|
## Authentication
|
|
|
|
### Session Validation
|
|
|
|
```typescript
|
|
import { validateSession } from '@buster/access-controls';
|
|
|
|
const user = await validateSession(sessionToken);
|
|
if (!user) {
|
|
throw new UnauthorizedError('Invalid session');
|
|
}
|
|
```
|
|
|
|
### API Key Authentication
|
|
|
|
```typescript
|
|
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
|
|
|
|
```typescript
|
|
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
|
|
|
|
```typescript
|
|
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
|
|
|
|
```typescript
|
|
import { checkRateLimit } from '@buster/access-controls';
|
|
|
|
const allowed = await checkRateLimit(userId, 'api_call');
|
|
if (!allowed) {
|
|
throw new RateLimitError('Too many requests');
|
|
}
|
|
```
|
|
|
|
### IP Allowlisting
|
|
|
|
```typescript
|
|
import { checkIpAllowlist } from '@buster/access-controls';
|
|
|
|
const allowed = await checkIpAllowlist(
|
|
organizationId,
|
|
request.ip
|
|
);
|
|
|
|
if (!allowed) {
|
|
throw new ForbiddenError('IP not allowed');
|
|
}
|
|
```
|
|
|
|
## Audit Logging
|
|
|
|
```typescript
|
|
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
|
|
|
|
```typescript
|
|
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
|
|
|
|
```typescript
|
|
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
|
|
|
|
```bash
|
|
# 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. |