From 7c1dd8b92af762fd18bf4ded7ecc64dc3bca583d Mon Sep 17 00:00:00 2001 From: NGPixel Date: Sun, 9 Apr 2017 21:13:53 -0400 Subject: [PATCH] Azure AD support + defaultReadAccess option + All Pages UI work --- app/data.yml | 4 + client/js/app.js | 1 + client/js/pages/all.js | 9 ++ client/scss/components/collapsable-nav.scss | 69 ++++----- client/scss/components/sidebar.scss | 4 - config.sample.yml | 7 + controllers/auth.js | 2 + libs/auth.js | 40 ++++- libs/config.js | 6 +- locales/en/auth.json | 3 +- models/user.js | 5 +- package.json | 2 + views/auth/login.pug | 4 + views/pages/admin/profile.pug | 1 + views/pages/admin/users-edit.pug | 3 + views/pages/admin/users.pug | 3 + views/pages/all.pug | 159 +++++++++++++------- 17 files changed, 206 insertions(+), 116 deletions(-) create mode 100644 client/js/pages/all.js diff --git a/app/data.yml b/app/data.yml index ca8faaf7..d1e4c3c3 100644 --- a/app/data.yml +++ b/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 diff --git a/client/js/app.js b/client/js/app.js index 1fe5b212..941b8253 100644 --- a/client/js/app.js +++ b/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) diff --git a/client/js/pages/all.js b/client/js/pages/all.js new file mode 100644 index 00000000..4bf4587d --- /dev/null +++ b/client/js/pages/all.js @@ -0,0 +1,9 @@ +'use strict' + +import $ from 'jquery' + +module.exports = (alerts, socket) => { + if ($('#page-type-all').length) { + + } +} diff --git a/client/scss/components/collapsable-nav.scss b/client/scss/components/collapsable-nav.scss index 35f76321..614108c9 100644 --- a/client/scss/components/collapsable-nav.scss +++ b/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,7 +59,6 @@ a { height: 50px; } - } } @@ -61,40 +87,3 @@ } } - -.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; - 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; - } - - } - -} diff --git a/client/scss/components/sidebar.scss b/client/scss/components/sidebar.scss index fbabf2bb..aab858d2 100644 --- a/client/scss/components/sidebar.scss +++ b/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; diff --git a/config.sample.yml b/config.sample.yml index 52feb7b9..24e1de61 100644 --- a/config.sample.yml +++ b/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 diff --git a/controllers/auth.js b/controllers/auth.js index cdb54d1b..65abb0b5 100644 --- a/controllers/auth.js +++ b/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 diff --git a/libs/auth.js b/libs/auth.js index 92fd06fe..e3120257 100644 --- a/libs/auth.js +++ b/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(() => { diff --git a/libs/config.js b/libs/config.js index 4caa90c9..dc9b351d 100644 --- a/libs/config.js +++ b/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 { diff --git a/locales/en/auth.json b/locales/en/auth.json index 50ce7873..f91c2a7d 100644 --- a/locales/en/auth.json +++ b/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" } -} \ No newline at end of file +} diff --git a/models/user.js b/models/user.js index d3fdcb43..9d953e64 100644 --- a/models/user.js +++ b/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, diff --git a/package.json b/package.json index 4ea8af70..051d2c2e 100644 --- a/package.json +++ b/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", diff --git a/views/auth/login.pug b/views/auth/login.pug index dd88f2e3..312a6f6d 100644 --- a/views/auth/login.pug +++ b/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 diff --git a/views/pages/admin/profile.pug b/views/pages/admin/profile.pug index 11d4730e..5a8eedd3 100644 --- a/views/pages/admin/profile.pug +++ b/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 diff --git a/views/pages/admin/users-edit.pug b/views/pages/admin/users-edit.pug index 826e4f63..c2e7a3c3 100644 --- a/views/pages/admin/users-edit.pug +++ b/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 diff --git a/views/pages/admin/users.pug b/views/pages/admin/users.pug index 85d9ac7b..5d250f38 100644 --- a/views/pages/admin/users.pug +++ b/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 diff --git a/views/pages/all.pug b/views/pages/all.pug index 002d2ac1..1ef0bc23 100644 --- a/views/pages/all.pug +++ b/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