From 5cda55086acfc1000f0a0062045db50ad415db59 Mon Sep 17 00:00:00 2001
From: Christoph Witzany
Date: Sun, 29 Oct 2017 11:16:40 +0100
Subject: Add mattermost authentication

---
 README.md                            | 22 +++++++++-------
 app.json                             | 12 +++++++++
 config.json.example                  |  5 ++++
 lib/config/default.js                |  5 ++++
 lib/config/dockerSecret.js           |  4 +++
 lib/config/environment.js            |  5 ++++
 lib/config/index.js                  |  1 +
 lib/models/user.js                   |  9 +++++++
 lib/response.js                      |  1 +
 lib/web/auth/index.js                |  1 +
 lib/web/auth/mattermost/index.js     | 49 ++++++++++++++++++++++++++++++++++++
 package.json                         |  2 ++
 public/views/index/body.ejs          |  4 +--
 public/views/shared/signin-modal.ejs |  9 +++++--
 14 files changed, 116 insertions(+), 13 deletions(-)
 create mode 100644 lib/web/auth/mattermost/index.js

diff --git a/README.md b/README.md
index 718f6863..894b6906 100644
--- a/README.md
+++ b/README.md
@@ -8,8 +8,8 @@ HackMD
 [![version][github-version-badge]][github-release-page]
 
 
-HackMD lets you create realtime collaborative markdown notes on all platforms.  
-Inspired by Hackpad, with more focus on speed and flexibility.  
+HackMD lets you create realtime collaborative markdown notes on all platforms.
+Inspired by Hackpad, with more focus on speed and flexibility.
 Still in the early stage, feel free to fork or contribute to HackMD.
 
 Thanks for using! :smile:
@@ -97,13 +97,13 @@ If you are upgrading HackMD from an older version, follow these steps:
 
 * [migration-to-0.5.0](https://github.com/hackmdio/migration-to-0.5.0)
 
-We don't use LZString to compress socket.io data and DB data after version 0.5.0.  
+We don't use LZString to compress socket.io data and DB data after version 0.5.0.
 Please run the migration tool if you're upgrading from the old version.
 
 * [migration-to-0.4.0](https://github.com/hackmdio/migration-to-0.4.0)
 
-We've dropped MongoDB after version 0.4.0.  
-So here is the migration tool for you to transfer the old DB data to the new DB.  
+We've dropped MongoDB after version 0.4.0.
+So here is the migration tool for you to transfer the old DB data to the new DB.
 This tool is also used for official service.
 
 # Configuration
@@ -141,6 +141,9 @@ There are some configs you need to change in the files below
 | HMD_GITLAB_BASEURL | no example | GitLab authentication endpoint, set to use other endpoint than GitLab.com (optional) |
 | HMD_GITLAB_CLIENTID | no example | GitLab API client id |
 | HMD_GITLAB_CLIENTSECRET | no example | GitLab API client secret |
+| HMD_MATTERMOST_BASEURL | no example | Mattermost authentication endpoint |
+| HMD_MATTERMOST_CLIENTID | no example | Mattermost API client id |
+| HMD_MATTERMOST_CLIENTSECRET | no example | Mattermost API client secret |
 | HMD_DROPBOX_CLIENTID | no example | Dropbox API client id |
 | HMD_DROPBOX_CLIENTSECRET | no example | Dropbox API client secret |
 | HMD_GOOGLE_CLIENTID | no example | Google API client id |
@@ -216,7 +219,7 @@ There are some configs you need to change in the files below
 
 | service | settings location | description |
 | ------- | --------- | ----------- |
-| facebook, twitter, github, gitlab, dropbox, google, ldap | environment variables or `config.json` | for signin |
+| facebook, twitter, github, gitlab, mattermost, dropbox, google, ldap | environment variables or `config.json` | for signin |
 | imgur | environment variables or `config.json` | for image upload |
 | google drive(`google/apiKey`, `google/clientID`), dropbox(`dropbox/appKey`) | `config.json` | for export and import |
 
@@ -228,6 +231,7 @@ There are some configs you need to change in the files below
 | twitter | `/auth/twitter/callback` |
 | github | `/auth/github/callback` |
 | gitlab | `/auth/gitlab/callback` |
+| mattermost | `/auth/mattermost/callback` |
 | dropbox | `/auth/dropbox/callback` |
 | google | `/auth/google/callback` |
 
@@ -249,9 +253,9 @@ hackmd/
 
 ## Operational Transformation
 
-From 0.3.2, we started supporting operational transformation.  
-It makes concurrent editing safe and will not break up other users' operations.  
-Additionally, now can show other clients' selections.  
+From 0.3.2, we started supporting operational transformation.
+It makes concurrent editing safe and will not break up other users' operations.
+Additionally, now can show other clients' selections.
 See more at [http://operational-transformation.github.io/](http://operational-transformation.github.io/)
 
 
diff --git a/app.json b/app.json
index d1804c52..b2116eb6 100644
--- a/app.json
+++ b/app.json
@@ -100,6 +100,18 @@
             "description": "GitLab API client scope (optional)",
             "required": false
         },
+        "HMD_MATTERMOST_BASEURL": {
+            "description": "Mattermost authentication endpoint",
+            "required": false
+        },
+        "HMD_MATTERMOST_CLIENTID": {
+            "description": "Mattermost API client id",
+            "required": false
+        },
+        "HMD_MATTERMOST_CLIENTSECRET": {
+            "description": "Mattermost API client secret",
+            "required": false
+        },
         "HMD_DROPBOX_CLIENTID": {
             "description": "Dropbox API client id",
             "required": false
diff --git a/config.json.example b/config.json.example
index e2d774c7..6dd20b79 100644
--- a/config.json.example
+++ b/config.json.example
@@ -48,6 +48,11 @@
             "clientSecret": "change this",
             "scope": "use 'read_user' scope for auth user only or remove this property if you need gitlab snippet import/export support (will result to be default scope 'api')"
         },
+        "mattermost": {
+            "baseURL": "change this",
+            "clientID": "change this",
+            "clientSecret": "change this"
+        },
         "dropbox": {
             "clientID": "change this",
             "clientSecret": "change this",
diff --git a/lib/config/default.js b/lib/config/default.js
index e7e2e4b3..273bad02 100644
--- a/lib/config/default.js
+++ b/lib/config/default.js
@@ -74,6 +74,11 @@ module.exports = {
     clientSecret: undefined,
     scope: undefined
   },
+  mattermost: {
+    baseURL: undefined,
+    clientID: undefined,
+    clientSecret: undefined
+  },
   dropbox: {
     clientID: undefined,
     clientSecret: undefined
diff --git a/lib/config/dockerSecret.js b/lib/config/dockerSecret.js
index eea2fafd..ac54fd19 100644
--- a/lib/config/dockerSecret.js
+++ b/lib/config/dockerSecret.js
@@ -38,6 +38,10 @@ if (fs.existsSync(basePath)) {
       clientID: getSecret('gitlab_clientID'),
       clientSecret: getSecret('gitlab_clientSecret')
     },
+    mattermost: {
+      clientID: getSecret('mattermost_clientID'),
+      clientSecret: getSecret('mattermost_clientSecret')
+    },
     dropbox: {
       clientID: getSecret('dropbox_clientID'),
       clientSecret: getSecret('dropbox_clientSecret')
diff --git a/lib/config/environment.js b/lib/config/environment.js
index 6f33d140..0c272f05 100644
--- a/lib/config/environment.js
+++ b/lib/config/environment.js
@@ -49,6 +49,11 @@ module.exports = {
     clientSecret: process.env.HMD_GITLAB_CLIENTSECRET,
     scope: process.env.HMD_GITLAB_SCOPE
   },
+  mattermost: {
+    baseURL: process.env.HMD_MATTERMOST_BASEURL,
+    clientID: process.env.HMD_MATTERMOST_CLIENTID,
+    clientSecret: process.env.HMD_MATTERMOST_CLIENTSECRET
+  },
   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 dfad28ed..addd8ba6 100644
--- a/lib/config/index.js
+++ b/lib/config/index.js
@@ -90,6 +90,7 @@ config.isTwitterEnable = config.twitter.consumerKey && config.twitter.consumerSe
 config.isEmailEnable = config.email
 config.isGitHubEnable = config.github.clientID && config.github.clientSecret
 config.isGitLabEnable = config.gitlab.clientID && config.gitlab.clientSecret
+config.isMattermostEnable = config.mattermost.clientID && config.mattermost.clientSecret
 config.isLDAPEnable = config.ldap.url
 config.isPDFExportEnable = config.allowpdfexport
 
diff --git a/lib/models/user.js b/lib/models/user.js
index e59b86cc..27566def 100644
--- a/lib/models/user.js
+++ b/lib/models/user.js
@@ -111,6 +111,15 @@ module.exports = function (sequelize, DataTypes) {
               photo = letterAvatars(profile.username)
             }
             break
+          case 'mattermost':
+            photo = profile.avatarUrl
+            if (photo) {
+              if (bigger) photo = photo.replace(/(\?s=)\d*$/i, '$1400')
+              else photo = photo.replace(/(\?s=)\d*$/i, '$196')
+            } else {
+              photo = letterAvatars(profile.username)
+            }
+            break
           case 'dropbox':
             // no image api provided, use gravatar
             photo = 'https://www.gravatar.com/avatar/' + md5(profile.emails[0].value)
diff --git a/lib/response.js b/lib/response.js
index 9e39ffb5..0c6a95ea 100755
--- a/lib/response.js
+++ b/lib/response.js
@@ -64,6 +64,7 @@ function showIndex (req, res, next) {
     twitter: config.isTwitterEnable,
     github: config.isGitHubEnable,
     gitlab: config.isGitLabEnable,
+    mattermost: config.isMattermostEnable,
     dropbox: config.isDropboxEnable,
     google: config.isGoogleEnable,
     ldap: config.isLDAPEnable,
diff --git a/lib/web/auth/index.js b/lib/web/auth/index.js
index b5ca8434..4b618101 100644
--- a/lib/web/auth/index.js
+++ b/lib/web/auth/index.js
@@ -33,6 +33,7 @@ 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.isMattermostEnable) authRouter.use(require('./mattermost'))
 if (config.isDropboxEnable) authRouter.use(require('./dropbox'))
 if (config.isGoogleEnable) authRouter.use(require('./google'))
 if (config.isLDAPEnable) authRouter.use(require('./ldap'))
diff --git a/lib/web/auth/mattermost/index.js b/lib/web/auth/mattermost/index.js
new file mode 100644
index 00000000..9ccf3de5
--- /dev/null
+++ b/lib/web/auth/mattermost/index.js
@@ -0,0 +1,49 @@
+'use strict'
+
+const Router = require('express').Router
+const passport = require('passport')
+const Mattermost = require('mattermost')
+const OAuthStrategy = require('passport-oauth2').Strategy
+const config = require('../../../config')
+const {setReturnToFromReferer, passportGeneralCallback} = require('../utils')
+
+const mattermost = new Mattermost.Client()
+
+let mattermostAuth = module.exports = Router()
+
+let mattermostStrategy = new OAuthStrategy({
+  authorizationURL: config.mattermost.baseURL + '/oauth/authorize',
+  tokenURL: config.mattermost.baseURL + '/oauth/access_token',
+  clientID: config.mattermost.clientID,
+  clientSecret: config.mattermost.clientSecret,
+  callbackURL: config.serverurl + '/auth/mattermost/callback'
+}, passportGeneralCallback)
+
+mattermostStrategy.userProfile = (accessToken, done) => {
+  mattermost.setUrl(config.mattermost.baseURL)
+  mattermost.token = accessToken
+  mattermost.useHeaderToken()
+  mattermost.getMe(
+      (data) => {
+        done(null, data)
+      },
+      (err) => {
+        done(err)
+      }
+  )
+}
+
+passport.use(mattermostStrategy)
+
+mattermostAuth.get('/auth/mattermost', function (req, res, next) {
+  setReturnToFromReferer(req)
+  passport.authenticate('oauth2')(req, res, next)
+})
+
+// mattermost auth callback
+mattermostAuth.get('/auth/mattermost/callback',
+  passport.authenticate('oauth2', {
+    successReturnToOrRedirect: config.serverurl + '/',
+    failureRedirect: config.serverurl + '/'
+  })
+)
diff --git a/package.json b/package.json
index a78bb2bd..b4942c5a 100644
--- a/package.json
+++ b/package.json
@@ -76,6 +76,7 @@
     "markdown-pdf": "^7.0.0",
     "mathjax": "~2.7.0",
     "mermaid": "~7.1.0",
+    "mattermost": "^3.4.0",
     "meta-marked": "^0.4.2",
     "method-override": "^2.3.7",
     "moment": "^2.17.1",
@@ -91,6 +92,7 @@
     "passport-google-oauth20": "^1.0.0",
     "passport-ldapauth": "^0.6.0",
     "passport-local": "^1.0.0",
+    "passport-oauth2": "^1.4.0",
     "passport-twitter": "^1.0.4",
     "passport.socketio": "^3.7.0",
     "pdfobject": "^2.0.201604172",
diff --git a/public/views/index/body.ejs b/public/views/index/body.ejs
index b9c5c426..230eb117 100644
--- a/public/views/index/body.ejs
+++ b/public/views/index/body.ejs
@@ -15,7 +15,7 @@
                                 <% if(allowAnonymous) { %>
                                 <a type="button" href="<%- url %>/new" class="btn btn-sm btn-primary"><i class="fa fa-plus"></i> <%= __('New guest note') %></a>
                                 <% } %>
-                                <% if(facebook || twitter || github || gitlab || dropbox || google || ldap || email) { %>
+                                <% if(facebook || twitter || github || gitlab || mattermost || dropbox || google || ldap || email) { %>
                                 <button class="btn btn-sm btn-success ui-signin" data-toggle="modal" data-target=".signin-modal"><%= __('Sign In') %></button>
                                 <% } %>
                             </div>
@@ -48,7 +48,7 @@
                     <% if (errorMessage && errorMessage.length > 0) { %>
                     <div class="alert alert-danger" style="max-width: 400px; margin: 0 auto;"><%= errorMessage %></div>
                     <% } %>
-                    <% if(facebook || twitter || github || gitlab || dropbox || google || ldap || email) { %>
+                    <% if(facebook || twitter || github || gitlab || mattermost || dropbox || google || ldap || email) { %>
                     <span class="ui-signin">
                         <br>
                         <a type="button" class="btn btn-lg btn-success ui-signin" data-toggle="modal" data-target=".signin-modal" style="min-width: 200px;"><%= __('Sign In') %></a>
diff --git a/public/views/shared/signin-modal.ejs b/public/views/shared/signin-modal.ejs
index a8af62e7..89b542e9 100644
--- a/public/views/shared/signin-modal.ejs
+++ b/public/views/shared/signin-modal.ejs
@@ -28,6 +28,11 @@
                     <i class="fa fa-gitlab"></i> <%= __('Sign in via %s', 'GitLab') %>
                 </a>
                 <% } %>
+                <% if(mattermost) { %>
+                <a href="<%- url %>/auth/mattermost" class="btn btn-lg btn-block btn-social btn-soundcloud">
+                    <i class="fa fa-mattermost"></i> <%= __('Sign in via %s', 'Mattermost') %>
+                </a>
+                <% } %>
                 <% if(dropbox) { %>
                 <a href="<%- url %>/auth/dropbox" class="btn btn-lg btn-block btn-social btn-dropbox">
                     <i class="fa fa-dropbox"></i> <%= __('Sign in via %s', 'Dropbox') %>
@@ -38,7 +43,7 @@
                     <i class="fa fa-google"></i> <%= __('Sign in via %s', 'Google') %>
                 </a>
                 <% } %>
-                <% if((facebook || twitter || github || gitlab || dropbox || google) && ldap) { %>
+                <% if((facebook || twitter || github || gitlab || mattermost || dropbox || google) && ldap) { %>
                 <hr>
                 <% }%>
                 <% if(ldap) { %>
@@ -63,7 +68,7 @@
                     </div>
                 </form>
                 <% } %>
-                <% if((facebook || twitter || github || gitlab || dropbox || google || ldap) && email) { %>
+                <% if((facebook || twitter || github || gitlab || mattermost || dropbox || google || ldap) && email) { %>
                 <hr>
                 <% }%>
                 <% if(email) { %>
-- 
cgit v1.2.3