Skip to content

Commit

Permalink
Add security headears
Browse files Browse the repository at this point in the history
  • Loading branch information
mczachurski committed Nov 10, 2024
1 parent 35b0934 commit 3976bed
Show file tree
Hide file tree
Showing 5 changed files with 110 additions and 49 deletions.
12 changes: 11 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,14 @@ Navigate to [http://localhost:4200/](http://localhost:4200/). The application wi

## Docker

In production environments, it is best to use a [docker image](https://hub.docker.com/repository/docker/mczachurski/vernissage-web).
In production environments, it is best to use a [docker image](https://hub.docker.com/repository/docker/mczachurski/vernissage-web).

## Enable security headers

It is recommended to include secure headers in responses on production environments. This can be achieved by setting the system variable: `VERNISSAGE_CSP_IMG`. For example:

```bash
export VERNISSAGE_CSP_IMG=https://s3.eu-central-1.amazonaws.com
```

The value of the variable should point to the server address from which images served in the application are to be retrieved. This address will be added to the `Content-Security-Policy` header.
3 changes: 2 additions & 1 deletion angular.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,8 @@
"maximumError": "4kb"
}
],
"outputHashing": "all"
"outputHashing": "all",
"subresourceIntegrity": true
},
"development": {
"optimization": false,
Expand Down
9 changes: 9 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
"blurhash": "^2.0.5",
"exifreader": "^4.23.5",
"express": "^4.21.0",
"helmet": "^8.0.0",
"hammerjs": "^2.0.8",
"ng-gallery": "^11.0.0",
"ngx-captcha": "^13.0.0",
Expand Down
134 changes: 87 additions & 47 deletions server.ts
Original file line number Diff line number Diff line change
@@ -1,62 +1,102 @@
import { APP_BASE_HREF } from '@angular/common';
import { CommonEngine } from '@angular/ssr';
import express from 'express';
import helmet from 'helmet';
import { fileURLToPath } from 'node:url';
import { dirname, join, resolve } from 'node:path';
import AppServerModule from './src/main.server';
import { REQUEST, RESPONSE } from 'express.tokens';

// The Express app is exported so that it can be used by serverless Functions.
export function app(): express.Express {
const server = express();
const serverDistFolder = dirname(fileURLToPath(import.meta.url));
const browserDistFolder = resolve(serverDistFolder, '../browser');
const indexHtml = join(serverDistFolder, 'index.server.html');

const commonEngine = new CommonEngine();

server.set('view engine', 'html');
server.set('views', browserDistFolder);

// Example Express Rest API endpoints
// server.get('/api/**', (req, res) => { });
// Serve static files from /browser
server.get('**', express.static(browserDistFolder, {
maxAge: '1y',
index: 'index.html',
}));

// All regular routes use the Angular engine
server.get('**', (req, res, next) => {
const { protocol, originalUrl, baseUrl, headers } = req;

commonEngine
.render({
bootstrap: AppServerModule,
documentFilePath: indexHtml,
url: `${protocol}://${headers.host}${originalUrl}`,
publicPath: browserDistFolder,
providers: [
{ provide: APP_BASE_HREF, useValue: baseUrl },
{ provide: REQUEST, useValue: req },
{ provide: RESPONSE, useValue: res }
],
})
.then((html) => res.send(html))
.catch((err) => next(err));
});

return server;
}
const server = express();

const cspImg = process.env['VERNISSAGE_CSP_IMG'] || undefined;
if (cspImg) {
server.use(
helmet({
contentSecurityPolicy: {
useDefaults: false,
directives: {
baseUri: ["'self'"],
defaultSrc: ["'none'"],
connectSrc: ["'self'"],
formAction: ["'self'"],
objectSrc: ["'none'"],
frameAncestors: ["'none'"],
fontSrc: ["'self'", "https://fonts.gstatic.com"],
imgSrc: ["'self'","data:", "blob:", cspImg],
scriptSrc: ["'self'", "'unsafe-inline'"],
scriptSrcAttr: ["'self'", "'unsafe-inline'"],
styleSrc: ["'self'", "'unsafe-inline'"],
manifestSrc: ["'self'"],
blockAllMixedContent: [],
upgradeInsecureRequests: [],
},
},
referrerPolicy: {
policy: "same-origin",
},
xContentTypeOptions: false,
strictTransportSecurity: {
maxAge: 31557600,
},
xFrameOptions: { action: "deny" }
}),
);
}

const serverDistFolder = dirname(fileURLToPath(import.meta.url));
const browserDistFolder = resolve(serverDistFolder, '../browser');
const indexHtml = join(serverDistFolder, 'index.server.html');

const commonEngine = new CommonEngine();

server.set('view engine', 'html');
server.set('views', browserDistFolder);

// Example Express Rest API endpoints
// server.get('/api/**', (req, res) => { });
// Serve static files from /browser
server.get('**', express.static(browserDistFolder, {
maxAge: '1y',
index: 'index.html',
}));

// All regular routes use the Angular engine
server.get('**', (req, res, next) => {
const { protocol, originalUrl, baseUrl, headers } = req;

commonEngine
.render({
bootstrap: AppServerModule,
documentFilePath: indexHtml,
url: `${protocol}://${headers.host}${originalUrl}`,
publicPath: browserDistFolder,
providers: [
{ provide: APP_BASE_HREF, useValue: baseUrl },
{ provide: REQUEST, useValue: req },
{ provide: RESPONSE, useValue: res }
],
})
.then((html) => {
res.set('Cache-Control', 'private, no-store, max-age=0');
return res.send(html)
})
.catch((err) => next(err));
});

return server;
}

function run(): void {
const port = process.env['PORT'] || 8080;
const port = 8081; // process.env['PORT'] || 8081;

// Start up the Node server
const server = app();
server.listen(port, () => {
console.log(`Node Express server listening on http://localhost:${port}`);
});
// Start up the Node server
const server = app();
server.listen(port, () => {
console.log(`Node Express server listening on http://localhost:${port}`);
});
}

run();
run();

0 comments on commit 3976bed

Please sign in to comment.