Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4264026bbe | |||
| 24b4a314a9 | |||
| ba825823a0 | |||
| fb16b7bbb8 |
Generated
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "picpeak-backend",
|
"name": "picpeak-backend",
|
||||||
"version": "1.0.117",
|
"version": "1.0.119",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "picpeak-backend",
|
"name": "picpeak-backend",
|
||||||
"version": "1.0.117",
|
"version": "1.0.119",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aws-sdk/client-s3": "^3.850.0",
|
"@aws-sdk/client-s3": "^3.850.0",
|
||||||
"@aws-sdk/lib-storage": "^3.850.0",
|
"@aws-sdk/lib-storage": "^3.850.0",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "picpeak-backend",
|
"name": "picpeak-backend",
|
||||||
"version": "1.0.117",
|
"version": "1.0.119",
|
||||||
"description": "Backend for PicPeak event photo sharing platform",
|
"description": "Backend for PicPeak event photo sharing platform",
|
||||||
"main": "server.js",
|
"main": "server.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
+32
-16
@@ -43,25 +43,36 @@ const PORT = process.env.PORT || 3000;
|
|||||||
app.set('trust proxy', 'loopback, linklocal, uniquelocal');
|
app.set('trust proxy', 'loopback, linklocal, uniquelocal');
|
||||||
|
|
||||||
// Security middleware with custom CSP
|
// Security middleware with custom CSP
|
||||||
|
// In native HTTP installs, do NOT force HTTPS for subresources.
|
||||||
|
const enableHsts = process.env.ENABLE_HSTS === 'true';
|
||||||
|
const cspDirectives = {
|
||||||
|
defaultSrc: ["'self'"],
|
||||||
|
scriptSrc: ["'self'", "'unsafe-inline'"], // Required for React
|
||||||
|
styleSrc: ["'self'", "'unsafe-inline'", "https:"], // Required for styled components
|
||||||
|
imgSrc: ["'self'", "data:", "https:", "blob:"], // Allow data URLs and external images
|
||||||
|
connectSrc: ["'self'"], // API connections
|
||||||
|
fontSrc: ["'self'", "https:", "data:"], // Web fonts
|
||||||
|
objectSrc: ["'none'"], // Disable plugins
|
||||||
|
mediaSrc: ["'self'"], // Audio/video
|
||||||
|
frameSrc: ["'none'"], // Disable iframes
|
||||||
|
};
|
||||||
|
// Only upgrade insecure requests when HSTS explicitly enabled (HTTPS deployment)
|
||||||
|
if (enableHsts) {
|
||||||
|
// In helmet, an empty array enables the directive
|
||||||
|
cspDirectives.upgradeInsecureRequests = [];
|
||||||
|
}
|
||||||
|
|
||||||
app.use(helmet({
|
app.use(helmet({
|
||||||
contentSecurityPolicy: {
|
contentSecurityPolicy: {
|
||||||
directives: {
|
// Avoid helmet adding defaults like upgrade-insecure-requests when not desired
|
||||||
defaultSrc: ["'self'"],
|
useDefaults: false,
|
||||||
scriptSrc: ["'self'", "'unsafe-inline'"], // Required for React
|
directives: cspDirectives,
|
||||||
styleSrc: ["'self'", "'unsafe-inline'", "https:"], // Required for styled components
|
|
||||||
imgSrc: ["'self'", "data:", "https:", "blob:"], // Allow data URLs and external images
|
|
||||||
connectSrc: ["'self'"], // API connections
|
|
||||||
fontSrc: ["'self'", "https:", "data:"], // Web fonts
|
|
||||||
objectSrc: ["'none'"], // Disable plugins
|
|
||||||
mediaSrc: ["'self'"], // Audio/video
|
|
||||||
frameSrc: ["'none'"], // Disable iframes
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
hsts: {
|
hsts: enableHsts ? {
|
||||||
maxAge: 31536000, // 1 year
|
maxAge: 31536000, // 1 year
|
||||||
includeSubDomains: true,
|
includeSubDomains: true,
|
||||||
preload: true
|
preload: true
|
||||||
},
|
} : false,
|
||||||
permittedCrossDomainPolicies: false,
|
permittedCrossDomainPolicies: false,
|
||||||
referrerPolicy: { policy: "strict-origin-when-cross-origin" }
|
referrerPolicy: { policy: "strict-origin-when-cross-origin" }
|
||||||
}));
|
}));
|
||||||
@@ -222,15 +233,20 @@ app.use('/api/secure-images', secureImagesRoutes);
|
|||||||
|
|
||||||
// Optional: Serve built frontend (native installs)
|
// Optional: Serve built frontend (native installs)
|
||||||
try {
|
try {
|
||||||
const serveFrontend = process.env.SERVE_FRONTEND === 'true';
|
const serveFrontendEnv = process.env.SERVE_FRONTEND; // 'true' | 'false' | undefined
|
||||||
const frontendDir = process.env.FRONTEND_DIR || path.join(__dirname, '../frontend/dist');
|
const frontendDir = process.env.FRONTEND_DIR || path.join(__dirname, '../frontend/dist');
|
||||||
if (serveFrontend && fs.existsSync(frontendDir)) {
|
const indexPath = path.join(frontendDir, 'index.html');
|
||||||
|
// Auto-serve when dist exists unless explicitly disabled
|
||||||
|
const shouldServe = (serveFrontendEnv === 'true') || ((serveFrontendEnv === undefined || serveFrontendEnv === 'auto') && fs.existsSync(indexPath));
|
||||||
|
if (shouldServe) {
|
||||||
logger.info(`Serving frontend from ${frontendDir}`);
|
logger.info(`Serving frontend from ${frontendDir}`);
|
||||||
app.use(express.static(frontendDir));
|
app.use(express.static(frontendDir));
|
||||||
// SPA fallback for non-API routes
|
// SPA fallback for non-API routes
|
||||||
app.get([ '/', '/admin', '/admin/*', '/gallery/*' ], (req, res) => {
|
app.get([ '/', '/admin', '/admin/*', '/gallery/*' ], (req, res) => {
|
||||||
res.sendFile(path.join(frontendDir, 'index.html'));
|
res.sendFile(indexPath);
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
logger.info('Frontend static serving disabled or dist not found', { serveFrontendEnv, frontendDir });
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.warn('Failed to enable frontend static serving', { error: e.message });
|
logger.warn('Failed to enable frontend static serving', { error: e.message });
|
||||||
|
|||||||
Reference in New Issue
Block a user