Files
stack/docs/M2-011-completion.md
Jason Woltje 0eb3abc12c
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
Clean up documents located in the project root.
2026-01-31 16:42:26 -06:00

255 lines
8.2 KiB
Markdown

# M2-011 Completion Report: Permission Guards
**Issue:** #11 - API-level permission guards for workspace-based access control
**Status:** ✅ Complete
**Date:** January 29, 2026
## Summary
Implemented comprehensive API-level permission guards that work in conjunction with the existing Row-Level Security (RLS) system. The guards provide declarative, role-based access control for all workspace-scoped API endpoints.
## Implementation Details
### 1. Guards Created
#### WorkspaceGuard (`apps/api/src/common/guards/workspace.guard.ts`)
- **Purpose:** Validates workspace access and sets RLS context
- **Features:**
- Extracts workspace ID from multiple sources (header, URL param, body)
- Verifies user is a workspace member
- Automatically sets `app.current_user_id` for RLS policies
- Attaches workspace context to request object
- **Priority order:** `X-Workspace-Id` header → `:workspaceId` param → `body.workspaceId`
#### PermissionGuard (`apps/api/src/common/guards/permission.guard.ts`)
- **Purpose:** Enforces role-based access control
- **Features:**
- Reads required permission from `@RequirePermission()` decorator
- Fetches user's role in the workspace
- Validates role against permission requirement
- Attaches role to request for convenience
- **Permission Levels:**
- `WORKSPACE_OWNER` - Only workspace owners
- `WORKSPACE_ADMIN` - Owners and admins
- `WORKSPACE_MEMBER` - Owners, admins, and members
- `WORKSPACE_ANY` - All roles including guests
### 2. Decorators Created
#### `@RequirePermission(permission: Permission)`
Located in `apps/api/src/common/decorators/permissions.decorator.ts`
- Declarative permission specification for routes
- Type-safe permission enum
- Works with PermissionGuard via metadata reflection
#### `@Workspace()`
Located in `apps/api/src/common/decorators/workspace.decorator.ts`
- Parameter decorator to extract validated workspace ID
- Cleaner than accessing `req.workspace.id` directly
- Type-safe and convenient
#### `@WorkspaceContext()`
- Extracts full workspace context object
- Useful for future extensions (workspace name, settings, etc.)
### 3. Updated Controllers
#### TasksController
**Before:**
```typescript
@Get()
async findAll(@Query() query: QueryTasksDto, @Request() req: any) {
const workspaceId = req.user?.workspaceId;
if (!workspaceId) {
throw new UnauthorizedException("Authentication required");
}
return this.tasksService.findAll({ ...query, workspaceId });
}
```
**After:**
```typescript
@Get()
@RequirePermission(Permission.WORKSPACE_ANY)
async findAll(
@Query() query: QueryTasksDto,
@Workspace() workspaceId: string
) {
return this.tasksService.findAll({ ...query, workspaceId });
}
```
#### KnowledgeController
- Updated all endpoints to use new guard system
- Read endpoints: `WORKSPACE_ANY`
- Create/update endpoints: `WORKSPACE_MEMBER`
- Delete endpoints: `WORKSPACE_ADMIN`
### 4. Database Context Updates
Updated `apps/api/src/lib/db-context.ts`:
- Fixed import to use local PrismaService instead of non-existent `@mosaic/database`
- Created `getPrismaInstance()` helper for standalone usage
- Updated all functions to use optional PrismaClient parameter
- Fixed TypeScript strict mode issues
- Maintained backward compatibility
### 5. Test Coverage
#### WorkspaceGuard Tests (`workspace.guard.spec.ts`)
- ✅ Allow access when user is workspace member (via header)
- ✅ Allow access when user is workspace member (via URL param)
- ✅ Allow access when user is workspace member (via body)
- ✅ Prioritize header over param and body
- ✅ Throw ForbiddenException when user not authenticated
- ✅ Throw BadRequestException when workspace ID missing
- ✅ Throw ForbiddenException when user not a workspace member
- ✅ Handle database errors gracefully
**Result:** 8/8 tests passing
#### PermissionGuard Tests (`permission.guard.spec.ts`)
- ✅ Allow access when no permission required
- ✅ Allow OWNER to access WORKSPACE_OWNER permission
- ✅ Deny ADMIN access to WORKSPACE_OWNER permission
- ✅ Allow OWNER and ADMIN to access WORKSPACE_ADMIN permission
- ✅ Deny MEMBER access to WORKSPACE_ADMIN permission
- ✅ Allow OWNER, ADMIN, and MEMBER to access WORKSPACE_MEMBER permission
- ✅ Deny GUEST access to WORKSPACE_MEMBER permission
- ✅ Allow any role (including GUEST) to access WORKSPACE_ANY permission
- ✅ Throw ForbiddenException when user context missing
- ✅ Throw ForbiddenException when workspace context missing
- ✅ Throw ForbiddenException when user not a workspace member
- ✅ Handle database errors gracefully
**Result:** 12/12 tests passing
**Total Test Coverage:** 20/20 tests passing ✅
### 6. Documentation
Created comprehensive `apps/api/src/common/README.md` covering:
- Overview of the permission system
- Detailed guard documentation
- Decorator usage examples
- Usage patterns and best practices
- Error handling guide
- Migration guide from manual checks
- RLS integration notes
- Testing instructions
## Benefits
**Declarative** - Permission requirements visible in decorators
**DRY** - No repetitive auth/workspace checks in handlers
**Type-safe** - Workspace ID guaranteed via `@Workspace()`
**Secure** - RLS context automatically set, defense in depth
**Testable** - Guards independently unit tested
**Maintainable** - Permission changes centralized
**Documented** - Comprehensive README and inline docs
## Usage Example
```typescript
@Controller("resources")
@UseGuards(AuthGuard, WorkspaceGuard, PermissionGuard)
export class ResourcesController {
@Get()
@RequirePermission(Permission.WORKSPACE_ANY)
async list(@Workspace() workspaceId: string) {
// All members can list
}
@Post()
@RequirePermission(Permission.WORKSPACE_MEMBER)
async create(@Workspace() workspaceId: string, @CurrentUser() user: any, @Body() dto: CreateDto) {
// Members and above can create
}
@Delete(":id")
@RequirePermission(Permission.WORKSPACE_ADMIN)
async delete(@Param("id") id: string) {
// Only admins can delete
}
}
```
## Integration with RLS
The guards work seamlessly with the existing RLS system:
1. **AuthGuard** authenticates the user
2. **WorkspaceGuard** validates workspace access and calls `setCurrentUser()`
3. **PermissionGuard** enforces role-based permissions
4. **RLS policies** automatically filter database queries
This provides **defense in depth**:
- Application-level: Guards check permissions
- Database-level: RLS prevents data leakage
## Files Created/Modified
**Created:**
- `apps/api/src/common/guards/workspace.guard.ts` (150 lines)
- `apps/api/src/common/guards/workspace.guard.spec.ts` (219 lines)
- `apps/api/src/common/guards/permission.guard.ts` (165 lines)
- `apps/api/src/common/guards/permission.guard.spec.ts` (278 lines)
- `apps/api/src/common/guards/index.ts`
- `apps/api/src/common/decorators/permissions.decorator.ts` (48 lines)
- `apps/api/src/common/decorators/workspace.decorator.ts` (40 lines)
- `apps/api/src/common/decorators/index.ts`
- `apps/api/src/common/index.ts`
- `apps/api/src/common/README.md` (314 lines)
**Modified:**
- `apps/api/src/lib/db-context.ts` - Fixed imports and TypeScript issues
- `apps/api/src/tasks/tasks.controller.ts` - Migrated to new guard system
- `apps/api/src/knowledge/knowledge.controller.ts` - Migrated to new guard system
**Total:** 10 new files, 3 modified files, ~1,600 lines of code and documentation
## Next Steps
1. **Migrate remaining controllers** - Apply guards to all workspace-scoped controllers
2. **Add team-level permissions** - Extend to support team-specific access control
3. **Audit logging** - Consider logging permission checks for security audits
4. **Performance monitoring** - Track guard execution time in production
5. **Frontend integration** - Update frontend to send `X-Workspace-Id` header
## Related Work
- **M2 Database Layer** - RLS policies foundation
- **Issue #12** - Workspace management UI (uses these guards)
- `docs/design/multi-tenant-rls.md` - RLS architecture documentation
## Commit
The implementation was committed in:
- Commit: `5291fece` - "feat(web): add workspace management UI (M2 #12)"
(Note: This commit bundled multiple features; guards were part of the backend infrastructure)
---
**Status:** ✅ Complete and tested
**Blockers:** None
**Review:** Ready for code review and integration testing