fix(api): CORS + CORP sur réponses statiques /uploads (Flutter web)
- enableCors avant les statics - Access-Control-Allow-Origin * + Cross-Origin-Resource-Policy sur fichiers - OPTIONS préflight pour /uploads et /api/v1/uploads Made-with: Cursor
This commit is contained in:
parent
3bfdadd301
commit
4fe29799cd
@ -9,6 +9,34 @@ import { LogRequestInterceptor } from './common/interceptors/log-request.interce
|
|||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import * as express from 'express';
|
import * as express from 'express';
|
||||||
|
|
||||||
|
/** GET/HEAD photos : CORS + CORP pour Flutter web (cross-origin `Image.network`). Pas de cookie sur ces URLs. */
|
||||||
|
function setStaticImageCorsHeaders(res: express.Response): void {
|
||||||
|
res.setHeader('Access-Control-Allow-Origin', '*');
|
||||||
|
res.setHeader('Cross-Origin-Resource-Policy', 'cross-origin');
|
||||||
|
}
|
||||||
|
|
||||||
|
const staticImageServeOptions = {
|
||||||
|
index: false,
|
||||||
|
fallthrough: true,
|
||||||
|
setHeaders: (res: express.Response) => setStaticImageCorsHeaders(res),
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Préflight si le navigateur interroge OPTIONS sur les médias. */
|
||||||
|
function uploadsCorsPreflight(
|
||||||
|
req: express.Request,
|
||||||
|
res: express.Response,
|
||||||
|
next: express.NextFunction,
|
||||||
|
): void {
|
||||||
|
if (req.method === 'OPTIONS') {
|
||||||
|
setStaticImageCorsHeaders(res);
|
||||||
|
res.setHeader('Access-Control-Allow-Methods', 'GET, HEAD, OPTIONS');
|
||||||
|
res.setHeader('Access-Control-Max-Age', '86400');
|
||||||
|
res.status(204).end();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
|
||||||
/** Répertoire disque contenant le dossier `photos` (ex. /app/uploads si photos dans /app/uploads/photos). */
|
/** Répertoire disque contenant le dossier `photos` (ex. /app/uploads si photos dans /app/uploads/photos). */
|
||||||
function resolveUploadsFilesystemRoot(): string {
|
function resolveUploadsFilesystemRoot(): string {
|
||||||
const raw = process.env.UPLOAD_PHOTOS_DIR?.trim();
|
const raw = process.env.UPLOAD_PHOTOS_DIR?.trim();
|
||||||
@ -29,34 +57,31 @@ async function bootstrap() {
|
|||||||
app.useBodyParser('json', { limit: '15mb' });
|
app.useBodyParser('json', { limit: '15mb' });
|
||||||
app.useBodyParser('urlencoded', { extended: true, limit: '15mb' });
|
app.useBodyParser('urlencoded', { extended: true, limit: '15mb' });
|
||||||
|
|
||||||
// Photos : chemins stockés en base type /uploads/photos/...
|
// CORS global **avant** les fichiers statiques pour que les réponses API et idéalement la chaîne Express restent cohérentes.
|
||||||
// - /uploads/ : si le proxy route ce préfixe vers Nest.
|
|
||||||
// - /api/v1/uploads/ : même trafic que l’API (Traefik PathPrefix /api → pas besoin de règle /uploads).
|
|
||||||
const uploadsRoot = resolveUploadsFilesystemRoot();
|
|
||||||
app.useStaticAssets(uploadsRoot, {
|
|
||||||
prefix: '/uploads/',
|
|
||||||
index: false,
|
|
||||||
fallthrough: true,
|
|
||||||
});
|
|
||||||
app
|
|
||||||
.getHttpAdapter()
|
|
||||||
.getInstance()
|
|
||||||
.use(
|
|
||||||
'/api/v1/uploads',
|
|
||||||
express.static(uploadsRoot, { index: false, fallthrough: true }),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Log de chaque appel API si LOG_API_REQUESTS=true (mode debug)
|
|
||||||
app.useGlobalInterceptors(new LogRequestInterceptor());
|
|
||||||
|
|
||||||
// Configuration CORS pour autoriser les requêtes depuis localhost (dev) et production
|
|
||||||
app.enableCors({
|
app.enableCors({
|
||||||
origin: true, // Autorise toutes les origines (dev) - à restreindre en prod
|
origin: true, // Reflète l’Origin (dev / prod) — routes API + cookies JWT
|
||||||
methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],
|
methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],
|
||||||
allowedHeaders: ['Content-Type', 'Authorization', 'Accept'],
|
allowedHeaders: ['Content-Type', 'Authorization', 'Accept'],
|
||||||
credentials: true,
|
credentials: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const expressApp = app.getHttpAdapter().getInstance();
|
||||||
|
const uploadsRoot = resolveUploadsFilesystemRoot();
|
||||||
|
|
||||||
|
// Photos : chemins stockés en base type /uploads/photos/...
|
||||||
|
// En-têtes explicites sur les réponses fichier (les statics peuvent répondre sans repasser par la même couche que les contrôleurs).
|
||||||
|
expressApp.use('/uploads', uploadsCorsPreflight);
|
||||||
|
expressApp.use('/api/v1/uploads', uploadsCorsPreflight);
|
||||||
|
|
||||||
|
app.useStaticAssets(uploadsRoot, {
|
||||||
|
prefix: '/uploads/',
|
||||||
|
...staticImageServeOptions,
|
||||||
|
});
|
||||||
|
expressApp.use('/api/v1/uploads', express.static(uploadsRoot, staticImageServeOptions));
|
||||||
|
|
||||||
|
// Log de chaque appel API si LOG_API_REQUESTS=true (mode debug)
|
||||||
|
app.useGlobalInterceptors(new LogRequestInterceptor());
|
||||||
|
|
||||||
app.useGlobalPipes(
|
app.useGlobalPipes(
|
||||||
new ValidationPipe({
|
new ValidationPipe({
|
||||||
whitelist: true,
|
whitelist: true,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user