2ee2b05d1c
* feat(security): implement CSP * fix(csp): update helmet version to latest * Squashed commit of the following: commit cc6a35e61db07759c1f32716185543bc48bce760 Author: Kalista Payne <kalista@habitica.com> Date: Fri Dec 12 17:27:50 2025 -0600 fix(CSP): more Amazon domains commit 985b86c29af866b2df942c21217d99390a2c6e92 Author: Kalista Payne <kalista@habitica.com> Date: Fri Dec 12 17:18:08 2025 -0600 fix(csp): more loggly allowance commit 166bd315272f9c3a42652f3a026c27a88ed1a549 Author: Kalista Payne <kalista@habitica.com> Date: Fri Dec 12 17:12:00 2025 -0600 fix(csp): data, inline, some refactoring commit 1a0a6c1806a53d43a7199bb2ef72cff610e908be Author: Kalista Payne <kalista@habitica.com> Date: Fri Dec 12 17:05:44 2025 -0600 fix(CSP): override default script-src commit 023d9886c835989da9c5901c168d66b572097dcf Author: Kalista Payne <kalista@habitica.com> Date: Fri Dec 12 16:56:24 2025 -0600 fix(CSP): unsafe-eval in default-src commit f51f0a0c93b60dfec7ce02be0ecd2587fc882fe0 Author: Kalista Payne <kalista@habitica.com> Date: Fri Dec 12 16:52:14 2025 -0600 fix(CSP): move trusted list to default-src commit 83b2ba7688dea38abb651cf5c27482a7a3648374 Author: Kalista Payne <kalista@habitica.com> Date: Fri Dec 12 16:38:05 2025 -0600 fix(CSP): explicit habitica/aws in script-src commit d5ca5172d5ad2fd8cec9402d7d2c9452c6ece7a1 Author: Kalista Payne <kalista@habitica.com> Date: Fri Dec 12 16:31:38 2025 -0600 fix(CSP): need escaped single quotes commit c677a1ffeff5793b6da228924e68d2c2e47794b2 Author: Kalista Payne <kalista@habitica.com> Date: Fri Dec 12 16:27:46 2025 -0600 fix(CSP): unsafe-eval commit 6ef35c3f7281c8426d9c333686be6bb65f00b3a8 Author: Kalista Payne <kalista@habitica.com> Date: Fri Dec 12 16:15:07 2025 -0600 fix(CSP): might need to skip entirely in dev but try no 'self' commit 5759fb37d82fa61b474f01e9ce5e2dc461f6ceba Author: Kalista Payne <kalista@habitica.com> Date: Fri Dec 12 15:51:26 2025 -0600 fix(csp): permit AWS in default-src commit 9f238abf9373bc29715657b945b2247ef23c9224 Author: Kalista Payne <kalista@habitica.com> Date: Fri Dec 5 17:22:25 2025 -0600 fix(csp): update helmet version to latest commit 9462e90f4f3058f4014137b3178b9751c5280e97 Author: Kalista Payne <kalista@habitica.com> Date: Tue Nov 25 09:27:05 2025 -0600 feat(security): implement CSP commit72539f9ba3Author: Kalista Payne <kalista@habitica.com> Date: Wed Dec 10 14:16:53 2025 -0600 5.42.2 commitdabd466719Author: Kalista Payne <kalista@habitica.com> Date: Wed Dec 10 14:16:48 2025 -0600 Revert "Chat optimization (#15545)" This reverts commit2917955ef0. commit8bf2304330Author: Kalista Payne <kalista@habitica.com> Date: Wed Dec 10 14:15:48 2025 -0600 chore(event): G1G1 date tweaks commit6937dc4e4eAuthor: Kalista Payne <kalista@habitica.com> Date: Mon Dec 8 16:37:04 2025 -0600 fix(subscription): couple more layout tweaks * fix(csp): move unsafe-eval to default? ig? * Revert "fix(csp): move unsafe-eval to default? ig?" This reverts commit 90476cbf6c59132024d4fbaeceb7cdb19a876303. * fix(security): no unsafe! yay! * fix(packages): remove webpack * fix(lint): object destructuring * fix(csp): remove Vue-Fragment * wip(i18n): load Moment locale from cache * fix(gulp): remove unneeded cache task * fix(i18n): add Moment weekday abbrevs to translations * fix(lint): destructuring ...why is this happening here and not develop lol * fix(csp): add amplitude to whitelist --------- Co-authored-by: Phillip Thelen <phillip@thelen.space>
179 lines
5.3 KiB
JavaScript
179 lines
5.3 KiB
JavaScript
// This module is only used to attach middlewares to the express app
|
|
import bodyParser from 'body-parser';
|
|
import nconf from 'nconf';
|
|
import morgan from 'morgan';
|
|
import cookieSession from 'cookie-session';
|
|
import mongoose from 'mongoose';
|
|
import compression from 'compression';
|
|
import methodOverride from 'method-override';
|
|
import passport from 'passport';
|
|
import basicAuth from 'express-basic-auth';
|
|
import helmet from 'helmet';
|
|
import setupExpress from '../libs/setupExpress';
|
|
import errorHandler from './errorHandler';
|
|
import notFoundHandler from './notFound';
|
|
import cors from './cors';
|
|
import staticMiddleware from './static';
|
|
import domainMiddleware from './domain';
|
|
// import favicon from 'serve-favicon';
|
|
// import path from 'path';
|
|
import maintenanceMode from './maintenanceMode';
|
|
import { ENABLE_CLUSTER } from '../libs/config';
|
|
import {
|
|
forceSSL,
|
|
forceHabitica,
|
|
} from './redirects';
|
|
import blocker from './blocker';
|
|
import v1 from './v1';
|
|
import v2 from './v2';
|
|
import appRoutes from './appRoutes';
|
|
import responseHandler from './response';
|
|
import {
|
|
attachTranslateFunction,
|
|
} from './language';
|
|
import {
|
|
logRequestData,
|
|
logSlowRequests,
|
|
} from './requestLogHandler';
|
|
|
|
const IS_PROD = nconf.get('IS_PROD');
|
|
const DISABLE_LOGGING = nconf.get('DISABLE_REQUEST_LOGGING') === 'true';
|
|
const ENABLE_HTTP_AUTH = nconf.get('SITE_HTTP_AUTH_ENABLED') === 'true';
|
|
const LOG_REQUESTS_EXCESSIVE_MODE = nconf.get('LOG_REQUESTS_EXCESSIVE_MODE') === 'true';
|
|
const SLOW_REQUEST_THRESHOLD = nconf.get('SLOW_REQUEST_THRESHOLD');
|
|
const DISABLE_SSL_ENFORCEMENT = nconf.get('DISABLE_SSL_ENFORCEMENT') === 'true';
|
|
const DISABLE_BASE_URL_ENFORCEMENT = nconf.get('DISABLE_BASE_URL_ENFORCEMENT') === 'true';
|
|
// const PUBLIC_DIR = path.join(__dirname, '/../../client');
|
|
|
|
const SESSION_SECRET = nconf.get('SESSION_SECRET');
|
|
const TEN_YEARS = 1000 * 60 * 60 * 24 * 365 * 10;
|
|
|
|
export default function attachMiddlewares (app, server) {
|
|
setupExpress(app);
|
|
|
|
if (LOG_REQUESTS_EXCESSIVE_MODE) {
|
|
app.use(logRequestData);
|
|
}
|
|
|
|
if (SLOW_REQUEST_THRESHOLD > 0) {
|
|
app.use(logSlowRequests);
|
|
}
|
|
|
|
if (ENABLE_CLUSTER) {
|
|
app.use(domainMiddleware(server, mongoose));
|
|
}
|
|
|
|
if (!IS_PROD && !DISABLE_LOGGING) app.use(morgan('dev'));
|
|
|
|
// See https://helmetjs.github.io/ for the list of headers enabled by default
|
|
app.use(helmet({
|
|
// New middlewares added by default in Helmet 4 are disabled
|
|
contentSecurityPolicy: {
|
|
directives: {
|
|
defaultSrc: [
|
|
'*.habitica.com',
|
|
'*.amazon.com',
|
|
'*.amazonaws.com',
|
|
'*.amplitude.com',
|
|
'*.loggly.com',
|
|
'*.payments-amazon.com',
|
|
'*.stripe.com',
|
|
'*.stripe.network',
|
|
],
|
|
imgSrc: [
|
|
'*',
|
|
'data:',
|
|
],
|
|
scriptSrc: [
|
|
'*.habitica.com',
|
|
'*.amazon.com',
|
|
'*.amazonaws.com',
|
|
'*.amplitude.com',
|
|
'*.loggly.com',
|
|
'*.payments-amazon.com',
|
|
'*.stripe.com',
|
|
'*.stripe.network',
|
|
],
|
|
upgradeInsecureRequests: IS_PROD ? [] : null,
|
|
},
|
|
},
|
|
expectCt: false,
|
|
permittedCrossDomainPolicies: false,
|
|
referrerPolicy: false,
|
|
}));
|
|
|
|
// add res.respond and res.t
|
|
app.use(responseHandler);
|
|
app.use(attachTranslateFunction);
|
|
|
|
app.use(compression());
|
|
// app.use(favicon(`${PUBLIC_DIR}/favicon.ico`));
|
|
|
|
app.use(maintenanceMode);
|
|
|
|
app.use(blocker);
|
|
|
|
app.use(cors);
|
|
if (!DISABLE_SSL_ENFORCEMENT) {
|
|
app.use(forceSSL);
|
|
}
|
|
if (!DISABLE_BASE_URL_ENFORCEMENT) {
|
|
app.use(forceHabitica);
|
|
}
|
|
|
|
app.use(bodyParser.urlencoded({
|
|
extended: true, // Uses 'qs' library as old connect middleware
|
|
limit: '10mb',
|
|
}));
|
|
app.use(function bodyMiddleware (req, res, next) { // eslint-disable-line prefer-arrow-callback
|
|
if (req.path === '/stripe/webhooks') {
|
|
// Do not parse the body for `/stripe/webhooks`
|
|
// See https://stripe.com/docs/webhooks/signatures#verify-official-libraries
|
|
bodyParser.raw({ type: 'application/json' })(req, res, next);
|
|
} else {
|
|
bodyParser.json({ limit: '10mb' })(req, res, next);
|
|
}
|
|
});
|
|
|
|
app.use(methodOverride());
|
|
|
|
app.use(cookieSession({
|
|
name: 'connect:sess', // Used to keep backward compatibility with Express 3 cookies
|
|
secret: SESSION_SECRET,
|
|
httpOnly: true, // so cookies are not accessible with browser JS
|
|
// TODO what about https only (secure) ?
|
|
maxAge: TEN_YEARS,
|
|
}));
|
|
|
|
// Initialize Passport! Also use passport.session() middleware, to support
|
|
// persistent login sessions (recommended).
|
|
app.use(passport.initialize());
|
|
app.use(passport.session());
|
|
|
|
// The site can require basic HTTP authentication to be accessed
|
|
if (ENABLE_HTTP_AUTH) {
|
|
const httpBasicAuthUsers = {};
|
|
const usernames = nconf.get('SITE_HTTP_AUTH_USERNAMES').split(',');
|
|
const passwords = nconf.get('SITE_HTTP_AUTH_PASSWORDS').split(',');
|
|
|
|
usernames.forEach((user, index) => {
|
|
httpBasicAuthUsers[user] = passwords[index];
|
|
});
|
|
|
|
app.use(basicAuth({
|
|
users: httpBasicAuthUsers,
|
|
challenge: true,
|
|
realm: 'Habitica',
|
|
}));
|
|
}
|
|
app.use('/api/v2', v2);
|
|
app.use('/api/v1', v1);
|
|
app.use(appRoutes); // the main app, also setup top-level routes
|
|
staticMiddleware(app);
|
|
|
|
app.use(notFoundHandler);
|
|
|
|
// Error handler middleware, define as the last one.
|
|
app.use(errorHandler);
|
|
}
|