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:
Wuesteon 2025-12-16 21:12:25 +01:00
parent 8c973e4354
commit 4c44764838
22 changed files with 1293 additions and 447 deletions

View 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

View 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"
}
}

View 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'],
};
}

View file

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

View 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"]
}