Skip to content
Open
Show file tree
Hide file tree
Changes from 26 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
a9c314b
feat(rbac): extend better-auth permissions with GRC resources
Marfuen Feb 2, 2026
565917e
feat(rbac): add PermissionGuard and @RequirePermission decorator
Marfuen Feb 2, 2026
1521c94
feat(rbac): sync portal permissions with app permissions
Marfuen Feb 2, 2026
68d4cbf
test(rbac): add PermissionGuard unit tests
Marfuen Feb 2, 2026
25ef8cf
feat(rbac): migrate controllers from RequireRoles to RequirePermission
Marfuen Feb 2, 2026
3f8c37c
feat(rbac): add assignment-based filtering for employee/contractor roles
Marfuen Feb 2, 2026
4c2d143
feat(rbac): add department-based policy visibility
Marfuen Feb 2, 2026
3304ee2
feat(auth): centralize auth on API with security hardening
Marfuen Feb 2, 2026
b1bd6d4
feat(rbac): add shared auth package and API integration
Marfuen Feb 2, 2026
2234ada
feat(rbac): enable dynamic access control for custom roles
Marfuen Feb 2, 2026
5060265
feat(rbac): add Custom Roles API for dynamic role management
Marfuen Feb 2, 2026
741b7ea
feat(rbac): support multiple roles for privilege escalation checks
Marfuen Feb 2, 2026
e490de8
fix(auth): prevent JWKS key regeneration causing session loss
Marfuen Feb 2, 2026
8708bfe
test(rbac): add unit tests for Custom Roles API
Marfuen Feb 2, 2026
882368b
docs: add testing guidelines for API development
Marfuen Feb 2, 2026
27acc06
refactor(docs): move API testing rules to apps/api
Marfuen Feb 2, 2026
1209d32
test(rbac): add privilege escalation test for role updates
Marfuen Feb 2, 2026
81bf6cd
feat(rbac): implement Custom Roles UI (ENG-148)
Marfuen Feb 2, 2026
eaf197e
chore(docs): add API endpoints for managing custom roles
Marfuen Feb 2, 2026
c59d04f
chore(auth): implement cleanup of stale JWKS records on secret change
Marfuen Feb 3, 2026
3a9e9f1
chore(permissions): implement user permissions resolution and route p…
Marfuen Feb 3, 2026
bb323bb
chore(api): migrate to session-based authentication and add controls …
Marfuen Feb 4, 2026
71d8751
refactor(auth): update permission checks to include cookie header
Marfuen Feb 4, 2026
2c2989f
refactor(api): update permission guard logging to include request det…
Marfuen Feb 4, 2026
fe67db9
refactor(api): remove unnecessary logging from getPolicy function
Marfuen Feb 4, 2026
09ded08
Merge origin/main into feat/rbac-v1
Marfuen Feb 4, 2026
2d3fa3c
chore(api): handle optional chaining for user ID in various actions
Marfuen Feb 4, 2026
7ef29c3
refactor(app): update policy editor to check version read-only state
Marfuen Feb 4, 2026
de2273a
refactor(api): enhance version content handling and validation in pol…
Marfuen Feb 4, 2026
c74407b
refactor(app): update PolicyArchiveSheet to use new design system com…
Marfuen Feb 4, 2026
7e4d534
feat(audit): implement audit logging interceptor and related function…
Marfuen Feb 5, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
31 changes: 31 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Project Rules

## API Architecture: Server Actions Migration

We are migrating away from Next.js server actions toward calling the NestJS API directly. The API enforces RBAC via `@RequirePermission` decorators, so all business logic should flow through it.

### Simple CRUD operations
Call the NestJS API directly from the client using custom hooks with useSWR for data fetching and useSWRMutation (or similar) for mutations. Delete the server action entirely.

- Client component calls the API via a hook
- No server action wrapper needed
- The NestJS API handles auth, validation, RBAC, and audit logging

### Multi-step orchestration
When an operation requires assembling multiple API calls (e.g., S3 upload + PATCH, read version + update policy), create a Next.js API route (`apps/app/src/app/api/...`) that orchestrates the calls. Delete the server action.

- Client component calls the Next.js API route
- Next.js API route calls the NestJS API endpoint(s) as needed
- Keeps orchestration server-side without exposing intermediate steps to the client

### What NOT to do
- Do NOT keep server actions as wrappers around API calls
- Do NOT use server actions for new features
- Do NOT add direct database (`@db`) access in the Next.js app for mutations — always go through the API

### API Client
- Server-side (Next.js API routes): use `apps/app/src/lib/api-server.ts` (`serverApi`)
- Client-side (hooks): call the NestJS API directly via fetch or a client-side API utility

### Tracking
Migration progress is tracked in Linear ticket ENG-165.
80 changes: 80 additions & 0 deletions apps/api/.cursorrules
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# API Development Rules

## Testing Requirements

**Every new feature MUST include tests.** This is mandatory, not optional.

### When to Write Tests

1. **New features**: Every new service, controller, or significant function MUST have accompanying unit tests
2. **Bug fixes**: Add a test that reproduces the bug before fixing it
3. **Refactoring**: Ensure existing tests pass; add tests if coverage is lacking

### When to Run Tests

Run tests after significant changes:

```bash
# Run tests for a specific module
npx jest src/<module-name> --passWithNoTests

# Run all API tests
npx turbo run test --filter=@comp/api

# Run tests in watch mode during development
npx jest --watch
```

### Test File Conventions

- Place test files next to the source file: `foo.service.ts` → `foo.service.spec.ts`
- Use Jest and NestJS testing utilities
- Mock external dependencies (database, external APIs)
- Test both success and error cases

### Minimum Test Coverage

- **Services**: Test all public methods, including error handling
- **Controllers**: Test endpoint routing and parameter passing
- **Guards**: Test authorization logic
- **Utils**: Test edge cases and error conditions

### Example Test Structure

```typescript
describe('MyService', () => {
describe('myMethod', () => {
it('should handle success case', async () => { ... });
it('should throw on invalid input', async () => { ... });
it('should handle edge case X', async () => { ... });
});
});
```

## Development Workflow

1. **Before coding**: Read existing code patterns in the module
2. **During coding**: Follow established patterns, add types
3. **After significant changes**:
- Run type-check: `npx turbo run typecheck --filter=@comp/api`
- Run tests: `npx jest src/<module>`
4. **Before committing**: Ensure all tests pass

## Code Style

### Authentication & Authorization

- Use `@UseGuards(HybridAuthGuard, PermissionGuard)` for protected endpoints
- Use `@RequirePermission('resource', 'action')` decorator for RBAC
- Access auth context via `@AuthContext()` decorator
- Access organization ID via `@OrganizationId()` decorator

### Error Handling

- Use NestJS exceptions: `BadRequestException`, `NotFoundException`, `ForbiddenException`
- Provide clear, actionable error messages

### Database Access

- Always scope queries by `organizationId` for multi-tenancy
- Use transactions for operations that modify multiple records
140 changes: 140 additions & 0 deletions apps/api/CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
# API Development Guidelines

This document provides guidelines for AI assistants (Claude, Cursor, etc.) when working on the API codebase.

## Project Structure

```
apps/api/src/
├── auth/ # Authentication (better-auth, guards, decorators)
├── roles/ # Custom roles CRUD API
├── <module>/ # Feature modules (controller, service, DTOs)
└── utils/ # Shared utilities
```

## Testing Requirements

### Mandatory Testing

**Every new feature MUST include tests.** Before marking a task as complete:

1. Write unit tests for new services and controllers
2. Run the tests to verify they pass
3. Commit tests alongside the feature code

### Running Tests

```bash
# Run tests for a specific module (from apps/api directory)
npx jest src/<module-name> --passWithNoTests

# Run tests for changed files only
npx jest --onlyChanged

# Run all API tests (from repo root)
npx turbo run test --filter=@comp/api

# Type-check before committing
npx turbo run typecheck --filter=@comp/api
```

### Test Patterns

Follow existing test patterns in the codebase:

```typescript
// Mock external dependencies
jest.mock('@trycompai/db', () => ({
db: {
someTable: {
findFirst: jest.fn(),
create: jest.fn(),
// ...
},
},
}));

// Mock ESM modules if needed (e.g., jose)
jest.mock('jose', () => ({
createRemoteJWKSet: jest.fn(),
jwtVerify: jest.fn(),
}));

// Override guards in controller tests
const module = await Test.createTestingModule({
controllers: [MyController],
providers: [{ provide: MyService, useValue: mockService }],
})
.overrideGuard(HybridAuthGuard)
.useValue({ canActivate: () => true })
.compile();
```

### What to Test

| Component | Test Coverage |
|-----------|---------------|
| Services | All public methods, validation logic, error handling |
| Controllers | Parameter passing to services, response mapping |
| Guards | Authorization decisions, edge cases |
| DTOs | Validation decorators (via e2e or integration tests) |
| Utils | All functions, edge cases, error conditions |

## Code Style

### Authentication & Authorization

- Use `@UseGuards(HybridAuthGuard, PermissionGuard)` for protected endpoints
- Use `@RequirePermission('resource', 'action')` decorator for RBAC
- Access auth context via `@AuthContext()` decorator
- Access organization ID via `@OrganizationId()` decorator

### Error Handling

- Use NestJS exceptions: `BadRequestException`, `NotFoundException`, `ForbiddenException`
- Provide clear, actionable error messages
- Don't expose internal details in error responses

### Database Access

- Use Prisma via `@trycompai/db`
- Always scope queries by `organizationId` for multi-tenancy
- Use transactions for operations that modify multiple records

## Development Workflow

1. **Before coding**: Read existing code patterns in the module
2. **During coding**: Follow established patterns, add types
3. **After coding**:
- Run `npx turbo run typecheck --filter=@comp/api`
- Write and run tests: `npx jest src/<module>`
- Commit with conventional commit message

## Common Commands

```bash
# Start API in development
npx turbo run dev --filter=@comp/api

# Type-check
npx turbo run typecheck --filter=@comp/api

# Run specific test file
npx jest src/roles/roles.service.spec.ts

# Generate Prisma client after schema changes
cd packages/db && npx prisma generate
```

## RBAC System

The API uses a hybrid RBAC system:

- **Built-in roles**: owner, admin, auditor, employee, contractor
- **Custom roles**: Stored in `organization_role` table
- **Permissions**: `resource:action` format (e.g., `control:read`)
- **Multiple roles**: Users can have multiple roles (comma-separated in `member.role`)

When checking permissions:
- Use `@RequirePermission('resource', 'action')` for endpoint protection
- For privilege escalation checks, combine permissions from all user roles
1 change: 1 addition & 0 deletions apps/api/nest-cli.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"$schema": "https://json.schemastore.org/nest-cli",
"collection": "@nestjs/schematics",
"sourceRoot": "src",
"entryFile": "src/main",
"compilerOptions": {
"deleteOutDir": true
}
Expand Down
1 change: 1 addition & 0 deletions apps/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"@prisma/client": "6.18.0",
"@prisma/instrumentation": "^6.13.0",
"@react-email/components": "^0.0.41",
"@thallesp/nestjs-better-auth": "^2.2.5",
"@trigger.dev/build": "4.0.6",
"@trigger.dev/sdk": "4.0.6",
"@trycompai/db": "1.3.21",
Expand Down
6 changes: 6 additions & 0 deletions apps/api/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ import { BrowserbaseModule } from './browserbase/browserbase.module';
import { TaskManagementModule } from './task-management/task-management.module';
import { AssistantChatModule } from './assistant-chat/assistant-chat.module';
import { TrainingModule } from './training/training.module';
import { RolesModule } from './roles/roles.module';
import { ControlsModule } from './controls/controls.module';
import { FrameworksModule } from './frameworks/frameworks.module';

@Module({
imports: [
Expand All @@ -53,6 +56,7 @@ import { TrainingModule } from './training/training.module';
},
]),
AuthModule,
RolesModule,
OrganizationModule,
PeopleModule,
RisksModule,
Expand Down Expand Up @@ -80,6 +84,8 @@ import { TrainingModule } from './training/training.module';
TaskManagementModule,
AssistantChatModule,
TrainingModule,
ControlsModule,
FrameworksModule,
],
controllers: [AppController],
providers: [
Expand Down
12 changes: 4 additions & 8 deletions apps/api/src/assistant-chat/assistant-chat.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,29 +8,25 @@ import {
UseGuards,
} from '@nestjs/common';
import {
ApiHeader,
ApiOperation,
ApiResponse,
ApiSecurity,
ApiTags,
} from '@nestjs/swagger';
import { AuthContext } from '../auth/auth-context.decorator';
import { HybridAuthGuard } from '../auth/hybrid-auth.guard';
import { PermissionGuard } from '../auth/permission.guard';
import { RequirePermission } from '../auth/require-permission.decorator';
import type { AuthContext as AuthContextType } from '../auth/types';
import { SaveAssistantChatHistoryDto } from './assistant-chat.dto';
import { AssistantChatService } from './assistant-chat.service';
import type { AssistantChatMessage } from './assistant-chat.types';

@ApiTags('Assistant Chat')
@Controller({ path: 'assistant-chat', version: '1' })
@UseGuards(HybridAuthGuard)
@UseGuards(HybridAuthGuard, PermissionGuard)
@RequirePermission('app', 'read')
@ApiSecurity('apikey')
@ApiHeader({
name: 'X-Organization-Id',
description:
'Organization ID (required for JWT auth, optional for API key auth)',
required: false,
})
export class AssistantChatController {
constructor(private readonly assistantChatService: AssistantChatService) {}

Expand Down
12 changes: 4 additions & 8 deletions apps/api/src/attachments/attachments.controller.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { Controller, Get, Param, UseGuards } from '@nestjs/common';
import {
ApiHeader,
ApiOperation,
ApiParam,
ApiResponse,
Expand All @@ -9,22 +8,19 @@ import {
} from '@nestjs/swagger';
import { OrganizationId } from '../auth/auth-context.decorator';
import { HybridAuthGuard } from '../auth/hybrid-auth.guard';
import { PermissionGuard } from '../auth/permission.guard';
import { RequirePermission } from '../auth/require-permission.decorator';
import { AttachmentsService } from './attachments.service';

@ApiTags('Attachments')
@Controller({ path: 'attachments', version: '1' })
@UseGuards(HybridAuthGuard)
@UseGuards(HybridAuthGuard, PermissionGuard)
@ApiSecurity('apikey')
@ApiHeader({
name: 'X-Organization-Id',
description:
'Organization ID (required for session auth, optional for API key auth)',
required: false,
})
export class AttachmentsController {
constructor(private readonly attachmentsService: AttachmentsService) {}

@Get(':attachmentId/download')
@RequirePermission('evidence', 'read')
@ApiOperation({
summary: 'Get attachment download URL',
description: 'Generate a fresh signed URL for downloading any attachment',
Expand Down
Loading
Loading