41 lines
1.8 KiB
TypeScript
41 lines
1.8 KiB
TypeScript
import { extname, join } from 'node:path';
|
|||
|
|
import type { NestExpressApplication } from '@nestjs/platform-express';
|
||
|
|
import type { NextFunction, Request, Response } from 'express';
|
||
|
|
|
||
|
|
/** Nombres con hash de Angular (main-HFVHQMEH.js, styles-RS7YTGMB.css): no cambian, se pueden cachear un año. */
|
||
|
|
const CON_HASH = /-[A-Z0-9]{8}\.(js|css)$/;
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Cabeceras de seguridad básicas. HSTS solo si la API atiende por HTTPS: con HTTP obligaría al
|
||
|
|
* navegador a usar un HTTPS que no existe.
|
||
|
|
*/
|
||
|
|
export function cabecerasSeguridad(https: boolean) {
|
||
|
|
return (_req: Request, res: Response, next: NextFunction): void => {
|
||
|
|
res.setHeader('X-Content-Type-Options', 'nosniff');
|
||
|
|
res.setHeader('X-Frame-Options', 'DENY');
|
||
|
|
res.setHeader('Referrer-Policy', 'same-origin');
|
||
|
|
if (https) res.setHeader('Strict-Transport-Security', 'max-age=31536000');
|
||
|
|
next();
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* La API sirve también la web compilada, así que todo queda en el mismo origen (sin CORS y con la
|
||
|
|
* cookie de sesión SameSite=strict). Las rutas de Angular (sin extensión y fuera de /api) devuelven
|
||
|
|
* index.html; un archivo que no existe responde 404 en lugar de index.html.
|
||
|
|
*/
|
||
|
|
export function servirWeb(app: NestExpressApplication, carpeta: string): void {
|
||
|
|
app.useStaticAssets(carpeta, {
|
||
|
|
index: false,
|
||
|
|
setHeaders: (res, ruta) => {
|
||
|
|
res.setHeader('Cache-Control', CON_HASH.test(ruta) ? 'public, max-age=31536000, immutable' : 'no-cache');
|
||
|
|
},
|
||
|
|
});
|
||
|
|
|
||
|
|
app.use((req: Request, res: Response, next: NextFunction) => {
|
||
|
|
const esRutaDeLaApp = (req.method === 'GET' || req.method === 'HEAD') && !req.path.startsWith('/api') && extname(req.path) === '';
|
||
|
|
if (!esRutaDeLaApp) return next();
|
||
|
|
res.setHeader('Cache-Control', 'no-cache');
|
||
|
|
res.sendFile(join(carpeta, 'index.html'), (error) => error && next(error));
|
||
|
|
});
|
||
|
|
}
|