# 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
