# FR-041 — CORS middleware infrastructure (2026-06-01)

**Commit**: `3d16d69` — v01.15.35 → v01.15.36
**Parent**: `__agent/feature-requests/FR-041-subdomain-per-service-architecture.md`
**Pattern**: `documentations/guidelines/development/cors-and-subdomain-architecture.md`

## What

Adds CORS middleware infrastructure to `DyNTS_AppServer`. After this commit, any subclass that wants to be reachable cross-origin overrides `getCorsSettings()` and returns a `DyNTS_Cors_Settings` object.

## Why

Pre-FR-041: every cross-service call was same-origin via gateway nginx proxy (e.g. `<app>.futdevpro.hu/auth-api/*` → `fdp-auth-service:39005/api/*`). No CORS needed because same-origin.

Post-FR-041: each sister service exposed at its own subdomain (`auth.futdevpro.hu`, `social.futdevpro.hu`, etc.). Cross-origin requires CORS allowlist.

Audit pre-FR-041: ZERO existing CORS implementation in the entire FDP stack (`grep -rn "cors\|Access-Control" dynamo-nts/src` returned 0 hits). Greenfield work.

## Design

### `DyNTS_Cors_Settings` interface

`src/_models/interfaces/cors-settings.interface.ts`:

```typescript
export interface DyNTS_Cors_Settings {
  allowedOrigins: string[] | ((origin: string) => boolean) | '*';
  allowCredentials?: boolean;        // default true
  allowedMethods?: string[];          // default GET/POST/PUT/DELETE/PATCH/OPTIONS
  allowedHeaders?: string[];          // default Authorization/Content-Type/X-Admin-Key/X-Requested-With
  exposedHeaders?: string[];          // default Content-Length/Content-Type
  maxAgeSeconds?: number;             // default 86400 (24h preflight cache)
}
```

### `mountCors(express)` protected method

`src/_services/server/app.server.ts` (~line 1422):

- Reads `this.getCorsSettings?.()` ONCE at mount time. Undefined → returns immediately (no CORS headers, back-compat).
- Express middleware: for each request, checks Origin against allowlist. Match → echoes the origin (NOT wildcard, because credentials require an exact origin echo) + sets Methods/Headers/Expose/MaxAge/Vary.
- OPTIONS preflight short-circuits with 204.

### `getCorsSettings?()` optional override

Added to the class as `getCorsSettings?(): DyNTS_Cors_Settings | undefined;`. Subclasses opt in by implementing it.

## Mount-points

`initOpenExpress()` + `initSecureExpress()` both call `mountCors(this.<express>)` after BodyParser, BEFORE route mounting. CORS preflight always intercepts before any route handler can interfere.

Timing verified: `asyncConstruct` line 410 `getAppParams()` (sets env via `FDPNTS_setEnvBase`) → line 1018 `initOpenExpress()` (calls `mountCors`). Env is always set by the time `getCorsSettings()` runs.

## Why hand-rolled (no `cors` npm dep)

- ~30 lines, zero new dependency
- Full control over Vary/credentials/preflight semantics
- Aligns with FDP "no unnecessary deps" philosophy

## Spec coverage

`src/_models/interfaces/cors-settings.interface.spec.ts` — 5 specs covering all `allowedOrigins` shapes (array, predicate, wildcard) + credential defaults + custom headers.

`npx jasmine` post-change: **1250/0** (146 pending pre-existing, all 5 new specs green).

## Consumer pattern

```typescript
import { DyNTS_Cors_Settings } from '@futdevpro/nts-dynamo';
import { FDPNTS_buildCorsSettings } from '@futdevpro/nts-fdp-templates';

export class App extends DyNTS_App {
  override getCorsSettings(): DyNTS_Cors_Settings | undefined {
    return FDPNTS_buildCorsSettings();   // env-aware allowlist from FDP-templates
  }
}
```

First consumer: `LIVE-projects/fdp-auth-service` commit `1319368`.

## What's NOT in this commit

- Per-env allowlist source (lives in `fdp-templates v01.15.38`, commit `0f793c4`)
- Helper for FDPNTS apps to bind that allowlist (lives in `fdp-templates-nts v01.15.45`, commit `6e748ee`)

## Wave-3 cleanup candidates

- Once every app overrides `getCorsSettings()`, consider making it abstract (no more optional `?`).
- OPTIONS-without-origin-match returns 204 + no CORS headers (functional: browser denies). Could be improved to 403 for clearer security audit.

## Cross-references

- `documentations/guidelines/development/cors-and-subdomain-architecture.md`
- `NPM-packages/fdp-templates/src/_constants/environment/fdp-cors-origins.const.ts` — allowlist
- `NPM-packages/fdp-templates-nts/src/_collections/cors-settings.util.ts` — consumer helper
- `LIVE-projects/fdp-auth-service/src/app.server.ts` — first consumer
