mirror of
https://github.com/wassname/talk.git
synced 2026-08-15 12:55:10 +08:00
added support for csp
This commit is contained in:
@@ -0,0 +1,49 @@
|
||||
const helmet = require('helmet');
|
||||
const { WEBSOCKET_LIVE_URI } = require('../config');
|
||||
const { BASE_PATH, BASE_URL, STATIC_URL } = require('../url');
|
||||
const { URL } = require('url');
|
||||
|
||||
// websocketSrc represents the host where we can connect for websocket requests.
|
||||
const websocketSrc = new URL(WEBSOCKET_LIVE_URI || BASE_URL).host;
|
||||
|
||||
// staticSrc represents any static asset hosted on the static host.
|
||||
const staticSrc = new URL(STATIC_URL).host;
|
||||
|
||||
// nonceSrc represents the nonce source that is used to indicate a safe resource
|
||||
// to load.
|
||||
const nonceSrc = (req, res) => `'nonce-${res.locals.nonce}'`;
|
||||
|
||||
module.exports = helmet.contentSecurityPolicy({
|
||||
directives: {
|
||||
reportUri: `${BASE_PATH}api/v1/csp`, // report all policy violations to our reporting uri
|
||||
defaultSrc: ["'none'"], // by default, do not allow anything at all
|
||||
scriptSrc: [
|
||||
"'self'",
|
||||
'https://ajax.googleapis.com', // for jquery
|
||||
staticSrc, // for any static files loaded from a cdn
|
||||
nonceSrc,
|
||||
],
|
||||
styleSrc: [
|
||||
"'self'",
|
||||
'https://maxcdn.bootstrapcdn.com', // for bootstrap css
|
||||
'https://fonts.googleapis.com', // for google fonts
|
||||
'https://code.getmdl.io', // for mdl css
|
||||
staticSrc, // for any static files loaded from a cdn
|
||||
nonceSrc,
|
||||
],
|
||||
connectSrc: ["'self'", websocketSrc],
|
||||
fontSrc: [
|
||||
"'self'",
|
||||
'https://maxcdn.bootstrapcdn.com', // for font-awesome
|
||||
'https://fonts.gstatic.com', // for google fonts
|
||||
staticSrc, // for any static files loaded from a cdn
|
||||
nonceSrc,
|
||||
],
|
||||
imgSrc: [
|
||||
"'self'",
|
||||
staticSrc, // for any static files loaded from a cdn
|
||||
nonceSrc,
|
||||
],
|
||||
},
|
||||
browserSniff: false,
|
||||
});
|
||||
@@ -0,0 +1,9 @@
|
||||
const uuid = require('uuid/v4');
|
||||
|
||||
// nonce is designed to create a random value that can be used in conjunction
|
||||
// with the csp middleware.
|
||||
module.exports = (req, res, next) => {
|
||||
res.locals.nonce = uuid();
|
||||
|
||||
next();
|
||||
};
|
||||
@@ -2,10 +2,17 @@
|
||||
|
||||
{% block title %}{{ t('talk-plugin-notifications.unsubscribe_page.unsubscribe') }}{% endblock %}
|
||||
|
||||
{% block css %}
|
||||
{{ super() }}
|
||||
<style nonce="{{ nonce }}" type="text/css">
|
||||
#success { display:none; }
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block html %}
|
||||
<div id="root">
|
||||
<div class="error-console container">{{ t('talk-plugin-notifications.unsubscribe_page.token_invalid') }}</div>
|
||||
<div id="success" style="display:none;" class="legend container">{{ t('talk-plugin-notifications.unsubscribe_page.are_unsubscribed') }}</div>
|
||||
<div id="success" class="legend container">{{ t('talk-plugin-notifications.unsubscribe_page.are_unsubscribed') }}</div>
|
||||
<form id="unsubscribe-form" class="container">
|
||||
<legend class="legend">{{ t('talk-plugin-notifications.unsubscribe_page.click_to_confirm') }}</legend>
|
||||
<button type="submit">{{ t('talk-plugin-notifications.unsubscribe_page.confirm') }}</button>
|
||||
@@ -15,7 +22,7 @@
|
||||
|
||||
{% block js %}
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
|
||||
<script type="text/javascript">
|
||||
<script nonce="{{ nonce }}" type="text/javascript">
|
||||
$(function() {
|
||||
var submitting = false;
|
||||
var payload = JSON.stringify({token: location.hash.replace('#', '')});
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
|
||||
{% block js %}
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
|
||||
<script type="text/javascript">
|
||||
<script nonce="{{ nonce }}" type="text/javascript">
|
||||
$(function() {
|
||||
function showError(error) {
|
||||
try {
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
const express = require('express');
|
||||
const Joi = require('joi');
|
||||
const { logger } = require('../../../services/logging');
|
||||
const router = express.Router();
|
||||
|
||||
const schema = Joi.object().keys({
|
||||
'csp-report': Joi.object().keys({
|
||||
'document-uri': Joi.string(),
|
||||
referrer: Joi.string(),
|
||||
'blocked-uri': Joi.string(),
|
||||
'violated-directive': Joi.string(),
|
||||
'original-policy': Joi.string(),
|
||||
}),
|
||||
});
|
||||
|
||||
const json = express.json({ type: 'application/csp-report' });
|
||||
|
||||
router.post('/', json, async (req, res, next) => {
|
||||
const { value, error: err } = Joi.validate(req.body, schema, {
|
||||
stripUnknown: true,
|
||||
presence: 'required',
|
||||
});
|
||||
if (err) {
|
||||
res.status(400).end();
|
||||
return;
|
||||
}
|
||||
|
||||
logger.error({ report: value }, 'csp violation reported');
|
||||
|
||||
res.status(202).end();
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
@@ -9,6 +9,7 @@ router.get('/', (req, res) => {
|
||||
});
|
||||
|
||||
router.use('/account', require('./account'));
|
||||
router.use('/csp', require('./csp'));
|
||||
router.use('/assets', require('./assets'));
|
||||
router.use('/auth', require('./auth'));
|
||||
router.use('/graph', require('./graph'));
|
||||
|
||||
+10
-6
@@ -9,7 +9,9 @@ const path = require('path');
|
||||
const compression = require('compression');
|
||||
const plugins = require('../services/plugins');
|
||||
const staticTemplate = require('../middleware/staticTemplate');
|
||||
const staticMiddleware = require('express-static-gzip');
|
||||
const contentSecurityPolicy = require('../middleware/contentSecurityPolicy');
|
||||
const nonce = require('../middleware/nonce');
|
||||
const staticServer = require('express-static-gzip');
|
||||
const { DISABLE_STATIC_SERVER } = require('../config');
|
||||
const { passport } = require('../services/passport');
|
||||
const { MOUNT_PATH } = require('../url');
|
||||
@@ -48,7 +50,7 @@ if (!DISABLE_STATIC_SERVER) {
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
router.use(
|
||||
'/static',
|
||||
staticMiddleware(dist, {
|
||||
staticServer(dist, {
|
||||
indexFromEmptyFile: false,
|
||||
enableBrotli: true,
|
||||
customCompressions: [
|
||||
@@ -74,10 +76,12 @@ router.use(compression());
|
||||
// STATIC ROUTES
|
||||
//==============================================================================
|
||||
|
||||
router.use('/admin', staticTemplate, require('./admin'));
|
||||
router.use('/account', staticTemplate, require('./account'));
|
||||
router.use('/login', staticTemplate, require('./login'));
|
||||
router.use('/embed', staticTemplate, require('./embed'));
|
||||
const staticMiddleware = [staticTemplate, nonce, contentSecurityPolicy];
|
||||
|
||||
router.use('/admin', ...staticMiddleware, require('./admin'));
|
||||
router.use('/account', ...staticMiddleware, require('./account'));
|
||||
router.use('/login', ...staticMiddleware, require('./login'));
|
||||
router.use('/embed', ...staticMiddleware, require('./embed'));
|
||||
|
||||
//==============================================================================
|
||||
// PASSPORT MIDDLEWARE
|
||||
|
||||
+4
-6
@@ -2,15 +2,13 @@ const express = require('express');
|
||||
const debug = require('debug')('talk:routes:plugins');
|
||||
const plugins = require('../services/plugins');
|
||||
const staticTemplate = require('../middleware/staticTemplate');
|
||||
const contentSecurityPolicy = require('../middleware/contentSecurityPolicy');
|
||||
const nonce = require('../middleware/nonce');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
// Routes mounted from plugins won't have access to our internal partials
|
||||
// directory, so we should make that available.
|
||||
router.use(staticTemplate, (req, res, next) => {
|
||||
res.locals.root = res.app.get('views');
|
||||
next();
|
||||
});
|
||||
// Apply the middleware.
|
||||
router.use(staticTemplate, nonce, contentSecurityPolicy);
|
||||
|
||||
// Inject server route plugins.
|
||||
plugins.get('server', 'router').forEach(plugin => {
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
|
||||
{% block js %}
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
|
||||
<script type="text/javascript">
|
||||
<script nonce="{{ nonce }}" type="text/javascript">
|
||||
$(function() {
|
||||
function showError(error) {
|
||||
try {
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
|
||||
{% block js %}
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
|
||||
<script>
|
||||
<script nonce="{{ nonce }}" type="text/javascript">
|
||||
$(function() {
|
||||
function showError(error) {
|
||||
try {
|
||||
|
||||
+1
-1
@@ -9,6 +9,6 @@
|
||||
{% endblock %}
|
||||
|
||||
{% block js %}
|
||||
<script src='https://www.google.com/recaptcha/api.js?render=explicit' async defer></script>
|
||||
<script nonce="{{ nonce }}" src='https://www.google.com/recaptcha/api.js?render=explicit' async defer></script>
|
||||
<script src="{{ resolve('coral-admin/bundle.js') }}"></script>
|
||||
{% endblock %}
|
||||
|
||||
+1
-1
@@ -11,6 +11,6 @@
|
||||
{% endblock %}
|
||||
|
||||
{% block js %}
|
||||
<script src='https://www.google.com/recaptcha/api.js?render=explicit' async defer></script>
|
||||
<script nonce="{{ nonce }}" src='https://www.google.com/recaptcha/api.js?render=explicit' async defer></script>
|
||||
<script src="{{ resolve('coral-login/bundle.js')}}"></script>
|
||||
{% endblock %}
|
||||
|
||||
Reference in New Issue
Block a user