From e4edab2b9d6c5ad7d370471029dfe1e2f7b8f97a Mon Sep 17 00:00:00 2001 From: Matteo Pagliazzi Date: Wed, 18 Dec 2019 19:02:15 +0100 Subject: [PATCH] Minimum password length + Static Pages fixes (was #11474) (#11506) * Revert "Revert "Minimum password length + Static Pages fixes (#11474)"" This reverts commit d1afbf4b924c82eb9ea3da3154160bdb617c397b. * add min length for reset password --- .../POST-auth_reset-password-set-new-one.js | 22 ++++++ .../user/auth/POST-register_local.test.js | 18 +++++ .../auth/PUT-user_update_password.test.js | 14 ++++ website/client/src/assets/scss/button.scss | 22 ++++-- website/client/src/assets/scss/form.scss | 17 ++++ .../client/src/components/auth/authForm.vue | 43 +++++++++- .../components/auth/registerLoginReset.vue | 79 +++++++++++++++---- website/client/src/components/static/home.vue | 69 ++++++++++------ .../src/components/static/staticWrapper.vue | 64 +++++++-------- website/common/locales/en/front.json | 1 + website/common/script/constants.js | 1 + website/common/script/index.js | 2 + website/server/controllers/api-v3/auth.js | 22 +++++- website/server/libs/auth/index.js | 5 ++ 14 files changed, 293 insertions(+), 86 deletions(-) diff --git a/test/api/v3/integration/user/auth/POST-auth_reset-password-set-new-one.js b/test/api/v3/integration/user/auth/POST-auth_reset-password-set-new-one.js index ed3cf27e63..51cea8bb89 100644 --- a/test/api/v3/integration/user/auth/POST-auth_reset-password-set-new-one.js +++ b/test/api/v3/integration/user/auth/POST-auth_reset-password-set-new-one.js @@ -189,6 +189,28 @@ describe('POST /user/auth/reset-password-set-new-one', () => { }); }); + it('renders the error page if the password is too short', async () => { + const user = await generateUser(); + + const code = encrypt(JSON.stringify({ + userId: user._id, + expiresAt: moment().add({ days: 1 }), + })); + await user.update({ + 'auth.local.passwordResetCode': code, + }); + + await expect(api.post(`${endpoint}`, { + newPassword: 'short', + confirmPassword: 'short', + code, + })).to.eventually.be.rejected.and.eql({ + code: 400, + error: 'BadRequest', + message: t('invalidReqParams'), + }); + }); + it('renders the success page and save the user', async () => { const user = await generateUser(); diff --git a/test/api/v3/integration/user/auth/POST-register_local.test.js b/test/api/v3/integration/user/auth/POST-register_local.test.js index 896e20a0c0..e435d77dd0 100644 --- a/test/api/v3/integration/user/auth/POST-register_local.test.js +++ b/test/api/v3/integration/user/auth/POST-register_local.test.js @@ -326,6 +326,24 @@ describe('POST /user/auth/local/register', () => { }); }); + it('requires minimum length for the password', async () => { + const username = generateRandomUserName(); + const email = `${username}@example.com`; + const password = '1234567'; + const confirmPassword = '1234567'; + + await expect(api.post('/user/auth/local/register', { + username, + email, + password, + confirmPassword, + })).to.eventually.be.rejected.and.eql({ + code: 400, + error: 'BadRequest', + message: t('invalidReqParams'), + }); + }); + it('requires a username', async () => { const email = `${generateRandomUserName()}@example.com`; const password = 'password'; diff --git a/test/api/v3/integration/user/auth/PUT-user_update_password.test.js b/test/api/v3/integration/user/auth/PUT-user_update_password.test.js index a46eb0f916..94fbd4f3e2 100644 --- a/test/api/v3/integration/user/auth/PUT-user_update_password.test.js +++ b/test/api/v3/integration/user/auth/PUT-user_update_password.test.js @@ -82,6 +82,20 @@ describe('PUT /user/auth/update-password', async () => { }); }); + it('returns an error when newPassword is too short', async () => { + const body = { + password, + newPassword: '1234567', + confirmPassword: '1234567', + }; + + await expect(user.put(ENDPOINT, body)).to.eventually.be.rejected.and.eql({ + code: 400, + error: 'BadRequest', + message: t('invalidReqParams'), + }); + }); + it('returns an error when confirmPassword is missing', async () => { const body = { password, diff --git a/website/client/src/assets/scss/button.scss b/website/client/src/assets/scss/button.scss index bc649e1dc5..0a2220a215 100644 --- a/website/client/src/assets/scss/button.scss +++ b/website/client/src/assets/scss/button.scss @@ -20,7 +20,7 @@ @include btn-focus-hover-shadow(); } - &:hover:not(.btn-flat):not(.disabled) { + &:hover:not(.btn-flat):not(.disabled):not(:disabled) { @include btn-focus-hover-shadow(); border-color: transparent; } @@ -49,7 +49,7 @@ background: $purple-200; } - &:hover:not(:disabled), &:active, &.active, &:focus { + &:hover:not(:disabled):not(.disabled), &:active:not(:disabled):not(.disabled), &.active:not(:disabled):not(.disabled), &:focus:not(:disabled):not(.disabled) { background: #5d3b9c !important; color: $white; } @@ -59,7 +59,7 @@ color: $gray-50; background: $white !important; - &:hover:not(:disabled):not(.disabled), &:active, &.active, &:focus { + &:hover:not(:disabled):not(.disabled), &:active:not(:disabled):not(.disabled), &.active:not(:disabled):not(.disabled), &:focus:not(:disabled):not(.disabled) { color: $purple-200 !important; } @@ -80,7 +80,7 @@ background: $green-100; } - &:hover:not(:disabled), &:active, &.active { + &:hover:not(:disabled):not(.disabled), &:active:not(:disabled):not(.disabled), &.active:not(:disabled):not(.disabled) { background: $green-50; } } @@ -96,8 +96,12 @@ background: $blue-50; } - &:hover:not(:disabled), &:active, &.active { - background: $blue-100; + &:hover:not(:disabled):not(.disabled) { + background-color: $blue-100; + } + + &:active:not(:disabled):not(.disabled), &.active:not(:disabled):not(.disabled) { + background: $blue-50; } } @@ -108,7 +112,11 @@ background: $red-50; } - &:hover:not(:disabled), &:active, &.active { + &:hover:not(:disabled):not(.disabled) { + background: $red-100; + } + + &:active:not(:disabled):not(.disabled), &.active:not(:disabled):not(.disabled) { background: $red-100; } } diff --git a/website/client/src/assets/scss/form.scss b/website/client/src/assets/scss/form.scss index 78b7e5397a..2e5934f5d5 100644 --- a/website/client/src/assets/scss/form.scss +++ b/website/client/src/assets/scss/form.scss @@ -65,6 +65,7 @@ input, textarea, input.form-control, textarea.form-control { padding-right: 40px; background-image: url(~@/assets/svg/for-css/alert.svg); background-size: 16px 16px; + border-color: $red-100 !important; } } @@ -276,3 +277,19 @@ $bg-disabled-control: #34303a; .toggle-switch-container.no-margin { margin-top: 0 !important; } + + + +// Disable default style Firefox for invalid elements. +// Selectors taken from view-source:resource://gre-resources/forms.css on Firefox +:not(output):-moz-ui-invalid { + box-shadow: none; +} + +:not(output):-moz-ui-invalid:-moz-focusring { + box-shadow: none; +} + +output:-moz-ui-invalid { + color: inherit; +} diff --git a/website/client/src/components/auth/authForm.vue b/website/client/src/components/auth/authForm.vue index 7d3fa83e7d..993ed9cb10 100644 --- a/website/client/src/components/auth/authForm.vue +++ b/website/client/src/components/auth/authForm.vue @@ -46,6 +46,13 @@ :placeholder="$t('usernamePlaceholder')" :class="{'input-valid': usernameValid, 'input-invalid': usernameInvalid}" > +
+ {{ issue }} +
+
+ {{ $t('minPasswordLength') }} +
+
+ {{ $t('passwordConfirmationMatch') }} +
@@ -194,7 +220,7 @@ import hello from 'hellojs'; import debounce from 'lodash/debounce'; import isEmail from 'validator/lib/isEmail'; import { setUpAxios } from '@/libs/auth'; - +import { MINIMUM_PASSWORD_LENGTH } from '@/../../common/script/constants'; import facebookSquareIcon from '@/assets/svg/facebook-square.svg'; import googleIcon from '@/assets/svg/google.svg'; @@ -223,6 +249,7 @@ export default { return isEmail(this.email); }, emailInvalid () { + if (this.email.length <= 3) return false; return !this.emailValid; }, usernameValid () { @@ -230,13 +257,23 @@ export default { return this.usernameIssues.length === 0; }, usernameInvalid () { + if (this.username.length < 1) return false; return !this.usernameValid; }, + passwordValid () { + if (this.password.length <= 0) return false; + return this.password.length >= MINIMUM_PASSWORD_LENGTH; + }, + passwordInvalid () { + if (this.password.length <= 0) return false; + return this.password.length < MINIMUM_PASSWORD_LENGTH; + }, passwordConfirmValid () { if (this.passwordConfirm.length <= 3) return false; return this.passwordConfirm === this.password; }, passwordConfirmInvalid () { + if (this.passwordConfirm.length <= 3) return false; return !this.passwordConfirmValid; }, }, diff --git a/website/client/src/components/auth/registerLoginReset.vue b/website/client/src/components/auth/registerLoginReset.vue index e76d1a902d..c771514c34 100644 --- a/website/client/src/components/auth/registerLoginReset.vue +++ b/website/client/src/components/auth/registerLoginReset.vue @@ -6,8 +6,7 @@
@@ -69,7 +68,7 @@ +
+ {{ $t('minPasswordLength') }} +
+
+ {{ $t('passwordConfirmationMatch') }} +
-
{{ $t('joinHabitica') }} -
-
+
+
@@ -522,6 +554,7 @@ import hello from 'hellojs'; import debounce from 'lodash/debounce'; import isEmail from 'validator/lib/isEmail'; +import { MINIMUM_PASSWORD_LENGTH } from '@/../../common/script/constants'; import gryphon from '@/assets/svg/gryphon.svg'; import habiticaIcon from '@/assets/svg/habitica-logo.svg'; import facebookSquareIcon from '@/assets/svg/facebook-square.svg'; @@ -580,6 +613,14 @@ export default { if (this.username.length < 1) return false; return !this.usernameValid; }, + passwordValid () { + if (this.password.length <= 0) return false; + return this.password.length >= MINIMUM_PASSWORD_LENGTH; + }, + passwordInvalid () { + if (this.password.length <= 0) return false; + return this.password.length < MINIMUM_PASSWORD_LENGTH; + }, passwordConfirmValid () { if (this.passwordConfirm.length <= 3) return false; return this.passwordConfirm === this.password; @@ -588,6 +629,12 @@ export default { if (this.passwordConfirm.length <= 3) return false; return !this.passwordConfirmValid; }, + signupFormInvalid () { + return this.usernameInvalid + || this.emailInvalid + || this.passwordInvalid + || this.passwordConfirmInvalid; + }, }, watch: { $route: { diff --git a/website/client/src/components/static/home.vue b/website/client/src/components/static/home.vue index ed3cb2d511..2f1919cbd6 100644 --- a/website/client/src/components/static/home.vue +++ b/website/client/src/components/static/home.vue @@ -53,9 +53,9 @@
{{ $t('or') }}
-

{{ $t('usernameLimitations') }} @@ -63,7 +63,7 @@ +

+ {{ $t('minPasswordLength') }} +
+
+ {{ $t('passwordConfirmationMatch') }} +

-
+
@@ -796,6 +800,7 @@ import lifehacker from '@/assets/images/home/lifehacker.svg'; import makeuseof from '@/assets/images/home/make-use-of.svg'; import thenewyorktimes from '@/assets/images/home/the-new-york-times.svg'; import * as Analytics from '@/libs/analytics'; +import { MINIMUM_PASSWORD_LENGTH } from '@/../../common/script/constants'; export default { data () { @@ -844,6 +849,14 @@ export default { if (this.username.length < 1) return false; return !this.usernameValid; }, + passwordValid () { + if (this.password.length <= 0) return false; + return this.password.length >= MINIMUM_PASSWORD_LENGTH; + }, + passwordInvalid () { + if (this.password.length <= 0) return false; + return this.password.length < MINIMUM_PASSWORD_LENGTH; + }, passwordConfirmValid () { if (this.passwordConfirm.length <= 3) return false; return this.passwordConfirm === this.password; @@ -852,6 +865,12 @@ export default { if (this.passwordConfirm.length <= 3) return false; return this.passwordConfirm !== this.password; }, + signupFormInvalid () { + return this.usernameInvalid + || this.emailInvalid + || this.passwordInvalid + || this.passwordConfirmInvalid; + }, }, watch: { username () { diff --git a/website/client/src/components/static/staticWrapper.vue b/website/client/src/components/static/staticWrapper.vue index 89cb9fb77c..922a33e840 100644 --- a/website/client/src/components/static/staticWrapper.vue +++ b/website/client/src/components/static/staticWrapper.vue @@ -85,10 +85,6 @@ } } - #bottom-wrap.purple-4 { - background-color: #271b3d; - } - #purple-footer { background-color: #271b3d; @@ -115,33 +111,6 @@ } } - #bottom-wrap { - padding-top: 10em; - } - - #bottom-background { - position: relative; - - .seamless_mountains_demo_repeat { - background-image: url('~@/assets/images/auth/seamless_mountains_demo.png'); - background-repeat: repeat-x; - width: 100%; - height: 300px; - position: absolute; - z-index: 0; - bottom: 0; - } - - .midground_foreground_extended2 { - background-image: url('~@/assets/images/auth/midground_foreground_extended2.png'); - position: relative; - width: 1500px; - max-width: 100%; - height: 150px; - margin: 0 auto; - } - } - .static-wrapper { .container-fluid { margin: 5em 2em 2em 2em; @@ -171,6 +140,39 @@ } + +