From 501f0a9a536eba8b5edf7cfda443b359f0978c19 Mon Sep 17 00:00:00 2001 From: NGPixel Date: Sat, 30 Sep 2017 23:47:14 -0400 Subject: [PATCH] feat: auth strategies over GraphQL + svg loading --- assets/svg/auth-icon-azure.svg | 9 ++++- assets/svg/auth-icon-local.svg | 8 +++++ client/js/app.js | 11 +++++- client/js/components/login.vue | 36 ++++++++++++++----- client/js/constants/graphql.js | 22 ++++++++++++ client/js/constants/index.js | 5 +++ client/js/modules/localization.js | 19 ++++------ client/scss/components/button.scss | 6 +++- client/scss/pages/_login.scss | 30 ++++++++++++++-- server/authentication/microsoft.js | 2 +- server/modules/graphql.js | 2 ++ server/schemas/resolvers-authentication.js | 40 ++++++++++++++++++++++ server/schemas/types.graphql | 3 +- 13 files changed, 165 insertions(+), 28 deletions(-) create mode 100644 assets/svg/auth-icon-local.svg create mode 100644 client/js/constants/graphql.js create mode 100644 client/js/constants/index.js create mode 100644 server/schemas/resolvers-authentication.js 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 }