# Common Guards and Decorators

This directory contains shared guards and decorators for workspace-based permission management in the Mosaic Stack API.

## Overview

The permission system provides:

- **Workspace isolation** via Row-Level Security (RLS)
- **Role-based access control** (RBAC) using workspace member roles
- **Declarative permission requirements** using decorators

## Guards

### AuthGuard

Located in `../auth/guards/auth.guard.ts`

Verifies user authentication and attaches user data to the request.

**Sets on request:**

- `request.user` - Authenticated user object
- `request.session` - User session data

### WorkspaceGuard

Validates workspace access and sets up RLS context.

**Responsibilities:**

1. Extracts workspace ID from request (header, param, or body)
2. Verifies user is a member of the workspace
3. Sets the current user context for RLS policies
4. Attaches workspace context to the request

**Sets on request:**

- `request.workspace.id` - Validated workspace ID
- `request.user.workspaceId` - Workspace ID (for backward compatibility)

**Workspace ID Sources (in priority order):**

1. `X-Workspace-Id` header
2. `:workspaceId` URL parameter
3. `workspaceId` in request body

**Example:**

```typescript
@Controller("tasks")
@UseGuards(AuthGuard, WorkspaceGuard)
export class TasksController {
  @Get()
  async getTasks(@Workspace() workspaceId: string) {
    // workspaceId is validated and RLS context is set
  }
}
```

### PermissionGuard

Enforces role-based access control using workspace member roles.

**Responsibilities:**

1. Reads required permission from `@RequirePermission()` decorator
2. Fetches user's role in the workspace
3. Checks if role satisfies the required permission
4. Attaches role to request for convenience

**Sets on request:**

- `request.user.workspaceRole` - User's role in the workspace

**Must be used after AuthGuard and WorkspaceGuard.**

**Example:**

```typescript
@Controller("admin")
@UseGuards(AuthGuard, WorkspaceGuard, PermissionGuard)
export class AdminController {
  @RequirePermission(Permission.WORKSPACE_ADMIN)
  @Delete("data")
  async deleteData() {
    // Only ADMIN or OWNER can execute
  }
}
```

## Decorators

### @RequirePermission(permission: Permission)

Specifies the minimum permission level required for a route.

**Permission Levels:**

| Permission         | Allowed Roles             | Use Case                                                   |
| ------------------ | ------------------------- | ---------------------------------------------------------- |
| `WORKSPACE_OWNER`  | OWNER                     | Critical operations (delete workspace, transfer ownership) |
| `WORKSPACE_ADMIN`  | OWNER, ADMIN              | Administrative functions (manage members, settings)        |
| `WORKSPACE_MEMBER` | OWNER, ADMIN, MEMBER      | Standard operations (create/edit content)                  |
| `WORKSPACE_ANY`    | All roles including GUEST | Read-only or basic access                                  |

**Example:**

```typescript
@RequirePermission(Permission.WORKSPACE_ADMIN)
@Post('invite')
async inviteMember(@Body() inviteDto: InviteDto) {
  // Only admins can invite members
}
```

### @Workspace()

Parameter decorator to extract the validated workspace ID.

**Example:**

```typescript
@Get()
async getTasks(@Workspace() workspaceId: string) {
  // workspaceId is guaranteed to be valid
}
```

### @WorkspaceContext()

Parameter decorator to extract the full workspace context.

**Example:**

```typescript
@Get()
async getTasks(@WorkspaceContext() workspace: { id: string }) {
  console.log(workspace.id);
}
```

### @CurrentUser()

Located in `../auth/decorators/current-user.decorator.ts`

Extracts the authenticated user from the request.

**Example:**

```typescript
@Post()
async create(@CurrentUser() user: any, @Body() dto: CreateDto) {
  // user contains authenticated user data
}
```

## Usage Patterns

### Basic Controller Setup

```typescript
import { Controller, Get, Post, UseGuards } from "@nestjs/common";
import { AuthGuard } from "../auth/guards/auth.guard";
import { WorkspaceGuard, PermissionGuard } from "../common/guards";
import { Workspace, Permission, RequirePermission } from "../common/decorators";
import { CurrentUser } from "../auth/decorators/current-user.decorator";

@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
  }
}
```

### Mixed Permissions

Different endpoints can have different permission requirements:

```typescript
@Controller("projects")
@UseGuards(AuthGuard, WorkspaceGuard, PermissionGuard)
export class ProjectsController {
  @Get()
  @RequirePermission(Permission.WORKSPACE_ANY)
  async list() {
    /* Anyone can view */
  }

  @Post()
  @RequirePermission(Permission.WORKSPACE_MEMBER)
  async create() {
    /* Members can create */
  }

  @Patch("settings")
  @RequirePermission(Permission.WORKSPACE_ADMIN)
  async updateSettings() {
    /* Only admins */
  }

  @Delete()
  @RequirePermission(Permission.WORKSPACE_OWNER)
  async deleteProject() {
    /* Only owner */
  }
}
```

### Workspace ID in Request

The workspace ID can be provided in multiple ways:

**Via Header (Recommended for SPAs):**

```typescript
// Frontend
fetch("/api/tasks", {
  headers: {
    Authorization: "Bearer <token>",
    "X-Workspace-Id": "workspace-uuid",
  },
});
```

**Via URL Parameter:**

```typescript
@Get(':workspaceId/tasks')
async getTasks(@Param('workspaceId') workspaceId: string) {
  // workspaceId extracted from URL
}
```

**Via Request Body:**

```typescript
@Post()
async create(@Body() dto: { workspaceId: string; name: string }) {
  // workspaceId extracted from body
}
```

## Row-Level Security (RLS)

When `WorkspaceGuard` is applied, it automatically:

1. Calls `setCurrentUser(userId)` to set the RLS context
2. All subsequent database queries are automatically filtered by RLS policies
3. Users can only access data in workspaces they're members of

**See:** `docs/design/multi-tenant-rls.md` for full RLS documentation.

## Testing

Tests are provided for both guards:

- `workspace.guard.spec.ts` - WorkspaceGuard tests
- `permission.guard.spec.ts` - PermissionGuard tests

**Run tests:**

```bash
npm test -- workspace.guard.spec
npm test -- permission.guard.spec
```

## Error Handling

### WorkspaceGuard Errors

- `ForbiddenException("User not authenticated")` - No authenticated user
- `BadRequestException("Workspace ID is required...")` - No workspace ID provided
- `ForbiddenException("You do not have access to this workspace")` - User is not a workspace member

### PermissionGuard Errors

- `ForbiddenException("Authentication and workspace context required")` - Missing user or workspace context
- `ForbiddenException("You are not a member of this workspace")` - User not found in workspace
- `ForbiddenException("Insufficient permissions. Required: ...")` - User role doesn't meet requirements

## Migration Guide

### Before (Manual Checks):

```typescript
@Get()
async getTasks(@Request() req: any) {
  const workspaceId = req.user?.workspaceId;
  if (!workspaceId) {
    throw new UnauthorizedException("Authentication required");
  }
  return this.tasksService.findAll(workspaceId);
}
```

### After (Guard-Based):

```typescript
@Get()
@UseGuards(AuthGuard, WorkspaceGuard, PermissionGuard)
@RequirePermission(Permission.WORKSPACE_ANY)
async getTasks(@Workspace() workspaceId: string) {
  return this.tasksService.findAll(workspaceId);
}
```

## Benefits

✅ **Declarative** - Permission requirements are clear from decorators
✅ **DRY** - No repetitive auth/workspace checks in every handler
✅ **Type-safe** - Workspace ID is guaranteed to exist when using `@Workspace()`
✅ **Secure** - RLS context automatically set, defense in depth
✅ **Testable** - Guards are independently testable
✅ **Maintainable** - Permission changes in one place

## Related Files

- `apps/api/src/lib/db-context.ts` - RLS utility functions
- `docs/design/multi-tenant-rls.md` - RLS architecture documentation
- `apps/api/prisma/schema.prisma` - Database schema with role definitions
