summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/config/default.js12
-rw-r--r--lib/config/dockerSecret.js3
-rw-r--r--lib/config/environment.js15
-rw-r--r--lib/config/index.js8
-rw-r--r--lib/migrations/20180326103000-use-text-in-tokens.js23
-rw-r--r--lib/models/user.js4
-rw-r--r--lib/response.js12
-rw-r--r--lib/web/auth/index.js1
-rw-r--r--lib/web/auth/oauth2/index.js106
-rw-r--r--lib/web/imageRouter/azure.js35
-rw-r--r--lib/web/imageRouter/filesystem.js3
-rw-r--r--lib/web/imageRouter/imgur.js2
-rw-r--r--lib/web/imageRouter/minio.js3
-rw-r--r--lib/web/imageRouter/s3.js3
14 files changed, 220 insertions, 10 deletions
diff --git a/lib/config/default.js b/lib/config/default.js
index 68849d36..1b124b3e 100644
--- a/lib/config/default.js
+++ b/lib/config/default.js
@@ -56,7 +56,7 @@ module.exports = {
heartbeatTimeout: 10000,
// document
documentMaxLength: 100000,
- // image upload setting, available options are imgur/s3/filesystem
+ // image upload setting, available options are imgur/s3/filesystem/azure
imageUploadType: 'filesystem',
imgur: {
clientID: undefined
@@ -74,7 +74,17 @@ module.exports = {
port: 9000
},
s3bucket: undefined,
+ azure: {
+ connectionString: undefined,
+ container: undefined
+ },
// authentication
+ oauth2: {
+ authorizationURL: undefined,
+ tokenURL: undefined,
+ clientID: undefined,
+ clientSecret: undefined
+ },
facebook: {
clientID: undefined,
clientSecret: undefined
diff --git a/lib/config/dockerSecret.js b/lib/config/dockerSecret.js
index b9116cd3..fd66ddfe 100644
--- a/lib/config/dockerSecret.js
+++ b/lib/config/dockerSecret.js
@@ -22,6 +22,9 @@ if (fs.existsSync(basePath)) {
accessKeyId: getSecret('s3_acccessKeyId'),
secretAccessKey: getSecret('s3_secretAccessKey')
},
+ azure: {
+ connectionString: getSecret('azure_connectionString')
+ },
facebook: {
clientID: getSecret('facebook_clientID'),
clientSecret: getSecret('facebook_clientSecret')
diff --git a/lib/config/environment.js b/lib/config/environment.js
index 3dde4786..e1c11569 100644
--- a/lib/config/environment.js
+++ b/lib/config/environment.js
@@ -45,6 +45,10 @@ module.exports = {
port: toIntegerConfig(process.env.HMD_MINIO_PORT)
},
s3bucket: process.env.HMD_S3_BUCKET,
+ azure: {
+ connectionString: process.env.HMD_AZURE_CONNECTION_STRING,
+ container: process.env.HMD_AZURE_CONTAINER
+ },
facebook: {
clientID: process.env.HMD_FACEBOOK_CLIENTID,
clientSecret: process.env.HMD_FACEBOOK_CLIENTSECRET
@@ -68,6 +72,17 @@ module.exports = {
clientID: process.env.HMD_MATTERMOST_CLIENTID,
clientSecret: process.env.HMD_MATTERMOST_CLIENTSECRET
},
+ oauth2: {
+ baseURL: process.env.HMD_OAUTH2_BASEURL,
+ userProfileURL: process.env.HMD_OAUTH2_USER_PROFILE_URL,
+ userProfileUsernameAttr: process.env.HMD_OAUTH2_USER_PROFILE_USERNAME_ATTR,
+ userProfileDisplayNameAttr: process.env.HMD_OAUTH2_USER_PROFILE_DISPLAY_NAME_ATTR,
+ userProfileEmailAttr: process.env.HMD_OAUTH2_USER_PROFILE_EMAIL_ATTR,
+ tokenURL: process.env.HMD_OAUTH2_TOKEN_URL,
+ authorizationURL: process.env.HMD_OAUTH2_AUTHORIZATION_URL,
+ clientID: process.env.HMD_OAUTH2_CLIENT_ID,
+ clientSecret: process.env.HMD_OAUTH2_CLIENT_SECRET
+ },
dropbox: {
clientID: process.env.HMD_DROPBOX_CLIENTID,
clientSecret: process.env.HMD_DROPBOX_CLIENTSECRET,
diff --git a/lib/config/index.js b/lib/config/index.js
index bdba5e0e..7853dbad 100644
--- a/lib/config/index.js
+++ b/lib/config/index.js
@@ -99,8 +99,12 @@ config.isGitLabEnable = config.gitlab.clientID && config.gitlab.clientSecret
config.isMattermostEnable = config.mattermost.clientID && config.mattermost.clientSecret
config.isLDAPEnable = config.ldap.url
config.isSAMLEnable = config.saml.idpSsoUrl
+config.isOAuth2Enable = config.oauth2.clientID && config.oauth2.clientSecret
config.isPDFExportEnable = config.allowPDFExport
+// Only update i18n files in development setups
+config.updateI18nFiles = (env === Environment.development)
+
// merge legacy values
let keys = Object.keys(config)
const uppercase = /[A-Z]/
@@ -127,8 +131,8 @@ if (config.sessionSecret === 'secret') {
}
// 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"')
+if (['filesystem', 's3', 'minio', 'imgur', 'azure'].indexOf(config.imageUploadType) === -1) {
+ logger.error('"imageuploadtype" is not correctly set. Please use "filesystem", "s3", "minio", "azure" or "imgur". Defaulting to "imgur"')
config.imageUploadType = 'imgur'
}
diff --git a/lib/migrations/20180326103000-use-text-in-tokens.js b/lib/migrations/20180326103000-use-text-in-tokens.js
new file mode 100644
index 00000000..f9507747
--- /dev/null
+++ b/lib/migrations/20180326103000-use-text-in-tokens.js
@@ -0,0 +1,23 @@
+'use strict'
+
+module.exports = {
+ up: function (queryInterface, Sequelize) {
+ return queryInterface.changeColumn('Users', 'accessToken', {
+ type: Sequelize.TEXT
+ }).then(function () {
+ return queryInterface.changeColumn('Users', 'refreshToken', {
+ type: Sequelize.TEXT
+ })
+ })
+ },
+
+ down: function (queryInterface, Sequelize) {
+ return queryInterface.changeColumn('Users', 'accessToken', {
+ type: Sequelize.STRING
+ }).then(function () {
+ return queryInterface.changeColumn('Users', 'refreshToken', {
+ type: Sequelize.STRING
+ })
+ })
+ }
+}
diff --git a/lib/models/user.js b/lib/models/user.js
index 019aab7e..5dd13869 100644
--- a/lib/models/user.js
+++ b/lib/models/user.js
@@ -26,10 +26,10 @@ module.exports = function (sequelize, DataTypes) {
type: DataTypes.TEXT
},
accessToken: {
- type: DataTypes.STRING
+ type: DataTypes.TEXT
},
refreshToken: {
- type: DataTypes.STRING
+ type: DataTypes.TEXT
},
deleteToken: {
type: DataTypes.UUID,
diff --git a/lib/response.js b/lib/response.js
index b1b89c78..4cfa9a74 100644
--- a/lib/response.js
+++ b/lib/response.js
@@ -18,7 +18,13 @@ var utils = require('./utils')
// public
var response = {
errorForbidden: function (res) {
- responseError(res, '403', 'Forbidden', 'oh no.')
+ const {req} = res
+ if (req.user) {
+ responseError(res, '403', 'Forbidden', 'oh no.')
+ } else {
+ req.flash('error', 'You are not allowed to access this page. Maybe try logging in?')
+ res.redirect(config.serverURL)
+ }
},
errorNotFound: function (res) {
responseError(res, '404', 'Not Found', 'oops.')
@@ -74,6 +80,8 @@ function showIndex (req, res, next) {
ldap: config.isLDAPEnable,
ldapProviderName: config.ldap.providerName,
saml: config.isSAMLEnable,
+ oauth2: config.isOAuth2Enable,
+ oauth2ProviderName: config.oauth2.providerName,
email: config.isEmailEnable,
allowEmailRegister: config.allowEmailRegister,
allowPDFExport: config.allowPDFExport,
@@ -126,7 +134,9 @@ function responseHackMD (res, note) {
google: config.isGoogleEnable,
ldap: config.isLDAPEnable,
ldapProviderName: config.ldap.providerName,
+ oauth2ProviderName: config.oauth2.providerName,
saml: config.isSAMLEnable,
+ oauth2: config.isOAuth2Enable,
email: config.isEmailEnable,
allowEmailRegister: config.allowEmailRegister,
allowPDFExport: config.allowPDFExport
diff --git a/lib/web/auth/index.js b/lib/web/auth/index.js
index eb42fb36..61e7c3f9 100644
--- a/lib/web/auth/index.js
+++ b/lib/web/auth/index.js
@@ -43,6 +43,7 @@ if (config.isDropboxEnable) authRouter.use(require('./dropbox'))
if (config.isGoogleEnable) authRouter.use(require('./google'))
if (config.isLDAPEnable) authRouter.use(require('./ldap'))
if (config.isSAMLEnable) authRouter.use(require('./saml'))
+if (config.isOAuth2Enable) authRouter.use(require('./oauth2'))
if (config.isEmailEnable) authRouter.use(require('./email'))
// logout
diff --git a/lib/web/auth/oauth2/index.js b/lib/web/auth/oauth2/index.js
new file mode 100644
index 00000000..f2a3132d
--- /dev/null
+++ b/lib/web/auth/oauth2/index.js
@@ -0,0 +1,106 @@
+'use strict'
+
+const Router = require('express').Router
+const passport = require('passport')
+const OAuth2Strategy = require('passport-oauth2').Strategy
+const config = require('../../../config')
+const {setReturnToFromReferer, passportGeneralCallback} = require('../utils')
+
+let oauth2Auth = module.exports = Router()
+
+class OAuth2CustomStrategy extends OAuth2Strategy {
+ constructor (options, verify) {
+ options.customHeaders = options.customHeaders || {}
+ super(options, verify)
+ this.name = 'oauth2'
+ this._userProfileURL = options.userProfileURL
+ this._oauth2.useAuthorizationHeaderforGET(true)
+ }
+
+ userProfile (accessToken, done) {
+ this._oauth2.get(this._userProfileURL, accessToken, function (err, body, res) {
+ var json
+
+ if (err) {
+ return done(new passport.InternalOAuthError('Failed to fetch user profile', err))
+ }
+
+ try {
+ json = JSON.parse(body)
+ } catch (ex) {
+ return done(new Error('Failed to parse user profile'))
+ }
+
+ let profile = parseProfile(json)
+ profile.provider = 'oauth2'
+
+ done(null, profile)
+ })
+ }
+}
+
+function extractProfileAttribute (data, path) {
+ // can handle stuff like `attrs[0].name`
+ path = path.split('.')
+ for (const segment of path) {
+ const m = segment.match(/([\d\w]+)\[(.*)\]/)
+ data = m ? data[m[1]][m[2]] : data[segment]
+ }
+ return data
+}
+
+function parseProfile (data) {
+ const username = extractProfileAttribute(data, config.oauth2.userProfileUsernameAttr)
+ const displayName = extractProfileAttribute(data, config.oauth2.userProfileDisplayNameAttr)
+ const email = extractProfileAttribute(data, config.oauth2.userProfileEmailAttr)
+
+ return {
+ id: username,
+ username: username,
+ displayName: displayName,
+ email: email
+ }
+}
+
+OAuth2CustomStrategy.prototype.userProfile = function (accessToken, done) {
+ this._oauth2.get(this._userProfileURL, accessToken, function (err, body, res) {
+ var json
+
+ if (err) {
+ return done(new passport.InternalOAuthError('Failed to fetch user profile', err))
+ }
+
+ try {
+ json = JSON.parse(body)
+ } catch (ex) {
+ return done(new Error('Failed to parse user profile'))
+ }
+
+ let profile = parseProfile(json)
+ profile.provider = 'oauth2'
+
+ done(null, profile)
+ })
+}
+
+passport.use(new OAuth2CustomStrategy({
+ authorizationURL: config.oauth2.authorizationURL,
+ tokenURL: config.oauth2.tokenURL,
+ clientID: config.oauth2.clientID,
+ clientSecret: config.oauth2.clientSecret,
+ callbackURL: config.serverURL + '/auth/oauth2/callback',
+ userProfileURL: config.oauth2.userProfileURL
+}, passportGeneralCallback))
+
+oauth2Auth.get('/auth/oauth2', function (req, res, next) {
+ setReturnToFromReferer(req)
+ passport.authenticate('oauth2')(req, res, next)
+})
+
+// github auth callback
+oauth2Auth.get('/auth/oauth2/callback',
+ passport.authenticate('oauth2', {
+ successReturnToOrRedirect: config.serverurl + '/',
+ failureRedirect: config.serverurl + '/'
+ })
+)
diff --git a/lib/web/imageRouter/azure.js b/lib/web/imageRouter/azure.js
new file mode 100644
index 00000000..cc98e5fc
--- /dev/null
+++ b/lib/web/imageRouter/azure.js
@@ -0,0 +1,35 @@
+'use strict'
+const path = require('path')
+
+const config = require('../../config')
+const logger = require('../../logger')
+
+const azure = require('azure-storage')
+
+exports.uploadImage = function (imagePath, callback) {
+ if (!imagePath || typeof imagePath !== 'string') {
+ callback(new Error('Image path is missing or wrong'), null)
+ return
+ }
+
+ if (!callback || typeof callback !== 'function') {
+ logger.error('Callback has to be a function')
+ return
+ }
+
+ var azureBlobService = azure.createBlobService(config.azure.connectionString)
+
+ azureBlobService.createContainerIfNotExists(config.azure.container, { publicAccessLevel: 'blob' }, function (err, result, response) {
+ if (err) {
+ callback(new Error(err.message), null)
+ } else {
+ azureBlobService.createBlockBlobFromLocalFile(config.azure.container, path.basename(imagePath), imagePath, function (err, result, response) {
+ if (err) {
+ callback(new Error(err.message), null)
+ } else {
+ callback(null, azureBlobService.getUrl(config.azure.container, result.name))
+ }
+ })
+ }
+ })
+}
diff --git a/lib/web/imageRouter/filesystem.js b/lib/web/imageRouter/filesystem.js
index da76ba5e..145876a9 100644
--- a/lib/web/imageRouter/filesystem.js
+++ b/lib/web/imageRouter/filesystem.js
@@ -2,6 +2,7 @@
const url = require('url')
const config = require('../../config')
+const logger = require('../../logger')
exports.uploadImage = function (imagePath, callback) {
if (!imagePath || typeof imagePath !== 'string') {
@@ -10,7 +11,7 @@ exports.uploadImage = function (imagePath, callback) {
}
if (!callback || typeof callback !== 'function') {
- callback(new Error('Callback has to be a function'), null)
+ logger.error('Callback has to be a function')
return
}
diff --git a/lib/web/imageRouter/imgur.js b/lib/web/imageRouter/imgur.js
index 31d5f55c..2a20002c 100644
--- a/lib/web/imageRouter/imgur.js
+++ b/lib/web/imageRouter/imgur.js
@@ -11,7 +11,7 @@ exports.uploadImage = function (imagePath, callback) {
}
if (!callback || typeof callback !== 'function') {
- callback(new Error('Callback has to be a function'), null)
+ logger.error('Callback has to be a function')
return
}
diff --git a/lib/web/imageRouter/minio.js b/lib/web/imageRouter/minio.js
index 099cb926..b921c2d2 100644
--- a/lib/web/imageRouter/minio.js
+++ b/lib/web/imageRouter/minio.js
@@ -4,6 +4,7 @@ const path = require('path')
const config = require('../../config')
const {getImageMimeType} = require('../../utils')
+const logger = require('../../logger')
const Minio = require('minio')
const minioClient = new Minio.Client({
@@ -21,7 +22,7 @@ exports.uploadImage = function (imagePath, callback) {
}
if (!callback || typeof callback !== 'function') {
- callback(new Error('Callback has to be a function'), null)
+ logger.error('Callback has to be a function')
return
}
diff --git a/lib/web/imageRouter/s3.js b/lib/web/imageRouter/s3.js
index bcd3ea60..626fe148 100644
--- a/lib/web/imageRouter/s3.js
+++ b/lib/web/imageRouter/s3.js
@@ -4,6 +4,7 @@ const path = require('path')
const config = require('../../config')
const {getImageMimeType} = require('../../utils')
+const logger = require('../../logger')
const AWS = require('aws-sdk')
const awsConfig = new AWS.Config(config.s3)
@@ -16,7 +17,7 @@ exports.uploadImage = function (imagePath, callback) {
}
if (!callback || typeof callback !== 'function') {
- callback(new Error('Callback has to be a function'), null)
+ logger.error('Callback has to be a function')
return
}