summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorMax Wu2017-05-14 17:50:25 +0800
committerGitHub2017-05-14 17:50:25 +0800
commitea9b1557f9834b2b140bd9b07488b5fca3953791 (patch)
tree8e96eb37c1715f860a698d5e7eeb2381b00af9d8 /lib
parentf85d1d880114b6787fdb2bac488341776deaad3d (diff)
parentc37b6669158750ab2222e81a5a8fc3a5c8c353f5 (diff)
Merge pull request #421 from hackmdio/BackendRefactor
Backend code refactoring - part 1
Diffstat (limited to 'lib')
-rw-r--r--lib/auth.js194
-rw-r--r--lib/config.js223
-rw-r--r--lib/config/default.js92
-rw-r--r--lib/config/defaultSSL.js17
-rw-r--r--lib/config/dockerSecret.js51
-rw-r--r--lib/config/enum.js16
-rw-r--r--lib/config/environment.js67
-rw-r--r--lib/config/index.js112
-rw-r--r--lib/config/oldEnvironment.js10
-rw-r--r--lib/config/utils.js8
-rw-r--r--lib/history.js6
-rw-r--r--lib/logger.js22
-rw-r--r--lib/models/index.js9
-rw-r--r--lib/models/note.js6
-rw-r--r--lib/models/revision.js4
-rw-r--r--lib/models/user.js4
-rw-r--r--lib/realtime.js13
-rwxr-xr-xlib/response.js36
-rw-r--r--lib/web/auth/dropbox/index.js29
-rw-r--r--lib/web/auth/email/index.js74
-rw-r--r--lib/web/auth/facebook/index.js29
-rw-r--r--lib/web/auth/github/index.js28
-rw-r--r--lib/web/auth/gitlab/index.js36
-rw-r--r--lib/web/auth/google/index.js27
-rw-r--r--lib/web/auth/index.js48
-rw-r--r--lib/web/auth/ldap/index.js74
-rw-r--r--lib/web/auth/twitter/index.js29
-rw-r--r--lib/web/auth/utils.js53
-rw-r--r--lib/web/baseRouter.js22
-rw-r--r--lib/web/historyRouter.js18
-rw-r--r--lib/web/imageRouter.js95
-rw-r--r--lib/web/middleware/checkURIValid.js14
-rw-r--r--lib/web/middleware/redirectWithoutTrailingSlashes.js17
-rw-r--r--lib/web/middleware/tooBusy.js13
-rw-r--r--lib/web/noteRouter.js24
-rw-r--r--lib/web/statusRouter.js92
-rw-r--r--lib/web/userRouter.js36
-rw-r--r--lib/web/utils.js9
-rw-r--r--lib/workers/dmpWorker.js4
39 files changed, 1193 insertions, 468 deletions
diff --git a/lib/auth.js b/lib/auth.js
deleted file mode 100644
index 3e129b95..00000000
--- a/lib/auth.js
+++ /dev/null
@@ -1,194 +0,0 @@
-'use strict'
-// auth
-// external modules
-var passport = require('passport')
-var FacebookStrategy = require('passport-facebook').Strategy
-var TwitterStrategy = require('passport-twitter').Strategy
-var GithubStrategy = require('passport-github').Strategy
-var GitlabStrategy = require('passport-gitlab2').Strategy
-var DropboxStrategy = require('passport-dropbox-oauth2').Strategy
-var GoogleStrategy = require('passport-google-oauth20').Strategy
-var LdapStrategy = require('passport-ldapauth')
-var LocalStrategy = require('passport-local').Strategy
-var validator = require('validator')
-
-// core
-var config = require('./config.js')
-var logger = require('./logger.js')
-var models = require('./models')
-
-function callback (accessToken, refreshToken, profile, done) {
- // logger.info(profile.displayName || profile.username);
- var stringifiedProfile = JSON.stringify(profile)
- models.User.findOrCreate({
- where: {
- profileid: profile.id.toString()
- },
- defaults: {
- profile: stringifiedProfile,
- accessToken: accessToken,
- refreshToken: refreshToken
- }
- }).spread(function (user, created) {
- if (user) {
- var needSave = false
- if (user.profile !== stringifiedProfile) {
- user.profile = stringifiedProfile
- needSave = true
- }
- if (user.accessToken !== accessToken) {
- user.accessToken = accessToken
- needSave = true
- }
- if (user.refreshToken !== refreshToken) {
- user.refreshToken = refreshToken
- needSave = true
- }
- if (needSave) {
- user.save().then(function () {
- if (config.debug) { logger.info('user login: ' + user.id) }
- return done(null, user)
- })
- } else {
- if (config.debug) { logger.info('user login: ' + user.id) }
- return done(null, user)
- }
- }
- }).catch(function (err) {
- logger.error('auth callback failed: ' + err)
- return done(err, null)
- })
-}
-
-function registerAuthMethod () {
-// facebook
- if (config.facebook) {
- passport.use(new FacebookStrategy({
- clientID: config.facebook.clientID,
- clientSecret: config.facebook.clientSecret,
- callbackURL: config.serverurl + '/auth/facebook/callback'
- }, callback))
- }
-// twitter
- if (config.twitter) {
- passport.use(new TwitterStrategy({
- consumerKey: config.twitter.consumerKey,
- consumerSecret: config.twitter.consumerSecret,
- callbackURL: config.serverurl + '/auth/twitter/callback'
- }, callback))
- }
-// github
- if (config.github) {
- passport.use(new GithubStrategy({
- clientID: config.github.clientID,
- clientSecret: config.github.clientSecret,
- callbackURL: config.serverurl + '/auth/github/callback'
- }, callback))
- }
-// gitlab
- if (config.gitlab) {
- passport.use(new GitlabStrategy({
- baseURL: config.gitlab.baseURL,
- clientID: config.gitlab.clientID,
- clientSecret: config.gitlab.clientSecret,
- scope: config.gitlab.scope,
- callbackURL: config.serverurl + '/auth/gitlab/callback'
- }, callback))
- }
-// dropbox
- if (config.dropbox) {
- passport.use(new DropboxStrategy({
- apiVersion: '2',
- clientID: config.dropbox.clientID,
- clientSecret: config.dropbox.clientSecret,
- callbackURL: config.serverurl + '/auth/dropbox/callback'
- }, callback))
- }
-// google
- if (config.google) {
- passport.use(new GoogleStrategy({
- clientID: config.google.clientID,
- clientSecret: config.google.clientSecret,
- callbackURL: config.serverurl + '/auth/google/callback'
- }, callback))
- }
-// ldap
- if (config.ldap) {
- passport.use(new LdapStrategy({
- server: {
- url: config.ldap.url || null,
- bindDn: config.ldap.bindDn || null,
- bindCredentials: config.ldap.bindCredentials || null,
- searchBase: config.ldap.searchBase || null,
- searchFilter: config.ldap.searchFilter || null,
- searchAttributes: config.ldap.searchAttributes || null,
- tlsOptions: config.ldap.tlsOptions || null
- }
- },
- function (user, done) {
- var profile = {
- id: 'LDAP-' + user.uidNumber,
- username: user.uid,
- displayName: user.displayName,
- emails: user.mail ? [user.mail] : [],
- avatarUrl: null,
- profileUrl: null,
- provider: 'ldap'
- }
- var stringifiedProfile = JSON.stringify(profile)
- models.User.findOrCreate({
- where: {
- profileid: profile.id.toString()
- },
- defaults: {
- profile: stringifiedProfile
- }
- }).spread(function (user, created) {
- if (user) {
- var needSave = false
- if (user.profile !== stringifiedProfile) {
- user.profile = stringifiedProfile
- needSave = true
- }
- if (needSave) {
- user.save().then(function () {
- if (config.debug) { logger.info('user login: ' + user.id) }
- return done(null, user)
- })
- } else {
- if (config.debug) { logger.info('user login: ' + user.id) }
- return done(null, user)
- }
- }
- }).catch(function (err) {
- logger.error('ldap auth failed: ' + err)
- return done(err, null)
- })
- }))
- }
-// email
- if (config.email) {
- passport.use(new LocalStrategy({
- usernameField: 'email'
- },
- function (email, password, done) {
- if (!validator.isEmail(email)) return done(null, false)
- models.User.findOne({
- where: {
- email: email
- }
- }).then(function (user) {
- if (!user) return done(null, false)
- if (!user.verifyPassword(password)) return done(null, false)
- return done(null, user)
- }).catch(function (err) {
- logger.error(err)
- return done(err)
- })
- }))
- }
-}
-
-module.exports = {
- registerAuthMethod: registerAuthMethod
-}
diff --git a/lib/config.js b/lib/config.js
deleted file mode 100644
index 31999b07..00000000
--- a/lib/config.js
+++ /dev/null
@@ -1,223 +0,0 @@
-'use strict'
-// external modules
-var fs = require('fs')
-var path = require('path')
-
-// configs
-var env = process.env.NODE_ENV || 'development'
-var config = require(path.join(__dirname, '..', 'config.json'))[env]
-var debug = process.env.DEBUG ? (process.env.DEBUG === 'true') : ((typeof config.debug === 'boolean') ? config.debug : (env === 'development'))
-
-// Create function that reads docker secrets but fails fast in case of a non docker environment
-var handleDockerSecret = fs.existsSync('/run/secrets/') ? function (secret) {
- return fs.existsSync('/run/secrets/' + secret) ? fs.readFileSync('/run/secrets/' + secret) : null
-} : function () {
- return null
-}
-
-// url
-var domain = process.env.DOMAIN || process.env.HMD_DOMAIN || config.domain || ''
-var urlpath = process.env.URL_PATH || process.env.HMD_URL_PATH || config.urlpath || ''
-var port = process.env.PORT || process.env.HMD_PORT || config.port || 3000
-var alloworigin = process.env.HMD_ALLOW_ORIGIN ? process.env.HMD_ALLOW_ORIGIN.split(',') : (config.alloworigin || ['localhost'])
-
-var usessl = !!config.usessl
-var protocolusessl = (usessl === true && typeof process.env.HMD_PROTOCOL_USESSL === 'undefined' && typeof config.protocolusessl === 'undefined')
- ? true : (process.env.HMD_PROTOCOL_USESSL ? (process.env.HMD_PROTOCOL_USESSL === 'true') : !!config.protocolusessl)
-var urladdport = process.env.HMD_URL_ADDPORT ? (process.env.HMD_URL_ADDPORT === 'true') : !!config.urladdport
-
-var usecdn = process.env.HMD_USECDN ? (process.env.HMD_USECDN === 'true') : ((typeof config.usecdn === 'boolean') ? config.usecdn : true)
-
-var allowanonymous = process.env.HMD_ALLOW_ANONYMOUS ? (process.env.HMD_ALLOW_ANONYMOUS === 'true') : ((typeof config.allowanonymous === 'boolean') ? config.allowanonymous : true)
-
-var allowfreeurl = process.env.HMD_ALLOW_FREEURL ? (process.env.HMD_ALLOW_FREEURL === 'true') : !!config.allowfreeurl
-
-var permissions = ['editable', 'limited', 'locked', 'protected', 'private']
-if (allowanonymous) {
- permissions.unshift('freely')
-}
-
-var defaultpermission = process.env.HMD_DEFAULT_PERMISSION || config.defaultpermission
-defaultpermission = permissions.indexOf(defaultpermission) !== -1 ? defaultpermission : 'editable'
-
-// db
-var dburl = process.env.HMD_DB_URL || process.env.DATABASE_URL || config.dburl
-var db = config.db || {}
-
-// ssl path
-var sslkeypath = (fs.existsSync('/run/secrets/key.pem') ? '/run/secrets/key.pem' : null) || config.sslkeypath || ''
-var sslcertpath = (fs.existsSync('/run/secrets/cert.pem') ? '/run/secrets/cert.pem' : null) || config.sslcertpath || ''
-var sslcapath = (fs.existsSync('/run/secrets/ca.pem') ? '/run/secrets/ca.pem' : null) || config.sslcapath || ''
-var dhparampath = (fs.existsSync('/run/secrets/dhparam.pem') ? '/run/secrets/dhparam.pem' : null) || config.dhparampath || ''
-
-// other path
-var tmppath = config.tmppath || './tmp'
-var defaultnotepath = config.defaultnotepath || './public/default.md'
-var docspath = config.docspath || './public/docs'
-var indexpath = config.indexpath || './public/views/index.ejs'
-var hackmdpath = config.hackmdpath || './public/views/hackmd.ejs'
-var errorpath = config.errorpath || './public/views/error.ejs'
-var prettypath = config.prettypath || './public/views/pretty.ejs'
-var slidepath = config.slidepath || './public/views/slide.ejs'
-
-// session
-var sessionname = config.sessionname || 'connect.sid'
-var sessionsecret = handleDockerSecret('sessionsecret') || config.sessionsecret || 'secret'
-var sessionlife = config.sessionlife || 14 * 24 * 60 * 60 * 1000 // 14 days
-
-// static files
-var staticcachetime = config.staticcachetime || 1 * 24 * 60 * 60 * 1000 // 1 day
-
-// socket.io
-var heartbeatinterval = config.heartbeatinterval || 5000
-var heartbeattimeout = config.heartbeattimeout || 10000
-
-// document
-var documentmaxlength = config.documentmaxlength || 100000
-
-// image upload setting, available options are imgur/s3/filesystem
-var imageUploadType = process.env.HMD_IMAGE_UPLOAD_TYPE || config.imageUploadType || 'imgur'
-
-config.s3 = config.s3 || {}
-var s3 = {
- accessKeyId: handleDockerSecret('s3_acccessKeyId') || process.env.HMD_S3_ACCESS_KEY_ID || config.s3.accessKeyId,
- secretAccessKey: handleDockerSecret('s3_secretAccessKey') || process.env.HMD_S3_SECRET_ACCESS_KEY || config.s3.secretAccessKey,
- region: process.env.HMD_S3_REGION || config.s3.region
-}
-var s3bucket = process.env.HMD_S3_BUCKET || config.s3.bucket
-
-// auth
-var facebook = ((process.env.HMD_FACEBOOK_CLIENTID && process.env.HMD_FACEBOOK_CLIENTSECRET) || (fs.existsSync('/run/secrets/facebook_clientID') && fs.existsSync('/run/secrets/facebook_clientSecret'))) ? {
- clientID: handleDockerSecret('facebook_clientID') || process.env.HMD_FACEBOOK_CLIENTID,
- clientSecret: handleDockerSecret('facebook_clientSecret') || process.env.HMD_FACEBOOK_CLIENTSECRET
-} : config.facebook || false
-var twitter = ((process.env.HMD_TWITTER_CONSUMERKEY && process.env.HMD_TWITTER_CONSUMERSECRET) || (fs.existsSync('/run/secrets/twitter_consumerKey') && fs.existsSync('/run/secrets/twitter_consumerSecret'))) ? {
- consumerKey: handleDockerSecret('twitter_consumerKey') || process.env.HMD_TWITTER_CONSUMERKEY,
- consumerSecret: handleDockerSecret('twitter_consumerSecret') || process.env.HMD_TWITTER_CONSUMERSECRET
-} : config.twitter || false
-var github = ((process.env.HMD_GITHUB_CLIENTID && process.env.HMD_GITHUB_CLIENTSECRET) || (fs.existsSync('/run/secrets/github_clientID') && fs.existsSync('/run/secrets/github_clientSecret'))) ? {
- clientID: handleDockerSecret('github_clientID') || process.env.HMD_GITHUB_CLIENTID,
- clientSecret: handleDockerSecret('github_clientSecret') || process.env.HMD_GITHUB_CLIENTSECRET
-} : config.github || false
-var gitlab = ((process.env.HMD_GITLAB_CLIENTID && process.env.HMD_GITLAB_CLIENTSECRET) || (fs.existsSync('/run/secrets/gitlab_clientID') && fs.existsSync('/run/secrets/gitlab_clientSecret'))) ? {
- baseURL: process.env.HMD_GITLAB_BASEURL,
- clientID: handleDockerSecret('gitlab_clientID') || process.env.HMD_GITLAB_CLIENTID,
- clientSecret: handleDockerSecret('gitlab_clientSecret') || process.env.HMD_GITLAB_CLIENTSECRET,
- scope: process.env.HMD_GITLAB_SCOPE
-} : (config.gitlab && config.gitlab.clientID && config.gitlab.clientSecret && config.gitlab) || false
-var dropbox = ((process.env.HMD_DROPBOX_CLIENTID && process.env.HMD_DROPBOX_CLIENTSECRET) || (fs.existsSync('/run/secrets/dropbox_clientID') && fs.existsSync('/run/secrets/dropbox_clientSecret'))) ? {
- clientID: handleDockerSecret('dropbox_clientID') || process.env.HMD_DROPBOX_CLIENTID,
- clientSecret: handleDockerSecret('dropbox_clientSecret') || process.env.HMD_DROPBOX_CLIENTSECRET
-} : (config.dropbox && config.dropbox.clientID && config.dropbox.clientSecret && config.dropbox) || false
-var google = ((process.env.HMD_GOOGLE_CLIENTID && process.env.HMD_GOOGLE_CLIENTSECRET) ||
- (fs.existsSync('/run/secrets/google_clientID') && fs.existsSync('/run/secrets/google_clientSecret'))) ? {
- clientID: handleDockerSecret('google_clientID') || process.env.HMD_GOOGLE_CLIENTID,
- clientSecret: handleDockerSecret('google_clientSecret') || process.env.HMD_GOOGLE_CLIENTSECRET
- } : (config.google && config.google.clientID && config.google.clientSecret && config.google) || false
-var ldap = config.ldap || ((
- process.env.HMD_LDAP_URL ||
- process.env.HMD_LDAP_BINDDN ||
- process.env.HMD_LDAP_BINDCREDENTIALS ||
- process.env.HMD_LDAP_TOKENSECRET ||
- process.env.HMD_LDAP_SEARCHBASE ||
- process.env.HMD_LDAP_SEARCHFILTER ||
- process.env.HMD_LDAP_SEARCHATTRIBUTES ||
- process.env.HMD_LDAP_TLS_CA ||
- process.env.HMD_LDAP_PROVIDERNAME
-) ? {} : false)
-if (process.env.HMD_LDAP_URL) { ldap.url = process.env.HMD_LDAP_URL }
-if (process.env.HMD_LDAP_BINDDN) { ldap.bindDn = process.env.HMD_LDAP_BINDDN }
-if (process.env.HMD_LDAP_BINDCREDENTIALS) { ldap.bindCredentials = process.env.HMD_LDAP_BINDCREDENTIALS }
-if (process.env.HMD_LDAP_TOKENSECRET) { ldap.tokenSecret = process.env.HMD_LDAP_TOKENSECRET }
-if (process.env.HMD_LDAP_SEARCHBASE) { ldap.searchBase = process.env.HMD_LDAP_SEARCHBASE }
-if (process.env.HMD_LDAP_SEARCHFILTER) { ldap.searchFilter = process.env.HMD_LDAP_SEARCHFILTER }
-if (process.env.HMD_LDAP_SEARCHATTRIBUTES) { ldap.searchAttributes = process.env.HMD_LDAP_SEARCHATTRIBUTES }
-if (process.env.HMD_LDAP_TLS_CA) {
- var ca = {
- ca: process.env.HMD_LDAP_TLS_CA.split(',')
- }
- ldap.tlsOptions = ldap.tlsOptions ? Object.assign(ldap.tlsOptions, ca) : ca
- if (Array.isArray(ldap.tlsOptions.ca) && ldap.tlsOptions.ca.length > 0) {
- var i, len, results
- results = []
- for (i = 0, len = ldap.tlsOptions.ca.length; i < len; i++) {
- results.push(fs.readFileSync(ldap.tlsOptions.ca[i], 'utf8'))
- }
- ldap.tlsOptions.ca = results
- }
-}
-if (process.env.HMD_LDAP_PROVIDERNAME) {
- ldap.providerName = process.env.HMD_LDAP_PROVIDERNAME
-}
-var imgur = handleDockerSecret('imgur_clientid') || process.env.HMD_IMGUR_CLIENTID || config.imgur || false
-var email = process.env.HMD_EMAIL ? (process.env.HMD_EMAIL === 'true') : !!config.email
-var allowemailregister = process.env.HMD_ALLOW_EMAIL_REGISTER ? (process.env.HMD_ALLOW_EMAIL_REGISTER === 'true') : ((typeof config.allowemailregister === 'boolean') ? config.allowemailregister : true)
-
-function getserverurl () {
- var url = ''
- if (domain) {
- var protocol = protocolusessl ? 'https://' : 'http://'
- url = protocol + domain
- if (urladdport && ((usessl && port !== 443) || (!usessl && port !== 80))) { url += ':' + port }
- }
- if (urlpath) { url += '/' + urlpath }
- return url
-}
-
-var version = '0.5.1'
-var minimumCompatibleVersion = '0.5.0'
-var maintenance = true
-var cwd = path.join(__dirname, '..')
-
-module.exports = {
- raw: config,
- handleDockerSecret: handleDockerSecret,
- version: version,
- minimumCompatibleVersion: minimumCompatibleVersion,
- maintenance: maintenance,
- domain: domain,
- urlpath: urlpath,
- debug: debug,
- port: port,
- alloworigin: alloworigin,
- usessl: usessl,
- serverurl: getserverurl(),
- usecdn: usecdn,
- allowanonymous: allowanonymous,
- allowfreeurl: allowfreeurl,
- defaultpermission: defaultpermission,
- dburl: dburl,
- db: db,
- sslkeypath: path.join(cwd, sslkeypath),
- sslcertpath: path.join(cwd, sslcertpath),
- sslcapath: path.join(cwd, sslcapath),
- dhparampath: path.join(cwd, dhparampath),
- tmppath: path.join(cwd, tmppath),
- defaultnotepath: path.join(cwd, defaultnotepath),
- docspath: path.join(cwd, docspath),
- indexpath: path.join(cwd, indexpath),
- hackmdpath: path.join(cwd, hackmdpath),
- errorpath: path.join(cwd, errorpath),
- prettypath: path.join(cwd, prettypath),
- slidepath: path.join(cwd, slidepath),
- sessionname: sessionname,
- sessionsecret: sessionsecret,
- sessionlife: sessionlife,
- staticcachetime: staticcachetime,
- heartbeatinterval: heartbeatinterval,
- heartbeattimeout: heartbeattimeout,
- documentmaxlength: documentmaxlength,
- facebook: facebook,
- twitter: twitter,
- github: github,
- gitlab: gitlab,
- dropbox: dropbox,
- google: google,
- ldap: ldap,
- imgur: imgur,
- email: email,
- allowemailregister: allowemailregister,
- imageUploadType: imageUploadType,
- s3: s3,
- s3bucket: s3bucket
-}
diff --git a/lib/config/default.js b/lib/config/default.js
new file mode 100644
index 00000000..a14a4294
--- /dev/null
+++ b/lib/config/default.js
@@ -0,0 +1,92 @@
+'use strict'
+
+module.exports = {
+ domain: '',
+ urlpath: '',
+ port: 3000,
+ urladdport: false,
+ alloworigin: ['localhost'],
+ usessl: false,
+ protocolusessl: false,
+ usecdn: true,
+ allowanonymous: true,
+ allowfreeurl: false,
+ defaultpermission: 'editable',
+ dburl: '',
+ db: {},
+ // ssl path
+ sslkeypath: '',
+ sslcertpath: '',
+ sslcapath: '',
+ dhparampath: '',
+ // other path
+ tmppath: './tmp',
+ defaultnotepath: './public/default.md',
+ docspath: './public/docs',
+ indexpath: './public/views/index.ejs',
+ hackmdpath: './public/views/hackmd.ejs',
+ errorpath: './public/views/error.ejs',
+ prettypath: './public/views/pretty.ejs',
+ slidepath: './public/views/slide.ejs',
+ // session
+ sessionname: 'connect.sid',
+ sessionsecret: 'secret',
+ sessionlife: 14 * 24 * 60 * 60 * 1000, // 14 days
+ staticcachetime: 1 * 24 * 60 * 60 * 1000, // 1 day
+ // socket.io
+ heartbeatinterval: 5000,
+ heartbeattimeout: 10000,
+ // document
+ documentmaxlength: 100000,
+ // image upload setting, available options are imgur/s3/filesystem
+ imageUploadType: 'filesystem',
+ imgur: {
+ clientID: undefined
+ },
+ s3: {
+ accessKeyId: undefined,
+ secretAccessKey: undefined,
+ region: undefined
+ },
+ s3bucket: undefined,
+ // authentication
+ facebook: {
+ clientID: undefined,
+ clientSecret: undefined
+ },
+ twitter: {
+ consumerKey: undefined,
+ consumerSecret: undefined
+ },
+ github: {
+ clientID: undefined,
+ clientSecret: undefined
+ },
+ gitlab: {
+ baseURL: undefined,
+ clientID: undefined,
+ clientSecret: undefined,
+ scope: undefined
+ },
+ dropbox: {
+ clientID: undefined,
+ clientSecret: undefined
+ },
+ google: {
+ clientID: undefined,
+ clientSecret: undefined
+ },
+ ldap: {
+ providerName: undefined,
+ url: undefined,
+ bindDn: undefined,
+ bindCredentials: undefined,
+ tokenSecret: undefined,
+ searchBase: undefined,
+ searchFilter: undefined,
+ searchAttributes: undefined,
+ tlsca: undefined
+ },
+ email: true,
+ allowemailregister: true
+}
diff --git a/lib/config/defaultSSL.js b/lib/config/defaultSSL.js
new file mode 100644
index 00000000..1f1d5590
--- /dev/null
+++ b/lib/config/defaultSSL.js
@@ -0,0 +1,17 @@
+'use strict'
+
+const fs = require('fs')
+
+function getFile (path) {
+ if (fs.existsSync(path)) {
+ return path
+ }
+ return undefined
+}
+
+module.exports = {
+ sslkeypath: getFile('/run/secrets/key.pem'),
+ sslcertpath: getFile('/run/secrets/cert.pem'),
+ sslcapath: getFile('/run/secrets/ca.pem'),
+ dhparampath: getFile('/run/secrets/dhparam.pem')
+}
diff --git a/lib/config/dockerSecret.js b/lib/config/dockerSecret.js
new file mode 100644
index 00000000..eea2fafd
--- /dev/null
+++ b/lib/config/dockerSecret.js
@@ -0,0 +1,51 @@
+'use strict'
+
+const fs = require('fs')
+const path = require('path')
+
+const basePath = path.resolve('/var/run/secrets/')
+
+function getSecret (secret) {
+ const filePath = path.join(basePath, secret)
+ if (fs.existsSync(filePath)) return fs.readFileSync(filePath)
+ return undefined
+}
+
+if (fs.existsSync(basePath)) {
+ module.exports = {
+ sessionsecret: getSecret('sessionsecret'),
+ sslkeypath: getSecret('sslkeypath'),
+ sslcertpath: getSecret('sslcertpath'),
+ sslcapath: getSecret('sslcapath'),
+ dhparampath: getSecret('dhparampath'),
+ s3: {
+ accessKeyId: getSecret('s3_acccessKeyId'),
+ secretAccessKey: getSecret('s3_secretAccessKey')
+ },
+ facebook: {
+ clientID: getSecret('facebook_clientID'),
+ clientSecret: getSecret('facebook_clientSecret')
+ },
+ twitter: {
+ consumerKey: getSecret('twitter_consumerKey'),
+ consumerSecret: getSecret('twitter_consumerSecret')
+ },
+ github: {
+ clientID: getSecret('github_clientID'),
+ clientSecret: getSecret('github_clientSecret')
+ },
+ gitlab: {
+ clientID: getSecret('gitlab_clientID'),
+ clientSecret: getSecret('gitlab_clientSecret')
+ },
+ dropbox: {
+ clientID: getSecret('dropbox_clientID'),
+ clientSecret: getSecret('dropbox_clientSecret')
+ },
+ google: {
+ clientID: getSecret('google_clientID'),
+ clientSecret: getSecret('google_clientSecret')
+ },
+ imgur: getSecret('imgur_clientid')
+ }
+}
diff --git a/lib/config/enum.js b/lib/config/enum.js
new file mode 100644
index 00000000..07cdfcfe
--- /dev/null
+++ b/lib/config/enum.js
@@ -0,0 +1,16 @@
+'use strict'
+
+exports.Environment = {
+ development: 'development',
+ production: 'production',
+ test: 'test'
+}
+
+exports.Permission = {
+ freely: 'freely',
+ editable: 'editable',
+ limited: 'limited',
+ locked: 'locked',
+ protected: 'protected',
+ private: 'private'
+}
diff --git a/lib/config/environment.js b/lib/config/environment.js
new file mode 100644
index 00000000..75381ffc
--- /dev/null
+++ b/lib/config/environment.js
@@ -0,0 +1,67 @@
+'use strict'
+
+const {toBooleanConfig} = require('./utils')
+
+module.exports = {
+ domain: process.env.HMD_DOMAIN,
+ urlpath: process.env.HMD_URL_PATH,
+ port: process.env.HMD_PORT,
+ urladdport: process.env.HMD_URL_ADDPORT,
+ usessl: toBooleanConfig(process.env.HMD_USESSL),
+ protocolusessl: toBooleanConfig(process.env.HMD_PROTOCOL_USESSL),
+ alloworigin: process.env.HMD_ALLOW_ORIGIN ? process.env.HMD_ALLOW_ORIGIN.split(',') : undefined,
+ usecdn: toBooleanConfig(process.env.HMD_USECDN),
+ allowanonymous: toBooleanConfig(process.env.HMD_ALLOW_ANONYMOUS),
+ allowfreeurl: toBooleanConfig(process.env.HMD_ALLOW_FREEURL),
+ defaultpermission: process.env.HMD_DEFAULT_PERMISSION,
+ dburl: process.env.HMD_DB_URL,
+ imageUploadType: process.env.HMD_IMAGE_UPLOAD_TYPE,
+ imgur: {
+ clientID: process.env.HMD_IMGUR_CLIENTID
+ },
+ s3: {
+ accessKeyId: process.env.HMD_S3_ACCESS_KEY_ID,
+ secretAccessKey: process.env.HMD_S3_SECRET_ACCESS_KEY,
+ region: process.env.HMD_S3_REGION
+ },
+ s3bucket: process.env.HMD_S3_BUCKET,
+ facebook: {
+ clientID: process.env.HMD_FACEBOOK_CLIENTID,
+ clientSecret: process.env.HMD_FACEBOOK_CLIENTSECRET
+ },
+ twitter: {
+ consumerKey: process.env.HMD_TWITTER_CONSUMERKEY,
+ consumerSecret: process.env.HMD_TWITTER_CONSUMERSECRET
+ },
+ github: {
+ clientID: process.env.HMD_GITHUB_CLIENTID,
+ clientSecret: process.env.HMD_GITHUB_CLIENTSECRET
+ },
+ gitlab: {
+ baseURL: process.env.HMD_GITLAB_BASEURL,
+ clientID: process.env.HMD_GITLAB_CLIENTID,
+ clientSecret: process.env.HMD_GITLAB_CLIENTSECRET,
+ scope: process.env.HMD_GITLAB_SCOPE
+ },
+ dropbox: {
+ clientID: process.env.HMD_DROPBOX_CLIENTID,
+ clientSecret: process.env.HMD_DROPBOX_CLIENTSECRET
+ },
+ google: {
+ clientID: process.env.HMD_GOOGLE_CLIENTID,
+ clientSecret: process.env.HMD_GOOGLE_CLIENTSECRET
+ },
+ ldap: {
+ providerName: process.env.HMD_LDAP_PROVIDERNAME,
+ url: process.env.HMD_LDAP_URL,
+ bindDn: process.env.HMD_LDAP_BINDDN,
+ bindCredentials: process.env.HMD_LDAP_BINDCREDENTIALS,
+ tokenSecret: process.env.HMD_LDAP_TOKENSECRET,
+ searchBase: process.env.HMD_LDAP_SEARCHBASE,
+ searchFilter: process.env.HMD_LDAP_SEARCHFILTER,
+ searchAttributes: process.env.HMD_LDAP_SEARCHATTRIBUTES,
+ tlsca: process.env.HMD_LDAP_TLS_CA
+ },
+ email: toBooleanConfig(process.env.HMD_EMAIL),
+ allowemailregister: toBooleanConfig(process.env.HMD_ALLOW_EMAIL_REGISTER)
+}
diff --git a/lib/config/index.js b/lib/config/index.js
new file mode 100644
index 00000000..6bc9a419
--- /dev/null
+++ b/lib/config/index.js
@@ -0,0 +1,112 @@
+'use strict'
+
+const fs = require('fs')
+const path = require('path')
+const {merge} = require('lodash')
+const deepFreeze = require('deep-freeze')
+const {Environment, Permission} = require('./enum')
+
+const appRootPath = path.join(__dirname, '../../')
+const env = process.env.NODE_ENV || Environment.development
+const debugConfig = {
+ debug: (env === Environment.development)
+}
+
+const packageConfig = {
+ version: '0.5.1',
+ minimumCompatibleVersion: '0.5.0'
+}
+
+const configFilePath = path.join(__dirname, '../../config.json')
+const fileConfig = fs.existsSync(configFilePath) ? require(configFilePath)[env] : undefined
+
+let config = require('./default')
+merge(config, require('./defaultSSL'))
+merge(config, debugConfig)
+merge(config, packageConfig)
+merge(config, fileConfig)
+merge(config, require('./oldEnvironment'))
+merge(config, require('./environment'))
+merge(config, require('./dockerSecret'))
+
+// load LDAP CA
+if (config.ldap.tlsca) {
+ let ca = config.ldap.tlsca.split(',')
+ let caContent = []
+ for (let i of ca) {
+ if (fs.existsSync(ca[i])) {
+ caContent.push(fs.readFileSync(ca[i], 'utf8'))
+ }
+ }
+ let tlsOptions = {
+ ca: caContent
+ }
+ config.ldap.tlsOptions = config.ldap.tlsOptions ? Object.assign(config.ldap.tlsOptions, tlsOptions) : tlsOptions
+}
+
+// Permission
+config.permission = Permission
+if (!config.allowanonymous) {
+ delete config.permission.freely
+}
+if (!(config.defaultpermission in config.permission)) {
+ config.defaultpermission = config.permission.editable
+}
+
+// cache result, cannot change config in runtime!!!
+config.isStandardHTTPsPort = (function isStandardHTTPsPort () {
+ return config.usessl && config.port === 443
+})()
+config.isStandardHTTPPort = (function isStandardHTTPPort () {
+ return !config.usessl && config.port === 80
+})()
+
+// cache serverURL
+config.serverurl = (function getserverurl () {
+ var url = ''
+ if (config.domain) {
+ var protocol = config.protocolusessl ? 'https://' : 'http://'
+ url = protocol + config.domain
+ if (config.urladdport) {
+ if (!config.isStandardHTTPPort || !config.isStandardHTTPsPort) {
+ url += ':' + config.port
+ }
+ }
+ }
+ if (config.urlpath) {
+ url += '/' + config.urlpath
+ }
+ return url
+})()
+
+config.Environment = Environment
+
+// auth method
+config.isFacebookEnable = config.facebook.clientID && config.facebook.clientSecret
+config.isGoogleEnable = config.google.clientID && config.google.clientSecret
+config.isDropboxEnable = config.dropbox.clientID && config.dropbox.clientSecret
+config.isTwitterEnable = config.twitter.consumerKey && config.twitter.consumerSecret
+config.isEmailEnable = config.email
+config.isGitHubEnable = config.github.clientID && config.github.clientSecret
+config.isGitLabEnable = config.gitlab.clientID && config.gitlab.clientSecret
+config.isLDAPEnable = config.ldap.url
+
+// generate correct path
+config.sslcapath = path.join(appRootPath, config.sslcapath)
+config.sslcertpath = path.join(appRootPath, config.sslcertpath)
+config.sslkeypath = path.join(appRootPath, config.sslkeypath)
+config.dhparampath = path.join(appRootPath, config.dhparampath)
+
+config.tmppath = path.join(appRootPath, config.tmppath)
+config.defaultnotepath = path.join(appRootPath, config.defaultnotepath)
+config.docspath = path.join(appRootPath, config.docspath)
+config.indexpath = path.join(appRootPath, config.indexpath)
+config.hackmdpath = path.join(appRootPath, config.hackmdpath)
+config.errorpath = path.join(appRootPath, config.errorpath)
+config.prettypath = path.join(appRootPath, config.prettypath)
+config.slidepath = path.join(appRootPath, config.slidepath)
+
+// maek config readonly
+config = deepFreeze(config)
+
+module.exports = config
diff --git a/lib/config/oldEnvironment.js b/lib/config/oldEnvironment.js
new file mode 100644
index 00000000..a3b13cb9
--- /dev/null
+++ b/lib/config/oldEnvironment.js
@@ -0,0 +1,10 @@
+'use strict'
+
+const {toBooleanConfig} = require('./utils')
+
+module.exports = {
+ debug: toBooleanConfig(process.env.DEBUG),
+ dburl: process.env.DATABASE_URL,
+ urlpath: process.env.URL_PATH,
+ port: process.env.PORT
+}
diff --git a/lib/config/utils.js b/lib/config/utils.js
new file mode 100644
index 00000000..11bbd8cb
--- /dev/null
+++ b/lib/config/utils.js
@@ -0,0 +1,8 @@
+'use strict'
+
+exports.toBooleanConfig = function toBooleanConfig (configValue) {
+ if (configValue && typeof configValue === 'string') {
+ return (configValue === 'true')
+ }
+ return configValue
+}
diff --git a/lib/history.js b/lib/history.js
index ffed3b96..f46ff49f 100644
--- a/lib/history.js
+++ b/lib/history.js
@@ -3,9 +3,9 @@
// external modules
// core
-var config = require('./config.js')
-var logger = require('./logger.js')
-var response = require('./response.js')
+var config = require('./config')
+var logger = require('./logger')
+var response = require('./response')
var models = require('./models')
// public
diff --git a/lib/logger.js b/lib/logger.js
index 2111d69a..f8b3895c 100644
--- a/lib/logger.js
+++ b/lib/logger.js
@@ -1,23 +1,23 @@
'use strict'
-var winston = require('winston')
-winston.emitErrs = true
+const winston = require('winston')
-var logger = new winston.Logger({
+class Logger extends winston.Logger {
+ // Implement stream.writable.write interface
+ write (chunk) {
+ this.info(chunk)
+ }
+}
+
+module.exports = new Logger({
transports: [
new winston.transports.Console({
level: 'debug',
handleExceptions: true,
json: false,
- colorize: true,
+ colorize: false,
timestamp: true
})
],
+ emitErrs: true,
exitOnError: false
})
-
-module.exports = logger
-module.exports.stream = {
- write: function (message, encoding) {
- logger.info(message)
- }
-}
diff --git a/lib/models/index.js b/lib/models/index.js
index 95f900b6..0679a7fc 100644
--- a/lib/models/index.js
+++ b/lib/models/index.js
@@ -3,13 +3,14 @@
var fs = require('fs')
var path = require('path')
var Sequelize = require('sequelize')
+const {cloneDeep} = require('lodash')
// core
-var config = require('../config.js')
-var logger = require('../logger.js')
+var config = require('../config')
+var logger = require('../logger')
-var dbconfig = config.db
-dbconfig.logging = config.debug ? logger.info : false
+var dbconfig = cloneDeep(config.db)
+dbconfig.logger = config.debug ? logger.info : false
var sequelize = null
diff --git a/lib/models/note.js b/lib/models/note.js
index f7c25bde..c0ef1374 100644
--- a/lib/models/note.js
+++ b/lib/models/note.js
@@ -15,11 +15,11 @@ var dmp = new DiffMatchPatch()
var S = require('string')
// core
-var config = require('../config.js')
-var logger = require('../logger.js')
+var config = require('../config')
+var logger = require('../logger')
// ot
-var ot = require('../ot/index.js')
+var ot = require('../ot')
// permission types
var permissionTypes = ['freely', 'editable', 'limited', 'locked', 'protected', 'private']
diff --git a/lib/models/revision.js b/lib/models/revision.js
index 581e7ea5..6f3a746f 100644
--- a/lib/models/revision.js
+++ b/lib/models/revision.js
@@ -7,8 +7,8 @@ var childProcess = require('child_process')
var shortId = require('shortid')
// core
-var config = require('../config.js')
-var logger = require('../logger.js')
+var config = require('../config')
+var logger = require('../logger')
var dmpWorker = createDmpWorker()
var dmpCallbackCache = {}
diff --git a/lib/models/user.js b/lib/models/user.js
index 042b0d2f..14c30bc3 100644
--- a/lib/models/user.js
+++ b/lib/models/user.js
@@ -5,8 +5,8 @@ var Sequelize = require('sequelize')
var scrypt = require('scrypt')
// core
-var logger = require('../logger.js')
-var letterAvatars = require('../letter-avatars.js')
+var logger = require('../logger')
+var letterAvatars = require('../letter-avatars')
module.exports = function (sequelize, DataTypes) {
var User = sequelize.define('User', {
diff --git a/lib/realtime.js b/lib/realtime.js
index 618fdad0..361bbf09 100644
--- a/lib/realtime.js
+++ b/lib/realtime.js
@@ -12,13 +12,13 @@ var chance = new Chance()
var moment = require('moment')
// core
-var config = require('./config.js')
-var logger = require('./logger.js')
-var history = require('./history.js')
+var config = require('./config')
+var logger = require('./logger')
+var history = require('./history')
var models = require('./models')
// ot
-var ot = require('./ot/index.js')
+var ot = require('./ot')
// public
var realtime = {
@@ -28,7 +28,8 @@ var realtime = {
secure: secure,
connection: connection,
getStatus: getStatus,
- isReady: isReady
+ isReady: isReady,
+ maintenance: true
}
function onAuthorizeSuccess (data, accept) {
@@ -699,7 +700,7 @@ function updateHistory (userId, note, time) {
}
function connection (socket) {
- if (config.maintenance) return
+ if (realtime.maintenance) return
parseNoteIdFromSocket(socket, function (err, noteId) {
if (err) {
return failConnection(500, err, socket)
diff --git a/lib/response.js b/lib/response.js
index 13a94bbf..a9abd1d4 100755
--- a/lib/response.js
+++ b/lib/response.js
@@ -10,8 +10,8 @@ var request = require('request')
var moment = require('moment')
// core
-var config = require('./config.js')
-var logger = require('./logger.js')
+var config = require('./config')
+var logger = require('./logger')
var models = require('./models')
// public
@@ -59,14 +59,14 @@ function showIndex (req, res, next) {
url: config.serverurl,
useCDN: config.usecdn,
allowAnonymous: config.allowanonymous,
- facebook: config.facebook,
- twitter: config.twitter,
- github: config.github,
- gitlab: config.gitlab,
- dropbox: config.dropbox,
- google: config.google,
- ldap: config.ldap,
- email: config.email,
+ facebook: config.isFacebookEnable,
+ twitter: config.isTwitterEnable,
+ github: config.isGitHubEnable,
+ gitlab: config.isGitLabEnable,
+ dropbox: config.isDropboxEnable,
+ google: config.isGoogleEnable,
+ ldap: config.isLDAPEnable,
+ email: config.isEmailEnable,
allowemailregister: config.allowemailregister,
signin: req.isAuthenticated(),
infoMessage: req.flash('info'),
@@ -89,14 +89,14 @@ function responseHackMD (res, note) {
title: title,
useCDN: config.usecdn,
allowAnonymous: config.allowanonymous,
- facebook: config.facebook,
- twitter: config.twitter,
- github: config.github,
- gitlab: config.gitlab,
- dropbox: config.dropbox,
- google: config.google,
- ldap: config.ldap,
- email: config.email,
+ facebook: config.isFacebookEnable,
+ twitter: config.isTwitterEnable,
+ github: config.isGitHubEnable,
+ gitlab: config.isGitLabEnable,
+ dropbox: config.isDropboxEnable,
+ google: config.isGoogleEnable,
+ ldap: config.isLDAPEnable,
+ email: config.isEmailEnable,
allowemailregister: config.allowemailregister
})
}
diff --git a/lib/web/auth/dropbox/index.js b/lib/web/auth/dropbox/index.js
new file mode 100644
index 00000000..c03fbc57
--- /dev/null
+++ b/lib/web/auth/dropbox/index.js
@@ -0,0 +1,29 @@
+'use strict'
+
+const Router = require('express').Router
+const passport = require('passport')
+const DropboxStrategy = require('passport-dropbox-oauth2').Strategy
+const config = require('../../../config')
+const {setReturnToFromReferer, passportGeneralCallback} = require('../utils')
+
+let dropboxAuth = module.exports = Router()
+
+passport.use(new DropboxStrategy({
+ apiVersion: '2',
+ clientID: config.dropbox.clientID,
+ clientSecret: config.dropbox.clientSecret,
+ callbackURL: config.serverurl + '/auth/dropbox/callback'
+}, passportGeneralCallback))
+
+dropboxAuth.get('/auth/dropbox', function (req, res, next) {
+ setReturnToFromReferer(req)
+ passport.authenticate('dropbox-oauth2')(req, res, next)
+})
+
+// dropbox auth callback
+dropboxAuth.get('/auth/dropbox/callback',
+ passport.authenticate('dropbox-oauth2', {
+ successReturnToOrRedirect: config.serverurl + '/',
+ failureRedirect: config.serverurl + '/'
+ })
+)
diff --git a/lib/web/auth/email/index.js b/lib/web/auth/email/index.js
new file mode 100644
index 00000000..760075f8
--- /dev/null
+++ b/lib/web/auth/email/index.js
@@ -0,0 +1,74 @@
+'use strict'
+
+const Router = require('express').Router
+const passport = require('passport')
+const validator = require('validator')
+const LocalStrategy = require('passport-local').Strategy
+const config = require('../../../config')
+const models = require('../../../models')
+const logger = require('../../../logger')
+const {setReturnToFromReferer} = require('../utils')
+const {urlencodedParser} = require('../../utils')
+const response = require('../../../response')
+
+let emailAuth = module.exports = Router()
+
+passport.use(new LocalStrategy({
+ usernameField: 'email'
+}, function (email, password, done) {
+ if (!validator.isEmail(email)) return done(null, false)
+ models.User.findOne({
+ where: {
+ email: email
+ }
+ }).then(function (user) {
+ if (!user) return done(null, false)
+ if (!user.verifyPassword(password)) return done(null, false)
+ return done(null, user)
+ }).catch(function (err) {
+ logger.error(err)
+ return done(err)
+ })
+}))
+
+if (config.allowemailregister) {
+ emailAuth.post('/register', urlencodedParser, function (req, res, next) {
+ if (!req.body.email || !req.body.password) return response.errorBadRequest(res)
+ if (!validator.isEmail(req.body.email)) return response.errorBadRequest(res)
+ models.User.findOrCreate({
+ where: {
+ email: req.body.email
+ },
+ defaults: {
+ password: req.body.password
+ }
+ }).spread(function (user, created) {
+ if (user) {
+ if (created) {
+ logger.debug('user registered: ' + user.id)
+ req.flash('info', "You've successfully registered, please signin.")
+ } else {
+ logger.debug('user found: ' + user.id)
+ req.flash('error', 'This email has been used, please try another one.')
+ }
+ return res.redirect(config.serverurl + '/')
+ }
+ req.flash('error', 'Failed to register your account, please try again.')
+ return res.redirect(config.serverurl + '/')
+ }).catch(function (err) {
+ logger.error('auth callback failed: ' + err)
+ return response.errorInternalError(res)
+ })
+ })
+}
+
+emailAuth.post('/login', urlencodedParser, function (req, res, next) {
+ if (!req.body.email || !req.body.password) return response.errorBadRequest(res)
+ if (!validator.isEmail(req.body.email)) return response.errorBadRequest(res)
+ setReturnToFromReferer(req)
+ passport.authenticate('local', {
+ successReturnToOrRedirect: config.serverurl + '/',
+ failureRedirect: config.serverurl + '/',
+ failureFlash: 'Invalid email or password.'
+ })(req, res, next)
+})
diff --git a/lib/web/auth/facebook/index.js b/lib/web/auth/facebook/index.js
new file mode 100644
index 00000000..0e5474d8
--- /dev/null
+++ b/lib/web/auth/facebook/index.js
@@ -0,0 +1,29 @@
+'use strict'
+
+const Router = require('express').Router
+const passport = require('passport')
+const FacebookStrategy = require('passport-facebook').Strategy
+
+const config = require('../../../config')
+const {setReturnToFromReferer, passportGeneralCallback} = require('../utils')
+
+let facebookAuth = module.exports = Router()
+
+passport.use(new FacebookStrategy({
+ clientID: config.facebook.clientID,
+ clientSecret: config.facebook.clientSecret,
+ callbackURL: config.serverurl + '/auth/facebook/callback'
+}, passportGeneralCallback))
+
+facebookAuth.get('/auth/facebook', function (req, res, next) {
+ setReturnToFromReferer(req)
+ passport.authenticate('facebook')(req, res, next)
+})
+
+// facebook auth callback
+facebookAuth.get('/auth/facebook/callback',
+ passport.authenticate('facebook', {
+ successReturnToOrRedirect: config.serverurl + '/',
+ failureRedirect: config.serverurl + '/'
+ })
+)
diff --git a/lib/web/auth/github/index.js b/lib/web/auth/github/index.js
new file mode 100644
index 00000000..2a26669c
--- /dev/null
+++ b/lib/web/auth/github/index.js
@@ -0,0 +1,28 @@
+'use strict'
+
+const Router = require('express').Router
+const passport = require('passport')
+const GithubStrategy = require('passport-github').Strategy
+const config = require('../../../config')
+const {setReturnToFromReferer, passportGeneralCallback} = require('../utils')
+
+let githubAuth = module.exports = Router()
+
+passport.use(new GithubStrategy({
+ clientID: config.github.clientID,
+ clientSecret: config.github.clientSecret,
+ callbackURL: config.serverurl + '/auth/github/callback'
+}, passportGeneralCallback))
+
+githubAuth.get('/auth/github', function (req, res, next) {
+ setReturnToFromReferer(req)
+ passport.authenticate('github')(req, res, next)
+})
+
+// github auth callback
+githubAuth.get('/auth/github/callback',
+ passport.authenticate('github', {
+ successReturnToOrRedirect: config.serverurl + '/',
+ failureRedirect: config.serverurl + '/'
+ })
+)
diff --git a/lib/web/auth/gitlab/index.js b/lib/web/auth/gitlab/index.js
new file mode 100644
index 00000000..51de1602
--- /dev/null
+++ b/lib/web/auth/gitlab/index.js
@@ -0,0 +1,36 @@
+'use strict'
+
+const Router = require('express').Router
+const passport = require('passport')
+const GitlabStrategy = require('passport-gitlab2').Strategy
+const config = require('../../../config')
+const response = require('../../../response')
+const {setReturnToFromReferer, passportGeneralCallback} = require('../utils')
+
+let gitlabAuth = module.exports = Router()
+
+passport.use(new GitlabStrategy({
+ baseURL: config.gitlab.baseURL,
+ clientID: config.gitlab.clientID,
+ clientSecret: config.gitlab.clientSecret,
+ scope: config.gitlab.scope,
+ callbackURL: config.serverurl + '/auth/gitlab/callback'
+}, passportGeneralCallback))
+
+gitlabAuth.get('/auth/gitlab', function (req, res, next) {
+ setReturnToFromReferer(req)
+ passport.authenticate('gitlab')(req, res, next)
+})
+
+// gitlab auth callback
+gitlabAuth.get('/auth/gitlab/callback',
+ passport.authenticate('gitlab', {
+ successReturnToOrRedirect: config.serverurl + '/',
+ failureRedirect: config.serverurl + '/'
+ })
+)
+
+if (!config.gitlab.scope || config.gitlab.scope === 'api') {
+ // gitlab callback actions
+ gitlabAuth.get('/auth/gitlab/callback/:noteId/:action', response.gitlabActions)
+}
diff --git a/lib/web/auth/google/index.js b/lib/web/auth/google/index.js
new file mode 100644
index 00000000..bf2a260f
--- /dev/null
+++ b/lib/web/auth/google/index.js
@@ -0,0 +1,27 @@
+'use strict'
+
+const Router = require('express').Router
+const passport = require('passport')
+var GoogleStrategy = require('passport-google-oauth20').Strategy
+const config = require('../../../config')
+const {setReturnToFromReferer, passportGeneralCallback} = require('../utils')
+
+let facebookAuth = module.exports = Router()
+
+passport.use(new GoogleStrategy({
+ clientID: config.google.clientID,
+ clientSecret: config.google.clientSecret,
+ callbackURL: config.serverurl + '/auth/google/callback'
+}, passportGeneralCallback))
+
+facebookAuth.get('/auth/google', function (req, res, next) {
+ setReturnToFromReferer(req)
+ passport.authenticate('google', { scope: ['profile'] })(req, res, next)
+})
+ // google auth callback
+facebookAuth.get('/auth/google/callback',
+ passport.authenticate('google', {
+ successReturnToOrRedirect: config.serverurl + '/',
+ failureRedirect: config.serverurl + '/'
+ })
+)
diff --git a/lib/web/auth/index.js b/lib/web/auth/index.js
new file mode 100644
index 00000000..b5ca8434
--- /dev/null
+++ b/lib/web/auth/index.js
@@ -0,0 +1,48 @@
+'use strict'
+
+const Router = require('express').Router
+const passport = require('passport')
+
+const config = require('../../config')
+const logger = require('../../logger')
+const models = require('../../models')
+
+const authRouter = module.exports = Router()
+
+// serialize and deserialize
+passport.serializeUser(function (user, done) {
+ logger.info('serializeUser: ' + user.id)
+ return done(null, user.id)
+})
+
+passport.deserializeUser(function (id, done) {
+ models.User.findOne({
+ where: {
+ id: id
+ }
+ }).then(function (user) {
+ logger.info('deserializeUser: ' + user.id)
+ return done(null, user)
+ }).catch(function (err) {
+ logger.error(err)
+ return done(err, null)
+ })
+})
+
+if (config.isFacebookEnable) authRouter.use(require('./facebook'))
+if (config.isTwitterEnable) authRouter.use(require('./twitter'))
+if (config.isGitHubEnable) authRouter.use(require('./github'))
+if (config.isGitLabEnable) authRouter.use(require('./gitlab'))
+if (config.isDropboxEnable) authRouter.use(require('./dropbox'))
+if (config.isGoogleEnable) authRouter.use(require('./google'))
+if (config.isLDAPEnable) authRouter.use(require('./ldap'))
+if (config.isEmailEnable) authRouter.use(require('./email'))
+
+// logout
+authRouter.get('/logout', function (req, res) {
+ if (config.debug && req.isAuthenticated()) {
+ logger.debug('user logout: ' + req.user.id)
+ }
+ req.logout()
+ res.redirect(config.serverurl + '/')
+})
diff --git a/lib/web/auth/ldap/index.js b/lib/web/auth/ldap/index.js
new file mode 100644
index 00000000..766c5cbc
--- /dev/null
+++ b/lib/web/auth/ldap/index.js
@@ -0,0 +1,74 @@
+'use strict'
+
+const Router = require('express').Router
+const passport = require('passport')
+const LDAPStrategy = require('passport-ldapauth')
+const config = require('../../../config')
+const models = require('../../../models')
+const logger = require('../../../logger')
+const {setReturnToFromReferer} = require('../utils')
+const {urlencodedParser} = require('../../utils')
+const response = require('../../../response')
+
+let ldapAuth = module.exports = Router()
+
+passport.use(new LDAPStrategy({
+ server: {
+ url: config.ldap.url || null,
+ bindDn: config.ldap.bindDn || null,
+ bindCredentials: config.ldap.bindCredentials || null,
+ searchBase: config.ldap.searchBase || null,
+ searchFilter: config.ldap.searchFilter || null,
+ searchAttributes: config.ldap.searchAttributes || null,
+ tlsOptions: config.ldap.tlsOptions || null
+ }
+}, function (user, done) {
+ var profile = {
+ id: 'LDAP-' + user.uidNumber,
+ username: user.uid,
+ displayName: user.displayName,
+ emails: user.mail ? [user.mail] : [],
+ avatarUrl: null,
+ profileUrl: null,
+ provider: 'ldap'
+ }
+ var stringifiedProfile = JSON.stringify(profile)
+ models.User.findOrCreate({
+ where: {
+ profileid: profile.id.toString()
+ },
+ defaults: {
+ profile: stringifiedProfile
+ }
+ }).spread(function (user, created) {
+ if (user) {
+ var needSave = false
+ if (user.profile !== stringifiedProfile) {
+ user.profile = stringifiedProfile
+ needSave = true
+ }
+ if (needSave) {
+ user.save().then(function () {
+ if (config.debug) { logger.debug('user login: ' + user.id) }
+ return done(null, user)
+ })
+ } else {
+ if (config.debug) { logger.debug('user login: ' + user.id) }
+ return done(null, user)
+ }
+ }
+ }).catch(function (err) {
+ logger.error('ldap auth failed: ' + err)
+ return done(err, null)
+ })
+}))
+
+ldapAuth.post('/auth/ldap', urlencodedParser, function (req, res, next) {
+ if (!req.body.username || !req.body.password) return response.errorBadRequest(res)
+ setReturnToFromReferer(req)
+ passport.authenticate('ldapauth', {
+ successReturnToOrRedirect: config.serverurl + '/',
+ failureRedirect: config.serverurl + '/',
+ failureFlash: true
+ })(req, res, next)
+})
diff --git a/lib/web/auth/twitter/index.js b/lib/web/auth/twitter/index.js
new file mode 100644
index 00000000..5429522d
--- /dev/null
+++ b/lib/web/auth/twitter/index.js
@@ -0,0 +1,29 @@
+'use strict'
+
+const Router = require('express').Router
+const passport = require('passport')
+const TwitterStrategy = require('passport-twitter').Strategy
+
+const config = require('../../../config')
+const {setReturnToFromReferer, passportGeneralCallback} = require('../utils')
+
+let twitterAuth = module.exports = Router()
+
+passport.use(new TwitterStrategy({
+ consumerKey: config.twitter.consumerKey,
+ consumerSecret: config.twitter.consumerSecret,
+ callbackURL: config.serverurl + '/auth/twitter/callback'
+}, passportGeneralCallback))
+
+twitterAuth.get('/auth/twitter', function (req, res, next) {
+ setReturnToFromReferer(req)
+ passport.authenticate('twitter')(req, res, next)
+})
+
+// twitter auth callback
+twitterAuth.get('/auth/twitter/callback',
+ passport.authenticate('twitter', {
+ successReturnToOrRedirect: config.serverurl + '/',
+ failureRedirect: config.serverurl + '/'
+ })
+)
diff --git a/lib/web/auth/utils.js b/lib/web/auth/utils.js
new file mode 100644
index 00000000..ff7a1237
--- /dev/null
+++ b/lib/web/auth/utils.js
@@ -0,0 +1,53 @@
+'use strict'
+
+const models = require('../../models')
+const config = require('../../config')
+const logger = require('../../logger')
+
+exports.setReturnToFromReferer = function setReturnToFromReferer (req) {
+ var referer = req.get('referer')
+ if (!req.session) req.session = {}
+ req.session.returnTo = referer
+}
+
+exports.passportGeneralCallback = function callback (accessToken, refreshToken, profile, done) {
+ var stringifiedProfile = JSON.stringify(profile)
+ models.User.findOrCreate({
+ where: {
+ profileid: profile.id.toString()
+ },
+ defaults: {
+ profile: stringifiedProfile,
+ accessToken: accessToken,
+ refreshToken: refreshToken
+ }
+ }).spread(function (user, created) {
+ if (user) {
+ var needSave = false
+ if (user.profile !== stringifiedProfile) {
+ user.profile = stringifiedProfile
+ needSave = true
+ }
+ if (user.accessToken !== accessToken) {
+ user.accessToken = accessToken
+ needSave = true
+ }
+ if (user.refreshToken !== refreshToken) {
+ user.refreshToken = refreshToken
+ needSave = true
+ }
+ if (needSave) {
+ user.save().then(function () {
+ if (config.debug) { logger.info('user login: ' + user.id) }
+ return done(null, user)
+ })
+ } else {
+ if (config.debug) { logger.info('user login: ' + user.id) }
+ return done(null, user)
+ }
+ }
+ }).catch(function (err) {
+ logger.error('auth callback failed: ' + err)
+ return done(err, null)
+ })
+}
diff --git a/lib/web/baseRouter.js b/lib/web/baseRouter.js
new file mode 100644
index 00000000..b918ce75
--- /dev/null
+++ b/lib/web/baseRouter.js
@@ -0,0 +1,22 @@
+'use strict'
+
+const Router = require('express').Router
+
+const response = require('../response')
+
+const baseRouter = module.exports = Router()
+
+// get index
+baseRouter.get('/', response.showIndex)
+// get 403 forbidden
+baseRouter.get('/403', function (req, res) {
+ response.errorForbidden(res)
+})
+// get 404 not found
+baseRouter.get('/404', function (req, res) {
+ response.errorNotFound(res)
+})
+// get 500 internal error
+baseRouter.get('/500', function (req, res) {
+ response.errorInternalError(res)
+})
diff --git a/lib/web/historyRouter.js b/lib/web/historyRouter.js
new file mode 100644
index 00000000..1b22c232
--- /dev/null
+++ b/lib/web/historyRouter.js
@@ -0,0 +1,18 @@
+'use strict'
+
+const Router = require('express').Router
+
+const {urlencodedParser} = require('./utils')
+const history = require('../history')
+const historyRouter = module.exports = Router()
+
+// get history
+historyRouter.get('/history', history.historyGet)
+// post history
+historyRouter.post('/history', urlencodedParser, history.historyPost)
+// post history by note id
+historyRouter.post('/history/:noteId', urlencodedParser, history.historyPost)
+// delete history
+historyRouter.delete('/history', history.historyDelete)
+// delete history by note id
+historyRouter.delete('/history/:noteId', history.historyDelete)
diff --git a/lib/web/imageRouter.js b/lib/web/imageRouter.js
new file mode 100644
index 00000000..592a497c
--- /dev/null
+++ b/lib/web/imageRouter.js
@@ -0,0 +1,95 @@
+'use strict'
+var fs = require('fs')
+var url = require('url')
+var path = require('path')
+
+const Router = require('express').Router
+const formidable = require('formidable')
+var imgur = require('imgur')
+
+const config = require('../config')
+const logger = require('../logger')
+const response = require('../response')
+
+const imageRouter = module.exports = Router()
+
+// upload image
+imageRouter.post('/uploadimage', function (req, res) {
+ var form = new formidable.IncomingForm()
+
+ form.keepExtensions = true
+
+ if (config.imageUploadType === 'filesystem') {
+ form.uploadDir = 'public/uploads'
+ }
+
+ form.parse(req, function (err, fields, files) {
+ if (err || !files.image || !files.image.path) {
+ response.errorForbidden(res)
+ } else {
+ if (config.debug) { logger.info('SERVER received uploadimage: ' + JSON.stringify(files.image)) }
+
+ try {
+ switch (config.imageUploadType) {
+ case 'filesystem':
+ res.send({
+ link: url.resolve(config.serverurl + '/', files.image.path.match(/^public\/(.+$)/)[1])
+ })
+
+ break
+
+ case 's3':
+ var AWS = require('aws-sdk')
+ var awsConfig = new AWS.Config(config.s3)
+ var s3 = new AWS.S3(awsConfig)
+ const {getImageMimeType} = require('../utils')
+ fs.readFile(files.image.path, function (err, buffer) {
+ if (err) {
+ logger.error(err)
+ res.status(500).end('upload image error')
+ return
+ }
+ var params = {
+ Bucket: config.s3bucket,
+ Key: path.join('uploads', path.basename(files.image.path)),
+ Body: buffer
+ }
+
+ var mimeType = getImageMimeType(files.image.path)
+ if (mimeType) { params.ContentType = mimeType }
+
+ s3.putObject(params, function (err, data) {
+ if (err) {
+ logger.error(err)
+ res.status(500).end('upload image error')
+ return
+ }
+ res.send({
+ link: `https://s3-${config.s3.region}.amazonaws.com/${config.s3bucket}/${params.Key}`
+ })
+ })
+ })
+ break
+ case 'imgur':
+ default:
+ imgur.setClientId(config.imgur.clientID)
+ imgur.uploadFile(files.image.path)
+ .then(function (json) {
+ if (config.debug) { logger.info('SERVER uploadimage success: ' + JSON.stringify(json)) }
+ res.send({
+ link: json.data.link.replace(/^http:\/\//i, 'https://')
+ })
+ })
+ .catch(function (err) {
+ logger.error(err)
+ return res.status(500).end('upload image error')
+ })
+ break
+ }
+ } catch (err) {
+ logger.error(err)
+ return res.status(500).end('upload image error')
+ }
+ }
+ })
+})
diff --git a/lib/web/middleware/checkURIValid.js b/lib/web/middleware/checkURIValid.js
new file mode 100644
index 00000000..88065e79
--- /dev/null
+++ b/lib/web/middleware/checkURIValid.js
@@ -0,0 +1,14 @@
+'use strict'
+
+const logger = require('../../logger')
+const response = require('../../response')
+
+module.exports = function (req, res, next) {
+ try {
+ decodeURIComponent(req.path)
+ } catch (err) {
+ logger.error(err)
+ return response.errorBadRequest(res)
+ }
+ next()
+}
diff --git a/lib/web/middleware/redirectWithoutTrailingSlashes.js b/lib/web/middleware/redirectWithoutTrailingSlashes.js
new file mode 100644
index 00000000..fbaba617
--- /dev/null
+++ b/lib/web/middleware/redirectWithoutTrailingSlashes.js
@@ -0,0 +1,17 @@
+'use strict'
+
+const config = require('../../config')
+
+module.exports = function (req, res, next) {
+ if (req.method === 'GET' && req.path.substr(-1) === '/' && req.path.length > 1) {
+ const queryString = req.url.slice(req.path.length)
+ const urlPath = req.path.slice(0, -1)
+ let serverURL = config.serverurl
+ if (config.urlpath) {
+ serverURL = serverURL.slice(0, -(config.urlpath.length + 1))
+ }
+ res.redirect(301, serverURL + urlPath + queryString)
+ } else {
+ next()
+ }
+}
diff --git a/lib/web/middleware/tooBusy.js b/lib/web/middleware/tooBusy.js
new file mode 100644
index 00000000..f1b72330
--- /dev/null
+++ b/lib/web/middleware/tooBusy.js
@@ -0,0 +1,13 @@
+'use strict'
+
+const toobusy = require('toobusy-js')
+
+const response = require('../../response')
+
+module.exports = function (req, res, next) {
+ if (toobusy()) {
+ response.errorServiceUnavailable(res)
+ } else {
+ next()
+ }
+}
diff --git a/lib/web/noteRouter.js b/lib/web/noteRouter.js
new file mode 100644
index 00000000..007c02c2
--- /dev/null
+++ b/lib/web/noteRouter.js
@@ -0,0 +1,24 @@
+'use strict'
+
+const Router = require('express').Router
+
+const response = require('../response')
+
+const noteRouter = module.exports = Router()
+
+// get new note
+noteRouter.get('/new', response.newNote)
+// get publish note
+noteRouter.get('/s/:shortid', response.showPublishNote)
+// publish note actions
+noteRouter.get('/s/:shortid/:action', response.publishNoteActions)
+// get publish slide
+noteRouter.get('/p/:shortid', response.showPublishSlide)
+// publish slide actions
+noteRouter.get('/p/:shortid/:action', response.publishSlideActions)
+// get note by id
+noteRouter.get('/:noteId', response.showNote)
+// note actions
+noteRouter.get('/:noteId/:action', response.noteActions)
+// note actions with action id
+noteRouter.get('/:noteId/:action/:actionId', response.noteActions)
diff --git a/lib/web/statusRouter.js b/lib/web/statusRouter.js
new file mode 100644
index 00000000..aa3a9b79
--- /dev/null
+++ b/lib/web/statusRouter.js
@@ -0,0 +1,92 @@
+'use strict'
+
+const Router = require('express').Router
+
+const response = require('../response')
+const realtime = require('../realtime')
+const config = require('../config')
+const models = require('../models')
+const logger = require('../logger')
+
+const {urlencodedParser} = require('./utils')
+
+const statusRouter = module.exports = Router()
+
+// get status
+statusRouter.get('/status', function (req, res, next) {
+ realtime.getStatus(function (data) {
+ res.set({
+ 'Cache-Control': 'private', // only cache by client
+ 'X-Robots-Tag': 'noindex, nofollow', // prevent crawling
+ 'HackMD-Version': config.version
+ })
+ res.send(data)
+ })
+})
+// get status
+statusRouter.get('/temp', function (req, res) {
+ var host = req.get('host')
+ if (config.alloworigin.indexOf(host) === -1) {
+ response.errorForbidden(res)
+ } else {
+ var tempid = req.query.tempid
+ if (!tempid) {
+ response.errorForbidden(res)
+ } else {
+ models.Temp.findOne({
+ where: {
+ id: tempid
+ }
+ }).then(function (temp) {
+ if (!temp) {
+ response.errorNotFound(res)
+ } else {
+ res.header('Access-Control-Allow-Origin', '*')
+ res.send({
+ temp: temp.data
+ })
+ temp.destroy().catch(function (err) {
+ if (err) {
+ logger.error('remove temp failed: ' + err)
+ }
+ })
+ }
+ }).catch(function (err) {
+ logger.error(err)
+ return response.errorInternalError(res)
+ })
+ }
+ }
+})
+// post status
+statusRouter.post('/temp', urlencodedParser, function (req, res) {
+ var host = req.get('host')
+ if (config.alloworigin.indexOf(host) === -1) {
+ response.errorForbidden(res)
+ } else {
+ var data = req.body.data
+ if (!data) {
+ response.errorForbidden(res)
+ } else {
+ if (config.debug) {
+ logger.info('SERVER received temp from [' + host + ']: ' + req.body.data)
+ }
+ models.Temp.create({
+ data: data
+ }).then(function (temp) {
+ if (temp) {
+ res.header('Access-Control-Allow-Origin', '*')
+ res.send({
+ status: 'ok',
+ id: temp.id
+ })
+ } else {
+ response.errorInternalError(res)
+ }
+ }).catch(function (err) {
+ logger.error(err)
+ return response.errorInternalError(res)
+ })
+ }
+ }
+})
diff --git a/lib/web/userRouter.js b/lib/web/userRouter.js
new file mode 100644
index 00000000..ecfbaf8b
--- /dev/null
+++ b/lib/web/userRouter.js
@@ -0,0 +1,36 @@
+'use strict'
+
+const Router = require('express').Router
+
+const response = require('../response')
+const models = require('../models')
+const logger = require('../logger')
+
+const UserRouter = module.exports = Router()
+
+// get me info
+UserRouter.get('/me', function (req, res) {
+ if (req.isAuthenticated()) {
+ models.User.findOne({
+ where: {
+ id: req.user.id
+ }
+ }).then(function (user) {
+ if (!user) { return response.errorNotFound(res) }
+ var profile = models.User.getProfile(user)
+ res.send({
+ status: 'ok',
+ id: req.user.id,
+ name: profile.name,
+ photo: profile.photo
+ })
+ }).catch(function (err) {
+ logger.error('read me failed: ' + err)
+ return response.errorInternalError(res)
+ })
+ } else {
+ res.send({
+ status: 'forbidden'
+ })
+ }
+})
diff --git a/lib/web/utils.js b/lib/web/utils.js
new file mode 100644
index 00000000..c9016523
--- /dev/null
+++ b/lib/web/utils.js
@@ -0,0 +1,9 @@
+'use strict'
+
+const bodyParser = require('body-parser')
+
+// create application/x-www-form-urlencoded parser
+exports.urlencodedParser = bodyParser.urlencoded({
+ extended: false,
+ limit: 1024 * 1024 * 10 // 10 mb
+})
diff --git a/lib/workers/dmpWorker.js b/lib/workers/dmpWorker.js
index b0ed0f43..60db0a12 100644
--- a/lib/workers/dmpWorker.js
+++ b/lib/workers/dmpWorker.js
@@ -4,8 +4,8 @@ var DiffMatchPatch = require('diff-match-patch')
var dmp = new DiffMatchPatch()
// core
-var config = require('../config.js')
-var logger = require('../logger.js')
+var config = require('../config')
+var logger = require('../logger')
process.on('message', function (data) {
if (!data || !data.msg || !data.cacheKey) {