summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/config/default.js3
-rw-r--r--lib/config/defaultSSL.js8
-rw-r--r--lib/config/environment.js2
-rw-r--r--lib/config/index.js15
-rw-r--r--lib/csp.js14
-rw-r--r--lib/letter-avatars.js17
-rw-r--r--lib/models/note.js9
-rw-r--r--lib/models/user.js10
-rw-r--r--lib/realtime.js2
-rw-r--r--lib/response.js7
-rw-r--r--lib/web/auth/saml/index.js4
-rw-r--r--lib/web/userRouter.js7
12 files changed, 73 insertions, 25 deletions
diff --git a/lib/config/default.js b/lib/config/default.js
index 19ddccf6..68849d36 100644
--- a/lib/config/default.js
+++ b/lib/config/default.js
@@ -18,6 +18,8 @@ module.exports = {
directives: {
},
addDefaults: true,
+ addDisqus: true,
+ addGoogleAnalytics: true,
upgradeInsecureRequests: 'auto',
reportURI: undefined
},
@@ -46,6 +48,7 @@ module.exports = {
// session
sessionName: 'connect.sid',
sessionSecret: 'secret',
+ sessionSecretLen: 128,
sessionLife: 14 * 24 * 60 * 60 * 1000, // 14 days
staticCacheTime: 1 * 24 * 60 * 60 * 1000, // 1 day
// socket.io
diff --git a/lib/config/defaultSSL.js b/lib/config/defaultSSL.js
index 362c62a1..ba020466 100644
--- a/lib/config/defaultSSL.js
+++ b/lib/config/defaultSSL.js
@@ -10,8 +10,8 @@ function getFile (path) {
}
module.exports = {
- sslkeypath: getFile('/run/secrets/key.pem'),
- sslcertpath: getFile('/run/secrets/cert.pem'),
- sslcapath: getFile('/run/secrets/ca.pem') !== undefined ? [getFile('/run/secrets/ca.pem')] : [],
- dhparampath: getFile('/run/secrets/dhparam.pem')
+ sslKeyPath: getFile('/run/secrets/key.pem'),
+ sslCertPath: getFile('/run/secrets/cert.pem'),
+ sslCAPath: getFile('/run/secrets/ca.pem') !== undefined ? [getFile('/run/secrets/ca.pem')] : [],
+ dhParamPath: getFile('/run/secrets/dhparam.pem')
}
diff --git a/lib/config/environment.js b/lib/config/environment.js
index cab3bc3e..3dde4786 100644
--- a/lib/config/environment.js
+++ b/lib/config/environment.js
@@ -26,6 +26,8 @@ module.exports = {
allowFreeURL: toBooleanConfig(process.env.HMD_ALLOW_FREEURL),
defaultPermission: process.env.HMD_DEFAULT_PERMISSION,
dbURL: process.env.HMD_DB_URL,
+ sessionSecret: process.env.HMD_SESSION_SECRET,
+ sessionLife: toIntegerConfig(process.env.HMD_SESSION_LIFE),
imageUploadType: process.env.HMD_IMAGE_UPLOAD_TYPE,
imgur: {
clientID: process.env.HMD_IMGUR_CLIENTID
diff --git a/lib/config/index.js b/lib/config/index.js
index fae51e52..bdba5e0e 100644
--- a/lib/config/index.js
+++ b/lib/config/index.js
@@ -1,6 +1,7 @@
'use strict'
+const crypto = require('crypto')
const fs = require('fs')
const path = require('path')
const {merge} = require('lodash')
@@ -52,7 +53,7 @@ if (config.ldap.tlsca) {
// Permission
config.permission = Permission
-if (!config.allowAnonymous && !config.allowAnonymousedits) {
+if (!config.allowAnonymous && !config.allowAnonymousEdits) {
delete config.permission.freely
}
if (!(config.defaultPermission in config.permission)) {
@@ -110,13 +111,21 @@ for (let i = keys.length; i--;) {
// and the config with uppercase is not set
// we set the new config using the old key.
if (uppercase.test(keys[i]) &&
- config[lowercaseKey] &&
- !config[keys[1]]) {
+ config[lowercaseKey] !== undefined &&
+ fileConfig[keys[i]] === undefined) {
logger.warn('config.js contains deprecated lowercase setting for ' + keys[i] + '. Please change your config.js file to replace ' + lowercaseKey + ' with ' + keys[i])
config[keys[i]] = config[lowercaseKey]
}
}
+// Generate session secret if it stays on default values
+if (config.sessionSecret === 'secret') {
+ logger.warn('Session secret not set. Using random generated one. Please set `sessionSecret` in your config.js file. All users will be logged out.')
+ config.sessionSecret = crypto.randomBytes(Math.ceil(config.sessionSecretLen / 2)) // generate crypto graphic random number
+ .toString('hex') // convert to hexadecimal format
+ .slice(0, config.sessionSecretLen) // return required number of characters
+}
+
// Validate upload upload providers
if (['filesystem', 's3', 'minio', 'imgur'].indexOf(config.imageUploadType) === -1) {
logger.error('"imageuploadtype" is not correctly set. Please use "filesystem", "s3", "minio" or "imgur". Defaulting to "imgur"')
diff --git a/lib/csp.js b/lib/csp.js
index 8a4aa088..d0f906a3 100644
--- a/lib/csp.js
+++ b/lib/csp.js
@@ -5,7 +5,7 @@ var CspStrategy = {}
var defaultDirectives = {
defaultSrc: ['\'self\''],
- scriptSrc: ['\'self\'', 'vimeo.com', 'https://gist.github.com', 'www.slideshare.net', 'https://query.yahooapis.com', 'https://*.disqus.com', '\'unsafe-eval\''],
+ scriptSrc: ['\'self\'', 'vimeo.com', 'https://gist.github.com', 'www.slideshare.net', 'https://query.yahooapis.com', '\'unsafe-eval\''],
// ^ TODO: Remove unsafe-eval - webpack script-loader issues https://github.com/hackmdio/hackmd/issues/594
imgSrc: ['*'],
styleSrc: ['\'self\'', '\'unsafe-inline\'', 'https://assets-cdn.github.com'], // unsafe-inline is required for some libs, plus used in views
@@ -22,11 +22,23 @@ var cdnDirectives = {
fontSrc: ['https://cdnjs.cloudflare.com', 'https://fonts.gstatic.com']
}
+var disqusDirectives = {
+ scriptSrc: ['https://*.disqus.com', 'https://*.disquscdn.com'],
+ styleSrc: ['https://*.disquscdn.com'],
+ fontSrc: ['https://*.disquscdn.com']
+}
+
+var googleAnalyticsDirectives = {
+ scriptSrc: ['https://www.google-analytics.com']
+}
+
CspStrategy.computeDirectives = function () {
var directives = {}
mergeDirectives(directives, config.csp.directives)
mergeDirectivesIf(config.csp.addDefaults, directives, defaultDirectives)
mergeDirectivesIf(config.useCDN, directives, cdnDirectives)
+ mergeDirectivesIf(config.csp.addDisqus, directives, disqusDirectives)
+ mergeDirectivesIf(config.csp.addGoogleAnalytics, directives, googleAnalyticsDirectives)
if (!areAllInlineScriptsAllowed(directives)) {
addInlineScriptExceptions(directives)
}
diff --git a/lib/letter-avatars.js b/lib/letter-avatars.js
index 7ba336b6..b5b1d9e7 100644
--- a/lib/letter-avatars.js
+++ b/lib/letter-avatars.js
@@ -1,16 +1,17 @@
'use strict'
// external modules
-var randomcolor = require('randomcolor')
+const randomcolor = require('randomcolor')
+const config = require('./config')
// core
-module.exports = function (name) {
- var color = randomcolor({
+exports.generateAvatar = function (name) {
+ const color = randomcolor({
seed: name,
luminosity: 'dark'
})
- var letter = name.substring(0, 1).toUpperCase()
+ const letter = name.substring(0, 1).toUpperCase()
- var svg = '<?xml version="1.0" encoding="UTF-8" standalone="no"?>'
+ let svg = '<?xml version="1.0" encoding="UTF-8" standalone="no"?>'
svg += '<svg xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://www.w3.org/2000/svg" height="96" width="96" version="1.1" viewBox="0 0 96 96">'
svg += '<g>'
svg += '<rect width="96" height="96" fill="' + color + '" />'
@@ -20,5 +21,9 @@ module.exports = function (name) {
svg += '</g>'
svg += '</svg>'
- return 'data:image/svg+xml;base64,' + new Buffer(svg).toString('base64')
+ return svg
+}
+
+exports.generateAvatarURL = function (name) {
+ return config.serverURL + '/user/' + name + '/avatar.svg'
}
diff --git a/lib/models/note.js b/lib/models/note.js
index 69393dd4..2a048e37 100644
--- a/lib/models/note.js
+++ b/lib/models/note.js
@@ -211,6 +211,15 @@ module.exports = function (sequelize, DataTypes) {
},
// parse note id by LZString is deprecated, here for compability
parseNoteIdByLZString: function (_callback) {
+ // Calculate minimal string length for an UUID that is encoded
+ // base64 encoded and optimize comparsion by using -1
+ // this should make a lot of LZ-String parsing errors obsolete
+ // as we can assume that a nodeId that is 48 chars or longer is a
+ // noteID.
+ const base64UuidLength = ((4 * 36) / 3) - 1
+ if (!(noteId.length > base64UuidLength)) {
+ return _callback(null, null)
+ }
// try to parse note id by LZString Base64
try {
var id = LZString.decompressFromBase64(noteId)
diff --git a/lib/models/user.js b/lib/models/user.js
index f421fe43..4c823355 100644
--- a/lib/models/user.js
+++ b/lib/models/user.js
@@ -6,7 +6,7 @@ var scrypt = require('scrypt')
// core
var logger = require('../logger')
-var letterAvatars = require('../letter-avatars')
+var {generateAvatarURL} = require('../letter-avatars')
module.exports = function (sequelize, DataTypes) {
var User = sequelize.define('User', {
@@ -108,7 +108,7 @@ module.exports = function (sequelize, DataTypes) {
if (bigger) photo = photo.replace(/(\?s=)\d*$/i, '$1400')
else photo = photo.replace(/(\?s=)\d*$/i, '$196')
} else {
- photo = letterAvatars(profile.username)
+ photo = generateAvatarURL(profile.username)
}
break
case 'mattermost':
@@ -117,7 +117,7 @@ module.exports = function (sequelize, DataTypes) {
if (bigger) photo = photo.replace(/(\?s=)\d*$/i, '$1400')
else photo = photo.replace(/(\?s=)\d*$/i, '$196')
} else {
- photo = letterAvatars(profile.username)
+ photo = generateAvatarURL(profile.username)
}
break
case 'dropbox':
@@ -140,7 +140,7 @@ module.exports = function (sequelize, DataTypes) {
if (bigger) photo += '?s=400'
else photo += '?s=96'
} else {
- photo = letterAvatars(profile.username)
+ photo = generateAvatarURL(profile.username)
}
break
case 'saml':
@@ -149,7 +149,7 @@ module.exports = function (sequelize, DataTypes) {
if (bigger) photo += '?s=400'
else photo += '?s=96'
} else {
- photo = letterAvatars(profile.username)
+ photo = generateAvatarURL(profile.username)
}
break
}
diff --git a/lib/realtime.js b/lib/realtime.js
index d8b0b4c5..070bde2d 100644
--- a/lib/realtime.js
+++ b/lib/realtime.js
@@ -788,7 +788,7 @@ function connection (socket) {
var note = notes[noteId]
// Only owner can change permission
if (note.owner && note.owner === socket.request.user.id) {
- if (permission === 'freely' && !config.allowAnonymous && !config.allowAnonymousedits) return
+ if (permission === 'freely' && !config.allowAnonymous && !config.allowAnonymousEdits) return
note.permission = permission
models.Note.update({
permission: permission
diff --git a/lib/response.js b/lib/response.js
index aeb594a3..d6fb3b42 100644
--- a/lib/response.js
+++ b/lib/response.js
@@ -65,7 +65,7 @@ function showIndex (req, res, next) {
url: config.serverURL,
useCDN: config.useCDN,
allowAnonymous: config.allowAnonymous,
- allowAnonymousEdits: config.allowAnonymousedits,
+ allowAnonymousEdits: config.allowAnonymousEdits,
facebook: config.isFacebookEnable,
twitter: config.isTwitterEnable,
github: config.isGitHubEnable,
@@ -100,7 +100,7 @@ function responseHackMD (res, note) {
title: title,
useCDN: config.useCDN,
allowAnonymous: config.allowAnonymous,
- allowAnonymousEdits: config.allowAnonymousedits,
+ allowAnonymousEdits: config.allowAnonymousEdits,
facebook: config.isFacebookEnable,
twitter: config.isTwitterEnable,
github: config.isGitHubEnable,
@@ -232,7 +232,8 @@ function showPublishNote (req, res, next) {
lastchangeuserprofile: note.lastchangeuser ? models.User.getProfile(note.lastchangeuser) : null,
robots: meta.robots || false, // default allow robots
GA: meta.GA,
- disqus: meta.disqus
+ disqus: meta.disqus,
+ cspNonce: res.locals.nonce
}
return renderPublish(data, res)
}).catch(function (err) {
diff --git a/lib/web/auth/saml/index.js b/lib/web/auth/saml/index.js
index 3ecbc6f3..b8d98340 100644
--- a/lib/web/auth/saml/index.js
+++ b/lib/web/auth/saml/index.js
@@ -20,14 +20,14 @@ passport.use(new SamlStrategy({
identifierFormat: config.saml.identifierFormat
}, function (user, done) {
// check authorization if needed
- if (config.saml.externalGroups && config.saml.grouptAttribute) {
+ if (config.saml.externalGroups && config.saml.groupAttribute) {
var externalGroups = intersection(config.saml.externalGroups, user[config.saml.groupAttribute])
if (externalGroups.length > 0) {
logger.error('saml permission denied: ' + externalGroups.join(', '))
return done('Permission denied', null)
}
}
- if (config.saml.requiredGroups && config.saml.grouptAttribute) {
+ if (config.saml.requiredGroups && config.saml.groupAttribute) {
if (intersection(config.saml.requiredGroups, user[config.saml.groupAttribute]).length === 0) {
logger.error('saml permission denied')
return done('Permission denied', null)
diff --git a/lib/web/userRouter.js b/lib/web/userRouter.js
index ecfbaf8b..963961c7 100644
--- a/lib/web/userRouter.js
+++ b/lib/web/userRouter.js
@@ -5,6 +5,7 @@ const Router = require('express').Router
const response = require('../response')
const models = require('../models')
const logger = require('../logger')
+const {generateAvatar} = require('../letter-avatars')
const UserRouter = module.exports = Router()
@@ -34,3 +35,9 @@ UserRouter.get('/me', function (req, res) {
})
}
})
+
+UserRouter.get('/user/:username/avatar.svg', function (req, res, next) {
+ res.setHeader('Content-Type', 'image/svg+xml')
+ res.setHeader('Cache-Control', 'public, max-age=86400')
+ res.send(generateAvatar(req.params.username))
+})