From 901dbb98e02dd6c5127519b18818f5e1dfeb96f4 Mon Sep 17 00:00:00 2001 From: Nicolas Giard Date: Mon, 17 Dec 2018 00:51:52 -0500 Subject: [PATCH] feat: register validation + create + admin improvements --- .editorconfig | 4 + Makefile | 1 + client/client-app.js | 2 + client/components/admin/admin-auth.vue | 12 + client/components/admin/admin-contribute.vue | 54 +++- client/components/admin/admin-rendering.vue | 6 +- client/components/admin/admin-users-edit.vue | 4 +- client/components/common/loader.vue | 51 +++ .../components/common/password-strength.vue | 79 +++++ client/components/editor.vue | 11 +- client/components/login.vue | 33 +- client/components/register.vue | 303 ++++++++++++++++++ .../register/register-mutation-create.gql | 13 + client/static/img/icon-browse.png | Bin 3106 -> 0 bytes client/static/img/icon-people.png | Bin 5006 -> 0 bytes client/static/img/icon-unlock.png | Bin 4343 -> 0 bytes client/static/svg/logo-icons8.svg | 18 ++ dev/docker/docker-compose.yml | 2 - package.json | 7 +- server/controllers/auth.js | 7 + server/graph/resolvers/authentication.js | 20 +- server/graph/schemas/authentication.graphql | 11 + server/helpers/error.js | 68 ++-- server/models/users.js | 19 ++ server/views/register.pug | 5 + 25 files changed, 668 insertions(+), 62 deletions(-) create mode 100644 client/components/common/loader.vue create mode 100644 client/components/common/password-strength.vue create mode 100644 client/components/register.vue create mode 100644 client/graph/register/register-mutation-create.gql delete mode 100644 client/static/img/icon-browse.png delete mode 100644 client/static/img/icon-people.png delete mode 100644 client/static/img/icon-unlock.png create mode 100644 client/static/svg/logo-icons8.svg create mode 100644 server/views/register.pug diff --git a/.editorconfig b/.editorconfig index 49af1a19..8f066db4 100644 --- a/.editorconfig +++ b/.editorconfig @@ -9,3 +9,7 @@ insert_final_newline = true [*.{jade,pug,md}] trim_trailing_whitespace = false + +[Makefile] +indent_style = tab +indent_size = 4 diff --git a/Makefile b/Makefile index b18fecc0..2ebf8e2e 100644 --- a/Makefile +++ b/Makefile @@ -30,6 +30,7 @@ docker-dev-down: ## Shutdown dockerized dev environment docker-compose -f ./dev/docker/docker-compose.yml -p wiki --project-directory . down docker-dev-rebuild: ## Rebuild dockerized dev image + rm -rf ./node_modules docker-compose -f ./dev/docker/docker-compose.yml -p wiki --project-directory . build --no-cache --force-rm docker-build: ## Run assets generation build in docker diff --git a/client/client-app.js b/client/client-app.js index 661c57e4..30f1f120 100644 --- a/client/client-app.js +++ b/client/client-app.js @@ -163,10 +163,12 @@ Vue.component('admin', () => import(/* webpackChunkName: "admin" */ './component Vue.component('editor', () => import(/* webpackPrefetch: -100, webpackChunkName: "editor" */ './components/editor.vue')) Vue.component('history', () => import(/* webpackChunkName: "history" */ './components/history.vue')) Vue.component('page-source', () => import(/* webpackChunkName: "source" */ './components/source.vue')) +Vue.component('loader', () => import(/* webpackPrefetch: true, webpackChunkName: "ui-extra" */ './components/common/loader.vue')) Vue.component('login', () => import(/* webpackPrefetch: true, webpackChunkName: "login" */ './components/login.vue')) Vue.component('nav-header', () => import(/* webpackMode: "eager" */ './components/common/nav-header.vue')) Vue.component('page-selector', () => import(/* webpackPrefetch: true, webpackChunkName: "ui-extra" */ './components/common/page-selector.vue')) Vue.component('profile', () => import(/* webpackChunkName: "profile" */ './components/profile.vue')) +Vue.component('register', () => import(/* webpackChunkName: "register" */ './components/register.vue')) Vue.component('v-card-chin', () => import(/* webpackPrefetch: true, webpackChunkName: "ui-extra" */ './components/common/v-card-chin.vue')) Vue.component('nav-footer', () => import(/* webpackChunkName: "theme-page" */ './themes/' + process.env.CURRENT_THEME + '/components/nav-footer.vue')) diff --git a/client/components/admin/admin-auth.vue b/client/components/admin/admin-auth.vue index 65c2cd43..61aba5a9 100644 --- a/client/components/admin/admin-auth.vue +++ b/client/components/admin/admin-auth.vue @@ -151,6 +151,18 @@ multiple chips ) + template(v-if='strategy.key === `local`') + v-divider.mt-3 + v-subheader.pl-0 Security + .pr-3 + v-switch.ml-3( + :disabled='true' + v-model='strategy.recaptcha' + label='Use reCAPTCHA by Google' + color='primary' + hint='Protects against spam robots and malicious registrations.' + persistent-hint + ) + + diff --git a/client/components/common/password-strength.vue b/client/components/common/password-strength.vue new file mode 100644 index 00000000..0203ab18 --- /dev/null +++ b/client/components/common/password-strength.vue @@ -0,0 +1,79 @@ + + + + + diff --git a/client/components/editor.vue b/client/components/editor.vue index 443d15a7..d7c4efb0 100644 --- a/client/components/editor.vue +++ b/client/components/editor.vue @@ -30,16 +30,6 @@ v-content component(:is='currentEditor') editor-modal-properties(v-model='dialogProps') - v-dialog(v-model='dialogProgress', persistent, max-width='350') - v-card(color='blue darken-3', dark) - v-card-text.text-xs-center.py-4 - atom-spinner.is-inline( - :animation-duration='1000' - :size='60' - color='#FFF' - ) - .subheading {{ $t('editor:save.processing') }} - .caption.blue--text.text--lighten-3 {{ $t('editor:save.pleaseWait') }} v-dialog(v-model='dialogEditorSelector', persistent, max-width='700') v-card.radius-7(color='blue darken-3', dark) v-card-text.text-xs-center.py-4 @@ -88,6 +78,7 @@ .caption.grey--text.text--darken-1 Drag-n-drop .caption.blue--text.text--lighten-2 This cannot be changed once the page is created. + loader(v-model='dialogProgress', :title='$t(`editor:save.processing`)', :subtitle='$t(`editor:save.pleaseWait`)') v-snackbar( :color='notification.style' bottom, diff --git a/client/components/login.vue b/client/components/login.vue index a65b63d7..24ffa9a3 100644 --- a/client/components/login.vue +++ b/client/components/login.vue @@ -15,7 +15,7 @@ v-toolbar(color='primary', flat, dense, dark) v-spacer .subheading(v-if='screen === "tfa"') {{ $t('auth:tfa.subtitle') }} - .subheading(v-else-if='selectedStrategy.key !== "local"') Login using {{ selectedStrategy.title }} + .subheading(v-else-if='selectedStrategy.key !== "local"') {{ $t('auth:loginUsingStrategy', { strategy: selectedStrategy.title }) }} .subheading(v-else) {{ $t('auth:loginRequired') }} v-spacer v-card-text.text-xs-center @@ -80,12 +80,12 @@ v-spacer v-card-actions.pb-3(v-if='selectedStrategy.key === "local"') v-spacer - a.caption(href='') Forgot your password? + a.caption(href='') {{ $t('auth:forgotPasswordLink') }} v-spacer template(v-if='isSocialShown') v-divider v-card-text.grey.lighten-4.text-xs-center - .pb-2.body-2.text-xs-center.grey--text.text--darken-2 or login using... + .pb-2.body-2.text-xs-center.grey--text.text--darken-2 {{ $t('auth:orLoginUsingStrategy') }} v-tooltip(top, v-for='strategy in strategies', :key='strategy.key') .social-login-btn.mr-2( slot='activator' @@ -99,8 +99,11 @@ v-divider v-card-actions.py-3(:class='isSocialShown ? "" : "grey lighten-4"') v-spacer - .caption Don't have an account yet? #[a.caption(href='') Create an account] + i18next.caption(path='auth:switchToRegister.text', tag='div') + a.caption(href='/register', place='link') {{ $t('auth:switchToRegister.link') }} v-spacer + + loader(v-model='isLoading', :color='loaderColor', :title='loaderTitle', :subtitle='$t(`auth:pleaseWait`)') nav-footer(color='grey darken-4') @@ -128,6 +131,8 @@ export default { securityCode: '', loginToken: '', isLoading: false, + loaderColor: 'grey darken-4', + loaderTitle: 'Working...', isShown: false } }, @@ -173,18 +178,20 @@ export default { if (this.username.length < 2) { this.$store.commit('showNotification', { style: 'red', - message: 'Enter a valid email / username.', + message: this.$t('auth:invalidEmailUsername'), icon: 'warning' }) this.$refs.iptEmail.focus() } else if (this.password.length < 2) { this.$store.commit('showNotification', { style: 'red', - message: 'Enter a valid password.', + message: this.$t('auth:invalidPassword'), icon: 'warning' }) this.$refs.iptPassword.focus() } else { + this.loaderColor = 'grey darken-4' + this.loaderTitle = this.$t('auth:signingIn') this.isLoading = true try { let resp = await this.$apollo.mutate({ @@ -205,23 +212,20 @@ export default { this.$nextTick(() => { this.$refs.iptTFA.focus() }) + this.isLoading = false } else { - this.$store.commit('showNotification', { - message: 'Login Successful! Redirecting...', - style: 'success', - icon: 'check' - }) + this.loaderColor = 'green darken-1' + this.loaderTitle = this.$t('auth:loginSuccess') Cookies.set('jwt', respObj.jwt, { expires: 365 }) _.delay(() => { window.location.replace('/') // TEMPORARY - USE RETURNURL }, 1000) } - this.isLoading = false } else { throw new Error(respObj.responseResult.message) } } else { - throw new Error('Authentication is unavailable.') + throw new Error(this.$t('auth:genericError')) } } catch (err) { console.error(err) @@ -270,7 +274,7 @@ export default { throw new Error(respObj.responseResult.message) } } else { - throw new Error('Authentication is unavailable.') + throw new Error(this.$t('auth:genericError')) } }).catch(err => { console.error(err) @@ -289,7 +293,6 @@ export default { query: strategiesQuery, update: (data) => data.authentication.strategies, watchLoading (isLoading) { - this.isLoading = isLoading this.$store.commit(`loading${isLoading ? 'Start' : 'Stop'}`, 'login-strategies-refresh') } } diff --git a/client/components/register.vue b/client/components/register.vue new file mode 100644 index 00000000..d253220c --- /dev/null +++ b/client/components/register.vue @@ -0,0 +1,303 @@ + + + + + diff --git a/client/graph/register/register-mutation-create.gql b/client/graph/register/register-mutation-create.gql new file mode 100644 index 00000000..16cc9836 --- /dev/null +++ b/client/graph/register/register-mutation-create.gql @@ -0,0 +1,13 @@ +mutation($email: String!, $password: String!, $name: String!) { + authentication { + register(email: $email, password: $password, name: $name) { + responseResult { + succeeded + errorCode + slug + message + } + jwt + } + } +} diff --git a/client/static/img/icon-browse.png b/client/static/img/icon-browse.png deleted file mode 100644 index 60a111995f6ea4a8cfc9da9952bee48c1383c575..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3106 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 KzgYM8=)VB)dk7-{ diff --git a/client/static/img/icon-people.png b/client/static/img/icon-people.png deleted file mode 100644 index 4c35b7d07c6be8b7f05a315c62bf8d531753c668..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5006 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 P+ARYeQ|)TamvR3G@z>Vq diff --git a/client/static/img/icon-unlock.png b/client/static/img/icon-unlock.png deleted file mode 100644 index 7fbd96b1596fca1eac5b00fee28f2c9d2b2ce3c8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4343 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 diff --git a/client/static/svg/logo-icons8.svg b/client/static/svg/logo-icons8.svg new file mode 100644 index 00000000..e9a6035e --- /dev/null +++ b/client/static/svg/logo-icons8.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/dev/docker/docker-compose.yml b/dev/docker/docker-compose.yml index 86c5e167..72f541b8 100644 --- a/dev/docker/docker-compose.yml +++ b/dev/docker/docker-compose.yml @@ -30,8 +30,6 @@ services: adminer: image: adminer:latest - environment: - ADMINER_DESIGN: pappu687 logging: driver: "none" networks: diff --git a/package.json b/package.json index dbb526fe..13c5051e 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "test": "eslint --format codeframe --ext .js,.vue . && pug-lint server/views && jest", "docker:dev:up": "docker-compose -f ./dev/docker/docker-compose.yml -p wiki --project-directory . up -d && docker-compose -f ./dev/docker/docker-compose.yml -p wiki --project-directory . exec wiki yarn dev", "docker:dev:down": "docker-compose -f ./dev/docker/docker-compose.yml -p wiki --project-directory . down", - "docker:dev:rebuild": "docker-compose -f ./dev/docker/docker-compose.yml -p wiki --project-directory . build --no-cache --force-rm", + "docker:dev:rebuild": "rmdir node_modules /s /q && docker-compose -f ./dev/docker/docker-compose.yml -p wiki --project-directory . build --no-cache --force-rm", "docker:build": "docker-compose -f ./dev/docker/docker-compose.yml -p wiki --project-directory . run wiki yarn build && docker-compose -f ./dev/docker/docker-compose.yml -p wiki --project-directory . down" }, "bin": { @@ -59,6 +59,7 @@ "connect-redis": "3.4.0", "cookie-parser": "1.4.3", "cors": "2.8.5", + "custom-error-instance": "2.1.1", "dependency-graph": "0.7.2", "diff": "3.5.0", "diff2html": "2.5.0", @@ -154,6 +155,7 @@ "subscriptions-transport-ws": "0.9.15", "uslug": "1.0.4", "uuid": "3.3.2", + "validate.js": "0.12.0", "validator": "10.9.0", "validator-as-promised": "1.0.2", "winston": "3.1.0", @@ -284,7 +286,8 @@ "webpack-subresource-integrity": "1.3.0", "whatwg-fetch": "3.0.0", "write-file-webpack-plugin": "4.4.1", - "xterm": "3.8.0" + "xterm": "3.8.0", + "zxcvbn": "4.4.2" }, "browserslist": [ "> 1%", diff --git a/server/controllers/auth.js b/server/controllers/auth.js index d5b7e110..fe20b107 100644 --- a/server/controllers/auth.js +++ b/server/controllers/auth.js @@ -18,6 +18,13 @@ router.get('/logout', function (req, res) { res.redirect('/') }) +/** + * Register form + */ +router.get('/register', function (req, res, next) { + res.render('register') +}) + /** * JWT Public Endpoints */ diff --git a/server/graph/resolvers/authentication.js b/server/graph/resolvers/authentication.js index 8ab56c31..d0af41ef 100644 --- a/server/graph/resolvers/authentication.js +++ b/server/graph/resolvers/authentication.js @@ -38,7 +38,7 @@ module.exports = { AuthenticationMutation: { async login(obj, args, context) { try { - let authResult = await WIKI.models.users.login(args, context) + const authResult = await WIKI.models.users.login(args, context) return { ...authResult, responseResult: graphHelper.generateSuccess('Login success') @@ -49,7 +49,7 @@ module.exports = { }, async loginTFA(obj, args, context) { try { - let authResult = await WIKI.models.users.loginTFA(args, context) + const authResult = await WIKI.models.users.loginTFA(args, context) return { ...authResult, responseResult: graphHelper.generateSuccess('TFA success') @@ -58,6 +58,22 @@ module.exports = { return graphHelper.generateError(err) } }, + async register(obj, args, context) { + try { + await WIKI.models.users.register(args, context) + const authResult = await WIKI.models.users.login({ + username: args.email, + password: args.password, + strategy: 'local' + }, context) + return { + jwt: authResult.jwt, + responseResult: graphHelper.generateSuccess('Registration success') + } + } catch (err) { + return graphHelper.generateError(err) + } + }, async updateStrategies(obj, args, context) { try { for (let str of args.strategies) { diff --git a/server/graph/schemas/authentication.graphql b/server/graph/schemas/authentication.graphql index aabb4295..e361762a 100644 --- a/server/graph/schemas/authentication.graphql +++ b/server/graph/schemas/authentication.graphql @@ -36,6 +36,12 @@ type AuthenticationMutation { securityCode: String! ): DefaultResponse + register( + email: String! + password: String! + name: String! + ): AuthenticationRegisterResponse + updateStrategies( strategies: [AuthenticationStrategyInput] ): DefaultResponse @auth(requires: ["manage:system"]) @@ -69,6 +75,11 @@ type AuthenticationLoginResponse { tfaLoginToken: String } +type AuthenticationRegisterResponse { + responseResult: ResponseStatus + jwt: String +} + input AuthenticationStrategyInput { isEnabled: Boolean! key: String! diff --git a/server/helpers/error.js b/server/helpers/error.js index 67e13d4f..3643372b 100644 --- a/server/helpers/error.js +++ b/server/helpers/error.js @@ -1,30 +1,44 @@ -class BaseError extends Error { - constructor (message) { - super(message) - this.name = this.constructor.name - Error.captureStackTrace(this, this.constructor) - } -} - -class AuthGenericError extends BaseError { constructor (message = 'An unexpected error occured during login.') { super(message) } } -class AuthLoginFailed extends BaseError { constructor (message = 'Invalid email / username or password.') { super(message) } } -class AuthProviderInvalid extends BaseError { constructor (message = 'Invalid authentication provider.') { super(message) } } -class AuthTFAFailed extends BaseError { constructor (message = 'Incorrect TFA Security Code.') { super(message) } } -class AuthTFAInvalid extends BaseError { constructor (message = 'Invalid TFA Security Code or Login Token.') { super(message) } } -class BruteInstanceIsInvalid extends BaseError { constructor (message = 'Invalid Brute Force Instance.') { super(message) } } -class BruteTooManyAttempts extends BaseError { constructor (message = 'Too many attempts! Try again later.') { super(message) } } -class LocaleInvalidNamespace extends BaseError { constructor (message = 'Invalid locale or namespace.') { super(message) } } -class UserCreationFailed extends BaseError { constructor (message = 'An unexpected error occured during user creation.') { super(message) } } +const CustomError = require('custom-error-instance') module.exports = { - BaseError, - AuthGenericError, - AuthLoginFailed, - AuthProviderInvalid, - AuthTFAFailed, - AuthTFAInvalid, - BruteInstanceIsInvalid, - BruteTooManyAttempts, - LocaleInvalidNamespace, - UserCreationFailed + AuthGenericError: CustomError('AuthGenericError', { + message: 'An unexpected error occured during login.', + code: 1001 + }), + AuthLoginFailed: CustomError('AuthLoginFailed', { + message: 'Invalid email / username or password.', + code: 1002 + }), + AuthProviderInvalid: CustomError('AuthProviderInvalid', { + message: 'Invalid authentication provider.', + code: 1003 + }), + AuthAccountAlreadyExists: CustomError('AuthAccountAlreadyExists', { + message: 'An account already exists using this email address.', + code: 1004 + }), + AuthTFAFailed: CustomError('AuthTFAFailed', { + message: 'Incorrect TFA Security Code.', + code: 1005 + }), + AuthTFAInvalid: CustomError('AuthTFAInvalid', { + message: 'Invalid TFA Security Code or Login Token.', + code: 1006 + }), + BruteInstanceIsInvalid: CustomError('BruteInstanceIsInvalid', { + message: 'Invalid Brute Force Instance.', + code: 1007 + }), + BruteTooManyAttempts: CustomError('BruteTooManyAttempts', { + message: 'Too many attempts! Try again later.', + code: 1008 + }), + LocaleInvalidNamespace: CustomError('LocaleInvalidNamespace', { + message: 'Invalid locale or namespace.', + code: 1009 + }), + UserCreationFailed: CustomError('UserCreationFailed', { + message: 'An unexpected error occured during user creation.', + code: 1010 + }) } diff --git a/server/models/users.js b/server/models/users.js index e5c44023..28e24c1e 100644 --- a/server/models/users.js +++ b/server/models/users.js @@ -292,4 +292,23 @@ module.exports = class User extends Model { } throw new WIKI.Error.AuthTFAInvalid() } + + static async register ({ email, password, name }, context) { + const usr = await WIKI.models.users.query().findOne({ email, providerKey: 'local' }) + if (!usr) { + await WIKI.models.users.query().insert({ + provider: 'local', + email, + name, + password, + locale: 'en', + defaultEditor: 'markdown', + tfaIsActive: false, + isSystem: false + }) + return true + } else { + throw new WIKI.Error.AuthAccountAlreadyExists() + } + } } diff --git a/server/views/register.pug b/server/views/register.pug new file mode 100644 index 00000000..952885f8 --- /dev/null +++ b/server/views/register.pug @@ -0,0 +1,5 @@ +extends master.pug + +block body + #root.is-fullscreen + register