mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-14 21:01:08 +02:00
1. Brevo email integration (API key, sender settings)
2. URL configuration fix (BASE_URL, FRONTEND_URL) 3. Password reset URL pointing to frontend instead of API
This commit is contained in:
parent
8c973e4354
commit
4c44764838
22 changed files with 1293 additions and 447 deletions
|
|
@ -20,6 +20,7 @@
|
|||
"dependencies": {
|
||||
"@calendar/shared": "workspace:*",
|
||||
"@manacore/shared-nestjs-auth": "workspace:*",
|
||||
"@manacore/shared-nestjs-cors": "workspace:*",
|
||||
"@nestjs/common": "^10.4.15",
|
||||
"@nestjs/config": "^3.3.0",
|
||||
"@nestjs/core": "^10.4.15",
|
||||
|
|
|
|||
|
|
@ -1,25 +1,17 @@
|
|||
import { NestFactory } from '@nestjs/core';
|
||||
import { ValidationPipe } from '@nestjs/common';
|
||||
import { createCorsConfig } from '@manacore/shared-nestjs-cors';
|
||||
import { AppModule } from './app.module';
|
||||
|
||||
async function bootstrap() {
|
||||
const app = await NestFactory.create(AppModule);
|
||||
|
||||
// Enable CORS for mobile and web apps
|
||||
const corsOrigins = process.env.CORS_ORIGINS?.split(',').map((origin) => origin.trim()) || [
|
||||
'http://localhost:3000',
|
||||
'http://localhost:5173',
|
||||
'http://localhost:5179',
|
||||
'http://localhost:8081',
|
||||
'exp://localhost:8081',
|
||||
'http://localhost:3001',
|
||||
];
|
||||
|
||||
app.enableCors({
|
||||
origin: corsOrigins,
|
||||
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
|
||||
credentials: true,
|
||||
});
|
||||
// Enable CORS with centralized configuration
|
||||
app.enableCors(
|
||||
createCorsConfig({
|
||||
corsOriginsEnv: process.env.CORS_ORIGINS,
|
||||
})
|
||||
);
|
||||
|
||||
// Enable validation
|
||||
app.useGlobalPipes(
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@
|
|||
"dependencies": {
|
||||
"@manacore/shared-errors": "workspace:*",
|
||||
"@manacore/shared-nestjs-auth": "workspace:*",
|
||||
"@manacore/shared-nestjs-cors": "workspace:*",
|
||||
"@nestjs/common": "^10.4.15",
|
||||
"@nestjs/config": "^3.3.0",
|
||||
"@nestjs/core": "^10.4.15",
|
||||
|
|
|
|||
|
|
@ -1,26 +1,17 @@
|
|||
import { NestFactory } from '@nestjs/core';
|
||||
import { ValidationPipe } from '@nestjs/common';
|
||||
import { createCorsConfig } from '@manacore/shared-nestjs-cors';
|
||||
import { AppModule } from './app.module';
|
||||
|
||||
async function bootstrap() {
|
||||
const app = await NestFactory.create(AppModule);
|
||||
|
||||
// Enable CORS for mobile and web apps
|
||||
const corsOrigins = process.env.CORS_ORIGINS?.split(',').map((origin) => origin.trim()) || [
|
||||
'http://localhost:3000',
|
||||
'http://localhost:5173',
|
||||
'http://localhost:5174',
|
||||
'http://localhost:5178',
|
||||
'http://localhost:8081',
|
||||
'exp://localhost:8081',
|
||||
'http://localhost:3001',
|
||||
];
|
||||
|
||||
app.enableCors({
|
||||
origin: corsOrigins,
|
||||
methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],
|
||||
credentials: true,
|
||||
});
|
||||
// Enable CORS with centralized configuration
|
||||
app.enableCors(
|
||||
createCorsConfig({
|
||||
corsOriginsEnv: process.env.CORS_ORIGINS,
|
||||
})
|
||||
);
|
||||
|
||||
// Global exception filter will be added later via module
|
||||
// app.useGlobalFilters(new AppExceptionFilter());
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@
|
|||
"dependencies": {
|
||||
"@clock/shared": "workspace:*",
|
||||
"@manacore/shared-nestjs-auth": "workspace:*",
|
||||
"@manacore/shared-nestjs-cors": "workspace:*",
|
||||
"@nestjs/common": "^10.4.15",
|
||||
"@nestjs/config": "^3.3.0",
|
||||
"@nestjs/core": "^10.4.15",
|
||||
|
|
|
|||
|
|
@ -1,25 +1,17 @@
|
|||
import { NestFactory } from '@nestjs/core';
|
||||
import { ValidationPipe } from '@nestjs/common';
|
||||
import { createCorsConfig } from '@manacore/shared-nestjs-cors';
|
||||
import { AppModule } from './app.module';
|
||||
|
||||
async function bootstrap() {
|
||||
const app = await NestFactory.create(AppModule);
|
||||
|
||||
// Enable CORS for mobile and web apps
|
||||
const corsOrigins = process.env.CORS_ORIGINS?.split(',').map((origin) => origin.trim()) || [
|
||||
'http://localhost:3000',
|
||||
'http://localhost:5173',
|
||||
'http://localhost:5186',
|
||||
'http://localhost:8081',
|
||||
'exp://localhost:8081',
|
||||
'http://localhost:3001',
|
||||
];
|
||||
|
||||
app.enableCors({
|
||||
origin: corsOrigins,
|
||||
methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],
|
||||
credentials: true,
|
||||
});
|
||||
// Enable CORS with centralized configuration
|
||||
app.enableCors(
|
||||
createCorsConfig({
|
||||
corsOriginsEnv: process.env.CORS_ORIGINS,
|
||||
})
|
||||
);
|
||||
|
||||
// Enable validation
|
||||
app.useGlobalPipes(
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@manacore/shared-nestjs-auth": "workspace:*",
|
||||
"@manacore/shared-nestjs-cors": "workspace:*",
|
||||
"@manacore/shared-storage": "workspace:*",
|
||||
"@nestjs/common": "^10.4.15",
|
||||
"@nestjs/config": "^3.3.0",
|
||||
|
|
|
|||
|
|
@ -1,25 +1,17 @@
|
|||
import { NestFactory } from '@nestjs/core';
|
||||
import { ValidationPipe } from '@nestjs/common';
|
||||
import { createCorsConfig } from '@manacore/shared-nestjs-cors';
|
||||
import { AppModule } from './app.module';
|
||||
|
||||
async function bootstrap() {
|
||||
const app = await NestFactory.create(AppModule);
|
||||
|
||||
// Enable CORS for mobile and web apps
|
||||
const corsOrigins = process.env.CORS_ORIGINS?.split(',').map((origin) => origin.trim()) || [
|
||||
'http://localhost:3000',
|
||||
'http://localhost:5173',
|
||||
'http://localhost:5184',
|
||||
'http://localhost:8081',
|
||||
'exp://localhost:8081',
|
||||
'http://localhost:3001',
|
||||
];
|
||||
|
||||
app.enableCors({
|
||||
origin: corsOrigins,
|
||||
methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],
|
||||
credentials: true,
|
||||
});
|
||||
// Enable CORS with centralized configuration
|
||||
app.enableCors(
|
||||
createCorsConfig({
|
||||
corsOriginsEnv: process.env.CORS_ORIGINS,
|
||||
})
|
||||
);
|
||||
|
||||
// Enable validation
|
||||
app.useGlobalPipes(
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@
|
|||
"@mana-core/nestjs-integration": "workspace:*",
|
||||
"@manacore/shared-errors": "workspace:*",
|
||||
"@manacore/shared-nestjs-auth": "workspace:*",
|
||||
"@manacore/shared-nestjs-cors": "workspace:*",
|
||||
"@manacore/shared-storage": "workspace:*",
|
||||
"@nestjs/common": "^10.4.15",
|
||||
"@nestjs/config": "^3.3.0",
|
||||
|
|
|
|||
|
|
@ -2,34 +2,18 @@ import { NestFactory } from '@nestjs/core';
|
|||
import { ValidationPipe } from '@nestjs/common';
|
||||
import { NestExpressApplication } from '@nestjs/platform-express';
|
||||
import { join } from 'path';
|
||||
import { createCorsConfigWithCallback } from '@manacore/shared-nestjs-cors';
|
||||
import { AppModule } from './app.module';
|
||||
|
||||
async function bootstrap() {
|
||||
const app = await NestFactory.create<NestExpressApplication>(AppModule);
|
||||
|
||||
// Enable CORS for mobile and web apps
|
||||
const allowedOrigins = process.env.CORS_ORIGINS?.split(',').map((origin) => origin.trim()) || [
|
||||
'http://localhost:3000',
|
||||
'http://localhost:5173',
|
||||
'http://localhost:5174',
|
||||
'http://localhost:5175',
|
||||
'http://localhost:8081',
|
||||
'exp://localhost:8081',
|
||||
'http://localhost:3001',
|
||||
];
|
||||
|
||||
app.enableCors({
|
||||
origin: (origin, callback) => {
|
||||
// Allow requests with no origin (like mobile apps or curl)
|
||||
if (!origin || allowedOrigins.includes(origin)) {
|
||||
callback(null, origin || '*');
|
||||
} else {
|
||||
callback(null, false);
|
||||
}
|
||||
},
|
||||
methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],
|
||||
credentials: true,
|
||||
});
|
||||
// Enable CORS with centralized configuration (callback mode for mobile app support)
|
||||
app.enableCors(
|
||||
createCorsConfigWithCallback({
|
||||
corsOriginsEnv: process.env.CORS_ORIGINS,
|
||||
})
|
||||
);
|
||||
|
||||
// Enable validation
|
||||
app.useGlobalPipes(
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@manacore/shared-nestjs-auth": "workspace:*",
|
||||
"@manacore/shared-nestjs-cors": "workspace:*",
|
||||
"@nestjs/common": "^10.4.9",
|
||||
"@nestjs/config": "^3.3.0",
|
||||
"@nestjs/core": "^10.4.9",
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@manacore/shared-nestjs-auth": "workspace:*",
|
||||
"@manacore/shared-nestjs-cors": "workspace:*",
|
||||
"@nestjs/common": "^10.4.15",
|
||||
"@nestjs/config": "^3.3.0",
|
||||
"@nestjs/core": "^10.4.15",
|
||||
|
|
|
|||
|
|
@ -1,25 +1,17 @@
|
|||
import { NestFactory } from '@nestjs/core';
|
||||
import { ValidationPipe } from '@nestjs/common';
|
||||
import { createCorsConfig } from '@manacore/shared-nestjs-cors';
|
||||
import { AppModule } from './app.module';
|
||||
|
||||
async function bootstrap() {
|
||||
const app = await NestFactory.create(AppModule);
|
||||
|
||||
// Enable CORS for mobile and web apps
|
||||
const corsOrigins = process.env.CORS_ORIGINS?.split(',').map((origin) => origin.trim()) || [
|
||||
'http://localhost:3000',
|
||||
'http://localhost:5173',
|
||||
'http://localhost:5177',
|
||||
'http://localhost:8081',
|
||||
'exp://localhost:8081',
|
||||
'http://localhost:3001',
|
||||
];
|
||||
|
||||
app.enableCors({
|
||||
origin: corsOrigins,
|
||||
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
|
||||
credentials: true,
|
||||
});
|
||||
// Enable CORS with centralized configuration
|
||||
app.enableCors(
|
||||
createCorsConfig({
|
||||
corsOriginsEnv: process.env.CORS_ORIGINS,
|
||||
})
|
||||
);
|
||||
|
||||
// Enable validation
|
||||
app.useGlobalPipes(
|
||||
|
|
|
|||
|
|
@ -114,6 +114,8 @@ services:
|
|||
PORT: 3002
|
||||
DATABASE_URL: postgresql://${POSTGRES_USER:-postgres}:${POSTGRES_PASSWORD}@postgres:5432/chat
|
||||
MANA_CORE_AUTH_URL: http://mana-core-auth:3001
|
||||
# CORS - Allow chat web app and main web app to access backend
|
||||
CORS_ORIGINS: https://chat.staging.manacore.ai,https://staging.manacore.ai,http://localhost:3000,http://localhost:5173
|
||||
SUPABASE_URL: ${SUPABASE_URL}
|
||||
SUPABASE_SERVICE_KEY: ${SUPABASE_SERVICE_ROLE_KEY}
|
||||
AZURE_OPENAI_ENDPOINT: ${AZURE_OPENAI_ENDPOINT}
|
||||
|
|
|
|||
267
docs/CORS_CONFIGURATION_GUIDE.md
Normal file
267
docs/CORS_CONFIGURATION_GUIDE.md
Normal file
|
|
@ -0,0 +1,267 @@
|
|||
# CORS Configuration Guide
|
||||
|
||||
## Problem
|
||||
|
||||
Every deployed app on staging was encountering CORS errors:
|
||||
|
||||
```
|
||||
Access to fetch at 'https://chat-api.staging.manacore.ai/api/v1/...' from origin
|
||||
'https://chat.staging.manacore.ai' has been blocked by CORS policy:
|
||||
No 'Access-Control-Allow-Origin' header is present on the requested resource.
|
||||
```
|
||||
|
||||
### Root Causes
|
||||
|
||||
1. **Missing `CORS_ORIGINS` environment variable** in docker-compose.staging.yml
|
||||
2. **Inconsistent CORS configuration** across backends (different patterns in each main.ts)
|
||||
3. **No centralized CORS management** leading to missing configurations during deployment
|
||||
|
||||
## Solution
|
||||
|
||||
Created `@manacore/shared-nestjs-cors` package providing standardized CORS configuration for all backends.
|
||||
|
||||
### Package Structure
|
||||
|
||||
```
|
||||
packages/shared-nestjs-cors/
|
||||
├── src/
|
||||
│ ├── cors-config.ts # CORS configuration utilities
|
||||
│ └── index.ts # Public exports
|
||||
├── package.json
|
||||
├── tsconfig.json
|
||||
└── README.md
|
||||
```
|
||||
|
||||
### Key Features
|
||||
|
||||
✅ **Automatic development origins** - Works in dev without configuration
|
||||
✅ **Staging/production via env var** - `CORS_ORIGINS` for deployed environments
|
||||
✅ **Mobile app support** - Includes `exp://` and custom protocols
|
||||
✅ **Prevents duplicates** - Deduplicates origin lists
|
||||
✅ **Consistent security** - Same methods, headers, credentials across all apps
|
||||
|
||||
## Usage
|
||||
|
||||
### 1. Add Package Dependency
|
||||
|
||||
```json
|
||||
// apps/{app}/apps/backend/package.json
|
||||
{
|
||||
"dependencies": {
|
||||
"@manacore/shared-nestjs-cors": "workspace:*"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Update main.ts
|
||||
|
||||
```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 centralized configuration
|
||||
app.enableCors(
|
||||
createCorsConfig({
|
||||
corsOriginsEnv: process.env.CORS_ORIGINS,
|
||||
})
|
||||
);
|
||||
|
||||
await app.listen(3000);
|
||||
}
|
||||
bootstrap();
|
||||
```
|
||||
|
||||
### 3. Configure Staging Environment
|
||||
|
||||
```yaml
|
||||
# docker-compose.staging.yml
|
||||
chat-backend:
|
||||
environment:
|
||||
# CORS - Allow chat web app and main web app to access backend
|
||||
CORS_ORIGINS: https://chat.staging.manacore.ai,https://staging.manacore.ai,http://localhost:3000,http://localhost:5173
|
||||
```
|
||||
|
||||
## Default Origins
|
||||
|
||||
The utility automatically includes these development origins:
|
||||
|
||||
```typescript
|
||||
// Common development ports (always available)
|
||||
[
|
||||
'http://localhost:3000', // Chat web (production build)
|
||||
'http://localhost:3001', // Auth service
|
||||
'http://localhost:3002', // Chat backend
|
||||
'http://localhost:5173', // Main web (Vite)
|
||||
'http://localhost:5174-5190', // Additional Vite instances
|
||||
'http://localhost:8081', // Expo mobile
|
||||
'exp://localhost:8081', // Expo mobile (exp:// protocol)
|
||||
]
|
||||
```
|
||||
|
||||
## Deployment Checklist
|
||||
|
||||
When deploying a new app to staging, ensure:
|
||||
|
||||
### ✅ Backend Configuration
|
||||
|
||||
1. Add `@manacore/shared-nestjs-cors` dependency to `package.json`
|
||||
2. Update `main.ts` to use `createCorsConfig()`
|
||||
3. Add `CORS_ORIGINS` to service environment in `docker-compose.staging.yml`
|
||||
|
||||
### ✅ Docker Compose
|
||||
|
||||
```yaml
|
||||
{app}-backend:
|
||||
environment:
|
||||
CORS_ORIGINS: https://{app}.staging.manacore.ai,https://{app}-api.staging.manacore.ai,https://staging.manacore.ai,http://localhost:5XXX,http://localhost:5173
|
||||
```
|
||||
|
||||
**Pattern:**
|
||||
- App's web frontend: `https://{app}.staging.manacore.ai`
|
||||
- App's API endpoint: `https://{app}-api.staging.manacore.ai`
|
||||
- Main web app: `https://staging.manacore.ai`
|
||||
- Local development: `http://localhost:{web-port},http://localhost:5173`
|
||||
|
||||
### ✅ Verification Steps
|
||||
|
||||
1. **Build and deploy** the backend with new CORS config
|
||||
2. **Open browser DevTools** (Network tab)
|
||||
3. **Navigate to** `https://{app}.staging.manacore.ai`
|
||||
4. **Check API requests** - should show `Access-Control-Allow-Origin` header
|
||||
5. **Verify no CORS errors** in console
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### CORS error still appears after deployment
|
||||
|
||||
**Symptoms:**
|
||||
- CORS errors persist after adding `CORS_ORIGINS`
|
||||
- Container logs show correct config
|
||||
|
||||
**Solutions:**
|
||||
|
||||
1. **Restart backend container**
|
||||
```bash
|
||||
ssh -i ~/.ssh/hetzner_deploy_key deploy@46.224.108.214
|
||||
cd ~/manacore-staging
|
||||
docker compose restart {app}-backend
|
||||
```
|
||||
|
||||
2. **Verify environment variable**
|
||||
```bash
|
||||
docker exec {app}-backend-staging printenv | grep CORS
|
||||
```
|
||||
|
||||
3. **Check backend logs**
|
||||
```bash
|
||||
docker logs {app}-backend-staging | grep -i cors
|
||||
```
|
||||
|
||||
4. **Rebuild if package was added**
|
||||
```bash
|
||||
# Rebuild Docker image with new dependency
|
||||
docker compose build {app}-backend
|
||||
docker compose up -d {app}-backend
|
||||
```
|
||||
|
||||
### Mobile app can't connect
|
||||
|
||||
**Solution:** Use callback-based CORS for mobile apps:
|
||||
|
||||
```typescript
|
||||
import { createCorsConfigWithCallback } from '@manacore/shared-nestjs-cors';
|
||||
|
||||
app.enableCors(
|
||||
createCorsConfigWithCallback({
|
||||
corsOriginsEnv: process.env.CORS_ORIGINS,
|
||||
})
|
||||
);
|
||||
```
|
||||
|
||||
This allows requests with no `Origin` header (common for mobile apps).
|
||||
|
||||
### Development origins not working
|
||||
|
||||
**Verification:**
|
||||
|
||||
```bash
|
||||
# Test CORS in development
|
||||
curl -H "Origin: http://localhost:5173" \
|
||||
-H "Access-Control-Request-Method: POST" \
|
||||
-X OPTIONS \
|
||||
http://localhost:3002/api/v1/health
|
||||
```
|
||||
|
||||
Should return:
|
||||
```
|
||||
Access-Control-Allow-Origin: http://localhost:5173
|
||||
Access-Control-Allow-Credentials: true
|
||||
```
|
||||
|
||||
## Migration from Manual CORS
|
||||
|
||||
### Before (Manual Configuration)
|
||||
|
||||
```typescript
|
||||
// ❌ Different in every backend
|
||||
const corsOrigins = process.env.CORS_ORIGINS?.split(',').map(o => o.trim()) || [
|
||||
'http://localhost:3000',
|
||||
'http://localhost:5173',
|
||||
// Different defaults per app
|
||||
];
|
||||
|
||||
app.enableCors({
|
||||
origin: corsOrigins,
|
||||
methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],
|
||||
credentials: true,
|
||||
});
|
||||
```
|
||||
|
||||
### After (Centralized Configuration)
|
||||
|
||||
```typescript
|
||||
// ✅ Consistent everywhere
|
||||
import { createCorsConfig } from '@manacore/shared-nestjs-cors';
|
||||
|
||||
app.enableCors(
|
||||
createCorsConfig({
|
||||
corsOriginsEnv: process.env.CORS_ORIGINS,
|
||||
})
|
||||
);
|
||||
```
|
||||
|
||||
## Updated Applications
|
||||
|
||||
The following backends have been migrated to use `@manacore/shared-nestjs-cors`:
|
||||
|
||||
- ✅ chat-backend (apps/chat/apps/backend)
|
||||
- ✅ picture-backend (apps/picture/apps/backend)
|
||||
- ✅ zitare-backend (apps/zitare/apps/backend)
|
||||
- ✅ contacts-backend (apps/contacts/apps/backend)
|
||||
- ✅ calendar-backend (apps/calendar/apps/backend)
|
||||
- ✅ clock-backend (apps/clock/apps/backend)
|
||||
- ✅ todo-backend (apps/todo/apps/backend)
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Always use `@manacore/shared-nestjs-cors`** for new backends
|
||||
2. **Always add `CORS_ORIGINS`** to docker-compose when deploying
|
||||
3. **Include both app domain and API domain** in CORS_ORIGINS
|
||||
4. **Include `staging.manacore.ai`** for cross-app navigation
|
||||
5. **Keep localhost ports** for local development testing
|
||||
|
||||
## Related Documentation
|
||||
|
||||
- [Staging Setup Guide](./STAGING_SETUP.md) - Complete staging deployment guide
|
||||
- [Package README](../packages/shared-nestjs-cors/README.md) - Detailed API documentation
|
||||
- [Deployment Architecture](./DEPLOYMENT_ARCHITECTURE.md) - Infrastructure overview
|
||||
|
||||
---
|
||||
|
||||
**Last Updated:** 2025-12-17
|
||||
**Author:** Claude Code (Automated CORS Solution)
|
||||
|
|
@ -315,6 +315,7 @@ docker compose up -d --force-recreate
|
|||
**Solution:**
|
||||
1. Add the HTTPS domain to `CORS_ORIGINS` in docker-compose.yml
|
||||
2. Recreate backend containers
|
||||
3. See [CORS Configuration Guide](./CORS_CONFIGURATION_GUIDE.md) for detailed instructions
|
||||
|
||||
### Caddy SSL Certificate Issues
|
||||
|
||||
|
|
@ -593,6 +594,7 @@ docker exec <container-name> printenv | grep -E 'CLIENT|CORS'
|
|||
|
||||
## Related Documentation
|
||||
|
||||
- [CORS Configuration Guide](./CORS_CONFIGURATION_GUIDE.md) - Solving CORS issues once and for all
|
||||
- [Local Development Guide](./LOCAL_DEVELOPMENT.md)
|
||||
- [CI/CD Deployment Guide](./DEPLOYMENT.md)
|
||||
- [Environment Variables](./ENVIRONMENT_VARIABLES.md)
|
||||
|
|
|
|||
189
packages/shared-nestjs-cors/README.md
Normal file
189
packages/shared-nestjs-cors/README.md
Normal file
|
|
@ -0,0 +1,189 @@
|
|||
# @manacore/shared-nestjs-cors
|
||||
|
||||
Centralized CORS configuration utility for all ManaCore NestJS backends.
|
||||
|
||||
## Problem
|
||||
|
||||
Every deployed app was encountering CORS errors because:
|
||||
1. Each backend had different CORS configuration patterns
|
||||
2. Missing `CORS_ORIGINS` environment variable in staging/production
|
||||
3. No consistent way to handle development vs production origins
|
||||
|
||||
## Solution
|
||||
|
||||
This package provides a standardized CORS configuration that:
|
||||
- ✅ Works in development without configuration
|
||||
- ✅ Supports staging/production via `CORS_ORIGINS` env var
|
||||
- ✅ Handles mobile app origins (exp://, myapp://)
|
||||
- ✅ Prevents duplicate origin definitions
|
||||
- ✅ Provides consistent security settings
|
||||
|
||||
## Usage
|
||||
|
||||
### Basic Setup (Recommended)
|
||||
|
||||
```typescript
|
||||
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 automatic origin detection
|
||||
app.enableCors(createCorsConfig({
|
||||
corsOriginsEnv: process.env.CORS_ORIGINS
|
||||
}));
|
||||
|
||||
await app.listen(3000);
|
||||
}
|
||||
bootstrap();
|
||||
```
|
||||
|
||||
### With Custom Development Origins
|
||||
|
||||
```typescript
|
||||
app.enableCors(createCorsConfig({
|
||||
corsOriginsEnv: process.env.CORS_ORIGINS,
|
||||
developmentOrigins: [
|
||||
'http://localhost:3000',
|
||||
'http://localhost:5173'
|
||||
]
|
||||
}));
|
||||
```
|
||||
|
||||
### With Mobile App Support
|
||||
|
||||
```typescript
|
||||
app.enableCors(createCorsConfig({
|
||||
corsOriginsEnv: process.env.CORS_ORIGINS,
|
||||
additionalOrigins: [
|
||||
'exp://localhost:8081', // Expo development
|
||||
'myapp://', // Custom mobile scheme
|
||||
]
|
||||
}));
|
||||
```
|
||||
|
||||
### Advanced: Callback-based CORS
|
||||
|
||||
For advanced scenarios (e.g., allowing server-to-server calls):
|
||||
|
||||
```typescript
|
||||
import { createCorsConfigWithCallback } from '@manacore/shared-nestjs-cors';
|
||||
|
||||
app.enableCors(createCorsConfigWithCallback({
|
||||
corsOriginsEnv: process.env.CORS_ORIGINS
|
||||
}));
|
||||
```
|
||||
|
||||
## Environment Variables
|
||||
|
||||
### Development (.env.development)
|
||||
|
||||
No configuration needed! Default origins cover common ports:
|
||||
- `http://localhost:3000-3020` (backends)
|
||||
- `http://localhost:5173-5190` (web apps)
|
||||
- `http://localhost:8081` (mobile)
|
||||
- `exp://localhost:8081` (Expo)
|
||||
|
||||
### Staging/Production
|
||||
|
||||
Set `CORS_ORIGINS` environment variable in your docker-compose.yml:
|
||||
|
||||
```yaml
|
||||
chat-backend:
|
||||
environment:
|
||||
CORS_ORIGINS: https://chat.staging.manacore.ai,https://chat-api.staging.manacore.ai,https://staging.manacore.ai
|
||||
```
|
||||
|
||||
## Default Configuration
|
||||
|
||||
The utility applies these NestJS CORS settings:
|
||||
|
||||
```typescript
|
||||
{
|
||||
origin: [...], // From corsOriginsEnv + developmentOrigins + additionalOrigins
|
||||
methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],
|
||||
credentials: true,
|
||||
allowedHeaders: ['Content-Type', 'Authorization', 'X-Requested-With']
|
||||
}
|
||||
```
|
||||
|
||||
## Migration Guide
|
||||
|
||||
### Before (Manual CORS)
|
||||
|
||||
```typescript
|
||||
// ❌ Old way - different in every backend
|
||||
const corsOrigins = process.env.CORS_ORIGINS?.split(',').map(o => o.trim()) || [
|
||||
'http://localhost:3000',
|
||||
'http://localhost:5173',
|
||||
// ... different defaults in each app
|
||||
];
|
||||
|
||||
app.enableCors({
|
||||
origin: corsOrigins,
|
||||
methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],
|
||||
credentials: true,
|
||||
});
|
||||
```
|
||||
|
||||
### After (Centralized)
|
||||
|
||||
```typescript
|
||||
// ✅ New way - consistent everywhere
|
||||
import { createCorsConfig } from '@manacore/shared-nestjs-cors';
|
||||
|
||||
app.enableCors(createCorsConfig({
|
||||
corsOriginsEnv: process.env.CORS_ORIGINS
|
||||
}));
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### CORS error in staging/production
|
||||
|
||||
**Symptom:** "Access to fetch has been blocked by CORS policy"
|
||||
|
||||
**Solution:** Ensure `CORS_ORIGINS` is set in docker-compose:
|
||||
```yaml
|
||||
environment:
|
||||
CORS_ORIGINS: https://your-app.staging.manacore.ai,https://staging.manacore.ai
|
||||
```
|
||||
|
||||
### Mobile app not connecting
|
||||
|
||||
**Symptom:** Mobile app fails to connect to backend
|
||||
|
||||
**Solution:** Add mobile origins:
|
||||
```typescript
|
||||
app.enableCors(createCorsConfig({
|
||||
corsOriginsEnv: process.env.CORS_ORIGINS,
|
||||
additionalOrigins: ['exp://localhost:8081']
|
||||
}));
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `createCorsConfig(options?)`
|
||||
|
||||
Creates standard CORS configuration.
|
||||
|
||||
**Parameters:**
|
||||
- `options.corsOriginsEnv` (string, optional): Comma-separated origins from env
|
||||
- `options.developmentOrigins` (string[], optional): Custom dev origins
|
||||
- `options.additionalOrigins` (string[], optional): Additional origins to allow
|
||||
|
||||
**Returns:** NestJS `CorsOptions` object
|
||||
|
||||
### `createCorsConfigWithCallback(options?)`
|
||||
|
||||
Creates CORS configuration with callback for advanced use cases.
|
||||
|
||||
**Parameters:** Same as `createCorsConfig`
|
||||
|
||||
**Returns:** NestJS `CorsOptions` object with `origin` callback
|
||||
|
||||
## License
|
||||
|
||||
Private - ManaCore Monorepo
|
||||
23
packages/shared-nestjs-cors/package.json
Normal file
23
packages/shared-nestjs-cors/package.json
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"name": "@manacore/shared-nestjs-cors",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"main": "./src/index.ts",
|
||||
"types": "./src/index.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./src/index.ts",
|
||||
"default": "./src/index.ts"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"type-check": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nestjs/common": "^11.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^22.10.1",
|
||||
"typescript": "^5.7.2"
|
||||
}
|
||||
}
|
||||
173
packages/shared-nestjs-cors/src/cors-config.ts
Normal file
173
packages/shared-nestjs-cors/src/cors-config.ts
Normal file
|
|
@ -0,0 +1,173 @@
|
|||
import type { CorsOptions } from '@nestjs/common/interfaces/external/cors-options.interface';
|
||||
|
||||
export interface CorsConfigOptions {
|
||||
/**
|
||||
* Comma-separated list of allowed origins from environment variable.
|
||||
* If not provided, uses development defaults.
|
||||
*/
|
||||
corsOriginsEnv?: string;
|
||||
|
||||
/**
|
||||
* Default origins for development. Only used if corsOriginsEnv is not provided.
|
||||
* Defaults to common localhost ports if not specified.
|
||||
*/
|
||||
developmentOrigins?: string[];
|
||||
|
||||
/**
|
||||
* Additional origins to always allow (e.g., for mobile apps).
|
||||
*/
|
||||
additionalOrigins?: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Default development origins for all apps.
|
||||
* Covers common web app ports (5173-5190) and backend ports.
|
||||
*/
|
||||
const DEFAULT_DEV_ORIGINS = [
|
||||
'http://localhost:3000', // Chat web (production build port)
|
||||
'http://localhost:3001', // Auth service
|
||||
'http://localhost:3002', // Chat backend
|
||||
'http://localhost:5173', // Main web (Vite)
|
||||
'http://localhost:5174', // Additional Vite instances
|
||||
'http://localhost:5175',
|
||||
'http://localhost:5176',
|
||||
'http://localhost:5177',
|
||||
'http://localhost:5178',
|
||||
'http://localhost:5179',
|
||||
'http://localhost:5180',
|
||||
'http://localhost:5181',
|
||||
'http://localhost:5182',
|
||||
'http://localhost:5183',
|
||||
'http://localhost:5184',
|
||||
'http://localhost:5185',
|
||||
'http://localhost:5186', // Calendar web
|
||||
'http://localhost:5187', // Clock web
|
||||
'http://localhost:5188', // Todo web
|
||||
'http://localhost:5189',
|
||||
'http://localhost:5190',
|
||||
'http://localhost:8081', // Expo mobile
|
||||
'exp://localhost:8081', // Expo mobile (exp:// protocol)
|
||||
];
|
||||
|
||||
/**
|
||||
* Creates a standardized CORS configuration for NestJS apps.
|
||||
*
|
||||
* This utility provides a consistent CORS setup across all ManaCore backends,
|
||||
* solving the common issue where staging/production deployments fail due to
|
||||
* missing CORS_ORIGINS environment variable.
|
||||
*
|
||||
* ## Usage
|
||||
*
|
||||
* ### Basic (recommended)
|
||||
* ```typescript
|
||||
* import { createCorsConfig } from '@manacore/shared-nestjs-cors';
|
||||
*
|
||||
* const app = await NestFactory.create(AppModule);
|
||||
* app.enableCors(createCorsConfig({
|
||||
* corsOriginsEnv: process.env.CORS_ORIGINS
|
||||
* }));
|
||||
* ```
|
||||
*
|
||||
* ### With custom development origins
|
||||
* ```typescript
|
||||
* app.enableCors(createCorsConfig({
|
||||
* corsOriginsEnv: process.env.CORS_ORIGINS,
|
||||
* developmentOrigins: ['http://localhost:3000', 'http://localhost:5173']
|
||||
* }));
|
||||
* ```
|
||||
*
|
||||
* ### With additional origins (e.g., mobile apps)
|
||||
* ```typescript
|
||||
* app.enableCors(createCorsConfig({
|
||||
* corsOriginsEnv: process.env.CORS_ORIGINS,
|
||||
* additionalOrigins: ['exp://localhost:8081', 'myapp://']
|
||||
* }));
|
||||
* ```
|
||||
*
|
||||
* ## Environment Variable Format
|
||||
*
|
||||
* The `CORS_ORIGINS` environment variable should be a comma-separated list:
|
||||
* ```
|
||||
* CORS_ORIGINS=https://app.staging.manacore.ai,https://api.staging.manacore.ai
|
||||
* ```
|
||||
*
|
||||
* ## Staging/Production Setup
|
||||
*
|
||||
* In docker-compose.staging.yml:
|
||||
* ```yaml
|
||||
* chat-backend:
|
||||
* environment:
|
||||
* CORS_ORIGINS: https://chat.staging.manacore.ai,https://chat-api.staging.manacore.ai
|
||||
* ```
|
||||
*
|
||||
* @param options - Configuration options
|
||||
* @returns NestJS CORS configuration object
|
||||
*/
|
||||
export function createCorsConfig(options: CorsConfigOptions = {}): CorsOptions {
|
||||
const {
|
||||
corsOriginsEnv,
|
||||
developmentOrigins = DEFAULT_DEV_ORIGINS,
|
||||
additionalOrigins = [],
|
||||
} = options;
|
||||
|
||||
// Parse CORS_ORIGINS from environment
|
||||
const envOrigins = corsOriginsEnv
|
||||
? corsOriginsEnv
|
||||
.split(',')
|
||||
.map((origin) => origin.trim())
|
||||
.filter(Boolean)
|
||||
: [];
|
||||
|
||||
// Combine all origins
|
||||
const allOrigins = [...envOrigins, ...developmentOrigins, ...additionalOrigins];
|
||||
|
||||
// Remove duplicates
|
||||
const uniqueOrigins = Array.from(new Set(allOrigins));
|
||||
|
||||
return {
|
||||
origin: uniqueOrigins,
|
||||
methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],
|
||||
credentials: true,
|
||||
allowedHeaders: ['Content-Type', 'Authorization', 'X-Requested-With'],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a CORS configuration with a custom origin callback.
|
||||
* Use this for more advanced CORS logic, such as allowing requests with no origin
|
||||
* (server-to-server calls) or implementing dynamic origin validation.
|
||||
*
|
||||
* @param options - Configuration options
|
||||
* @returns NestJS CORS configuration object with callback
|
||||
*/
|
||||
export function createCorsConfigWithCallback(options: CorsConfigOptions = {}): CorsOptions {
|
||||
const {
|
||||
corsOriginsEnv,
|
||||
developmentOrigins = DEFAULT_DEV_ORIGINS,
|
||||
additionalOrigins = [],
|
||||
} = options;
|
||||
|
||||
const envOrigins = corsOriginsEnv
|
||||
? corsOriginsEnv
|
||||
.split(',')
|
||||
.map((origin) => origin.trim())
|
||||
.filter(Boolean)
|
||||
: [];
|
||||
|
||||
const allOrigins = [...envOrigins, ...developmentOrigins, ...additionalOrigins];
|
||||
const uniqueOrigins = Array.from(new Set(allOrigins));
|
||||
|
||||
return {
|
||||
origin: (origin, callback) => {
|
||||
// Allow requests with no origin (like mobile apps or curl)
|
||||
if (!origin || uniqueOrigins.includes(origin)) {
|
||||
callback(null, origin || '*');
|
||||
} else {
|
||||
callback(null, false);
|
||||
}
|
||||
},
|
||||
methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],
|
||||
credentials: true,
|
||||
allowedHeaders: ['Content-Type', 'Authorization', 'X-Requested-With'],
|
||||
};
|
||||
}
|
||||
2
packages/shared-nestjs-cors/src/index.ts
Normal file
2
packages/shared-nestjs-cors/src/index.ts
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
export { createCorsConfig, createCorsConfigWithCallback } from './cors-config';
|
||||
export type { CorsConfigOptions } from './cors-config';
|
||||
18
packages/shared-nestjs-cors/tsconfig.json
Normal file
18
packages/shared-nestjs-cors/tsconfig.json
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"module": "commonjs",
|
||||
"lib": ["ES2020"],
|
||||
"declaration": true,
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
916
pnpm-lock.yaml
generated
916
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue