diff --git a/assets/svg/auth-icon-azure.svg b/assets/svg/auth-icon-azure.svg
index cf13b912..13f1c52e 100644
--- a/assets/svg/auth-icon-azure.svg
+++ b/assets/svg/auth-icon-azure.svg
@@ -1 +1,8 @@
-
\ No newline at end of file
+
diff --git a/assets/svg/auth-icon-local.svg b/assets/svg/auth-icon-local.svg
new file mode 100644
index 00000000..da2fe997
--- /dev/null
+++ b/assets/svg/auth-icon-local.svg
@@ -0,0 +1,8 @@
+
diff --git a/client/js/app.js b/client/js/app.js
index a42068a1..cd4f94c9 100644
--- a/client/js/app.js
+++ b/client/js/app.js
@@ -3,6 +3,8 @@
/* global siteConfig */
/* eslint-disable no-new */
+import CONSTANTS from './constants'
+
import Vue from 'vue'
import VueResource from 'vue-resource'
import VueClipboards from 'vue-clipboards'
@@ -54,11 +56,18 @@ import contentViewComponent from './pages/content-view.component.js'
import editorComponent from './components/editor.component.js'
import sourceViewComponent from './pages/source-view.component.js'
+// ====================================
+// Initialize Global Vars
+// ====================================
+
+window.wiki = null
+window.CONSTANTS = CONSTANTS
+
// ====================================
// Initialize Apollo Client (GraphQL)
// ====================================
-window.apollo = new ApolloClient({
+window.graphQL = new ApolloClient({
networkInterface: createBatchingNetworkInterface({
uri: window.location.protocol + '//' + window.location.host + siteConfig.path + '/graphql'
}),
diff --git a/client/js/components/login.vue b/client/js/components/login.vue
index 9db5f58c..d725ce5a 100644
--- a/client/js/components/login.vue
+++ b/client/js/components/login.vue
@@ -7,11 +7,8 @@
| {{ error.title }}
span {{ error.message }}
.login-providers(v-show='strategies.length > 1')
- button.is-active(:title='$t("auth:providers.local")')
- i.nc-icon-outline.ui-1_database
- span {{ $t('auth:providers.local') }}
- button(v-for='strategy in strategies', @onclick='selectProvider(strategy.key, strategy.useForm)', :title='strategy.title')
- //-!= strategy.icon
+ button(v-for='strategy in strategies', :class='{ "is-active": strategy.key === selectedStrategy }', @click='selectStrategy(strategy.key, strategy.useForm)', :title='strategy.title')
+ em(v-html='strategy.icon')
span {{ strategy.title }}
.login-frame
h1 {{ siteTitle }}
@@ -32,7 +29,8 @@ export default {
data() {
return {
error: false,
- strategies: []
+ strategies: [],
+ selectedStrategy: 'local'
}
},
computed: {
@@ -41,9 +39,31 @@ export default {
}
},
methods: {
- selectProvider(key, useForm) {
-
+ selectStrategy(key, useForm) {
+ this.selectedStrategy = key
+ if (!useForm) {
+ window.location.assign(siteConfig.path + '/login/' + key)
+ }
+ },
+ refreshStrategies() {
+ graphQL.query({
+ query: CONSTANTS.GRAPHQL.GQL_QUERY_AUTHENTICATION,
+ variables: {
+ mode: 'active'
+ }
+ }).then(resp => {
+ if (resp.data.authentication) {
+ this.strategies = resp.data.authentication
+ } else {
+ throw new Error('No authentication providers available!')
+ }
+ }).catch(err => {
+ console.error(err)
+ })
}
+ },
+ mounted() {
+ this.refreshStrategies()
}
}
diff --git a/client/js/constants/graphql.js b/client/js/constants/graphql.js
new file mode 100644
index 00000000..e189cce7
--- /dev/null
+++ b/client/js/constants/graphql.js
@@ -0,0 +1,22 @@
+import gql from 'graphql-tag'
+
+export default {
+ GQL_QUERY_AUTHENTICATION: gql`
+ query($mode: String!) {
+ authentication(mode:$mode) {
+ key
+ useForm
+ title
+ icon
+ }
+ }
+ `,
+ GQL_QUERY_TRANSLATIONS: gql`
+ query($locale: String!, $namespace: String!) {
+ translations(locale:$locale, namespace:$namespace) {
+ key
+ value
+ }
+ }
+ `
+}
diff --git a/client/js/constants/index.js b/client/js/constants/index.js
new file mode 100644
index 00000000..56e9acc7
--- /dev/null
+++ b/client/js/constants/index.js
@@ -0,0 +1,5 @@
+import GRAPHQL from './graphql'
+
+export default {
+ GRAPHQL
+}
diff --git a/client/js/modules/localization.js b/client/js/modules/localization.js
index cf696af1..2ac94cd7 100644
--- a/client/js/modules/localization.js
+++ b/client/js/modules/localization.js
@@ -1,11 +1,10 @@
import i18next from 'i18next'
import i18nextXHR from 'i18next-xhr-backend'
import i18nextCache from 'i18next-localstorage-cache'
-import gql from 'graphql-tag'
import VueI18Next from '@panter/vue-i18next'
import loSet from 'lodash/set'
-/* global siteConfig */
+/* global siteConfig, graphQL, CONSTANTS */
module.exports = {
VueI18Next,
@@ -19,16 +18,12 @@ module.exports = {
parse: (data) => data,
ajax: (url, opts, cb, data) => {
let langParams = url.split('/')
- console.info(langParams)
- window.apollo.query({
- query: gql`
- {
- translations(locale:"${langParams[0]}", namespace:"${langParams[1]}") {
- key
- value
- }
- }
- `
+ graphQL.query({
+ query: CONSTANTS.GRAPHQL.GQL_QUERY_TRANSLATIONS,
+ variables: {
+ locale: langParams[0],
+ namespace: langParams[1]
+ }
}).then(resp => {
let ns = {}
if (resp.data.translations.length > 0) {
diff --git a/client/scss/components/button.scss b/client/scss/components/button.scss
index c53754a3..a02562b5 100644
--- a/client/scss/components/button.scss
+++ b/client/scss/components/button.scss
@@ -61,7 +61,11 @@
background-color: mc($color,'800');
color: #FFF;
animation: none;
- }
+ }
+
+ &:focus {
+ box-shadow: inset 0 0 0 3px rgba(255,255,255, .4);
+ }
}
}
diff --git a/client/scss/pages/_login.scss b/client/scss/pages/_login.scss
index be4bd602..be90d44f 100644
--- a/client/scss/pages/_login.scss
+++ b/client/scss/pages/_login.scss
@@ -35,6 +35,7 @@
.login-frame {
border-radius: 0 6px 6px 0;
+ border-left: none;
}
}
@@ -104,6 +105,10 @@
align-items: center;
transition: all .4s ease;
+ &:focus {
+ outline: none;
+ }
+
@include until($tablet) {
justify-content: center;
}
@@ -114,12 +119,24 @@
&:first-child {
border-top: none;
+
+ &.is-active {
+ border-top: 1px solid rgba(255,255,255, .5);
+ }
}
&.is-active {
- background-color: mc('grey', '100');
- background-image: radial-gradient(circle at top left, rgba(mc('grey', '200'),1) 0%,rgba(255,255,255,1) 100%);
+ background-image: linear-gradient(to right, rgba(255,255,255,1) 0%,rgba(255,255,255,.77) 100%);
color: mc('light-blue', '700');
+ cursor: default;
+
+ &:hover {
+ background-color: transparent;
+ }
+
+ svg path {
+ fill: mc('light-blue', '800');
+ }
}
i {
@@ -160,7 +177,8 @@
}
&-frame {
- background-image: radial-gradient(circle at top left, rgba(255,255,255,1) 5%,rgba(240,240,240,.6) 100%);
+ background-image: radial-gradient(circle at top center, rgba(255,255,255,1) 5%,rgba(255,255,255,.6) 100%);
+ border: 1px solid rgba(255,255,255, .5);
border-radius: 6px;
width: 400px;
padding: 1rem;
@@ -178,6 +196,7 @@
font-size: 2rem;
font-weight: 600;
color: mc('light-blue', '700');
+ text-shadow: 1px 1px 0 #FFF;
padding: 0;
margin: 0;
}
@@ -186,6 +205,7 @@
font-size: 1.5rem;
font-weight: 300;
color: mc('grey', '700');
+ text-shadow: 1px 1px 0 #FFF;
padding: 0;
margin: 0 0 25px 0;
}
@@ -200,6 +220,7 @@
border: 1px solid #FFF;
border-radius: 3px;
background-color: rgba(255,255,255,.9);
+ box-shadow: inset 0 0 0 3px rgba(255,255,255, .25);
padding: 0 15px;
height: 40px;
margin: 0 0 10px 0;
@@ -212,6 +233,9 @@
&:focus {
outline: none;
border-color: mc('light-blue','500');
+ background-color: rgba(255,255,255,1);
+ box-shadow: inset 0 0 0 3px rgba(mc('light-blue','500'), .25);
+ color: mc('light-blue', '800');
}
}
diff --git a/server/authentication/microsoft.js b/server/authentication/microsoft.js
index 20e3e1d9..b761aa41 100644
--- a/server/authentication/microsoft.js
+++ b/server/authentication/microsoft.js
@@ -14,7 +14,7 @@ module.exports = {
useForm: false,
props: ['clientId', 'clientSecret', 'callbackURL'],
init (passport, conf) {
- passport.use('windowslive',
+ passport.use('microsoft',
new WindowsLiveStrategy({
clientID: conf.clientId,
clientSecret: conf.clientSecret,
diff --git a/server/modules/graphql.js b/server/modules/graphql.js
index e625afe6..aa6a6f7d 100644
--- a/server/modules/graphql.js
+++ b/server/modules/graphql.js
@@ -10,6 +10,7 @@ const _ = require('lodash')
const typeDefs = fs.readFileSync(path.join(wiki.SERVERPATH, 'schemas/types.graphql'), 'utf8')
const DateScalar = require('../schemas/scalar-date')
+const AuthenticationResolvers = require('../schemas/resolvers-authentication')
const CommentResolvers = require('../schemas/resolvers-comment')
const DocumentResolvers = require('../schemas/resolvers-document')
const FileResolvers = require('../schemas/resolvers-file')
@@ -21,6 +22,7 @@ const TranslationResolvers = require('../schemas/resolvers-translation')
const UserResolvers = require('../schemas/resolvers-user')
const resolvers = _.merge(
+ AuthenticationResolvers,
CommentResolvers,
DocumentResolvers,
FileResolvers,
diff --git a/server/schemas/resolvers-authentication.js b/server/schemas/resolvers-authentication.js
new file mode 100644
index 00000000..572c9d37
--- /dev/null
+++ b/server/schemas/resolvers-authentication.js
@@ -0,0 +1,40 @@
+const _ = require('lodash')
+const fs = require('fs-extra')
+const path = require('path')
+
+/* global wiki */
+
+module.exports = {
+ Query: {
+ authentication(obj, args, context, info) {
+ switch (args.mode) {
+ case 'active':
+ let strategies = _.chain(wiki.auth.strategies).map(str => {
+ return {
+ key: str.key,
+ title: str.title,
+ useForm: str.useForm
+ }
+ }).sortBy(['title']).value()
+ let localStrategy = _.remove(strategies, str => str.key === 'local')
+ return _.concat(localStrategy, strategies)
+ case 'all':
+
+ break
+ default:
+ return null
+ }
+ }
+ },
+ Mutation: {},
+ AuthenticationProvider: {
+ icon (ap, args) {
+ return fs.readFileAsync(path.join(wiki.ROOTPATH, `assets/svg/auth-icon-${ap.key}.svg`), 'utf8').catch(err => {
+ if (err.code === 'ENOENT') {
+ return null
+ }
+ throw err
+ })
+ }
+ }
+}
diff --git a/server/schemas/types.graphql b/server/schemas/types.graphql
index c6af303d..a47305b4 100644
--- a/server/schemas/types.graphql
+++ b/server/schemas/types.graphql
@@ -32,10 +32,11 @@ interface Base {
# TYPES
type AuthenticationProvider {
- id: String!
+ key: String!
useForm: Boolean!
title: String!
props: [String]
+ icon: String
config: String
}