Azure AD support + defaultReadAccess option + All Pages UI work

This commit is contained in:
NGPixel 2017-04-09 21:13:53 -04:00
parent 5a010f224f
commit 7c1dd8b92a
17 changed files with 206 additions and 116 deletions

View File

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

View File

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

9
client/js/pages/all.js Normal file
View File

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

View File

@ -1,25 +1,52 @@
.has-collapsable-nav { .has-collapsable-nav {
background-color: mc('blue-grey', '50'); background-color: mc('blue-grey', '50');
display: flex;
justify-content: flex-start;
align-items: stretch;
} }
.collapsable-nav { .collapsable-nav {
width: 300px; width: 300px;
background-color: mc($primary, '500'); background-color: mc('blue-grey', '900');
color: #FFF; color: #FFF;
min-height: 80vh; min-height: 80vh;
transition: all .6s ease; transition: all .6s ease;
border-left: 1px solid darken(mc('blue-grey', '900'), 5%);
&:last-child {
border-bottom-right-radius: 5px;
}
&.has-children { &.has-children {
width: 50px; 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 { li {
border-top: none;
display: none; display: none;
} }
} }
li { li {
display: flex; 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 { &.is-active {
display: flex; display: flex;
@ -32,7 +59,6 @@
a { a {
height: 50px; 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;
}
}
}

View File

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

View File

@ -54,6 +54,7 @@ lang: en
public: false public: false
auth: auth:
defaultReadAccess: false
local: local:
enabled: true enabled: true
google: google:
@ -85,6 +86,12 @@ auth:
searchFilter: (uid={{username}}) searchFilter: (uid={{username}})
tlsEnabled: false tlsEnabled: false
tlsCertPath: C:\example\root_ca_cert.crt 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 # Secret key to use when encrypting sessions

View File

@ -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/facebook', passport.authenticate('facebook', { scope: ['public_profile', 'email'] }))
router.get('/login/github', passport.authenticate('github', { scope: ['user: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/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/ms/callback', passport.authenticate('windowslive', { failureRedirect: '/login', successRedirect: '/' }))
router.get('/login/google/callback', passport.authenticate('google', { 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/facebook/callback', passport.authenticate('facebook', { failureRedirect: '/login', successRedirect: '/' }))
router.get('/login/github/callback', passport.authenticate('github', { 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/slack/callback', passport.authenticate('slack', { failureRedirect: '/login', successRedirect: '/' }))
router.get('/login/azure/callback', passport.authenticate('azure_ad_oauth2', { failureRedirect: '/login', successRedirect: '/' }))
/** /**
* Logout * Logout

View File

@ -2,13 +2,6 @@
/* global appconfig, appdata, db, winston */ /* 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') const fs = require('fs')
module.exports = function (passport) { module.exports = function (passport) {
@ -34,6 +27,7 @@ module.exports = function (passport) {
// Local Account // Local Account
if (!appdata.capabilities.manyAuthProviders || (appconfig.auth.local && appconfig.auth.local.enabled)) { if (!appdata.capabilities.manyAuthProviders || (appconfig.auth.local && appconfig.auth.local.enabled)) {
const LocalStrategy = require('passport-local').Strategy
passport.use('local', passport.use('local',
new LocalStrategy({ new LocalStrategy({
usernameField: 'email', usernameField: 'email',
@ -60,6 +54,7 @@ module.exports = function (passport) {
// Google ID // Google ID
if (appdata.capabilities.manyAuthProviders && appconfig.auth.google && appconfig.auth.google.enabled) { if (appdata.capabilities.manyAuthProviders && appconfig.auth.google && appconfig.auth.google.enabled) {
const GoogleStrategy = require('passport-google-oauth20').Strategy
passport.use('google', passport.use('google',
new GoogleStrategy({ new GoogleStrategy({
clientID: appconfig.auth.google.clientId, clientID: appconfig.auth.google.clientId,
@ -79,6 +74,7 @@ module.exports = function (passport) {
// Microsoft Accounts // Microsoft Accounts
if (appdata.capabilities.manyAuthProviders && appconfig.auth.microsoft && appconfig.auth.microsoft.enabled) { if (appdata.capabilities.manyAuthProviders && appconfig.auth.microsoft && appconfig.auth.microsoft.enabled) {
const WindowsLiveStrategy = require('passport-windowslive').Strategy
passport.use('windowslive', passport.use('windowslive',
new WindowsLiveStrategy({ new WindowsLiveStrategy({
clientID: appconfig.auth.microsoft.clientId, clientID: appconfig.auth.microsoft.clientId,
@ -98,6 +94,7 @@ module.exports = function (passport) {
// Facebook // Facebook
if (appdata.capabilities.manyAuthProviders && appconfig.auth.facebook && appconfig.auth.facebook.enabled) { if (appdata.capabilities.manyAuthProviders && appconfig.auth.facebook && appconfig.auth.facebook.enabled) {
const FacebookStrategy = require('passport-facebook').Strategy
passport.use('facebook', passport.use('facebook',
new FacebookStrategy({ new FacebookStrategy({
clientID: appconfig.auth.facebook.clientId, clientID: appconfig.auth.facebook.clientId,
@ -118,6 +115,7 @@ module.exports = function (passport) {
// GitHub // GitHub
if (appdata.capabilities.manyAuthProviders && appconfig.auth.github && appconfig.auth.github.enabled) { if (appdata.capabilities.manyAuthProviders && appconfig.auth.github && appconfig.auth.github.enabled) {
const GitHubStrategy = require('passport-github2').Strategy
passport.use('github', passport.use('github',
new GitHubStrategy({ new GitHubStrategy({
clientID: appconfig.auth.github.clientId, clientID: appconfig.auth.github.clientId,
@ -138,6 +136,7 @@ module.exports = function (passport) {
// Slack // Slack
if (appdata.capabilities.manyAuthProviders && appconfig.auth.slack && appconfig.auth.slack.enabled) { if (appdata.capabilities.manyAuthProviders && appconfig.auth.slack && appconfig.auth.slack.enabled) {
const SlackStrategy = require('passport-slack').Strategy
passport.use('slack', passport.use('slack',
new SlackStrategy({ new SlackStrategy({
clientID: appconfig.auth.slack.clientId, clientID: appconfig.auth.slack.clientId,
@ -157,6 +156,7 @@ module.exports = function (passport) {
// LDAP // LDAP
if (appdata.capabilities.manyAuthProviders && appconfig.auth.ldap && appconfig.auth.ldap.enabled) { if (appdata.capabilities.manyAuthProviders && appconfig.auth.ldap && appconfig.auth.ldap.enabled) {
const LdapStrategy = require('passport-ldapauth').Strategy
passport.use('ldapauth', passport.use('ldapauth',
new LdapStrategy({ new LdapStrategy({
server: { 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 // Create users for first-time
db.onReady.then(() => { db.onReady.then(() => {

View File

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

View File

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

View File

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

View File

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

View File

@ -47,6 +47,10 @@ html(data-logic='login')
button.ms(onclick='window.location.assign("/login/ms")') button.ms(onclick='window.location.assign("/login/ms")')
i.icon-windows2 i.icon-windows2
span Microsoft Account 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 if appconfig.auth.google && appconfig.auth.google.enabled
button.google(onclick='window.location.assign("/login/google")') button.google(onclick='window.location.assign("/login/google")')
i.icon-google i.icon-google

View File

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

View File

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

View File

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

View File

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