# 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