diff --git a/client/components/login.vue b/client/components/login.vue index 650c720f..56295f7b 100644 --- a/client/components/login.vue +++ b/client/components/login.vue @@ -106,16 +106,16 @@ export default { } else { this.isLoading = true graphQL.mutate({ - mutation: CONSTANTS.GRAPHQL.GQL_MUTATION_LOGIN, + mutation: CONSTANTS.GRAPH.AUTHENTICATION.MUTATION_LOGIN, variables: { username: this.username, password: this.password, provider: this.selectedStrategy } }).then(resp => { - if (resp.data.login) { - let respObj = resp.data.login - if (respObj.succeeded === true) { + if (_.has(resp, 'data.authentication.login')) { + let respObj = _.get(resp, 'data.authentication.login', {}) + if (respObj.operation.succeeded === true) { if (respObj.tfaRequired === true) { this.screen = 'tfa' this.securityCode = '' @@ -132,7 +132,7 @@ export default { } this.isLoading = false } else { - throw new Error(respObj.message) + throw new Error(respObj.operation.message) } } else { throw new Error('Authentication is unavailable.') @@ -159,15 +159,15 @@ export default { } else { this.isLoading = true graphQL.mutate({ - mutation: CONSTANTS.GRAPHQL.GQL_MUTATION_LOGINTFA, + mutation: CONSTANTS.GRAPH.AUTHENTICATION.MUTATION_LOGINTFA, variables: { loginToken: this.loginToken, securityCode: this.securityCode } }).then(resp => { - if (resp.data.loginTFA) { - let respObj = resp.data.loginTFA - if (respObj.succeeded === true) { + if (_.has(resp, 'data.authentication.loginTFA')) { + let respObj = _.get(resp, 'data.authentication.loginTFA', {}) + if (respObj.operation.succeeded === true) { this.$store.dispatch('alert', { style: 'success', icon: 'gg-check', @@ -175,7 +175,7 @@ export default { }) this.isLoading = false } else { - throw new Error(respObj.message) + throw new Error(respObj.operation.message) } } else { throw new Error('Authentication is unavailable.') @@ -382,6 +382,10 @@ export default { } } + em { + height: 20px; + } + span { font-weight: 600; diff --git a/client/constants/graphql.js b/client/constants/graphql.js index f5feec89..40d84c0c 100644 --- a/client/constants/graphql.js +++ b/client/constants/graphql.js @@ -33,32 +33,46 @@ export default { } } } - ` - }, - GQL_QUERY_TRANSLATIONS: gql` - query($locale: String!, $namespace: String!) { - translations(locale:$locale, namespace:$namespace) { - key - value + `, + MUTATION_LOGIN: gql` + mutation($username: String!, $password: String!, $provider: String!) { + authentication { + login(username: $username, password: $password, provider: $provider) { + operation { + succeeded + code + slug + message + } + tfaRequired + tfaLoginToken + } + } } - } - `, - GQL_MUTATION_LOGIN: gql` - mutation($username: String!, $password: String!, $provider: String!) { - login(username: $username, password: $password, provider: $provider) { - succeeded - message - tfaRequired - tfaLoginToken + `, + MUTATION_LOGINTFA: gql` + mutation($loginToken: String!, $securityCode: String!) { + authentication { + loginTFA(loginToken: $loginToken, securityCode: $securityCode) { + operation { + succeeded + code + slug + message + } + } + } } - } - `, - GQL_MUTATION_LOGINTFA: gql` - mutation($loginToken: String!, $securityCode: String!) { - loginTFA(loginToken: $loginToken, securityCode: $securityCode) { - succeeded - message + ` + }, + TRANSLATIONS: { + QUERY_NAMESPACE: gql` + query($locale: String!, $namespace: String!) { + translations(locale:$locale, namespace:$namespace) { + key + value + } } - } - ` + ` + } } diff --git a/client/modules/localization.js b/client/modules/localization.js index c13e2719..26e36c9c 100644 --- a/client/modules/localization.js +++ b/client/modules/localization.js @@ -19,7 +19,7 @@ module.exports = { ajax: (url, opts, cb, data) => { let langParams = url.split('/') graphQL.query({ - query: CONSTANTS.GRAPHQL.GQL_QUERY_TRANSLATIONS, + query: CONSTANTS.GRAPH.TRANSLATIONS.QUERY_NAMESPACE, variables: { locale: langParams[0], namespace: langParams[1] diff --git a/client/scss/app.scss b/client/scss/app.scss index 67f0454d..11f30520 100644 --- a/client/scss/app.scss +++ b/client/scss/app.scss @@ -1,5 +1,7 @@ @import "global"; +@import "base/reset"; +@import "base/base"; @import 'base/icons'; @import "../libs/animate/animate"; diff --git a/client/scss/components/setup.scss b/client/scss/components/setup.scss index e073d87e..03e6be92 100644 --- a/client/scss/components/setup.scss +++ b/client/scss/components/setup.scss @@ -2,7 +2,7 @@ background-color: #1565c0; background-image: url('../static/svg/config-bg.svg'); width: 100%; - min-height: 100%; + min-height: 100vh; padding-top: 1rem; .welcome { diff --git a/client/scss/global.scss b/client/scss/global.scss index 761f4149..8f06dc4f 100644 --- a/client/scss/global.scss +++ b/client/scss/global.scss @@ -4,6 +4,4 @@ $primary: 'indigo'; @import "base/variables"; @import "base/material"; -@import "base/reset"; @import "base/mixins"; -@import "base/base"; diff --git a/client/static/svg/auth-icon-auth0.svg b/client/static/svg/auth-icon-auth0.svg new file mode 100644 index 00000000..9bf44793 --- /dev/null +++ b/client/static/svg/auth-icon-auth0.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/client/static/svg/auth-icon-discord.svg b/client/static/svg/auth-icon-discord.svg new file mode 100644 index 00000000..281dd379 --- /dev/null +++ b/client/static/svg/auth-icon-discord.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/client/static/svg/auth-icon-dropbox.svg b/client/static/svg/auth-icon-dropbox.svg new file mode 100644 index 00000000..155c67c2 --- /dev/null +++ b/client/static/svg/auth-icon-dropbox.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/client/static/svg/auth-icon-ldap.svg b/client/static/svg/auth-icon-ldap.svg index 679fc237..2ab8ff17 100644 --- a/client/static/svg/auth-icon-ldap.svg +++ b/client/static/svg/auth-icon-ldap.svg @@ -1,7 +1,4 @@ - - - - diff --git a/client/static/svg/auth-icon-oauth2.svg b/client/static/svg/auth-icon-oauth2.svg new file mode 100644 index 00000000..3ef6ea34 --- /dev/null +++ b/client/static/svg/auth-icon-oauth2.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/client/static/svg/auth-icon-twitch.svg b/client/static/svg/auth-icon-twitch.svg new file mode 100644 index 00000000..75a60a5b --- /dev/null +++ b/client/static/svg/auth-icon-twitch.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/dev/webpack/webpack.common.js b/dev/webpack/webpack.common.js index 07aa27a4..26584d18 100644 --- a/dev/webpack/webpack.common.js +++ b/dev/webpack/webpack.common.js @@ -5,7 +5,6 @@ const webpack = require('webpack') const CopyWebpackPlugin = require('copy-webpack-plugin') const ExtractTextPlugin = require('extract-text-webpack-plugin') const NameAllModulesPlugin = require('name-all-modules-plugin') -const LodashModuleReplacementPlugin = require('lodash-webpack-plugin') const babelConfig = fs.readJsonSync(path.join(process.cwd(), '.babelrc')) const postCSSConfig = { @@ -24,6 +23,7 @@ module.exports = { }, output: { path: path.join(process.cwd(), 'assets'), + publicPath: '/', filename: 'js/[name].js', chunkFilename: 'js/[name].chunk.js' }, @@ -104,6 +104,10 @@ module.exports = { { loader: 'css-loader' }, + { + loader: 'postcss-loader', + options: postCSSConfig + }, { loader: 'stylus-loader' } @@ -114,37 +118,43 @@ module.exports = { test: /\.vue$/, loader: 'vue-loader', options: { - extractCSS: ExtractTextPlugin, - postcss: postCSSConfig, loaders: { - css: [ - { - loader: 'vue-style-loader' - }, - { - loader: 'css-loader' - } - ], - scss: [ - { - loader: 'vue-style-loader' - }, - { - loader: 'css-loader' - }, - { - loader: 'sass-loader', - options: { - sourceMap: false + css: ExtractTextPlugin.extract({ + fallback: 'vue-style-loader', + use: [ + { + loader: 'css-loader' + }, + { + loader: 'postcss-loader', + options: postCSSConfig } - }, - { - loader: 'sass-resources-loader', - options: { - resources: path.join(process.cwd(), '/client/scss/global.scss') + ] + }), + scss: ExtractTextPlugin.extract({ + fallback: 'vue-style-loader', + use: [ + { + loader: 'css-loader' + }, + { + loader: 'postcss-loader', + options: postCSSConfig + }, + { + loader: 'sass-loader', + options: { + sourceMap: false + } + }, + { + loader: 'sass-resources-loader', + options: { + resources: path.join(process.cwd(), '/client/scss/global.scss') + } } - } - ], + ] + }), js: [ { loader: 'cache-loader', @@ -209,7 +219,6 @@ module.exports = { ], { }), - new LodashModuleReplacementPlugin(), new webpack.NamedModulesPlugin(), new webpack.NamedChunksPlugin((chunk) => { if (chunk.name) { @@ -237,7 +246,8 @@ module.exports = { 'mdi': path.resolve(process.cwd(), 'node_modules/vue-material-design-icons'), // Duplicates fixes: 'apollo-link': path.join(process.cwd(), 'node_modules/apollo-link'), - 'apollo-utilities': path.join(process.cwd(), 'node_modules/apollo-utilities') + 'apollo-utilities': path.join(process.cwd(), 'node_modules/apollo-utilities'), + 'uc.micro': path.join(process.cwd(), 'node_modules/uc.micro') }, extensions: [ '.js', diff --git a/dev/webpack/webpack.dev.js b/dev/webpack/webpack.dev.js index 7c128354..3dea6c0f 100644 --- a/dev/webpack/webpack.dev.js +++ b/dev/webpack/webpack.dev.js @@ -12,8 +12,7 @@ module.exports = merge(common, { client: ['./client/index.js', 'webpack-hot-middleware/client'] }, output: { - pathinfo: true, - publicPath: '/' + pathinfo: true }, plugins: [ new SimpleProgressWebpackPlugin({ diff --git a/dev/webpack/webpack.prod.js b/dev/webpack/webpack.prod.js index ab1a64a8..47d8dfc5 100644 --- a/dev/webpack/webpack.prod.js +++ b/dev/webpack/webpack.prod.js @@ -8,6 +8,7 @@ const ExtractTextPlugin = require('extract-text-webpack-plugin') const OfflinePlugin = require('offline-plugin') const DuplicatePackageCheckerPlugin = require('duplicate-package-checker-webpack-plugin') const SimpleProgressWebpackPlugin = require('simple-progress-webpack-plugin') +const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin') const common = require('./webpack.common.js') @@ -35,7 +36,14 @@ module.exports = merge(common, { new webpack.DefinePlugin({ 'process.env.NODE_ENV': JSON.stringify('production') }), - new ExtractTextPlugin('css/bundle.css'), + new ExtractTextPlugin({ + filename: 'css/bundle.css', + allChunks: true + }), + new OptimizeCssAssetsPlugin({ + cssProcessorOptions: { discardComments: { removeAll: true } }, + canPrint: true + }), new OfflinePlugin({ publicPath: '/', externals: ['/'], diff --git a/package.json b/package.json index 8bffc2c2..497fe1b5 100644 --- a/package.json +++ b/package.json @@ -182,6 +182,7 @@ "name-all-modules-plugin": "1.0.1", "node-sass": "4.7.2", "offline-plugin": "4.9.0", + "optimize-css-assets-webpack-plugin": "3.2.0", "postcss-flexbugs-fixes": "3.3.0", "postcss-flexibility": "2.0.0", "postcss-loader": "2.1.1", diff --git a/server/core/auth.js b/server/core/auth.js index c23ac426..82a77672 100644 --- a/server/core/auth.js +++ b/server/core/auth.js @@ -33,10 +33,12 @@ module.exports = { // Load authentication strategies const modules = _.values(autoload(path.join(WIKI.SERVERPATH, 'modules/authentication'))) + console.info(WIKI.config.auth) _.forEach(modules, (strategy) => { const strategyConfig = _.get(WIKI.config.auth.strategies, strategy.key, {}) strategyConfig.callbackURL = `${WIKI.config.site.host}${WIKI.config.site.path}login/${strategy.key}/callback` if (strategyConfig.isEnabled) { + console.info(strategy.title) try { strategy.init(passport, strategyConfig) } catch (err) { diff --git a/server/graph/resolvers/authentication.js b/server/graph/resolvers/authentication.js index 6c0fd271..428e1e49 100644 --- a/server/graph/resolvers/authentication.js +++ b/server/graph/resolvers/authentication.js @@ -29,6 +29,30 @@ module.exports = { return prv } }, + AuthenticationMutation: { + async login(obj, args, context) { + try { + let authResult = await WIKI.db.User.login(args, context) + return { + ...authResult, + operation: graphHelper.generateSuccess('Login success') + } + } catch (err) { + return graphHelper.generateError(err) + } + }, + async loginTFA(obj, args, context) { + try { + let authResult = await WIKI.db.User.loginTFA(args, context) + return { + ...authResult, + operation: graphHelper.generateSuccess('TFA success') + } + } catch (err) { + return graphHelper.generateError(err) + } + } + }, AuthenticationProvider: { icon (ap, args) { return fs.readFile(path.join(WIKI.ROOTPATH, `assets/svg/auth-icon-${ap.key}.svg`), 'utf8').catch(err => { diff --git a/server/graph/resolvers/user.js b/server/graph/resolvers/user.js index b5137b31..f5071f5b 100644 --- a/server/graph/resolvers/user.js +++ b/server/graph/resolvers/user.js @@ -19,22 +19,6 @@ module.exports = { limit: 1 }) }, - login(obj, args, context) { - return WIKI.db.User.login(args, context).catch(err => { - return { - succeeded: false, - message: err.message - } - }) - }, - loginTFA(obj, args, context) { - return WIKI.db.User.loginTFA(args, context).catch(err => { - return { - succeeded: false, - message: err.message - } - }) - }, modifyUser(obj, args) { return WIKI.db.User.update({ email: args.email, diff --git a/server/graph/schemas/authentication.graphql b/server/graph/schemas/authentication.graphql index 41b0f3d7..fd9161c5 100644 --- a/server/graph/schemas/authentication.graphql +++ b/server/graph/schemas/authentication.graphql @@ -1,3 +1,7 @@ +# =============================================== +# AUTHENTICATION +# =============================================== + extend type Query { authentication: AuthenticationQuery } @@ -6,6 +10,10 @@ extend type Mutation { authentication: AuthenticationMutation } +# ----------------------------------------------- +# QUERIES +# ----------------------------------------------- + type AuthenticationQuery { providers( filter: String @@ -13,7 +21,22 @@ type AuthenticationQuery { ): [AuthenticationProvider] } +# ----------------------------------------------- +# MUTATIONS +# ----------------------------------------------- + type AuthenticationMutation { + login( + username: String! + password: String! + provider: String! + ): AuthenticationLoginResponse + + loginTFA( + loginToken: String! + securityCode: String! + ): DefaultResponse + updateProvider( provider: String! isEnabled: Boolean! @@ -21,6 +44,10 @@ type AuthenticationMutation { ): DefaultResponse } +# ----------------------------------------------- +# TYPES +# ----------------------------------------------- + type AuthenticationProvider { isEnabled: Boolean! key: String! @@ -30,3 +57,9 @@ type AuthenticationProvider { icon: String config: [KeyValuePair] } + +type AuthenticationLoginResponse { + operation: ResponseStatus + tfaRequired: Boolean + tfaLoginToken: String +} diff --git a/server/graph/schemas/common.graphql b/server/graph/schemas/common.graphql index 9d9058bb..ce687589 100644 --- a/server/graph/schemas/common.graphql +++ b/server/graph/schemas/common.graphql @@ -162,13 +162,6 @@ type OperationResult { data: String } -type LoginResult { - succeeded: Boolean! - message: String - tfaRequired: Boolean - tfaLoginToken: String -} - # Query (Read) type Query { comments(id: Int): [Comment] @@ -265,17 +258,6 @@ type Mutation { id: Int! ): OperationResult - login( - username: String! - password: String! - provider: String! - ): LoginResult - - loginTFA( - loginToken: String! - securityCode: String! - ): OperationResult - modifyComment( id: Int! content: String! diff --git a/server/helpers/graph.js b/server/helpers/graph.js index b3d151f5..689bb50f 100644 --- a/server/helpers/graph.js +++ b/server/helpers/graph.js @@ -2,6 +2,23 @@ const _ = require('lodash') const Filter = require('scim-query-filter-parser') module.exports = { + generateSuccess (msg) { + return { + succeeded: true, + code: 0, + slug: 'ok', + message: _.defaultTo(msg, 'Operation succeeded.') + } + }, + generateError (err, complete = true) { + const error = { + succeeded: false, + code: err.code || 1, + slug: err.name, + message: err.message || 'An unexpected error occured.' + } + return (complete) ? { operation: error } : error + }, filter (arr, filterString) { const prvFilter = new Filter(_.toString(filterString).replace(/'/g, `"`)) return arr.filter(prvFilter.test) diff --git a/server/models/user.js b/server/models/user.js index 90405846..3b2013d7 100644 --- a/server/models/user.js +++ b/server/models/user.js @@ -103,8 +103,6 @@ module.exports = (sequelize, DataTypes) => { let loginToken = await securityHelper.generateToken(32) await WIKI.redis.set(`tfa:${loginToken}`, user.id, 'EX', 600) return resolve({ - succeeded: true, - message: 'Login Successful. Awaiting 2FA security code.', tfaRequired: true, tfaLoginToken: loginToken }) @@ -117,8 +115,6 @@ module.exports = (sequelize, DataTypes) => { return context.req.logIn(user, err => { if (err) { return reject(err) } resolve({ - succeeded: true, - message: 'Login Successful', tfaRequired: false }) }) diff --git a/server/views/main/setup.pug b/server/views/main/setup.pug new file mode 100644 index 00000000..9237a588 --- /dev/null +++ b/server/views/main/setup.pug @@ -0,0 +1,6 @@ +extends ../master.pug + +block body + body + #app.setup.is-fullscreen + setup(telemetry-id=telemetryClientID, wiki-version=packageObj.version, :langs!=JSON.stringify(data.langs).replace(/"/g, "'")) diff --git a/yarn.lock b/yarn.lock index 724ccacd..3616915f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2796,7 +2796,7 @@ cssnano@4.0.0-rc.2: is-resolvable "^1.0.0" postcss "^6.0.0" -cssnano@^3.10.0: +cssnano@^3.10.0, cssnano@^3.4.0: version "3.10.0" resolved "https://registry.yarnpkg.com/cssnano/-/cssnano-3.10.0.tgz#4f38f6cea2b9b17fa01490f23f1dc68ea65c1c38" dependencies: @@ -5749,6 +5749,13 @@ klaw@2.1.1: dependencies: graceful-fs "^4.1.9" +last-call-webpack-plugin@^2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/last-call-webpack-plugin/-/last-call-webpack-plugin-2.1.2.tgz#ad80c6e310998294d2ed2180a68e9589e4768c44" + dependencies: + lodash "^4.17.4" + webpack-sources "^1.0.1" + lazy-cache@^1.0.3: version "1.0.4" resolved "https://registry.yarnpkg.com/lazy-cache/-/lazy-cache-1.0.4.tgz#a1d78fc3a50474cb80845d3b3b6e1da49a446e8e" @@ -6905,6 +6912,13 @@ optimist@^0.6.1: minimist "~0.0.1" wordwrap "~0.0.2" +optimize-css-assets-webpack-plugin@3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/optimize-css-assets-webpack-plugin/-/optimize-css-assets-webpack-plugin-3.2.0.tgz#09a40c4cefde1dd0142444a873c56aa29eb18e6f" + dependencies: + cssnano "^3.4.0" + last-call-webpack-plugin "^2.1.2" + optionator@^0.8.1, optionator@^0.8.2: version "0.8.2" resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.2.tgz#364c5e409d3f4d6301d6c0b4c05bba50180aeb64"