60 lines
1.9 KiB
JavaScript
60 lines
1.9 KiB
JavaScript
const express = require('express');
|
|
const path = require('path');
|
|
const crypto = require('crypto');
|
|
const fs = require('fs');
|
|
const app = express();
|
|
const port = 8080;
|
|
|
|
// Generate a random nonce
|
|
function generateNonce() {
|
|
return crypto.randomBytes(16).toString('base64');
|
|
}
|
|
|
|
// Security headers middleware
|
|
app.use((req, res, next) => {
|
|
const nonce = generateNonce();
|
|
res.locals.nonce = nonce;
|
|
|
|
// Content Security Policy with nonce and hash
|
|
res.setHeader(
|
|
'Content-Security-Policy',
|
|
"default-src 'self'; " +
|
|
`script-src 'self' 'nonce-${nonce}' 'sha256-ryQsJ+aghKKD/CeXgx8jtsnZT3Epp3EjIw8RyHIq544='; ` +
|
|
"style-src 'self' 'unsafe-inline'; " +
|
|
"img-src 'self' data: https: http:; " +
|
|
"font-src 'self'; " +
|
|
"connect-src 'self'"
|
|
);
|
|
|
|
// Other security headers
|
|
res.setHeader('X-Content-Type-Options', 'nosniff');
|
|
res.setHeader('X-Frame-Options', 'DENY');
|
|
res.setHeader('X-XSS-Protection', '1; mode=block');
|
|
res.setHeader('Referrer-Policy', 'strict-origin-when-cross-origin');
|
|
res.setHeader('Permissions-Policy', 'geolocation=(), microphone=(), camera=()');
|
|
res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
|
|
|
|
next();
|
|
});
|
|
|
|
// Custom middleware to inject nonce into HTML
|
|
app.use((req, res, next) => {
|
|
if (req.path.endsWith('.html')) {
|
|
const filePath = path.join(__dirname, '../docker/resume', req.path);
|
|
let html = fs.readFileSync(filePath, 'utf8');
|
|
|
|
// Add nonce to all script tags
|
|
html = html.replace(/<script/g, `<script nonce="${res.locals.nonce}"`);
|
|
|
|
res.send(html);
|
|
} else {
|
|
next();
|
|
}
|
|
});
|
|
|
|
// Serve static files from the docker/resume directory
|
|
app.use(express.static(path.join(__dirname, '../docker/resume')));
|
|
|
|
app.listen(port, () => {
|
|
console.log(`Local development server running at http://localhost:${port}`);
|
|
});
|