diff options
Diffstat (limited to 'lib')
-rw-r--r-- | lib/config/default.js | 12 | ||||
-rw-r--r-- | lib/config/dockerSecret.js | 3 | ||||
-rw-r--r-- | lib/config/environment.js | 15 | ||||
-rw-r--r-- | lib/config/index.js | 8 | ||||
-rw-r--r-- | lib/migrations/20180326103000-use-text-in-tokens.js | 23 | ||||
-rw-r--r-- | lib/models/user.js | 4 | ||||
-rw-r--r-- | lib/response.js | 12 | ||||
-rw-r--r-- | lib/web/auth/index.js | 1 | ||||
-rw-r--r-- | lib/web/auth/oauth2/index.js | 106 | ||||
-rw-r--r-- | lib/web/imageRouter/azure.js | 35 | ||||
-rw-r--r-- | lib/web/imageRouter/filesystem.js | 3 | ||||
-rw-r--r-- | lib/web/imageRouter/imgur.js | 2 | ||||
-rw-r--r-- | lib/web/imageRouter/minio.js | 3 | ||||
-rw-r--r-- | lib/web/imageRouter/s3.js | 3 |
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 } |