feat(cors): add cross-app communication bundle

Add includeAllManaApps option to enable all ManaCore apps to communicate
with each other without manually listing each app's domains.

**Changes:**
- Added MANACORE_STAGING_ORIGINS, MANACORE_PRODUCTION_ORIGINS, and
  MANACORE_ALL_APP_ORIGINS constants
- Added includeAllManaApps flag to CorsConfigOptions interface
- Updated createCorsConfig() and createCorsConfigWithCallback() to support
  the new flag
- Updated mana-core-auth to use includeAllManaApps: true (auth needs to be
  accessible by all apps)
- Updated documentation with usage examples and decision matrix

**Benefits:**
- One-line configuration enables cross-app communication
- Automatically stays in sync as new apps are added
- No need to manually update CORS_ORIGINS for each app
- Works in both staging and production environments

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Wuesteon 2025-12-17 17:57:06 +01:00
parent 4c44764838
commit 3504172e60
6 changed files with 283 additions and 12 deletions

View file

@ -36,6 +36,7 @@ packages/shared-nestjs-cors/
**Automatic development origins** - Works in dev without configuration
**Staging/production via env var** - `CORS_ORIGINS` for deployed environments
**Cross-app communication bundle** - Enable all ManaCore apps with one flag
**Mobile app support** - Includes `exp://` and custom protocols
**Prevents duplicates** - Deduplicates origin lists
**Consistent security** - Same methods, headers, credentials across all apps
@ -55,6 +56,10 @@ packages/shared-nestjs-cors/
### 2. Update main.ts
#### Option A: Basic Setup (Single App)
For apps that only need to be accessed by their own frontend:
```typescript
// apps/{app}/apps/backend/src/main.ts
import { NestFactory } from '@nestjs/core';
@ -76,6 +81,38 @@ async function bootstrap() {
bootstrap();
```
#### Option B: Cross-App Communication (Recommended for Shared Services)
For backends that need to communicate with multiple ManaCore apps:
```typescript
// apps/{app}/apps/backend/src/main.ts
import { NestFactory } from '@nestjs/core';
import { createCorsConfig } from '@manacore/shared-nestjs-cors';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
// Enable CORS with ALL ManaCore apps (staging + production)
app.enableCors(
createCorsConfig({
corsOriginsEnv: process.env.CORS_ORIGINS,
includeAllManaApps: true, // 🎯 Enables cross-app communication
})
);
await app.listen(3000);
}
bootstrap();
```
**When to use `includeAllManaApps: true`:**
- ✅ Shared services (auth, notifications, file storage)
- ✅ APIs that need to be called from multiple apps
- ✅ Apps with cross-app navigation or embedded widgets
- ❌ Simple single-purpose apps with dedicated frontend (saves bandwidth)
### 3. Configure Staging Environment
```yaml
@ -88,6 +125,8 @@ chat-backend:
## Default Origins
### Development Origins
The utility automatically includes these development origins:
```typescript
@ -103,6 +142,104 @@ The utility automatically includes these development origins:
]
```
### ManaCore App Bundles
When using `includeAllManaApps: true`, the following origin bundles are automatically included:
#### Staging Bundle (`MANACORE_STAGING_ORIGINS`)
```typescript
[
'https://staging.manacore.ai', // Main web
'https://auth.staging.manacore.ai', // Auth service
'https://chat.staging.manacore.ai', // Chat app
'https://chat-api.staging.manacore.ai',
'https://picture.staging.manacore.ai', // Picture app
'https://picture-api.staging.manacore.ai',
'https://zitare.staging.manacore.ai', // Zitare app
'https://zitare-api.staging.manacore.ai',
'https://contacts.staging.manacore.ai', // Contacts app
'https://contacts-api.staging.manacore.ai',
'https://calendar.staging.manacore.ai', // Calendar app
'https://calendar-api.staging.manacore.ai',
'https://clock.staging.manacore.ai', // Clock app
'https://clock-api.staging.manacore.ai',
'https://todo.staging.manacore.ai', // Todo app
'https://todo-api.staging.manacore.ai',
]
```
#### Production Bundle (`MANACORE_PRODUCTION_ORIGINS`)
```typescript
[
'https://manacore.ai', // Main web
'https://auth.manacore.ai', // Auth service
'https://chat.manacore.ai', // Chat app
'https://chat-api.manacore.ai',
'https://picture.manacore.ai', // Picture app
'https://picture-api.manacore.ai',
// ... all other production apps
]
```
#### Combined Bundle (`MANACORE_ALL_APP_ORIGINS`)
When `includeAllManaApps: true`, both staging and production bundles are included automatically.
**Benefits:**
- 🎯 One-line configuration enables all cross-app communication
- 🔄 Automatically stays in sync as new apps are added
- 🚀 No need to manually update CORS_ORIGINS for each app
- ✅ Works in both staging and production environments
## Cross-App Communication
### Problem: Apps Need to Call Each Other
In a microservices architecture, apps often need to communicate:
- **Chat app** fetches user profiles from **Contacts API**
- **Calendar app** sends notifications via **Notifications service**
- **Picture app** uses **Auth service** for authentication
Without proper CORS setup, each backend would need to manually list every other app:
```yaml
# ❌ OLD WAY: Manually list every app (tedious and error-prone)
chat-backend:
environment:
CORS_ORIGINS: https://chat.staging.manacore.ai,https://contacts.staging.manacore.ai,https://calendar.staging.manacore.ai,https://picture.staging.manacore.ai,https://zitare.staging.manacore.ai,...
```
### Solution: Use `includeAllManaApps: true`
Enable cross-app communication with one flag:
```typescript
// ✅ NEW WAY: One flag enables all ManaCore apps
app.enableCors(
createCorsConfig({
corsOriginsEnv: process.env.CORS_ORIGINS,
includeAllManaApps: true, // 🎯 Automatically includes all apps
})
);
```
This automatically allows requests from:
- All staging apps (`*.staging.manacore.ai`)
- All production apps (`*.manacore.ai`)
- All development ports (`localhost:*`)
### When to Use Cross-App Bundle
| Use Case | `includeAllManaApps` |
|----------|----------------------|
| **Shared services** (Auth, Notifications, Storage) | ✅ `true` |
| **Public APIs** called by multiple apps | ✅ `true` |
| **Apps with embedded widgets** from other apps | ✅ `true` |
| **Simple apps** with dedicated frontend only | ❌ `false` |
| **Third-party integrations** (webhooks) | ❌ `false` (use `additionalOrigins`) |
## Deployment Checklist
When deploying a new app to staging, ensure:

View file

@ -17,6 +17,12 @@ export interface CorsConfigOptions {
* Additional origins to always allow (e.g., for mobile apps).
*/
additionalOrigins?: string[];
/**
* Include all ManaCore app origins for cross-app communication.
* When true, automatically includes all staging and production URLs.
*/
includeAllManaApps?: boolean;
}
/**
@ -49,6 +55,91 @@ const DEFAULT_DEV_ORIGINS = [
'exp://localhost:8081', // Expo mobile (exp:// protocol)
];
/**
* All ManaCore staging app origins.
* Use this bundle to allow cross-app communication in staging environment.
*/
export const MANACORE_STAGING_ORIGINS = [
// Main apps
'https://staging.manacore.ai', // Main web
'https://auth.staging.manacore.ai', // Auth service
// Chat app
'https://chat.staging.manacore.ai',
'https://chat-api.staging.manacore.ai',
// Picture app
'https://picture.staging.manacore.ai',
'https://picture-api.staging.manacore.ai',
// Zitare app
'https://zitare.staging.manacore.ai',
'https://zitare-api.staging.manacore.ai',
// Contacts app
'https://contacts.staging.manacore.ai',
'https://contacts-api.staging.manacore.ai',
// Calendar app
'https://calendar.staging.manacore.ai',
'https://calendar-api.staging.manacore.ai',
// Clock app
'https://clock.staging.manacore.ai',
'https://clock-api.staging.manacore.ai',
// Todo app
'https://todo.staging.manacore.ai',
'https://todo-api.staging.manacore.ai',
];
/**
* All ManaCore production app origins.
* Use this bundle to allow cross-app communication in production environment.
*/
export const MANACORE_PRODUCTION_ORIGINS = [
// Main apps
'https://manacore.ai', // Main web
'https://auth.manacore.ai', // Auth service
// Chat app
'https://chat.manacore.ai',
'https://chat-api.manacore.ai',
// Picture app
'https://picture.manacore.ai',
'https://picture-api.manacore.ai',
// Zitare app
'https://zitare.manacore.ai',
'https://zitare-api.manacore.ai',
// Contacts app
'https://contacts.manacore.ai',
'https://contacts-api.manacore.ai',
// Calendar app
'https://calendar.manacore.ai',
'https://calendar-api.manacore.ai',
// Clock app
'https://clock.manacore.ai',
'https://clock-api.manacore.ai',
// Todo app
'https://todo.manacore.ai',
'https://todo-api.manacore.ai',
];
/**
* Combined bundle of all ManaCore app origins (staging + production).
* Use this for maximum cross-app compatibility across all environments.
*/
export const MANACORE_ALL_APP_ORIGINS = [
...MANACORE_STAGING_ORIGINS,
...MANACORE_PRODUCTION_ORIGINS,
];
/**
* Creates a standardized CORS configuration for NestJS apps.
*
@ -68,6 +159,14 @@ const DEFAULT_DEV_ORIGINS = [
* }));
* ```
*
* ### With cross-app communication bundle (enables all ManaCore apps)
* ```typescript
* app.enableCors(createCorsConfig({
* corsOriginsEnv: process.env.CORS_ORIGINS,
* includeAllManaApps: true // Includes all staging + production app URLs
* }));
* ```
*
* ### With custom development origins
* ```typescript
* app.enableCors(createCorsConfig({
@ -93,6 +192,7 @@ const DEFAULT_DEV_ORIGINS = [
*
* ## Staging/Production Setup
*
* ### Simple setup (no cross-app communication needed)
* In docker-compose.staging.yml:
* ```yaml
* chat-backend:
@ -100,6 +200,15 @@ const DEFAULT_DEV_ORIGINS = [
* CORS_ORIGINS: https://chat.staging.manacore.ai,https://chat-api.staging.manacore.ai
* ```
*
* ### Cross-app setup (allow all ManaCore apps to communicate)
* ```typescript
* // main.ts
* app.enableCors(createCorsConfig({
* corsOriginsEnv: process.env.CORS_ORIGINS,
* includeAllManaApps: true // No need to list each app individually
* }));
* ```
*
* @param options - Configuration options
* @returns NestJS CORS configuration object
*/
@ -108,6 +217,7 @@ export function createCorsConfig(options: CorsConfigOptions = {}): CorsOptions {
corsOriginsEnv,
developmentOrigins = DEFAULT_DEV_ORIGINS,
additionalOrigins = [],
includeAllManaApps = false,
} = options;
// Parse CORS_ORIGINS from environment
@ -119,7 +229,12 @@ export function createCorsConfig(options: CorsConfigOptions = {}): CorsOptions {
: [];
// Combine all origins
const allOrigins = [...envOrigins, ...developmentOrigins, ...additionalOrigins];
const allOrigins = [
...envOrigins,
...developmentOrigins,
...additionalOrigins,
...(includeAllManaApps ? MANACORE_ALL_APP_ORIGINS : []),
];
// Remove duplicates
const uniqueOrigins = Array.from(new Set(allOrigins));
@ -145,6 +260,7 @@ export function createCorsConfigWithCallback(options: CorsConfigOptions = {}): C
corsOriginsEnv,
developmentOrigins = DEFAULT_DEV_ORIGINS,
additionalOrigins = [],
includeAllManaApps = false,
} = options;
const envOrigins = corsOriginsEnv
@ -154,7 +270,12 @@ export function createCorsConfigWithCallback(options: CorsConfigOptions = {}): C
.filter(Boolean)
: [];
const allOrigins = [...envOrigins, ...developmentOrigins, ...additionalOrigins];
const allOrigins = [
...envOrigins,
...developmentOrigins,
...additionalOrigins,
...(includeAllManaApps ? MANACORE_ALL_APP_ORIGINS : []),
];
const uniqueOrigins = Array.from(new Set(allOrigins));
return {

View file

@ -1,2 +1,8 @@
export { createCorsConfig, createCorsConfigWithCallback } from './cors-config';
export {
createCorsConfig,
createCorsConfigWithCallback,
MANACORE_STAGING_ORIGINS,
MANACORE_PRODUCTION_ORIGINS,
MANACORE_ALL_APP_ORIGINS,
} from './cors-config';
export type { CorsConfigOptions } from './cors-config';

3
pnpm-lock.yaml generated
View file

@ -4507,6 +4507,9 @@ importers:
'@google/generative-ai':
specifier: ^0.24.1
version: 0.24.1
'@manacore/shared-nestjs-cors':
specifier: workspace:*
version: link:../../packages/shared-nestjs-cors
'@nestjs/common':
specifier: ^10.4.15
version: 10.4.20(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2)

View file

@ -23,6 +23,7 @@
"dependencies": {
"@getbrevo/brevo": "^3.0.1",
"@google/generative-ai": "^0.24.1",
"@manacore/shared-nestjs-cors": "workspace:*",
"@nestjs/common": "^10.4.15",
"@nestjs/config": "^3.3.0",
"@nestjs/core": "^10.4.15",

View file

@ -3,6 +3,7 @@ import { ValidationPipe } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import helmet from 'helmet';
import cookieParser from 'cookie-parser';
import { createCorsConfig } from '@manacore/shared-nestjs-cors';
import { AppModule } from './app.module';
async function bootstrap() {
@ -19,15 +20,17 @@ async function bootstrap() {
);
app.use(cookieParser());
// CORS configuration
const corsOrigins = configService.get<string[]>('cors.origin') || [];
console.log('📋 CORS Origins configured:', corsOrigins);
app.enableCors({
origin: corsOrigins,
credentials: true,
methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'],
allowedHeaders: ['Content-Type', 'Authorization', 'X-Requested-With', 'X-App-Id'],
});
// CORS configuration with cross-app communication
// Auth service needs to be accessible by ALL ManaCore apps
const corsOriginsEnv = configService.get<string>('cors.origin');
console.log('📋 CORS Origins from env:', corsOriginsEnv);
app.enableCors(
createCorsConfig({
corsOriginsEnv,
includeAllManaApps: true, // 🎯 Enable all ManaCore apps to authenticate
additionalOrigins: [], // Keep X-App-Id support for custom headers
})
);
// Global validation pipe
app.useGlobalPipes(