Browse Source

Azure AD support + defaultReadAccess option + All Pages UI work

NGPixel 8 years ago
parent
commit
7c1dd8b92a

+ 4 - 0
app/data.yml

@@ -21,6 +21,7 @@ defaults:
     lang: en
     public: false
     auth:
+      defaultReadAccess: false
       local:
         enabled: true
       microsoft:
@@ -35,6 +36,8 @@ defaults:
         enabled: false
       ldap:
         enabled: false
+      azure:
+        enabled: false
     db: mongodb://localhost/wiki
     sessionSecret: null
     admin: null
@@ -55,6 +58,7 @@ defaults:
       loggly: false
       papertrail: false
       rollbar: false
+      sentry: false
 langs:
   -
     id: en

+ 1 - 0
client/js/app.js

@@ -54,6 +54,7 @@ $(() => {
   // ====================================
 
   require('./pages/view.js')(alerts)
+  require('./pages/all.js')(alerts, socket)
   require('./pages/create.js')(alerts, socket)
   require('./pages/edit.js')(alerts, socket)
   require('./pages/source.js')(alerts)

+ 9 - 0
client/js/pages/all.js

@@ -0,0 +1,9 @@
+'use strict'
+
+import $ from 'jquery'
+
+module.exports = (alerts, socket) => {
+  if ($('#page-type-all').length) {
+
+  }
+}

+ 29 - 40
client/scss/components/collapsable-nav.scss

@@ -1,25 +1,52 @@
 .has-collapsable-nav {
   background-color: mc('blue-grey', '50');
+  display: flex;
+  justify-content: flex-start;
+  align-items: stretch;
 }
 
 .collapsable-nav {
   width: 300px;
-  background-color: mc($primary, '500');
+  background-color: mc('blue-grey', '900');
   color: #FFF;
   min-height: 80vh;
   transition: all .6s ease;
+  border-left: 1px solid darken(mc('blue-grey', '900'), 5%);
+
+  &:last-child {
+    border-bottom-right-radius: 5px;
+  }
 
   &.has-children {
     width: 50px;
+    background-color: mc($primary, '500');
+    border-left: 1px solid mc($primary, '700');
+
+    &:nth-child(2) {
+      border-left: 1px solid darken(mc('blue-grey', '900'), 5%);
+    }
 
     li {
+      border-top: none;
       display: none;
     }
+
   }
 
   li {
     display: flex;
-    border-top: 1px solid mc($primary, '700');
+    border-top: 1px solid darken(mc('blue-grey', '900'), 5%);
+
+    &.is-title {
+      background-color: mc('blue-grey', '800');
+      padding: 8px 15px;
+      color: mc('blue-grey', '300');
+      font-size: 13px;
+      letter-spacing: 1px;
+      text-transform: uppercase;
+      box-shadow: 0 0 5px rgba(0,0,0,0.3);
+      margin-right:1px;
+    }
 
     &.is-active {
       display: flex;
@@ -32,48 +59,10 @@
       a {
         height: 50px;
       }
-
-    }
-
-  }
-
-  a {
-    display: flex;
-    height: 40px;
-    width: 100%;
-    align-items: center;
-    padding: 0 15px;
-    color: #FFF;
-    cursor: pointer;
-    transition: all .4s ease;
-    background-color: rgba(0,0,0,0);
-
-    i {
-      font-size: 14px;
-      margin-right: 10px;
-    }
-
-    &:hover {
-      background-color: rgba(0,0,0,.1);
-      text-decoration: none;
     }
 
   }
 
-}
-
-.collapsable-nav-sub {
-  width: 300px;
-  background-color: mc('blue-grey', '800');
-  border-left: 1px solid mc('blue-grey', '900');
-  color: #FFF;
-  min-height: 80vh;
-
-  li {
-    display: flex;
-    border-top: 1px solid mc('blue-grey', '900');
-  }
-
   a {
     display: flex;
     height: 40px;

+ 0 - 4
client/scss/components/sidebar.scss

@@ -87,10 +87,6 @@
     width: 50px;
 
     aside {
-      .sidebar-label {
-        margin-right: 1px;
-      }
-
       .sidebar-menu li a {
         padding: 10px 0;
         justify-content: center;

+ 7 - 0
config.sample.yml

@@ -54,6 +54,7 @@ lang: en
 public: false
 
 auth:
+  defaultReadAccess: false
   local:
     enabled: true
   google:
@@ -85,6 +86,12 @@ auth:
     searchFilter: (uid={{username}})
     tlsEnabled: false
     tlsCertPath: C:\example\root_ca_cert.crt
+  azure:
+    enabled: false
+    clientID: APP_ID
+    clientSecret: APP_SECRET_KEY,
+    resource: '00000002-0000-0000-c000-000000000000',
+    tenant: 'YOUR_TENANT.onmicrosoft.com'
 
 # ---------------------------------------------------------------------
 # Secret key to use when encrypting sessions

+ 2 - 0
controllers/auth.js

@@ -94,12 +94,14 @@ router.get('/login/google', passport.authenticate('google', { scope: ['profile',
 router.get('/login/facebook', passport.authenticate('facebook', { scope: ['public_profile', 'email'] }))
 router.get('/login/github', passport.authenticate('github', { scope: ['user:email'] }))
 router.get('/login/slack', passport.authenticate('slack', { scope: ['identity.basic', 'identity.email'] }))
+router.get('/login/azure', passport.authenticate('azure_ad_oauth2'))
 
 router.get('/login/ms/callback', passport.authenticate('windowslive', { failureRedirect: '/login', successRedirect: '/' }))
 router.get('/login/google/callback', passport.authenticate('google', { failureRedirect: '/login', successRedirect: '/' }))
 router.get('/login/facebook/callback', passport.authenticate('facebook', { failureRedirect: '/login', successRedirect: '/' }))
 router.get('/login/github/callback', passport.authenticate('github', { failureRedirect: '/login', successRedirect: '/' }))
 router.get('/login/slack/callback', passport.authenticate('slack', { failureRedirect: '/login', successRedirect: '/' }))
+router.get('/login/azure/callback', passport.authenticate('azure_ad_oauth2', { failureRedirect: '/login', successRedirect: '/' }))
 
 /**
  * Logout

+ 33 - 7
libs/auth.js

@@ -2,13 +2,6 @@
 
 /* global appconfig, appdata, db, winston */
 
-const LocalStrategy = require('passport-local').Strategy
-const GoogleStrategy = require('passport-google-oauth20').Strategy
-const WindowsLiveStrategy = require('passport-windowslive').Strategy
-const FacebookStrategy = require('passport-facebook').Strategy
-const GitHubStrategy = require('passport-github2').Strategy
-const SlackStrategy = require('passport-slack').Strategy
-const LdapStrategy = require('passport-ldapauth').Strategy
 const fs = require('fs')
 
 module.exports = function (passport) {
@@ -34,6 +27,7 @@ module.exports = function (passport) {
   // Local Account
 
   if (!appdata.capabilities.manyAuthProviders || (appconfig.auth.local && appconfig.auth.local.enabled)) {
+    const LocalStrategy = require('passport-local').Strategy
     passport.use('local',
       new LocalStrategy({
         usernameField: 'email',
@@ -60,6 +54,7 @@ module.exports = function (passport) {
   // Google ID
 
   if (appdata.capabilities.manyAuthProviders && appconfig.auth.google && appconfig.auth.google.enabled) {
+    const GoogleStrategy = require('passport-google-oauth20').Strategy
     passport.use('google',
       new GoogleStrategy({
         clientID: appconfig.auth.google.clientId,
@@ -79,6 +74,7 @@ module.exports = function (passport) {
   // Microsoft Accounts
 
   if (appdata.capabilities.manyAuthProviders && appconfig.auth.microsoft && appconfig.auth.microsoft.enabled) {
+    const WindowsLiveStrategy = require('passport-windowslive').Strategy
     passport.use('windowslive',
       new WindowsLiveStrategy({
         clientID: appconfig.auth.microsoft.clientId,
@@ -98,6 +94,7 @@ module.exports = function (passport) {
   // Facebook
 
   if (appdata.capabilities.manyAuthProviders && appconfig.auth.facebook && appconfig.auth.facebook.enabled) {
+    const FacebookStrategy = require('passport-facebook').Strategy
     passport.use('facebook',
       new FacebookStrategy({
         clientID: appconfig.auth.facebook.clientId,
@@ -118,6 +115,7 @@ module.exports = function (passport) {
   // GitHub
 
   if (appdata.capabilities.manyAuthProviders && appconfig.auth.github && appconfig.auth.github.enabled) {
+    const GitHubStrategy = require('passport-github2').Strategy
     passport.use('github',
       new GitHubStrategy({
         clientID: appconfig.auth.github.clientId,
@@ -138,6 +136,7 @@ module.exports = function (passport) {
   // Slack
 
   if (appdata.capabilities.manyAuthProviders && appconfig.auth.slack && appconfig.auth.slack.enabled) {
+    const SlackStrategy = require('passport-slack').Strategy
     passport.use('slack',
       new SlackStrategy({
         clientID: appconfig.auth.slack.clientId,
@@ -157,6 +156,7 @@ module.exports = function (passport) {
   // LDAP
 
   if (appdata.capabilities.manyAuthProviders && appconfig.auth.ldap && appconfig.auth.ldap.enabled) {
+    const LdapStrategy = require('passport-ldapauth').Strategy
     passport.use('ldapauth',
       new LdapStrategy({
         server: {
@@ -187,6 +187,32 @@ module.exports = function (passport) {
     ))
   }
 
+  // AZURE AD
+
+  if (appdata.capabilities.manyAuthProviders && appconfig.auth.azure && appconfig.auth.azure.enabled) {
+    const AzureAdOAuth2Strategy = require('passport-azure-ad-oauth2').Strategy
+    const jwt = require('jsonwebtoken')
+    passport.use('azure_ad_oauth2',
+      new AzureAdOAuth2Strategy({
+        clientID: appconfig.auth.azure.clientId,
+        clientSecret: appconfig.auth.azure.clientSecret,
+        callbackURL: appconfig.host + '/login/azure/callback',
+        resource: appconfig.auth.azure.resource,
+        tenant: appconfig.auth.azure.tenant
+      },
+      (accessToken, refreshToken, params, profile, cb) => {
+        let waadProfile = jwt.decode(params.id_token)
+        waadProfile.id = waadProfile.oid
+        waadProfile.provider = 'azure'
+        db.User.processProfile(waadProfile).then((user) => {
+          return cb(null, user) || true
+        }).catch((err) => {
+          return cb(err, null) || true
+        })
+      }
+    ))
+  }
+
   // Create users for first-time
 
   db.onReady.then(() => {

+ 2 - 4
libs/config.js

@@ -1,7 +1,5 @@
 'use strict'
 
-/* global winston */
-
 const fs = require('fs')
 const yaml = require('js-yaml')
 const _ = require('lodash')
@@ -25,7 +23,7 @@ module.exports = (confPaths) => {
     appconfig = yaml.safeLoad(fs.readFileSync(confPaths.config, 'utf8'))
     appdata = yaml.safeLoad(fs.readFileSync(confPaths.data, 'utf8'))
   } catch (ex) {
-    winston.error(ex)
+    console.error(ex)
     process.exit(1)
   }
 
@@ -41,7 +39,7 @@ module.exports = (confPaths) => {
       socialEnabled: (_.chain(appconfig.auth).omit('local').reject({ enabled: false }).value().length > 0)
     }
     if (appconfig.authStrategies.list.length < 1) {
-      winston.error(new Error('You must enable at least 1 authentication strategy!'))
+      console.error(new Error('You must enable at least 1 authentication strategy!'))
       process.exit(1)
     }
   } else {

+ 2 - 1
locales/en/auth.json

@@ -2,10 +2,11 @@
   "providers": {
     "local": "Local",
     "windowslive": "Microsoft Account",
+    "azure": "Azure Active Directory",
     "google": "Google ID",
     "facebook": "Facebook",
     "github": "GitHub",
     "slack": "Slack",
     "ldap": "LDAP / Active Directory"
   }
-}
+}

+ 2 - 3
models/user.js

@@ -72,9 +72,8 @@ userSchema.statics.processProfile = (profile) => {
   }, {
     new: true
   }).then((user) => {
-    // LDAP - Handle unregistered accounts
-    // Todo: Allow this behavior for any provider...
-    if (!user && profile.provider === 'ldap') {
+    // Handle unregistered accounts
+    if (!user && profile.provider !== 'local' && (appconfig.auth.defaultReadAccess || profile.provider === 'ldap' || profile.provider === 'azure')) {
       let nUsr = {
         email: primaryEmail,
         provider: profile.provider,

+ 2 - 0
package.json

@@ -70,6 +70,7 @@
     "image-size": "^0.5.1",
     "jimp": "github:ngpixel/jimp",
     "js-yaml": "^3.8.3",
+    "jsonwebtoken": "^7.3.0",
     "klaw": "^1.3.1",
     "levelup": "^1.3.5",
     "lodash": "^4.17.3",
@@ -91,6 +92,7 @@
     "multer": "^1.2.1",
     "ora": "^1.2.0",
     "passport": "^0.3.2",
+    "passport-azure-ad-oauth2": "0.0.4",
     "passport-facebook": "^2.1.1",
     "passport-github2": "^0.1.10",
     "passport-google-oauth20": "^1.0.0",

+ 4 - 0
views/auth/login.pug

@@ -47,6 +47,10 @@ html(data-logic='login')
             button.ms(onclick='window.location.assign("/login/ms")')
               i.icon-windows2
               span Microsoft Account
+          if appconfig.auth.azure && appconfig.auth.azure.enabled
+            button.ms(onclick='window.location.assign("/login/azure")')
+              i.icon-windows2
+              span Azure AD
           if appconfig.auth.google && appconfig.auth.google.enabled
             button.google(onclick='window.location.assign("/login/google")')
               i.icon-google

+ 1 - 0
views/pages/admin/profile.pug

@@ -36,6 +36,7 @@ block adminContent
               case user.provider
                 when 'local': i.icon-server
                 when 'windowslive': i.icon-windows2.is-blue
+                when 'azure': i.icon-windows2.is-blue
                 when 'google': i.icon-google.is-blue
                 when 'facebook': i.icon-facebook.is-indigo
                 when 'github': i.icon-github.is-grey

+ 3 - 0
views/pages/admin/users-edit.pug

@@ -30,6 +30,9 @@ block adminContent
               when 'windowslive'
                 i.icon-windows2.is-blue
                 | Microsoft Account
+              when 'azure'
+                i.icon-windows2.is-blue
+                | Azure Active Directory
               when 'google'
                 i.icon-google.is-blue
                 | Google ID

+ 3 - 0
views/pages/admin/users.pug

@@ -37,6 +37,9 @@ block adminContent
                 when 'windowslive'
                   i.icon-windows2.is-blue
                   | Microsoft Account
+                when 'azure'
+                  i.icon-windows2.is-blue
+                  | Azure Active Directory
                 when 'google'
                   i.icon-google.is-blue
                   | Google ID

+ 102 - 57
views/pages/all.pug

@@ -7,61 +7,106 @@ block content
 
   #page-type-all
     .container.is-fluid.has-collapsable-nav
-      .columns.is-gapless
-
-        .column.is-narrow.is-hidden-touch.sidebar.is-collapsed
-
-          aside
-            .sidebar-label
-              span NAV
-            ul.sidebar-menu
+      .sidebar.is-collapsed
+        aside
+          .sidebar-label
+            span NAV
+          ul.sidebar-menu
+            li
+              a(href='/')
+                i.icon-home
+                span Home
+            if !isGuest
               li
-                a(href='/')
-                  i.icon-home
-                  span Home
-              if !isGuest
-                li
-                  a(href='/admin')
-                    i.icon-head
-                    span Account
-              else
-                li
-                  a(href='/login')
-                    i.icon-unlock
-                    span Login
-
-        .column.is-narrow
-          ul.collapsable-nav.has-children
-            li: a
-              i.icon-file
-              span Page 1
-            li: a
-              i.icon-file
-              span Page 2
-            li: a
-              i.icon-file
-              span Page 3
-            li.is-active: a
-              i.icon-folder2
-              span Page 4
-            li: a
-              i.icon-file
-              span Page 5
-        .column.is-narrow
-          ul.collapsable-nav-sub
-            li: a
-              i.icon-file
-              span Page 1
-            li: a
-              i.icon-file
-              span Page 2
-            li: a
-              i.icon-file
-              span Page 3
-            li: a
-              i.icon-file
-              span Page 4
-            li: a
-              i.icon-file
-              span Page 5
-
+                a(href='/admin')
+                  i.icon-head
+                  span Account
+            else
+              li
+                a(href='/login')
+                  i.icon-unlock
+                  span Login
+      ul.collapsable-nav.has-children
+        li: a
+          i.icon-file
+          span Page 1
+        li: a
+          i.icon-file
+          span Page 2
+        li: a
+          i.icon-file
+          span Page 3
+        li.is-active: a
+          i.icon-folder2
+          span Page 4
+        li: a
+          i.icon-file
+          span Page 5
+      ul.collapsable-nav.has-children
+        li.is-title page-4
+        li: a
+          i.icon-file
+          span Page 1
+        li.is-active: a
+          i.icon-file
+          span Page 2
+        li: a
+          i.icon-file
+          span Page 3
+        li: a
+          i.icon-file
+          span Page 4
+        li: a
+          i.icon-file
+          span Page 5
+      ul.collapsable-nav.has-children
+        li.is-title page-4
+        li: a
+          i.icon-file
+          span Page 1
+        li.is-active: a
+          i.icon-file
+          span Page 2
+        li: a
+          i.icon-file
+          span Page 3
+        li: a
+          i.icon-file
+          span Page 4
+        li: a
+          i.icon-file
+          span Page 5
+      ul.collapsable-nav.has-children
+        li.is-title page-4
+        li: a
+          i.icon-file
+          span Page 1
+        li.is-active: a
+          i.icon-file
+          span Page 2
+        li: a
+          i.icon-file
+          span Page 3
+        li: a
+          i.icon-file
+          span Page 4
+        li: a
+          i.icon-file
+          span Page 5
+      ul.collapsable-nav
+        li.is-title Sub-Pages
+        li: a
+          i.icon-file
+          span Page 1
+        li: a
+          i.icon-file
+          span Page 2
+        li: a
+          i.icon-file
+          span Page 3
+        li: a
+          i.icon-file
+          span Page 4
+        li: a
+          i.icon-file
+          span Page 5