System Architecture
Core Features
Data Management
Frontend Components
Extensibility
<details>
<summary>Relevant source files</summary>
The following files were used as context for generating this wiki page:
- [apps/api/src/auth/hybrid-auth.guard.ts](https://github.com/blade47/comp/blob/main/apps/api/src/auth/hybrid-auth.guard.ts)
- [apps/api/src/auth/types.ts](https://github.com/blade47/comp/blob/main/apps/api/src/auth/types.ts)
- [apps/api/src/auth/platform-admin.guard.ts](https://github.com/blade47/comp/blob/main/apps/api/src/auth/platform-admin.guard.ts)
- [apps/api/src/auth/auth-context.decorator.ts](https://github.com/blade47/comp/blob/main/apps/api/src/auth/auth-context.decorator.ts)
- [apps/api/src/auth/auth.module.ts](https://github.com/blade47/comp/blob/main/apps/api/src/auth/auth.module.ts)
- [apps/api/src/auth/api-key.service.ts](https://github.com/blade47/comp/blob/main/apps/api/src/auth/api-key.service.ts)
- [apps/api/src/auth/internal-token.guard.ts](https://github.com/blade47/comp/blob/main/apps/api/src/auth/internal-token.guard.ts)
- [apps/api/src/auth/api-key.guard.ts](https://github.com/blade47/comp/blob/main/apps/api/src/auth/api-key.guard.ts)
- [apps/api/src/auth/organization.decorator.ts](https://github.com/blade47/comp/blob/main/apps/api/src/auth/organization.decorator.ts)
- [apps/api/src/auth/role-validator.guard.ts](https://github.com/blade47/comp/blob/main/apps/api/src/auth/role-validator.guard.ts)
</details>
The authentication and authorization system within the API (`apps/api`) is designed to secure endpoints by verifying the identity of incoming requests and ensuring they have the necessary permissions. It supports multiple authentication mechanisms, including API Keys for external integrations, JSON Web Tokens (JWT) for internal frontend applications, and an internal token for inter-service communication.
This system leverages NestJS Guards and custom decorators to provide a flexible and robust security layer, allowing granular control over access to resources based on authentication type, user identity, organization context, and assigned roles.
## Authentication Context and Types
The core of the authentication system relies on defining a clear context for authenticated requests. This context is captured by the `AuthenticatedRequest` interface, which extends the standard `Request` object, and the `AuthContext` interface, used for extracting authentication details.
### AuthenticatedRequest and AuthContext
These interfaces define the structure for authentication-related data that is attached to the request object after successful authentication.
\`\`\`typescript
export interface AuthenticatedRequest extends Request {
organizationId: string;
authType: 'api-key' | 'jwt';
isApiKey: boolean;
userId?: string; // Only available for JWT auth
userEmail?: string; // Only available for JWT auth
userRoles: string[] | null;
}
export interface AuthContext {
organizationId: string;
authType: 'api-key' | 'jwt';
isApiKey: boolean;
userId?: string; // Only available for JWT auth
userEmail?: string; // Only available for JWT auth
userRoles: string[] | null;
}
\`\`\`
Sources: [apps/api/src/auth/types.ts:3-17](https://github.com/blade47/comp/blob/main/apps/api/src/auth/types.ts#L3-L17)
<Callout variant="info" title="Context Availability">
The `userId` and `userEmail` fields are only populated when authentication is performed via JWT (session-based authentication). For API Key authentication, these fields will be undefined, as API keys are organization-scoped and not tied to a specific user.
</Callout>
## Authentication Guards
The API uses several NestJS `CanActivate` guards to enforce different authentication and authorization policies.
### HybridAuthGuard
The `HybridAuthGuard` is the primary authentication guard, designed to handle both API Key and JWT-based authentication seamlessly. It attempts to authenticate a request first using an API Key, and if that fails, it tries JWT authentication.
<Steps>
<Step>
### Request Interception
The guard intercepts incoming requests and inspects the `x-api-key` and `authorization` headers.
</Step>
<Step>
### API Key Authentication Attempt
If an `x-api-key` header is present, the guard delegates to `handleApiKeyAuth`.
</Step>
<Step>
### JWT Authentication Attempt
If no `x-api-key` is found, or API key authentication fails, the guard checks for an `Authorization: Bearer` header and delegates to `handleJwtAuth`.
</Step>
<Step>
### Context Assignment
Upon successful authentication, the guard populates the `AuthenticatedRequest` object with relevant details like `organizationId`, `userId`, `userEmail`, `userRoles`, `authType`, and `isApiKey`.
</Step>
</Steps>
#### Hybrid Authentication Flow
Sources: [apps/api/src/auth/hybrid-auth.guard.ts:25-34](https://github.com/blade47/comp/blob/main/apps/api/src/auth/hybrid-auth.guard.ts#L25-L34), [apps/api/src/auth/hybrid-auth.guard.ts:36-54](https://github.com/blade47/comp/blob/main/apps/api/src/auth/hybrid-auth.guard.ts#L36-L54), [apps/api/src/auth/hybrid-auth.guard.ts:56-173](https://github.com/blade47/comp/blob/main/apps/api/src/auth/hybrid-auth.guard.ts#L56-L173)
<Accordions>
<Accordion title="JWT Verification and Key Rotation">
The `handleJwtAuth` method uses `jose` library's `createRemoteJWKSet` to fetch JSON Web Key Sets (JWKS) from the `betterAuthUrl`. It includes a retry mechanism for key mismatch errors (`ERR_JWKS_NO_MATCHING_KEY`). If a key mismatch occurs, it attempts to fetch a fresh JWKS with no cache to ensure it has the latest keys for verification. This helps in handling JWT key rotation scenarios.
Sources: [apps/api/src/auth/hybrid-auth.guard.ts:77-119](https://github.com/blade47/comp/blob/main/apps/api/src/auth/hybrid-auth.guard.ts#L77-L119)
</Accordion>
<Accordion title="Organization Context for JWT">
For JWT authentication, an `X-Organization-Id` header is explicitly required. The guard verifies that the authenticated user (`userId` from JWT payload) is a member of the specified organization using `db.member.findFirst`.
Sources: [apps/api/src/auth/hybrid-auth.guard.ts:125-139](https://github.com/blade47/comp/blob/main/apps/api/src/auth/hybrid-auth.guard.ts#L125-L139), [apps/api/src/auth/hybrid-auth.guard.ts:175-194](https://github.com/blade47/comp/blob/main/apps/api/src/auth/hybrid-auth.guard.ts#L175-L194)
</Accordion>
</Accordions>
### ApiKeyService and ApiKeyGuard
The `ApiKeyService` is responsible for the logic of handling API keys, while `ApiKeyGuard` integrates this service into the NestJS guard system.
#### ApiKeyService
This service provides methods for hashing, extracting, and validating API keys.
\`\`\`typescript
@Injectable()
export class ApiKeyService {
private hashApiKey(apiKey: string, salt?: string): string { /* ... */ }
extractApiKey(apiKeyHeader?: string): string | null { /* ... */ }
async validateApiKey(apiKey: string): Promise<string | null> { /* ... */ }
}
\`\`\`
Sources: [apps/api/src/auth/api-key.service.ts:10-12](https://github.com/blade47/comp/blob/main/apps/api/src/auth/api-key.service.ts#L10-L12)
<Accordions>
<Accordion title="API Key Hashing and Storage">
API keys are stored in the database (`db.apiKey`) in a hashed format, optionally with a salt. The `hashApiKey` method uses SHA256 for hashing. When validating, the provided API key is hashed with the stored salt (or without for backward compatibility) and compared against the stored hash.
Sources: [apps/api/src/auth/api-key.service.ts:14-25](https://github.com/blade47/comp/blob/main/apps/api/src/auth/api-key.service.ts#L14-L25), [apps/api/src/auth/api-key.service.ts:58-62](https://github.com/blade47/comp/blob/main/apps/api/src/auth/api-key.service.ts#L58-L62)
</Accordion>
</Accordions>
#### ApiKeyGuard
This guard specifically handles API key authentication. It extracts the `X-API-Key` header, validates it using `ApiKeyService`, and attaches the `organizationId` to the request.
\`\`\`mermaid
sequenceDiagram
participant Client
participant ApiKeyGuard
participant ApiKeyService
participant Database
Client->>ApiKeyGuard: Request with X-API-Key
ApiKeyGuard->>ApiKeyService: extractApiKey(header)
ApiKeyService-->>ApiKeyGuard: Extracted Key
ApiKeyGuard->>ApiKeyService: validateApiKey(key)
ApiKeyService->>Database: findMany({ isActive: true })
Database-->>ApiKeyService: API Key Records (hashed, salt, orgId, expiresAt)
ApiKeyService->>ApiKeyService: Hash provided key with record salts
ApiKeyService->>ApiKeyService: Find matching record & check expiry
alt Key Valid & Not Expired
ApiKeyService->>Database: update({ id: matchingRecord.id, data: { lastUsedAt: now() } })
Database-->>ApiKeyService: Update successful
ApiKeyService-->>ApiKeyGuard: organizationId
ApiKeyGuard->>ApiKeyGuard: Set request.organizationId
ApiKeyGuard-->>Client: Access Granted
else Key Invalid or Expired
ApiKeyService-->>ApiKeyGuard: null
ApiKeyGuard->>Client: UnauthorizedException
end
\`\`\`
Sources: [apps/api/src/auth/api-key.guard.ts:13-30](https://github.com/blade47/comp/blob/main/apps/api/src/auth/api-key.guard.ts#L13-L30), [apps/api/src/auth/api-key.service.ts:39-88](https://github.com/blade47/comp/blob/main/apps/api/src/auth/api-key.service.ts#L39-L88)
### PlatformAdminGuard
This guard is used to restrict access to endpoints that should only be accessible by platform administrators. It exclusively uses JWT authentication.
1. **JWT Requirement**: It strictly requires a `Bearer` JWT token.
2. **JWT Verification**: Verifies the JWT against the `betterAuthUrl`'s JWKS endpoint, similar to `HybridAuthGuard`.
3. **Admin Check**: After verifying the JWT and extracting the `userId`, it queries the database (`db.user`) to check if `user.isPlatformAdmin` is true.
4. **Context Assignment**: If successful, it sets `request.userId`, `request.userEmail`, and `request.isPlatformAdmin`.
Sources: [apps/api/src/auth/platform-admin.guard.ts:28-34](https://github.com/blade47/comp/blob/main/apps/api/src/auth/platform-admin.guard.ts#L28-L34), [apps/api/src/auth/platform-admin.guard.ts:40-45](https://github.com/blade47/comp/blob/main/apps/api/src/auth/platform-admin.guard.ts#L40-L45), [apps/api/src/auth/platform-admin.guard.ts:60-104](https://github.com/blade47/comp/blob/main/apps/api/src/auth/platform-admin.guard.ts#L60-L104)
### RoleValidatorGuard
The `RoleValidatorGuard` enables role-based access control (RBAC) for specific endpoints. It is instantiated using the `RequireRoles` factory function, which defines the roles required for access.
- **Role Check**: Compares the `userRoles` from the `AuthenticatedRequest` (populated by `HybridAuthGuard`) against the `allowedRoles` configured for the guard.
- **API Key Exemption**: Requests authenticated via API keys are explicitly allowed to bypass role checks, as API keys are organization-scoped and not tied to specific user roles. However, they still require an `organizationId`.
- **JWT Requirement**: For role-based authorization, JWT authentication is mandatory, ensuring `userId`, `organizationId`, and `userRoles` are available.
Sources: [apps/api/src/auth/role-validator.guard.ts:20-22](https://github.com/blade47/comp/blob/main/apps/api/src/auth/role-validator.guard.ts#L20-L22), [apps/api/src/auth/role-validator.guard.ts:24-34](https://github.com/blade47/comp/blob/main/apps/api/src/auth/role-validator.guard.ts#L24-L34), [apps/api/src/auth/role-validator.guard.ts:36-40](https://github.com/blade47/comp/blob/main/apps/api/src/auth/role-validator.guard.ts#L36-L40), [apps/api/src/auth/role-validator.guard.ts:42-46](https://github.com/blade47/comp/blob/main/apps/api/src/auth/role-validator.guard.ts#L42-L46)
### InternalTokenGuard
This guard protects internal API endpoints that should only be accessible by other internal services.
- **Token Check**: It expects an `X-Internal-Token` header in the request.
- **Environment Variable**: The value of this header is compared against the `process.env.INTERNAL_API_TOKEN` environment variable.
- **Production Enforcement**: In production environments, `INTERNAL_API_TOKEN` must be configured. If not, an `UnauthorizedException` is thrown.
- **Development Flexibility**: In non-production environments, if `INTERNAL_API_TOKEN` is not set, the guard allows the request to proceed, facilitating local development.
Sources: [apps/api/src/auth/internal-token.guard.ts:15-28](https://github.com/blade47/comp/blob/main/apps/api/src/auth/internal-token.guard.ts#L15-L28), [apps/api/src/auth/internal-token.guard.ts:30-35](https://github.com/blade47/comp/blob/main/apps/api/src/auth/internal-token.guard.ts#L30-L35)
## Auth Context Decorators
Custom parameter decorators simplify accessing authentication details within controllers and resolvers. These decorators rely on the `HybridAuthGuard` (or `ApiKeyGuard` for `Organization` decorator) having successfully populated the request object.
Sources: [apps/api/src/auth/auth-context.decorator.ts](https://github.com/blade47/comp/blob/main/apps/api/src/auth/auth-context.decorator.ts), [apps/api/src/auth/organization.decorator.ts](https://github.com/blade47/comp/blob/main/apps/api/src/auth/organization.decorator.ts)
| Decorator | Description
The `AuthModule` registers and exports the following guards and services:
- `ApiKeyService`
- `ApiKeyGuard`
- `HybridAuthGuard`
- `InternalTokenGuard`
</Callout>
## AuthModule
The `AuthModule` is a NestJS module that encapsulates all authentication-related services and guards. It makes these components available for dependency injection throughout the application.
\`\`\`typescript
import { Module } from '@nestjs/common';
import { ApiKeyGuard } from './api-key.guard';
import { ApiKeyService } from './api-key.service';
import { HybridAuthGuard } from './hybrid-auth.guard';
import { InternalTokenGuard } from './internal-token.guard';
@Module({
providers: [ApiKeyService, ApiKeyGuard, HybridAuthGuard, InternalTokenGuard],
exports: [ApiKeyService, ApiKeyGuard, HybridAuthGuard, InternalTokenGuard],
})
export class AuthModule {}
\`\`\`
Sources: [apps/api/src/auth/auth.module.ts:1-12](https://github.com/blade47/comp/blob/main/apps/api/src/auth/auth.module.ts#L1-L12)