Преглед на файлове

feat: self-contained auth modules + login UI + icons

NGPixel преди 7 години
родител
ревизия
cac3d21c6e

Файловите разлики са ограничени, защото са твърде много
+ 0 - 0
assets/svg/auth-icon-azure.svg


+ 3 - 0
assets/svg/auth-icon-facebook.svg

@@ -0,0 +1,3 @@
+<svg width="100%" height="100%" viewBox="0 0 500 500" preserveAspectRatio="xMinYMin meet">
+	<path fill="#3B5998" d="M288.714,500l0,-228.073l76.554,0l11.461,-88.885l-88.017,0l0,-56.749c0,-25.735 7.145,-43.271 44.049,-43.271l47.067,-0.022l0,-79.498c-8.141,-1.081 -36.082,-3.502 -68.584,-3.502c-67.862,0 -114.321,41.422 -114.321,117.492l0,65.55l-76.751,0l0,88.885l76.751,0l0,228.071l91.791,0l0,0.002Z" style="fill-rule:nonzero;"/>
+</svg>

Файловите разлики са ограничени, защото са твърде много
+ 0 - 0
assets/svg/auth-icon-github.svg


+ 1 - 0
assets/svg/auth-icon-google.svg

@@ -0,0 +1 @@
+<svg width="2443" height="2500" viewBox="0 0 256 262" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid"><path d="M255.878 133.451c0-10.734-.871-18.567-2.756-26.69H130.55v48.448h71.947c-1.45 12.04-9.283 30.172-26.69 42.356l-.244 1.622 38.755 30.023 2.685.268c24.659-22.774 38.875-56.282 38.875-96.027" fill="#4285F4"/><path d="M130.55 261.1c35.248 0 64.839-11.605 86.453-31.622l-41.196-31.913c-11.024 7.688-25.82 13.055-45.257 13.055-34.523 0-63.824-22.773-74.269-54.25l-1.531.13-40.298 31.187-.527 1.465C35.393 231.798 79.49 261.1 130.55 261.1" fill="#34A853"/><path d="M56.281 156.37c-2.756-8.123-4.351-16.827-4.351-25.82 0-8.994 1.595-17.697 4.206-25.82l-.073-1.73L15.26 71.312l-1.335.635C5.077 89.644 0 109.517 0 130.55s5.077 40.905 13.925 58.602l42.356-32.782" fill="#FBBC05"/><path d="M130.55 50.479c24.514 0 41.05 10.589 50.479 19.438l36.844-35.974C195.245 12.91 165.798 0 130.55 0 79.49 0 35.393 29.301 13.925 71.947l42.211 32.783c10.59-31.477 39.891-54.251 74.414-54.251" fill="#EB4335"/></svg>

+ 10 - 0
assets/svg/auth-icon-ldap.svg

@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 19.2.1, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+	 width="48px" height="48px" viewBox="0 0 48 48" enable-background="new 0 0 48 48" xml:space="preserve">
+<path fill="#458BC4" d="M44.804,30.404l-20-27C24.615,3.15,24.308,3.023,24,3.023V46c0.2,0,0.401-0.061,0.573-0.181l20-14
+	c0.222-0.155,0.37-0.393,0.414-0.659C45.03,30.895,44.964,30.622,44.804,30.404z"/>
+<path fill="#43A6DD" d="M23.196,3.405l-20,27c-0.16,0.218-0.227,0.49-0.184,0.756c0.044,0.267,0.192,0.504,0.414,0.659l20,14
+	C23.599,45.939,23.8,46,24,46V3.023C23.692,3.023,23.385,3.15,23.196,3.405z"/>
+</svg>

+ 1 - 0
assets/svg/auth-icon-microsoft.svg

@@ -0,0 +1 @@
+<svg width="2490" height="2500" viewBox="0 0 256 257" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid"><path d="M0 36.357L104.62 22.11l.045 100.914-104.57.595L0 36.358zm104.57 98.293l.08 101.002L.081 221.275l-.006-87.302 104.494.677zm12.682-114.405L255.968 0v121.74l-138.716 1.1V20.246zM256 135.6l-.033 121.191-138.716-19.578-.194-101.84L256 135.6z" fill="#00ADEF"/></svg>

+ 1 - 0
assets/svg/auth-icon-slack.svg

@@ -0,0 +1 @@
+<svg width="2500" height="2500" viewBox="0 0 256 256" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid"><path d="M165.964 15.838c-3.89-11.975-16.752-18.528-28.725-14.636-11.975 3.89-18.528 16.752-14.636 28.725l58.947 181.365c4.048 11.187 16.132 17.473 27.732 14.135 12.1-3.483 19.475-16.334 15.614-28.217L165.964 15.838" fill="#DFA22F"/><path d="M74.626 45.516C70.734 33.542 57.873 26.989 45.9 30.879 33.924 34.77 27.37 47.631 31.263 59.606l58.948 181.366c4.047 11.186 16.132 17.473 27.732 14.132 12.099-3.481 19.474-16.332 15.613-28.217L74.626 45.516" fill="#3CB187"/><path d="M240.162 166.045c11.975-3.89 18.526-16.75 14.636-28.726-3.89-11.973-16.752-18.527-28.725-14.636L44.708 181.632c-11.187 4.046-17.473 16.13-14.135 27.73 3.483 12.099 16.334 19.475 28.217 15.614l181.372-58.93" fill="#CE1E5B"/><path d="M82.508 217.27l43.347-14.084-14.086-43.352-43.35 14.09 14.089 43.347" fill="#392538"/><path d="M173.847 187.591c16.388-5.323 31.62-10.273 43.348-14.084l-14.088-43.36-43.35 14.09 14.09 43.354" fill="#BB242A"/><path d="M210.484 74.706c11.974-3.89 18.527-16.751 14.637-28.727-3.89-11.973-16.752-18.526-28.727-14.636L15.028 90.293C3.842 94.337-2.445 106.422.896 118.022c3.481 12.098 16.332 19.474 28.217 15.613l181.371-58.93" fill="#72C5CD"/><path d="M52.822 125.933c11.805-3.836 27.025-8.782 43.354-14.086-5.323-16.39-10.273-31.622-14.084-43.352l-43.36 14.092 14.09 43.346" fill="#248C73"/><path d="M144.16 96.256l43.356-14.088a546179.21 546179.21 0 0 0-14.089-43.36L130.07 52.9l14.09 43.356" fill="#62803A"/></svg>

+ 0 - 13
client/scss/login.scss

@@ -1,13 +0,0 @@
-@charset "utf-8";
-
-$primary: 'indigo';
-
-@import "base/variables";
-@import "base/colors";
-@import "base/reset";
-@import "base/mixins";
-@import "base/fonts";
-@import "base/base";
-
-@import "libs/animate";
-@import 'pages/login';

+ 89 - 32
client/scss/pages/_login.scss

@@ -9,23 +9,65 @@
   justify-content: center;
   justify-content: center;
 
 
   &-container {
   &-container {
+    position: relative;
     display: flex;
     display: flex;
-    width: 650px;
+    width: 450px;
     align-items: stretch;
     align-items: stretch;
     box-shadow: 0 14px 28px rgba(0,0,0,0.25), 0 10px 10px rgba(0,0,0,0.22);
     box-shadow: 0 14px 28px rgba(0,0,0,0.25), 0 10px 10px rgba(0,0,0,0.22);
+
+    &.is-expanded {
+      width: 650px;
+    }
+
+    @include until($tablet) {
+      width: 350px;
+
+      &.is-expanded {
+        width: 400px;
+      }
+    }
+  }
+
+  &-error {
+    position: absolute;
+    bottom: 100%;
+    width: 100%;
+    min-height: 50px;
+    background-image: radial-gradient(ellipse at top left, rgba(mc('red', '900'),.9) 0%,rgba(mc('red', '400'),.8) 100%);
+    border: 1px solid #FFF;
+    color: #FFF;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+
+    strong {
+      font-weight: 600;
+      text-transform: uppercase;
+      display: block;
+      padding: 0 1rem 0 0;
+      border-right: 1px solid #FFF;
+    }
+    span {
+      padding: 0 0 0 1rem;
+      display: block;
+    }
   }
   }
 
 
   &-providers {
   &-providers {
     display: flex;
     display: flex;
     flex-direction: column;
     flex-direction: column;
-    width: 200px;
+    width: 250px;
     border: 1px solid #FFF;
     border: 1px solid #FFF;
     background-color: mc('grey', '900');
     background-color: mc('grey', '900');
     z-index: 1;
     z-index: 1;
 
 
+    @include until($tablet) {
+      width: 50px;
+    }
+
     button {
     button {
       flex: 1 1;
       flex: 1 1;
-      padding: 0 15px;
+      padding: 5px 15px;
       border: none;
       border: none;
       color: #FFF;
       color: #FFF;
       background-color: mc('grey', '800');
       background-color: mc('grey', '800');
@@ -34,6 +76,18 @@
       font-weight: 600;
       font-weight: 600;
       text-align: left;
       text-align: left;
       min-height: 40px;
       min-height: 40px;
+      display: flex;
+      justify-content: flex-start;
+      align-items: center;
+      transition: all .4s ease;
+
+      @include until($tablet) {
+        justify-content: center;
+      }
+
+      &:hover {
+        background-color: mc('grey', '600');
+      }
 
 
       &:first-child {
       &:first-child {
         border-top: none;
         border-top: none;
@@ -48,10 +102,36 @@
       i {
       i {
         margin-right: 10px;
         margin-right: 10px;
         font-size: 16px;
         font-size: 16px;
+
+        @include until($tablet) {
+          margin-right: 0;
+          font-size: 20px;
+        }
+      }
+
+      svg {
+        margin-right: 10px;
+        width: auto;
+        height: 20px;
+        max-width: 18px;
+        max-height: 20px;
+
+        path {
+          fill: #FFF;
+        }
+
+        @include until($tablet) {
+          margin-right: 0;
+          font-size: 20px;
+        }
       }
       }
 
 
       span {
       span {
         font-weight: 600;
         font-weight: 600;
+
+        @include until($tablet) {
+          display: none;
+        }
       }
       }
     }
     }
   }
   }
@@ -59,7 +139,7 @@
   &-frame {
   &-frame {
     background-image: radial-gradient(circle at top left, rgba(255,255,255,1) 0%,rgba(240,240,240,.6) 100%);
     background-image: radial-gradient(circle at top left, rgba(255,255,255,1) 0%,rgba(240,240,240,.6) 100%);
     border: 1px solid #FFF;
     border: 1px solid #FFF;
-    width: 450px;
+    width: 400px;
     padding: 1rem;
     padding: 1rem;
     color: mc('grey', '700');
     color: mc('grey', '700');
     display: flex;
     display: flex;
@@ -67,6 +147,10 @@
     flex-direction: column;
     flex-direction: column;
     text-align: center;
     text-align: center;
 
 
+    @include until($tablet) {
+      width: 350px;
+    }
+
     h1 {
     h1 {
       font-size: 2rem;
       font-size: 2rem;
       font-weight: 600;
       font-weight: 600;
@@ -83,29 +167,6 @@
       margin: 0 0 25px 0;
       margin: 0 0 25px 0;
     }
     }
 
 
-    h3 {
-      font-size: 1.25rem;
-      font-weight: normal;
-      color: #FB8C00;
-      padding: 0;
-      margin: 0;
-      animation: shake 1s ease;
-
-      > .fa {
-        margin-right: 7px;
-      }
-
-    }
-
-    h4 {
-      font-size: .8rem;
-      font-weight: normal;
-      color: rgba(255,255,255,0.7);
-      padding: 0;
-      margin: 0 0 15px 0;
-      animation: fadeIn 3s ease;
-    }
-
     form {
     form {
       display: flex;
       display: flex;
       flex-direction: column;
       flex-direction: column;
@@ -147,14 +208,10 @@
     font-weight: 400;
     font-weight: 400;
     text-shadow: 1px 1px 0 #000;
     text-shadow: 1px 1px 0 #000;
 
 
-    .icon {
-      font-size: 1.2rem;
-      margin: 0 8px;
-    }
-
     a {
     a {
       font-weight: 600;
       font-weight: 600;
       color: #FFF;
       color: #FFF;
+      margin-left: .25rem;
     }
     }
 
 
   }
   }

+ 25 - 20
server/authentication/azure.js

@@ -8,24 +8,29 @@
 
 
 const AzureAdOAuth2Strategy = require('passport-azure-ad-oauth2').Strategy
 const AzureAdOAuth2Strategy = require('passport-azure-ad-oauth2').Strategy
 
 
-module.exports = (passport, conf) => {
-  const jwt = require('jsonwebtoken')
-  passport.use('azure_ad_oauth2',
-    new AzureAdOAuth2Strategy({
-      clientID: conf.clientId,
-      clientSecret: conf.clientSecret,
-      callbackURL: conf.callbackURL,
-      resource: conf.resource,
-      tenant: conf.tenant
-    }, (accessToken, refreshToken, params, profile, cb) => {
-      let waadProfile = jwt.decode(params.id_token)
-      waadProfile.id = waadProfile.oid
-      waadProfile.provider = 'azure'
-      wiki.db.User.processProfile(waadProfile).then((user) => {
-        return cb(null, user) || true
-      }).catch((err) => {
-        return cb(err, null) || true
-      })
-    }
-    ))
+module.exports = {
+  key: 'azure',
+  title: 'Azure Active Directory',
+  props: ['clientId', 'clientSecret', 'callbackURL', 'resource', 'tenant'],
+  init (passport, conf) {
+    const jwt = require('jsonwebtoken')
+    passport.use('azure_ad_oauth2',
+      new AzureAdOAuth2Strategy({
+        clientID: conf.clientId,
+        clientSecret: conf.clientSecret,
+        callbackURL: conf.callbackURL,
+        resource: conf.resource,
+        tenant: conf.tenant
+      }, (accessToken, refreshToken, params, profile, cb) => {
+        let waadProfile = jwt.decode(params.id_token)
+        waadProfile.id = waadProfile.oid
+        waadProfile.provider = 'azure'
+        wiki.db.User.processProfile(waadProfile).then((user) => {
+          return cb(null, user) || true
+        }).catch((err) => {
+          return cb(err, null) || true
+        })
+      }
+      ))
+  }
 }
 }

+ 20 - 15
server/authentication/facebook.js

@@ -8,19 +8,24 @@
 
 
 const FacebookStrategy = require('passport-facebook').Strategy
 const FacebookStrategy = require('passport-facebook').Strategy
 
 
-module.exports = (passport, conf) => {
-  passport.use('facebook',
-    new FacebookStrategy({
-      clientID: conf.clientId,
-      clientSecret: conf.clientSecret,
-      callbackURL: conf.callbackURL,
-      profileFields: ['id', 'displayName', 'email']
-    }, function (accessToken, refreshToken, profile, cb) {
-      wiki.db.User.processProfile(profile).then((user) => {
-        return cb(null, user) || true
-      }).catch((err) => {
-        return cb(err, null) || true
-      })
-    }
-    ))
+module.exports = {
+  key: 'facebook',
+  title: 'Facebook',
+  props: ['clientId', 'clientSecret', 'callbackURL'],
+  init (passport, conf) {
+    passport.use('facebook',
+      new FacebookStrategy({
+        clientID: conf.clientId,
+        clientSecret: conf.clientSecret,
+        callbackURL: conf.callbackURL,
+        profileFields: ['id', 'displayName', 'email']
+      }, function (accessToken, refreshToken, profile, cb) {
+        wiki.db.User.processProfile(profile).then((user) => {
+          return cb(null, user) || true
+        }).catch((err) => {
+          return cb(err, null) || true
+        })
+      }
+      ))
+  }
 }
 }

+ 20 - 15
server/authentication/github.js

@@ -8,19 +8,24 @@
 
 
 const GitHubStrategy = require('passport-github2').Strategy
 const GitHubStrategy = require('passport-github2').Strategy
 
 
-module.exports = (passport, conf) => {
-  passport.use('github',
-    new GitHubStrategy({
-      clientID: conf.clientId,
-      clientSecret: conf.clientSecret,
-      callbackURL: conf.callbackURL,
-      scope: ['user:email']
-    }, (accessToken, refreshToken, profile, cb) => {
-      wiki.db.User.processProfile(profile).then((user) => {
-        return cb(null, user) || true
-      }).catch((err) => {
-        return cb(err, null) || true
-      })
-    }
-    ))
+module.exports = {
+  key: 'github',
+  title: 'GitHub',
+  props: ['clientId', 'clientSecret', 'callbackURL'],
+  init (passport, conf) {
+    passport.use('github',
+      new GitHubStrategy({
+        clientID: conf.clientId,
+        clientSecret: conf.clientSecret,
+        callbackURL: conf.callbackURL,
+        scope: ['user:email']
+      }, (accessToken, refreshToken, profile, cb) => {
+        wiki.db.User.processProfile(profile).then((user) => {
+          return cb(null, user) || true
+        }).catch((err) => {
+          return cb(err, null) || true
+        })
+      }
+      ))
+  }
 }
 }

+ 19 - 14
server/authentication/google.js

@@ -8,18 +8,23 @@
 
 
 const GoogleStrategy = require('passport-google-oauth20').Strategy
 const GoogleStrategy = require('passport-google-oauth20').Strategy
 
 
-module.exports = (passport, conf) => {
-  passport.use('google',
-    new GoogleStrategy({
-      clientID: conf.clientId,
-      clientSecret: conf.clientSecret,
-      callbackURL: conf.callbackURL
-    }, (accessToken, refreshToken, profile, cb) => {
-      wiki.db.User.processProfile(profile).then((user) => {
-        return cb(null, user) || true
-      }).catch((err) => {
-        return cb(err, null) || true
-      })
-    }
-    ))
+module.exports = {
+  key: 'google',
+  title: 'Google ID',
+  props: ['clientId', 'clientSecret', 'callbackURL'],
+  init (passport, conf) {
+    passport.use('google',
+      new GoogleStrategy({
+        clientID: conf.clientId,
+        clientSecret: conf.clientSecret,
+        callbackURL: conf.callbackURL
+      }, (accessToken, refreshToken, profile, cb) => {
+        wiki.db.User.processProfile(profile).then((user) => {
+          return cb(null, user) || true
+        }).catch((err) => {
+          return cb(err, null) || true
+        })
+      }
+      ))
+  }
 }
 }

+ 33 - 28
server/authentication/ldap.js

@@ -9,32 +9,37 @@
 const LdapStrategy = require('passport-ldapauth').Strategy
 const LdapStrategy = require('passport-ldapauth').Strategy
 const fs = require('fs')
 const fs = require('fs')
 
 
-module.exports = (passport, conf) => {
-  passport.use('ldapauth',
-    new LdapStrategy({
-      server: {
-        url: conf.url,
-        bindDn: conf.bindDn,
-        bindCredentials: conf.bindCredentials,
-        searchBase: conf.searchBase,
-        searchFilter: conf.searchFilter,
-        searchAttributes: ['displayName', 'name', 'cn', 'mail'],
-        tlsOptions: (conf.tlsEnabled) ? {
-          ca: [
-            fs.readFileSync(conf.tlsCertPath)
-          ]
-        } : {}
-      },
-      usernameField: 'email',
-      passReqToCallback: false
-    }, (profile, cb) => {
-      profile.provider = 'ldap'
-      profile.id = profile.dn
-      wiki.db.User.processProfile(profile).then((user) => {
-        return cb(null, user) || true
-      }).catch((err) => {
-        return cb(err, null) || true
-      })
-    }
-    ))
+module.exports = {
+  key: 'ldap',
+  title: 'LDAP / Active Directory',
+  props: ['url', 'bindDn', 'bindCredentials', 'searchBase', 'searchFilter', 'tlsEnabled', 'tlsCertPath'],
+  init (passport, conf) {
+    passport.use('ldapauth',
+      new LdapStrategy({
+        server: {
+          url: conf.url,
+          bindDn: conf.bindDn,
+          bindCredentials: conf.bindCredentials,
+          searchBase: conf.searchBase,
+          searchFilter: conf.searchFilter,
+          searchAttributes: ['displayName', 'name', 'cn', 'mail'],
+          tlsOptions: (conf.tlsEnabled) ? {
+            ca: [
+              fs.readFileSync(conf.tlsCertPath)
+            ]
+          } : {}
+        },
+        usernameField: 'email',
+        passReqToCallback: false
+      }, (profile, cb) => {
+        profile.provider = 'ldap'
+        profile.id = profile.dn
+        wiki.db.User.processProfile(profile).then((user) => {
+          return cb(null, user) || true
+        }).catch((err) => {
+          return cb(err, null) || true
+        })
+      }
+      ))
+  }
 }
 }

+ 26 - 21
server/authentication/local.js

@@ -8,25 +8,30 @@
 
 
 const LocalStrategy = require('passport-local').Strategy
 const LocalStrategy = require('passport-local').Strategy
 
 
-module.exports = (passport, conf) => {
-  passport.use('local',
-    new LocalStrategy({
-      usernameField: 'email',
-      passwordField: 'password'
-    }, (uEmail, uPassword, done) => {
-      wiki.db.User.findOne({ email: uEmail, provider: 'local' }).then((user) => {
-        if (user) {
-          return user.validatePassword(uPassword).then(() => {
-            return done(null, user) || true
-          }).catch((err) => {
-            return done(err, null)
-          })
-        } else {
-          return done(new Error('INVALID_LOGIN'), null)
-        }
-      }).catch((err) => {
-        done(err, null)
-      })
-    }
-    ))
+module.exports = {
+  key: 'local',
+  title: 'Local',
+  props: [],
+  init (passport, conf) {
+    passport.use('local',
+      new LocalStrategy({
+        usernameField: 'email',
+        passwordField: 'password'
+      }, (uEmail, uPassword, done) => {
+        wiki.db.User.findOne({ email: uEmail, provider: 'local' }).then((user) => {
+          if (user) {
+            return user.validatePassword(uPassword).then(() => {
+              return done(null, user) || true
+            }).catch((err) => {
+              return done(err, null)
+            })
+          } else {
+            return done(new Error('INVALID_LOGIN'), null)
+          }
+        }).catch((err) => {
+          done(err, null)
+        })
+      }
+      ))
+  }
 }
 }

+ 19 - 14
server/authentication/microsoft.js

@@ -8,18 +8,23 @@
 
 
 const WindowsLiveStrategy = require('passport-windowslive').Strategy
 const WindowsLiveStrategy = require('passport-windowslive').Strategy
 
 
-module.exports = (passport, conf) => {
-  passport.use('windowslive',
-    new WindowsLiveStrategy({
-      clientID: conf.clientId,
-      clientSecret: conf.clientSecret,
-      callbackURL: conf.callbackURL
-    }, function (accessToken, refreshToken, profile, cb) {
-      wiki.db.User.processProfile(profile).then((user) => {
-        return cb(null, user) || true
-      }).catch((err) => {
-        return cb(err, null) || true
-      })
-    }
-    ))
+module.exports = {
+  key: 'microsoft',
+  title: 'Microsoft Account',
+  props: ['clientId', 'clientSecret', 'callbackURL'],
+  init (passport, conf) {
+    passport.use('windowslive',
+      new WindowsLiveStrategy({
+        clientID: conf.clientId,
+        clientSecret: conf.clientSecret,
+        callbackURL: conf.callbackURL
+      }, function (accessToken, refreshToken, profile, cb) {
+        wiki.db.User.processProfile(profile).then((user) => {
+          return cb(null, user) || true
+        }).catch((err) => {
+          return cb(err, null) || true
+        })
+      }
+      ))
+  }
 }
 }

+ 19 - 14
server/authentication/slack.js

@@ -8,18 +8,23 @@
 
 
 const SlackStrategy = require('passport-slack').Strategy
 const SlackStrategy = require('passport-slack').Strategy
 
 
-module.exports = (passport, conf) => {
-  passport.use('slack',
-    new SlackStrategy({
-      clientID: conf.clientId,
-      clientSecret: conf.clientSecret,
-      callbackURL: conf.callbackURL
-    }, (accessToken, refreshToken, profile, cb) => {
-      wiki.db.User.processProfile(profile).then((user) => {
-        return cb(null, user) || true
-      }).catch((err) => {
-        return cb(err, null) || true
-      })
-    }
-    ))
+module.exports = {
+  key: 'slack',
+  title: 'Slack',
+  props: ['clientId', 'clientSecret', 'callbackURL'],
+  init (passport, conf) {
+    passport.use('slack',
+      new SlackStrategy({
+        clientID: conf.clientId,
+        clientSecret: conf.clientSecret,
+        callbackURL: conf.callbackURL
+      }, (accessToken, refreshToken, profile, cb) => {
+        wiki.db.User.processProfile(profile).then((user) => {
+          return cb(null, user) || true
+        }).catch((err) => {
+          return cb(err, null) || true
+        })
+      }
+      ))
+  }
 }
 }

+ 2 - 1
server/controllers/auth.js

@@ -37,7 +37,8 @@ const bruteforce = new ExpressBrute(EBstore, {
  */
  */
 router.get('/login', function (req, res, next) {
 router.get('/login', function (req, res, next) {
   res.render('auth/login', {
   res.render('auth/login', {
-    usr: res.locals.usr
+    authStrategies: wiki.auth.strategies,
+    hasMultipleStrategies: Object.keys(wiki.config.auth.strategies).length > 0
   })
   })
 })
 })
 
 

+ 4 - 7
server/master.js

@@ -16,6 +16,7 @@ module.exports = Promise.join(
   // Load global modules
   // Load global modules
   // ----------------------------------------
   // ----------------------------------------
 
 
+  wiki.auth = require('./modules/auth').init()
   wiki.disk = require('./modules/disk').init()
   wiki.disk = require('./modules/disk').init()
   wiki.docs = require('./modules/documents').init()
   wiki.docs = require('./modules/documents').init()
   wiki.git = require('./modules/git').init(false)
   wiki.git = require('./modules/git').init(false)
@@ -38,7 +39,6 @@ module.exports = Promise.join(
   const http = require('http')
   const http = require('http')
   const i18nBackend = require('i18next-node-fs-backend')
   const i18nBackend = require('i18next-node-fs-backend')
   const path = require('path')
   const path = require('path')
-  const passport = require('passport')
   const passportSocketIo = require('passport.socketio')
   const passportSocketIo = require('passport.socketio')
   const session = require('express-session')
   const session = require('express-session')
   const SessionRedisStore = require('connect-redis')(session)
   const SessionRedisStore = require('connect-redis')(session)
@@ -78,10 +78,6 @@ module.exports = Promise.join(
   // Passport Authentication
   // Passport Authentication
   // ----------------------------------------
   // ----------------------------------------
 
 
-  require('./modules/auth').init(passport)
-  wiki.rights = require('./modules/rights')
-  // wiki.rights.init()
-
   let sessionStore = new SessionRedisStore({
   let sessionStore = new SessionRedisStore({
     client: wiki.redis
     client: wiki.redis
   })
   })
@@ -95,8 +91,8 @@ module.exports = Promise.join(
     saveUninitialized: false
     saveUninitialized: false
   }))
   }))
   app.use(flash())
   app.use(flash())
-  app.use(passport.initialize())
-  app.use(passport.session())
+  app.use(wiki.auth.passport.initialize())
+  app.use(wiki.auth.passport.session())
 
 
   // ----------------------------------------
   // ----------------------------------------
   // SEO
   // SEO
@@ -135,6 +131,7 @@ module.exports = Promise.join(
   // View accessible data
   // View accessible data
   // ----------------------------------------
   // ----------------------------------------
 
 
+  app.locals.basedir = wiki.ROOTPATH
   app.locals._ = require('lodash')
   app.locals._ = require('lodash')
   app.locals.t = wiki.lang.t.bind(wiki.lang)
   app.locals.t = wiki.lang.t.bind(wiki.lang)
   app.locals.moment = require('moment')
   app.locals.moment = require('moment')

+ 1 - 1
server/middlewares/flash.js

@@ -9,7 +9,7 @@
  * @return     {any}               void
  * @return     {any}               void
  */
  */
 module.exports = (req, res, next) => {
 module.exports = (req, res, next) => {
-  res.locals.appflash = req.flash('alert')
+  res.locals.flash = req.flash('alert')
 
 
   next()
   next()
 }
 }

+ 26 - 12
server/modules/auth.js

@@ -3,10 +3,16 @@
 /* global wiki */
 /* global wiki */
 
 
 const _ = require('lodash')
 const _ = require('lodash')
+const passport = require('passport')
+const fs = require('fs-extra')
+const path = require('path')
 
 
 module.exports = {
 module.exports = {
-  init(passport) {
-  // Serialization user methods
+  strategies: {},
+  init() {
+    this.passport = passport
+
+    // Serialization user methods
 
 
     passport.serializeUser(function (user, done) {
     passport.serializeUser(function (user, done) {
       done(null, user._id)
       done(null, user._id)
@@ -27,20 +33,26 @@ module.exports = {
 
 
     // Load authentication strategies
     // Load authentication strategies
 
 
-    wiki.config.authStrategies = {
-      list: _.pickBy(wiki.config.auth, strategy => strategy.enabled),
-      socialEnabled: (_.chain(wiki.config.auth).omit('local').filter(['enabled', true]).value().length > 0)
-    }
-
-    _.forOwn(wiki.config.authStrategies.list, (strategyConfig, strategyName) => {
-      strategyConfig.callbackURL = `${wiki.config.site.host}/login/${strategyName}/callback`
-      require(`../authentication/${strategyName}`)(passport, strategyConfig)
-      wiki.logger.info(`Authentication Provider ${_.upperFirst(strategyName)}: OK`)
+    _.forOwn(wiki.config.auth.strategies, (strategyConfig, strategyKey) => {
+      strategyConfig.callbackURL = `${wiki.config.site.host}${wiki.config.site.path}/login/${strategyKey}/callback`
+      let strategy = require(`../authentication/${strategyKey}`)
+      strategy.init(passport, strategyConfig)
+      fs.readFile(path.join(wiki.ROOTPATH, `assets/svg/auth-icon-${strategyKey}.svg`), 'utf8').then(iconData => {
+        strategy.icon = iconData
+      }).catch(err => {
+        if (err.code === 'ENOENT') {
+          strategy.icon = '[missing icon]'
+        } else {
+          wiki.logger.error(err)
+        }
+      })
+      this.strategies[strategy.key] = strategy
+      wiki.logger.info(`Authentication Provider ${strategyKey}: OK`)
     })
     })
 
 
     // Create Guest account for first-time
     // Create Guest account for first-time
 
 
-    return wiki.db.User.findOne({
+    wiki.db.User.findOne({
       where: {
       where: {
         provider: 'local',
         provider: 'local',
         email: 'guest@example.com'
         email: 'guest@example.com'
@@ -88,5 +100,7 @@ module.exports = {
     //     })
     //     })
     //   } else { return true }
     //   } else { return true }
     // })
     // })
+
+    return this
   }
   }
 }
 }

+ 20 - 42
server/views/auth/login.pug

@@ -3,52 +3,30 @@ extends ../master.pug
 block body
 block body
   body
   body
     .login#root
     .login#root
-      .login-container
-        if config.authStrategies.socialEnabled
+      .login-container(:class={ "is-expanded": hasMultipleStrategies })
+        if flash.length > 0
+          .login-error
+            strong
+              i.icon-warning-outline
+              = flash[0].title
+            span= flash[0].message
+        if hasMultipleStrategies
           .login-providers
           .login-providers
-            button.is-active(onclick='window.location.assign("/login/ms")')
+            button.is-active(title=t('auth:providers.local'))
               i.nc-icon-outline.ui-1_database
               i.nc-icon-outline.ui-1_database
               span= t('auth:providers.local')
               span= t('auth:providers.local')
-            if config.auth.microsoft && config.auth.microsoft.enabled
-              button(onclick='window.location.assign("/login/ms")')
-                i.icon-windows2
-                span= t('auth:providers.windowslive')
-            if config.auth.azure && config.auth.azure.enabled
-              button(onclick='window.location.assign("/login/azure")')
-                i.icon-windows2
-                span= t('auth:providers.azure')
-            if config.auth.google && config.auth.google.enabled
-              button(onclick='window.location.assign("/login/google")')
-                i.icon-google
-                span= t('auth:providers.google')
-            if config.auth.facebook && config.auth.facebook.enabled
-              button(onclick='window.location.assign("/login/facebook")')
-                i.icon-facebook
-                span= t('auth:providers.facebook')
-            if config.auth.github && config.auth.github.enabled
-              button(onclick='window.location.assign("/login/github")')
-                i.icon-github
-                span= t('auth:providers.github')
-            if config.auth.slack && config.auth.slack.enabled
-              button(onclick='window.location.assign("/login/slack")')
-                i.icon-slack
-                span= t('auth:providers.slack')
+            each strategy in authStrategies
+              button(onclick='window.location.assign("/login/' + strategy.key + '")', title=strategy.title)
+                != strategy.icon
+                span= strategy.title
         .login-frame
         .login-frame
           h1= config.site.title
           h1= config.site.title
           h2= t('auth:loginrequired')
           h2= t('auth:loginrequired')
-          if appflash.length > 0
-            h3
-              i.icon-warning-outline
-              = appflash[0].title
-            h4= appflash[0].message
-          if config.auth.local.enabled
-            form(method='post', action='/login')
-              input#login-user(type='text', name='email', placeholder=t('auth:fields.emailuser'))
-              input#login-pass(type='password', name='password', placeholder=t('auth:fields.password'))
-              button.button.is-light-green.is-fullwidth(type='submit')
-                span= t('auth:actions.login')
+          form(method='post', action='/login')
+            input#login-user(type='text', name='email', placeholder=t('auth:fields.emailuser'))
+            input#login-pass(type='password', name='password', placeholder=t('auth:fields.password'))
+            button.button.is-light-green.is-fullwidth(type='submit')
+              span= t('auth:actions.login')
       .login-copyright
       .login-copyright
-        = t('footer.poweredby') + ' '
-        a.icon(href='https://github.com/Requarks/wiki')
-          i.icon-github
-        a(href='https://wiki.requarks.io/') Wiki.js
+        = t('footer.poweredby')
+        a(href='https://wiki.js.org', rel='external', title='Wiki.js') Wiki.js

Някои файлове не бяха показани, защото твърде много файлове са промени