Minimum password length + Static Pages fixes (was #11474) (#11506)

* Revert "Revert "Minimum password length + Static Pages fixes (#11474)""

This reverts commit d1afbf4b92.

* add min length for reset password
This commit is contained in:
Matteo Pagliazzi
2019-12-18 19:02:15 +01:00
committed by GitHub
parent fb1ea935e6
commit e4edab2b9d
14 changed files with 293 additions and 86 deletions

View File

@@ -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 () => { it('renders the success page and save the user', async () => {
const user = await generateUser(); const user = await generateUser();

View File

@@ -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 () => { it('requires a username', async () => {
const email = `${generateRandomUserName()}@example.com`; const email = `${generateRandomUserName()}@example.com`;
const password = 'password'; const password = 'password';

View File

@@ -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 () => { it('returns an error when confirmPassword is missing', async () => {
const body = { const body = {
password, password,

View File

@@ -20,7 +20,7 @@
@include btn-focus-hover-shadow(); @include btn-focus-hover-shadow();
} }
&:hover:not(.btn-flat):not(.disabled) { &:hover:not(.btn-flat):not(.disabled):not(:disabled) {
@include btn-focus-hover-shadow(); @include btn-focus-hover-shadow();
border-color: transparent; border-color: transparent;
} }
@@ -49,7 +49,7 @@
background: $purple-200; 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; background: #5d3b9c !important;
color: $white; color: $white;
} }
@@ -59,7 +59,7 @@
color: $gray-50; color: $gray-50;
background: $white !important; 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; color: $purple-200 !important;
} }
@@ -80,7 +80,7 @@
background: $green-100; 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; background: $green-50;
} }
} }
@@ -96,8 +96,12 @@
background: $blue-50; background: $blue-50;
} }
&:hover:not(:disabled), &:active, &.active { &:hover:not(:disabled):not(.disabled) {
background: $blue-100; background-color: $blue-100;
}
&:active:not(:disabled):not(.disabled), &.active:not(:disabled):not(.disabled) {
background: $blue-50;
} }
} }
@@ -108,7 +112,11 @@
background: $red-50; 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; background: $red-100;
} }
} }

View File

@@ -65,6 +65,7 @@ input, textarea, input.form-control, textarea.form-control {
padding-right: 40px; padding-right: 40px;
background-image: url(~@/assets/svg/for-css/alert.svg); background-image: url(~@/assets/svg/for-css/alert.svg);
background-size: 16px 16px; background-size: 16px 16px;
border-color: $red-100 !important;
} }
} }
@@ -276,3 +277,19 @@ $bg-disabled-control: #34303a;
.toggle-switch-container.no-margin { .toggle-switch-container.no-margin {
margin-top: 0 !important; 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;
}

View File

@@ -46,6 +46,13 @@
:placeholder="$t('usernamePlaceholder')" :placeholder="$t('usernamePlaceholder')"
:class="{'input-valid': usernameValid, 'input-invalid': usernameInvalid}" :class="{'input-valid': usernameValid, 'input-invalid': usernameInvalid}"
> >
<div
v-for="issue in usernameIssues"
:key="issue"
class="input-error"
>
{{ issue }}
</div>
</div> </div>
<div <div
v-if="!registering" v-if="!registering"
@@ -97,7 +104,17 @@
class="form-control" class="form-control"
type="password" type="password"
:placeholder="$t(registering ? 'passwordPlaceholder' : 'password')" :placeholder="$t(registering ? 'passwordPlaceholder' : 'password')"
:class="{
'input-valid': registering ? passwordValid : false,
'input-invalid': registering ? passwordInvalid: false,
}"
> >
<div
v-if="passwordInvalid && registering"
class="input-error"
>
{{ $t('minPasswordLength') }}
</div>
</div> </div>
<div <div
v-if="registering" v-if="registering"
@@ -115,6 +132,12 @@
:placeholder="$t('confirmPasswordPlaceholder')" :placeholder="$t('confirmPasswordPlaceholder')"
:class="{'input-invalid': passwordConfirmInvalid, 'input-valid': passwordConfirmValid}" :class="{'input-invalid': passwordConfirmInvalid, 'input-valid': passwordConfirmValid}"
> >
<div
v-if="passwordConfirmInvalid"
class="input-error"
>
{{ $t('passwordConfirmationMatch') }}
</div>
<small <small
v-once v-once
class="form-text" class="form-text"
@@ -183,8 +206,11 @@
text-align: center; text-align: center;
} }
.input-valid { .input-error {
color: #fff; margin-top: 0.25em;
font-weight: normal;
font-size: 90%;
width: 100%;
} }
} }
</style> </style>
@@ -194,7 +220,7 @@ import hello from 'hellojs';
import debounce from 'lodash/debounce'; import debounce from 'lodash/debounce';
import isEmail from 'validator/lib/isEmail'; import isEmail from 'validator/lib/isEmail';
import { setUpAxios } from '@/libs/auth'; import { setUpAxios } from '@/libs/auth';
import { MINIMUM_PASSWORD_LENGTH } from '@/../../common/script/constants';
import facebookSquareIcon from '@/assets/svg/facebook-square.svg'; import facebookSquareIcon from '@/assets/svg/facebook-square.svg';
import googleIcon from '@/assets/svg/google.svg'; import googleIcon from '@/assets/svg/google.svg';
@@ -223,6 +249,7 @@ export default {
return isEmail(this.email); return isEmail(this.email);
}, },
emailInvalid () { emailInvalid () {
if (this.email.length <= 3) return false;
return !this.emailValid; return !this.emailValid;
}, },
usernameValid () { usernameValid () {
@@ -230,13 +257,23 @@ export default {
return this.usernameIssues.length === 0; return this.usernameIssues.length === 0;
}, },
usernameInvalid () { usernameInvalid () {
if (this.username.length < 1) return false;
return !this.usernameValid; 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 () { passwordConfirmValid () {
if (this.passwordConfirm.length <= 3) return false; if (this.passwordConfirm.length <= 3) return false;
return this.passwordConfirm === this.password; return this.passwordConfirm === this.password;
}, },
passwordConfirmInvalid () { passwordConfirmInvalid () {
if (this.passwordConfirm.length <= 3) return false;
return !this.passwordConfirmValid; return !this.passwordConfirmValid;
}, },
}, },

View File

@@ -6,8 +6,7 @@
<form <form
v-if="!forgotPassword && !resetPasswordSetNewOne" v-if="!forgotPassword && !resetPasswordSetNewOne"
id="login-form" id="login-form"
@submit.prevent="handleSubmit" @submit.prevent.stop="handleSubmit"
@keyup.enter="handleSubmit"
> >
<div class="text-center"> <div class="text-center">
<div> <div>
@@ -69,7 +68,7 @@
<input <input
id="usernameInput" id="usernameInput"
v-model="username" v-model="username"
class="form-control" class="form-control input-with-error"
type="text" type="text"
:placeholder="$t('usernamePlaceholder')" :placeholder="$t('usernamePlaceholder')"
:class="{'input-valid': usernameValid, 'input-invalid': usernameInvalid}" :class="{'input-valid': usernameValid, 'input-invalid': usernameInvalid}"
@@ -132,7 +131,17 @@
class="form-control" class="form-control"
type="password" type="password"
:placeholder="$t(registering ? 'passwordPlaceholder' : 'password')" :placeholder="$t(registering ? 'passwordPlaceholder' : 'password')"
:class="{
'input-invalid input-with-error': registering && passwordInvalid,
'input-valid': registering && passwordValid
}"
> >
<div
v-if="passwordInvalid && registering"
class="input-error"
>
{{ $t('minPasswordLength') }}
</div>
</div> </div>
<div <div
v-if="registering" v-if="registering"
@@ -145,11 +154,17 @@
<input <input
id="confirmPasswordInput" id="confirmPasswordInput"
v-model="passwordConfirm" v-model="passwordConfirm"
class="form-control" class="form-control input-with-error"
type="password" type="password"
:placeholder="$t('confirmPasswordPlaceholder')" :placeholder="$t('confirmPasswordPlaceholder')"
:class="{'input-invalid': passwordConfirmInvalid, 'input-valid': passwordConfirmValid}" :class="{'input-invalid': passwordConfirmInvalid, 'input-valid': passwordConfirmValid}"
> >
<div
v-if="passwordConfirmInvalid"
class="input-error"
>
{{ $t('passwordConfirmationMatch') }}
</div>
<small <small
v-once v-once
class="form-text" class="form-text"
@@ -157,22 +172,22 @@
></small> ></small>
</div> </div>
<div class="text-center"> <div class="text-center">
<div <button
v-if="registering" v-if="registering"
v-once type="submit"
class="btn btn-info" class="btn btn-info"
@click="register()" :disabled="signupFormInvalid"
> >
{{ $t('joinHabitica') }} {{ $t('joinHabitica') }}
</div> </button>
<div <button
v-if="!registering" v-if="!registering"
v-once v-once
type="submit"
class="btn btn-info" class="btn btn-info"
@click="login()"
> >
{{ $t('login') }} {{ $t('login') }}
</div> </button>
<div class="toggle-links"> <div class="toggle-links">
<router-link <router-link
v-if="registering" v-if="registering"
@@ -275,10 +290,17 @@
<input <input
id="passwordInput" id="passwordInput"
v-model="password" v-model="password"
class="form-control" class="form-control input-with-error"
type="password" type="password"
:placeholder="$t('password')" :placeholder="$t('password')"
:class="{'input-invalid': passwordInvalid, 'input-valid': passwordValid}"
> >
<div
v-if="passwordInvalid"
class="input-error"
>
{{ $t('minPasswordLength') }}
</div>
</div> </div>
<div class="form-group"> <div class="form-group">
<label <label
@@ -288,10 +310,17 @@
<input <input
id="confirmPasswordInput" id="confirmPasswordInput"
v-model="passwordConfirm" v-model="passwordConfirm"
class="form-control" class="form-control input-with-error"
type="password" type="password"
:placeholder="$t('confirmPasswordPlaceholder')" :placeholder="$t('confirmPasswordPlaceholder')"
:class="{'input-invalid': passwordConfirmInvalid, 'input-valid': passwordConfirmValid}"
> >
<div
v-if="passwordConfirmInvalid"
class="input-error"
>
{{ $t('passwordConfirmationMatch') }}
</div>
</div> </div>
<div class="text-center"> <div class="text-center">
<div <div
@@ -426,10 +455,14 @@
color: $white; color: $white;
} }
#usernameInput.input-invalid { .input-with-error.input-invalid {
margin-bottom: 0.5em; margin-bottom: 0.5em;
} }
#confirmPasswordInput + .input-error {
margin-bottom: 2em;
}
.form-text { .form-text {
font-size: 14px; font-size: 14px;
color: $white; color: $white;
@@ -480,7 +513,7 @@
background-image: url('~@/assets/images/auth/seamless_mountains_demo.png'); background-image: url('~@/assets/images/auth/seamless_mountains_demo.png');
background-repeat: repeat-x; background-repeat: repeat-x;
width: 100%; width: 100%;
height: 500px; height: 300px;
position: absolute; position: absolute;
z-index: 0; z-index: 0;
bottom: 0; bottom: 0;
@@ -512,7 +545,6 @@
color: #fff; color: #fff;
font-size: 90%; font-size: 90%;
width: 100%; width: 100%;
text-align: center;
} }
</style> </style>
@@ -522,6 +554,7 @@ import hello from 'hellojs';
import debounce from 'lodash/debounce'; import debounce from 'lodash/debounce';
import isEmail from 'validator/lib/isEmail'; import isEmail from 'validator/lib/isEmail';
import { MINIMUM_PASSWORD_LENGTH } from '@/../../common/script/constants';
import gryphon from '@/assets/svg/gryphon.svg'; import gryphon from '@/assets/svg/gryphon.svg';
import habiticaIcon from '@/assets/svg/habitica-logo.svg'; import habiticaIcon from '@/assets/svg/habitica-logo.svg';
import facebookSquareIcon from '@/assets/svg/facebook-square.svg'; import facebookSquareIcon from '@/assets/svg/facebook-square.svg';
@@ -580,6 +613,14 @@ export default {
if (this.username.length < 1) return false; if (this.username.length < 1) return false;
return !this.usernameValid; 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 () { passwordConfirmValid () {
if (this.passwordConfirm.length <= 3) return false; if (this.passwordConfirm.length <= 3) return false;
return this.passwordConfirm === this.password; return this.passwordConfirm === this.password;
@@ -588,6 +629,12 @@ export default {
if (this.passwordConfirm.length <= 3) return false; if (this.passwordConfirm.length <= 3) return false;
return !this.passwordConfirmValid; return !this.passwordConfirmValid;
}, },
signupFormInvalid () {
return this.usernameInvalid
|| this.emailInvalid
|| this.passwordInvalid
|| this.passwordConfirmInvalid;
},
}, },
watch: { watch: {
$route: { $route: {

View File

@@ -53,9 +53,9 @@
<div class="strike"> <div class="strike">
<span>{{ $t('or') }}</span> <span>{{ $t('or') }}</span>
</div> </div>
<div <form
class="form" class="form"
@keyup.enter="register()" @submit.prevent.stop="register()"
> >
<p class="form-text"> <p class="form-text">
{{ $t('usernameLimitations') }} {{ $t('usernameLimitations') }}
@@ -63,7 +63,7 @@
<input <input
id="usernameInput" id="usernameInput"
v-model="username" v-model="username"
class="form-control" class="form-control input-with-error"
type="text" type="text"
:placeholder="$t('username')" :placeholder="$t('username')"
:class="{'input-valid': usernameValid, 'input-invalid': usernameInvalid}" :class="{'input-valid': usernameValid, 'input-invalid': usernameInvalid}"
@@ -85,32 +85,49 @@
> >
<input <input
v-model="password" v-model="password"
class="form-control" class="form-control input-with-error"
type="password" type="password"
:placeholder="$t('password')" :placeholder="$t('password')"
:class="{'input-valid': password.length > 3}" :class="{
'input-valid': passwordValid,
'input-invalid': passwordInvalid,
}"
> >
<div
v-if="passwordInvalid"
class="input-error"
>
{{ $t('minPasswordLength') }}
</div>
<input <input
v-model="passwordConfirm" v-model="passwordConfirm"
class="form-control" class="form-control input-with-error"
type="password" type="password"
:placeholder="$t('confirmPassword')" :placeholder="$t('confirmPassword')"
:class="{ :class="{
'input-invalid': passwordConfirmInvalid, 'input-invalid': passwordConfirmInvalid,
'input-valid': passwordConfirmValid}" 'input-valid': passwordConfirmValid}"
> >
<div
v-if="passwordConfirmInvalid"
class="input-error"
>
{{ $t('passwordConfirmationMatch') }}
</div>
<p <p
v-once v-once
class="form-text" class="form-text"
v-html="$t('termsAndAgreement')" v-html="$t('termsAndAgreement')"
></p> ></p>
<button <button
class="sign-up" class="btn btn-block btn-info sign-up"
:disabled="signupFormInvalid"
type="submit"
@click="register()" @click="register()"
> >
{{ $t('signup') }} {{ $t('signup') }}
</button> </button>
</div> </form>
</div> </div>
<div class="col-12"> <div class="col-12">
<div <div
@@ -519,7 +536,7 @@
transition: border .5s, color .5s; transition: border .5s, color .5s;
} }
#usernameInput.input-invalid { .input-invalid.input-with-error {
margin-bottom: 0.5em; margin-bottom: 0.5em;
} }
@@ -537,21 +554,9 @@
background-color: #36205d; background-color: #36205d;
} }
button.sign-up { .sign-up {
width: 100%; padding-top: 11px;
height: 48px; padding-bottom: 11px;
color: #fff;
border: none;
border-radius: 2px;
background-color: #2995cd;
font-size: 16px;
transition: all .5s ease;
}
.sign-up:hover {
background-color: #50b5e9;
box-shadow: 0 4px 4px 0 rgba(26, 24, 29, 0.16), 0 1px 8px 0 rgba(26, 24, 29, 0.12);
cursor: pointer;
} }
::-webkit-input-placeholder { /* Chrome/Opera/Safari */ ::-webkit-input-placeholder { /* Chrome/Opera/Safari */
@@ -769,7 +774,6 @@
color: #fff; color: #fff;
font-size: 90%; font-size: 90%;
width: 100%; width: 100%;
text-align: center;
margin-bottom: 1em; margin-bottom: 1em;
} }
</style> </style>
@@ -796,6 +800,7 @@ import lifehacker from '@/assets/images/home/lifehacker.svg';
import makeuseof from '@/assets/images/home/make-use-of.svg'; import makeuseof from '@/assets/images/home/make-use-of.svg';
import thenewyorktimes from '@/assets/images/home/the-new-york-times.svg'; import thenewyorktimes from '@/assets/images/home/the-new-york-times.svg';
import * as Analytics from '@/libs/analytics'; import * as Analytics from '@/libs/analytics';
import { MINIMUM_PASSWORD_LENGTH } from '@/../../common/script/constants';
export default { export default {
data () { data () {
@@ -844,6 +849,14 @@ export default {
if (this.username.length < 1) return false; if (this.username.length < 1) return false;
return !this.usernameValid; 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 () { passwordConfirmValid () {
if (this.passwordConfirm.length <= 3) return false; if (this.passwordConfirm.length <= 3) return false;
return this.passwordConfirm === this.password; return this.passwordConfirm === this.password;
@@ -852,6 +865,12 @@ export default {
if (this.passwordConfirm.length <= 3) return false; if (this.passwordConfirm.length <= 3) return false;
return this.passwordConfirm !== this.password; return this.passwordConfirm !== this.password;
}, },
signupFormInvalid () {
return this.usernameInvalid
|| this.emailInvalid
|| this.passwordInvalid
|| this.passwordConfirmInvalid;
},
}, },
watch: { watch: {
username () { username () {

View File

@@ -85,10 +85,6 @@
} }
} }
#bottom-wrap.purple-4 {
background-color: #271b3d;
}
#purple-footer { #purple-footer {
background-color: #271b3d; 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 { .static-wrapper {
.container-fluid { .container-fluid {
margin: 5em 2em 2em 2em; margin: 5em 2em 2em 2em;
@@ -171,6 +140,39 @@
} }
</style> </style>
<style lang="scss" scoped>
#bottom-wrap.purple-4 {
background-color: #271b3d;
}
#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;
}
}
</style>
<script> <script>
import AppFooter from '@/components/appFooter'; import AppFooter from '@/components/appFooter';
import StaticHeader from './header.vue'; import StaticHeader from './header.vue';

View File

@@ -274,6 +274,7 @@
"usernameTOSRequirements": "Usernames must conform to our <a href='/static/terms' target='_blank'>Terms of Service</a> and <a href='/static/community-guidelines' target='_blank'>Community Guidelines</a>. If you didnt previously set a login name, your username was auto-generated.", "usernameTOSRequirements": "Usernames must conform to our <a href='/static/terms' target='_blank'>Terms of Service</a> and <a href='/static/community-guidelines' target='_blank'>Community Guidelines</a>. If you didnt previously set a login name, your username was auto-generated.",
"usernameTaken": "Username already taken.", "usernameTaken": "Username already taken.",
"passwordConfirmationMatch": "Password confirmation doesn't match password.", "passwordConfirmationMatch": "Password confirmation doesn't match password.",
"minPasswordLength": "Password must be 8 characters or more.",
"passwordResetPage": "Reset Password", "passwordResetPage": "Reset Password",
"passwordReset": "If we have your email on file, instructions for setting a new password have been sent to your email.", "passwordReset": "If we have your email on file, instructions for setting a new password have been sent to your email.",
"invalidLoginCredentialsLong": "Uh-oh - your email address / username or password is incorrect.\n- Make sure they are typed correctly. Your username and password are case-sensitive.\n- You may have signed up with Facebook or Google-sign-in, not email so double-check by trying them.\n- If you forgot your password, click \"Forgot Password\".", "invalidLoginCredentialsLong": "Uh-oh - your email address / username or password is incorrect.\n- Make sure they are typed correctly. Your username and password are case-sensitive.\n- You may have signed up with Facebook or Google-sign-in, not email so double-check by trying them.\n- If you forgot your password, click \"Forgot Password\".",

View File

@@ -25,6 +25,7 @@ export const GUILDS_PER_PAGE = 30; // number of guilds to return per page when u
export const PARTY_LIMIT_MEMBERS = 30; export const PARTY_LIMIT_MEMBERS = 30;
export const MINIMUM_PASSWORD_LENGTH = 8;
export const TRANSFORMATION_DEBUFFS_LIST = { export const TRANSFORMATION_DEBUFFS_LIST = {
snowball: 'salt', snowball: 'salt',
spookySparkles: 'opaquePotion', spookySparkles: 'opaquePotion',

View File

@@ -15,6 +15,7 @@ import {
MAX_SUMMARY_SIZE_FOR_GUILDS, MAX_SUMMARY_SIZE_FOR_GUILDS,
MIN_SHORTNAME_SIZE_FOR_CHALLENGES, MIN_SHORTNAME_SIZE_FOR_CHALLENGES,
PARTY_LIMIT_MEMBERS, PARTY_LIMIT_MEMBERS,
MINIMUM_PASSWORD_LENGTH,
SUPPORTED_SOCIAL_NETWORKS, SUPPORTED_SOCIAL_NETWORKS,
TAVERN_ID, TAVERN_ID,
} from './constants'; } from './constants';
@@ -106,6 +107,7 @@ api.constants = {
CHAT_FLAG_LIMIT_FOR_HIDING, CHAT_FLAG_LIMIT_FOR_HIDING,
CHAT_FLAG_FROM_MOD, CHAT_FLAG_FROM_MOD,
CHAT_FLAG_FROM_SHADOW_MUTE, CHAT_FLAG_FROM_SHADOW_MUTE,
MINIMUM_PASSWORD_LENGTH,
}; };
// TODO Move these under api.constants // TODO Move these under api.constants
api.maxLevel = MAX_LEVEL; api.maxLevel = MAX_LEVEL;

View File

@@ -245,6 +245,10 @@ api.updatePassword = {
}, },
newPassword: { newPassword: {
notEmpty: { errorMessage: res.t('missingNewPassword') }, notEmpty: { errorMessage: res.t('missingNewPassword') },
isLength: {
options: { min: common.constants.MINIMUM_PASSWORD_LENGTH },
errorMessage: res.t('minPasswordLength'),
},
}, },
confirmPassword: { confirmPassword: {
notEmpty: { errorMessage: res.t('missingNewPassword') }, notEmpty: { errorMessage: res.t('missingNewPassword') },
@@ -387,13 +391,23 @@ api.resetPasswordSetNewOne = {
if (!isValidCode) throw new NotAuthorized(res.t('invalidPasswordResetCode')); if (!isValidCode) throw new NotAuthorized(res.t('invalidPasswordResetCode'));
req.checkBody('newPassword', res.t('missingNewPassword')).notEmpty(); req.checkBody({
req.checkBody('confirmPassword', res.t('missingNewPassword')).notEmpty(); newPassword: {
notEmpty: { errorMessage: res.t('missingNewPassword') },
isLength: {
options: { min: common.constants.MINIMUM_PASSWORD_LENGTH },
errorMessage: res.t('minPasswordLength'),
},
},
confirmPassword: {
notEmpty: { errorMessage: res.t('missingNewPassword') },
},
});
const validationErrors = req.validationErrors(); const validationErrors = req.validationErrors();
if (validationErrors) throw validationErrors; if (validationErrors) throw validationErrors;
const { newPassword } = req.body; const { newPassword, confirmPassword } = req.body;
const { confirmPassword } = req.body;
if (newPassword !== confirmPassword) { if (newPassword !== confirmPassword) {
throw new BadRequest(res.t('passwordConfirmationMatch')); throw new BadRequest(res.t('passwordConfirmationMatch'));

View File

@@ -93,8 +93,13 @@ async function registerLocal (req, res, { isV3 = false }) {
}, },
password: { password: {
notEmpty: true, notEmpty: true,
errorMessage: res.t('missingPassword'), errorMessage: res.t('missingPassword'),
equals: { options: [req.body.confirmPassword], errorMessage: res.t('passwordConfirmationMatch') }, equals: { options: [req.body.confirmPassword], errorMessage: res.t('passwordConfirmationMatch') },
isLength: {
options: { min: common.constants.MINIMUM_PASSWORD_LENGTH },
errorMessage: res.t('minPasswordLength'),
},
}, },
}); });