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 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). */
|
||||
function resolveUploadsFilesystemRoot(): string {
|
||||
const raw = process.env.UPLOAD_PHOTOS_DIR?.trim();
|
||||
@ -29,34 +57,31 @@ async function bootstrap() {
|
||||
app.useBodyParser('json', { limit: '15mb' });
|
||||
app.useBodyParser('urlencoded', { extended: true, limit: '15mb' });
|
||||
|
||||
// Photos : chemins stockés en base type /uploads/photos/...
|
||||
// - /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
|
||||
// CORS global **avant** les fichiers statiques pour que les réponses API et idéalement la chaîne Express restent cohérentes.
|
||||
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'],
|
||||
allowedHeaders: ['Content-Type', 'Authorization', 'Accept'],
|
||||
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(
|
||||
new ValidationPipe({
|
||||
whitelist: true,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user