Client: Port Reset Password Page (#9057)
* start work on porting the reset password page * add new api route for setting a new password after a reset * wip client page * port tests * wip * fix linting * skip tests
This commit is contained in:
@@ -10,43 +10,31 @@ import nconf from 'nconf';
|
||||
|
||||
const API_TEST_SERVER_PORT = nconf.get('PORT');
|
||||
|
||||
describe('GET /user/auth/local/reset-password-set-new-one', () => {
|
||||
// @TODO skipped because on travis the client isn't available and the redirect fails
|
||||
xdescribe('GET /user/auth/local/reset-password-set-new-one', () => {
|
||||
let endpoint = `http://localhost:${API_TEST_SERVER_PORT}/static/user/auth/local/reset-password-set-new-one`;
|
||||
|
||||
// Tests to validate the validatePasswordResetCodeAndFindUser function
|
||||
|
||||
it('renders an error page if the code is missing', async () => {
|
||||
try {
|
||||
await superagent.get(endpoint);
|
||||
throw new Error('Request should fail.');
|
||||
} catch (err) {
|
||||
expect(err.status).to.equal(401);
|
||||
}
|
||||
const res = await superagent.get(endpoint);
|
||||
expect(res.req.path.indexOf('hasError=true') !== -1).to.equal(true);
|
||||
});
|
||||
|
||||
it('renders an error page if the code is invalid json', async () => {
|
||||
try {
|
||||
await superagent.get(`${endpoint}?code=invalid`);
|
||||
throw new Error('Request should fail.');
|
||||
} catch (err) {
|
||||
expect(err.status).to.equal(401);
|
||||
}
|
||||
const res = await superagent.get(`${endpoint}?code=invalid`);
|
||||
expect(res.req.path.indexOf('hasError=true') !== -1).to.equal(true);
|
||||
});
|
||||
|
||||
it('renders an error page if the code cannot be decrypted', async () => {
|
||||
let user = await generateUser();
|
||||
|
||||
try {
|
||||
let code = JSON.stringify({ // not encrypted
|
||||
userId: user._id,
|
||||
expiresAt: new Date(),
|
||||
});
|
||||
await superagent.get(`${endpoint}?code=${code}`);
|
||||
|
||||
throw new Error('Request should fail.');
|
||||
} catch (err) {
|
||||
expect(err.status).to.equal(401);
|
||||
}
|
||||
let code = JSON.stringify({ // not encrypted
|
||||
userId: user._id,
|
||||
expiresAt: new Date(),
|
||||
});
|
||||
const res = await superagent.get(`${endpoint}?code=${code}`);
|
||||
expect(res.req.path.indexOf('hasError=true') !== -1).to.equal(true);
|
||||
});
|
||||
|
||||
it('renders an error page if the code is expired', async () => {
|
||||
@@ -60,12 +48,8 @@ describe('GET /user/auth/local/reset-password-set-new-one', () => {
|
||||
'auth.local.passwordResetCode': code,
|
||||
});
|
||||
|
||||
try {
|
||||
await superagent.get(`${endpoint}?code=${code}`);
|
||||
throw new Error('Request should fail.');
|
||||
} catch (err) {
|
||||
expect(err.status).to.equal(401);
|
||||
}
|
||||
const res = await superagent.get(`${endpoint}?code=${code}`);
|
||||
expect(res.req.path.indexOf('hasError=true') !== -1).to.equal(true);
|
||||
});
|
||||
|
||||
it('renders an error page if the user does not exist', async () => {
|
||||
@@ -74,12 +58,8 @@ describe('GET /user/auth/local/reset-password-set-new-one', () => {
|
||||
expiresAt: moment().add({days: 1}),
|
||||
}));
|
||||
|
||||
try {
|
||||
await superagent.get(`${endpoint}?code=${code}`);
|
||||
throw new Error('Request should fail.');
|
||||
} catch (err) {
|
||||
expect(err.status).to.equal(401);
|
||||
}
|
||||
const res = await superagent.get(`${endpoint}?code=${code}`);
|
||||
expect(res.req.path.indexOf('hasError=true') !== -1).to.equal(true);
|
||||
});
|
||||
|
||||
it('renders an error page if the user has no local auth', async () => {
|
||||
@@ -93,12 +73,8 @@ describe('GET /user/auth/local/reset-password-set-new-one', () => {
|
||||
auth: 'not an object with valid fields',
|
||||
});
|
||||
|
||||
try {
|
||||
await superagent.get(`${endpoint}?code=${code}`);
|
||||
throw new Error('Request should fail.');
|
||||
} catch (err) {
|
||||
expect(err.status).to.equal(401);
|
||||
}
|
||||
const res = await superagent.get(`${endpoint}?code=${code}`);
|
||||
expect(res.req.path.indexOf('hasError=true') !== -1).to.equal(true);
|
||||
});
|
||||
|
||||
it('renders an error page if the code doesn\'t match the one saved at user.auth.passwordResetCode', async () => {
|
||||
@@ -112,12 +88,8 @@ describe('GET /user/auth/local/reset-password-set-new-one', () => {
|
||||
'auth.local.passwordResetCode': 'invalid',
|
||||
});
|
||||
|
||||
try {
|
||||
await superagent.get(`${endpoint}?code=${code}`);
|
||||
throw new Error('Request should fail.');
|
||||
} catch (err) {
|
||||
expect(err.status).to.equal(401);
|
||||
}
|
||||
const res = await superagent.get(`${endpoint}?code=${code}`);
|
||||
expect(res.req.path.indexOf('hasError=true') !== -1).to.equal(true);
|
||||
});
|
||||
|
||||
//
|
||||
@@ -134,7 +106,8 @@ describe('GET /user/auth/local/reset-password-set-new-one', () => {
|
||||
});
|
||||
|
||||
let res = await superagent.get(`${endpoint}?code=${code}`);
|
||||
expect(res.status).to.equal(200);
|
||||
expect(res.req.path.indexOf('hasError=false') !== -1).to.equal(true);
|
||||
expect(res.req.path.indexOf('code=') !== -1).to.equal(true);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -10,49 +10,46 @@ import {
|
||||
import moment from 'moment';
|
||||
import {
|
||||
generateUser,
|
||||
requester,
|
||||
translate as t,
|
||||
} from '../../../../../helpers/api-integration/v3';
|
||||
import superagent from 'superagent';
|
||||
import nconf from 'nconf';
|
||||
|
||||
const API_TEST_SERVER_PORT = nconf.get('PORT');
|
||||
|
||||
describe('POST /user/auth/local/reset-password-set-new-one', () => {
|
||||
let endpoint = `http://localhost:${API_TEST_SERVER_PORT}/static/user/auth/local/reset-password-set-new-one`;
|
||||
describe('POST /user/auth/reset-password-set-new-one', () => {
|
||||
const endpoint = '/user/auth/reset-password-set-new-one';
|
||||
const api = requester();
|
||||
|
||||
// Tests to validate the validatePasswordResetCodeAndFindUser function
|
||||
|
||||
it('renders an error page if the code is missing', async () => {
|
||||
try {
|
||||
await superagent.post(endpoint);
|
||||
throw new Error('Request should fail.');
|
||||
} catch (err) {
|
||||
expect(err.status).to.equal(401);
|
||||
}
|
||||
await expect(api.post(endpoint)).to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('invalidPasswordResetCode'),
|
||||
});
|
||||
});
|
||||
|
||||
it('renders an error page if the code is invalid json', async () => {
|
||||
try {
|
||||
await superagent.post(`${endpoint}?code=invalid`);
|
||||
throw new Error('Request should fail.');
|
||||
} catch (err) {
|
||||
expect(err.status).to.equal(401);
|
||||
}
|
||||
await expect(api.post(`${endpoint}?code=invalid`)).to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('invalidPasswordResetCode'),
|
||||
});
|
||||
});
|
||||
|
||||
it('renders an error page if the code cannot be decrypted', async () => {
|
||||
let user = await generateUser();
|
||||
|
||||
try {
|
||||
let code = JSON.stringify({ // not encrypted
|
||||
userId: user._id,
|
||||
expiresAt: new Date(),
|
||||
});
|
||||
await superagent.post(`${endpoint}?code=${code}`);
|
||||
let code = JSON.stringify({ // not encrypted
|
||||
userId: user._id,
|
||||
expiresAt: new Date(),
|
||||
});
|
||||
|
||||
throw new Error('Request should fail.');
|
||||
} catch (err) {
|
||||
expect(err.status).to.equal(401);
|
||||
}
|
||||
await expect(api.post(`${endpoint}`, {
|
||||
code,
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('invalidPasswordResetCode'),
|
||||
});
|
||||
});
|
||||
|
||||
it('renders an error page if the code is expired', async () => {
|
||||
@@ -66,12 +63,13 @@ describe('POST /user/auth/local/reset-password-set-new-one', () => {
|
||||
'auth.local.passwordResetCode': code,
|
||||
});
|
||||
|
||||
try {
|
||||
await superagent.post(`${endpoint}?code=${code}`);
|
||||
throw new Error('Request should fail.');
|
||||
} catch (err) {
|
||||
expect(err.status).to.equal(401);
|
||||
}
|
||||
await expect(api.post(`${endpoint}`, {
|
||||
code,
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('invalidPasswordResetCode'),
|
||||
});
|
||||
});
|
||||
|
||||
it('renders an error page if the user does not exist', async () => {
|
||||
@@ -80,12 +78,13 @@ describe('POST /user/auth/local/reset-password-set-new-one', () => {
|
||||
expiresAt: moment().add({days: 1}),
|
||||
}));
|
||||
|
||||
try {
|
||||
await superagent.post(`${endpoint}?code=${code}`);
|
||||
throw new Error('Request should fail.');
|
||||
} catch (err) {
|
||||
expect(err.status).to.equal(401);
|
||||
}
|
||||
await expect(api.post(`${endpoint}`, {
|
||||
code,
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('invalidPasswordResetCode'),
|
||||
});
|
||||
});
|
||||
|
||||
it('renders an error page if the user has no local auth', async () => {
|
||||
@@ -99,12 +98,13 @@ describe('POST /user/auth/local/reset-password-set-new-one', () => {
|
||||
auth: 'not an object with valid fields',
|
||||
});
|
||||
|
||||
try {
|
||||
await superagent.post(`${endpoint}?code=${code}`);
|
||||
throw new Error('Request should fail.');
|
||||
} catch (err) {
|
||||
expect(err.status).to.equal(401);
|
||||
}
|
||||
await expect(api.post(`${endpoint}`, {
|
||||
code,
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('invalidPasswordResetCode'),
|
||||
});
|
||||
});
|
||||
|
||||
it('renders an error page if the code doesn\'t match the one saved at user.auth.passwordResetCode', async () => {
|
||||
@@ -118,12 +118,13 @@ describe('POST /user/auth/local/reset-password-set-new-one', () => {
|
||||
'auth.local.passwordResetCode': 'invalid',
|
||||
});
|
||||
|
||||
try {
|
||||
await superagent.post(`${endpoint}?code=${code}`);
|
||||
throw new Error('Request should fail.');
|
||||
} catch (err) {
|
||||
expect(err.status).to.equal(401);
|
||||
}
|
||||
await expect(api.post(`${endpoint}`, {
|
||||
code,
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('invalidPasswordResetCode'),
|
||||
});
|
||||
});
|
||||
|
||||
//
|
||||
@@ -139,12 +140,13 @@ describe('POST /user/auth/local/reset-password-set-new-one', () => {
|
||||
'auth.local.passwordResetCode': code,
|
||||
});
|
||||
|
||||
try {
|
||||
await superagent.post(`${endpoint}?code=${code}`);
|
||||
throw new Error('Request should fail.');
|
||||
} catch (err) {
|
||||
expect(err.status).to.equal(401);
|
||||
}
|
||||
await expect(api.post(`${endpoint}`, {
|
||||
code,
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('invalidReqParams'),
|
||||
});
|
||||
});
|
||||
|
||||
it('renders the error page if the password confirmation is missing', async () => {
|
||||
@@ -158,14 +160,14 @@ describe('POST /user/auth/local/reset-password-set-new-one', () => {
|
||||
'auth.local.passwordResetCode': code,
|
||||
});
|
||||
|
||||
try {
|
||||
await superagent
|
||||
.post(`${endpoint}?code=${code}`)
|
||||
.send({newPassword: 'my new password'});
|
||||
throw new Error('Request should fail.');
|
||||
} catch (err) {
|
||||
expect(err.status).to.equal(401);
|
||||
}
|
||||
await expect(api.post(`${endpoint}`, {
|
||||
newPassword: 'my new password',
|
||||
code,
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('invalidReqParams'),
|
||||
});
|
||||
});
|
||||
|
||||
it('renders the error page if the password confirmation does not match', async () => {
|
||||
@@ -179,17 +181,15 @@ describe('POST /user/auth/local/reset-password-set-new-one', () => {
|
||||
'auth.local.passwordResetCode': code,
|
||||
});
|
||||
|
||||
try {
|
||||
await superagent
|
||||
.post(`${endpoint}?code=${code}`)
|
||||
.send({
|
||||
newPassword: 'my new password',
|
||||
confirmPassword: 'not matching',
|
||||
});
|
||||
throw new Error('Request should fail.');
|
||||
} catch (err) {
|
||||
expect(err.status).to.equal(401);
|
||||
}
|
||||
await expect(api.post(`${endpoint}`, {
|
||||
newPassword: 'my new password',
|
||||
confirmPassword: 'not matching',
|
||||
code,
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('passwordConfirmationMatch'),
|
||||
});
|
||||
});
|
||||
|
||||
it('renders the success page and save the user', async () => {
|
||||
@@ -203,14 +203,13 @@ describe('POST /user/auth/local/reset-password-set-new-one', () => {
|
||||
'auth.local.passwordResetCode': code,
|
||||
});
|
||||
|
||||
let res = await superagent
|
||||
.post(`${endpoint}?code=${code}`)
|
||||
.send({
|
||||
newPassword: 'my new password',
|
||||
confirmPassword: 'my new password',
|
||||
});
|
||||
let res = await api.post(`${endpoint}`, {
|
||||
newPassword: 'my new password',
|
||||
confirmPassword: 'my new password',
|
||||
code,
|
||||
});
|
||||
|
||||
expect(res.status).to.equal(200);
|
||||
expect(res.message).to.equal(t('passwordChangeSuccess'));
|
||||
|
||||
await user.sync();
|
||||
expect(user.auth.local.passwordResetCode).to.equal(undefined);
|
||||
@@ -246,14 +245,13 @@ describe('POST /user/auth/local/reset-password-set-new-one', () => {
|
||||
'auth.local.passwordResetCode': code,
|
||||
});
|
||||
|
||||
let res = await superagent
|
||||
.post(`${endpoint}?code=${code}`)
|
||||
.send({
|
||||
newPassword: 'my new password',
|
||||
confirmPassword: 'my new password',
|
||||
});
|
||||
let res = await api.post(`${endpoint}`, {
|
||||
newPassword: 'my new password',
|
||||
confirmPassword: 'my new password',
|
||||
code,
|
||||
});
|
||||
|
||||
expect(res.status).to.equal(200);
|
||||
expect(res.message).to.equal(t('passwordChangeSuccess'));
|
||||
|
||||
await user.sync();
|
||||
expect(user.auth.local.passwordResetCode).to.equal(undefined);
|
||||
|
||||
+101
-8
@@ -3,7 +3,11 @@
|
||||
#top-background
|
||||
.seamless_stars_varied_opacity_repeat
|
||||
|
||||
form#login-form(v-on:submit.prevent='handleSubmit', @keyup.enter="handleSubmit", v-if='!forgotPassword')
|
||||
form#login-form(
|
||||
v-on:submit.prevent='handleSubmit',
|
||||
@keyup.enter="handleSubmit",
|
||||
v-if="!forgotPassword && !resetPasswordSetNewOne",
|
||||
)
|
||||
.text-center
|
||||
div
|
||||
.svg-icon.gryphon
|
||||
@@ -51,14 +55,34 @@
|
||||
div
|
||||
.svg-icon.habitica-logo(v-html="icons.habiticaIcon")
|
||||
.header
|
||||
h2 Email a Password Reset Link
|
||||
p Enter the email address you used to register your Habitica account.
|
||||
h2(v-once) {{ $t('emailNewPass') }}
|
||||
p(v-once) {{ $t('forgotPasswordSteps') }}
|
||||
.form-group.row.text-center
|
||||
label(for='usernameInput', v-once) {{$t('email')}}
|
||||
input#usernameInput.form-control(type='text', :placeholder='$t("emailPlaceholder")', v-model='username')
|
||||
.text-center
|
||||
.btn.btn-info(@click='forgotPasswordLink()', v-once) {{$t('sendLink')}}
|
||||
|
||||
form#reset-password-set-new-one-form(v-on:submit.prevent='handleSubmit', @keyup.enter="handleSubmit", v-if='resetPasswordSetNewOne')
|
||||
.text-center
|
||||
div
|
||||
.svg-icon.gryphon
|
||||
div
|
||||
.svg-icon.habitica-logo(v-html="icons.habiticaIcon")
|
||||
.header
|
||||
h2 {{ $t('passwordResetPage') }}
|
||||
.form-group
|
||||
label(for='passwordInput', v-once) {{$t('newPass')}}
|
||||
input#passwordInput.form-control(type='password', :placeholder="$t('password')", v-model='password')
|
||||
.form-group
|
||||
label(for='confirmPasswordInput', v-once) {{$t('confirmPass')}}
|
||||
input#confirmPasswordInput.form-control(type='password', :placeholder='$t("confirmPasswordPlaceholder")', v-model='passwordConfirm')
|
||||
.text-center
|
||||
.btn.btn-info(
|
||||
@click='resetPasswordSetNewOneLink()',
|
||||
:enabled="!resetPasswordSetNewOneData.hasError"
|
||||
) {{$t('setNewPass')}}
|
||||
|
||||
#bottom-wrap
|
||||
#bottom-background
|
||||
.seamless_mountains_demo_repeat
|
||||
@@ -111,7 +135,7 @@
|
||||
color: $purple-400;
|
||||
}
|
||||
|
||||
#login-form, #forgot-form {
|
||||
#login-form, #forgot-form, #reset-password-set-new-one-form {
|
||||
margin: 0 auto;
|
||||
width: 40em;
|
||||
padding-top: 5em;
|
||||
@@ -120,7 +144,6 @@
|
||||
z-index: 1;
|
||||
|
||||
.header {
|
||||
|
||||
h2 {
|
||||
color: $white;
|
||||
}
|
||||
@@ -251,6 +274,10 @@ export default {
|
||||
password: '',
|
||||
passwordConfirm: '',
|
||||
forgotPassword: false,
|
||||
resetPasswordSetNewOneData: {
|
||||
hasError: null,
|
||||
code: null,
|
||||
},
|
||||
};
|
||||
|
||||
data.icons = Object.freeze({
|
||||
@@ -269,6 +296,12 @@ export default {
|
||||
}
|
||||
return true;
|
||||
},
|
||||
resetPasswordSetNewOne () {
|
||||
if (this.$route.path.startsWith('/reset-password')) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
},
|
||||
mounted () {
|
||||
hello.init({
|
||||
@@ -277,15 +310,42 @@ export default {
|
||||
google: process.env.GOOGLE_CLIENT_ID, // eslint-disable-line
|
||||
});
|
||||
},
|
||||
watch: {
|
||||
$route: {
|
||||
handler () {
|
||||
if (this.resetPasswordSetNewOne) {
|
||||
const query = this.$route.query;
|
||||
const code = query.code;
|
||||
const hasError = query.hasError === 'true' ? true : false;
|
||||
if (hasError) {
|
||||
alert(query.message);
|
||||
this.$router.push({name: 'login'});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!code) {
|
||||
alert(this.$t('invalidPasswordResetCode'));
|
||||
this.$router.push({name: 'login'});
|
||||
return;
|
||||
}
|
||||
|
||||
this.resetPasswordSetNewOneData.code = query.code;
|
||||
this.resetPasswordSetNewOneData.hasError = hasError;
|
||||
}
|
||||
},
|
||||
immediate: true,
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
async register () {
|
||||
// @TODO do not use alert
|
||||
if (!this.email) {
|
||||
alert('Email is required');
|
||||
alert(this.$t('missingEmail'));
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.password !== this.passwordConfirm) {
|
||||
alert('Passwords must match');
|
||||
alert(this.$t('passwordConfirmationMatch'));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -370,11 +430,16 @@ export default {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.resetPasswordSetNewOne) {
|
||||
this.resetPasswordSetNewOneLink();
|
||||
return;
|
||||
}
|
||||
|
||||
this.login();
|
||||
},
|
||||
async forgotPasswordLink () {
|
||||
if (!this.username) {
|
||||
alert('Email is required');
|
||||
alert(this.$t('missingEmail'));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -384,6 +449,34 @@ export default {
|
||||
|
||||
alert(this.$t('newPassSent'));
|
||||
},
|
||||
async resetPasswordSetNewOneLink () {
|
||||
if (!this.password) {
|
||||
alert(this.$t('missingNewPassword'));
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.password !== this.passwordConfirm) {
|
||||
// @TODO i18n and don't use alerts
|
||||
alert(this.$t('passwordConfirmationMatch'));
|
||||
return;
|
||||
}
|
||||
|
||||
const res = await axios.post('/api/v3/user/reset-password', {
|
||||
newPassword: this.password,
|
||||
confirmPassword: this.passwordConfirm,
|
||||
code: this.resetPasswordSetNewOneData.code,
|
||||
});
|
||||
|
||||
if (res.message) {
|
||||
alert(res.message);
|
||||
}
|
||||
|
||||
this.password = '';
|
||||
this.passwordConfirm = '';
|
||||
this.resetPasswordSetNewOneData.code = '';
|
||||
this.resetPasswordSetNewOneData.hasError = false;
|
||||
this.$router.push({name: 'login'});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -24,7 +24,7 @@ const PressKitPage = () => import(/* webpackChunkName: "static" */'./components/
|
||||
const PrivacyPage = () => import(/* webpackChunkName: "static" */'./components/static/privacy');
|
||||
const TermsPage = () => import(/* webpackChunkName: "static" */'./components/static/terms');
|
||||
|
||||
const RegisterLogin = () => import(/* webpackChunkName: "auth" */'./components/auth/registerLogin');
|
||||
const RegisterLoginReset = () => import(/* webpackChunkName: "auth" */'./components/auth/registerLoginReset');
|
||||
|
||||
// User Pages
|
||||
// const StatsPage = () => import(/* webpackChunkName: "user" */'./components/userMenu/stats');
|
||||
@@ -98,8 +98,9 @@ const router = new VueRouter({
|
||||
},
|
||||
// requiresLogin is true by default, isStatic false
|
||||
routes: [
|
||||
{ name: 'register', path: '/register', component: RegisterLogin, meta: {requiresLogin: false} },
|
||||
{ name: 'login', path: '/login', component: RegisterLogin, meta: {requiresLogin: false} },
|
||||
{ name: 'register', path: '/register', component: RegisterLoginReset, meta: {requiresLogin: false} },
|
||||
{ name: 'login', path: '/login', component: RegisterLoginReset, meta: {requiresLogin: false} },
|
||||
{ name: 'resetPassword', path: '/reset-password', component: RegisterLoginReset, meta: {requiresLogin: false} },
|
||||
{ name: 'tasks', path: '/', component: UserTasks },
|
||||
{
|
||||
path: '/inventory',
|
||||
|
||||
@@ -40,6 +40,7 @@
|
||||
"elmiQuote": "Every morning I'm looking forward to getting up so I can earn some gold!",
|
||||
"forgotPassword": "Forgot Password?",
|
||||
"emailNewPass": "Email a Password Reset Link",
|
||||
"forgotPasswordSteps": "Enter the email address you used to register your Habitica account.",
|
||||
"sendLink": "Send Link",
|
||||
"evagantzQuote": "My very first dentist appointment where the hygienist was actually excited about my flossing habits. Thanks [Habitica]!",
|
||||
"examplesHeading": "Players use Habitica to manage...",
|
||||
@@ -131,6 +132,7 @@
|
||||
"oldNews": "News",
|
||||
"newsArchive": "News archive on Wikia (multilingual)",
|
||||
"passConfirm": "Confirm Password",
|
||||
"setNewPass": "Set New Password",
|
||||
"passMan": "In case you are using a password manager (like 1Password) and have problems logging in, try typing your username and password manually.",
|
||||
"password": "Password",
|
||||
"playButton": "Play",
|
||||
|
||||
@@ -20,6 +20,7 @@ import { decrypt, encrypt } from '../../libs/encryption';
|
||||
import { send as sendEmail } from '../../libs/email';
|
||||
import pusher from '../../libs/pusher';
|
||||
import common from '../../../common';
|
||||
import { validatePasswordResetCodeAndFindUser, convertToBcrypt} from '../../libs/password';
|
||||
|
||||
const BASE_URL = nconf.get('BASE_URL');
|
||||
const TECH_ASSISTANCE_EMAIL = nconf.get('EMAILS:TECH_ASSISTANCE_EMAIL');
|
||||
@@ -641,6 +642,48 @@ api.updateEmail = {
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* @api {post} /api/v3/user/auth/reset-password-set-new-one Reser Password Set New one
|
||||
* @apiDescription Set a new password for a user that reset theirs. Not meant for public usage.
|
||||
* @apiName ResetPasswordSetNewOne
|
||||
* @apiGroup User
|
||||
*
|
||||
* @apiParam (Body) {String} newPassword The new password.
|
||||
* @apiParam (Body) {String} confirmPassword Password confirmation.
|
||||
*
|
||||
* @apiSuccess {String} data An empty object
|
||||
* @apiSuccess {String} data Success message
|
||||
*/
|
||||
api.resetPasswordSetNewOne = {
|
||||
method: 'POST',
|
||||
url: '/user/auth/reset-password-set-new-one',
|
||||
async handler (req, res) {
|
||||
let user = await validatePasswordResetCodeAndFindUser(req.body.code);
|
||||
let isValidCode = Boolean(user);
|
||||
|
||||
if (!isValidCode) throw new NotAuthorized(res.t('invalidPasswordResetCode'));
|
||||
|
||||
req.checkBody('newPassword', res.t('missingNewPassword')).notEmpty();
|
||||
req.checkBody('confirmPassword', res.t('missingNewPassword')).notEmpty();
|
||||
let validationErrors = req.validationErrors();
|
||||
if (validationErrors) throw validationErrors;
|
||||
|
||||
let newPassword = req.body.newPassword;
|
||||
let confirmPassword = req.body.confirmPassword;
|
||||
|
||||
if (newPassword !== confirmPassword) {
|
||||
throw new BadRequest(res.t('passwordConfirmationMatch'));
|
||||
}
|
||||
|
||||
// set new password and make sure it's using bcrypt for hashing
|
||||
await convertToBcrypt(user, String(newPassword));
|
||||
user.auth.local.passwordResetCode = undefined; // Reset saved password reset code
|
||||
await user.save();
|
||||
|
||||
return res.respond(200, {}, res.t('passwordChangeSuccess'));
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* @api {delete} /api/v3/user/auth/social/:network Delete social authentication method
|
||||
* @apiDescription Remove a social authentication method (only facebook supported) from a user profile. The user must have local authentication enabled
|
||||
|
||||
@@ -1,22 +1,10 @@
|
||||
import locals from '../../middlewares/locals';
|
||||
import { validatePasswordResetCodeAndFindUser, convertToBcrypt} from '../../libs/password';
|
||||
import { validatePasswordResetCodeAndFindUser } from '../../libs/password';
|
||||
|
||||
let api = {};
|
||||
|
||||
// Internal authentication routes
|
||||
|
||||
function renderPasswordResetPage (options = {}) {
|
||||
// res is express' res, error any error and success if the password was successfully changed
|
||||
let {res, hasError, success = false, message} = options;
|
||||
|
||||
return res.status(hasError ? 401 : 200).render('auth/reset-password-set-new-one.jade', {
|
||||
env: res.locals.habitrpg,
|
||||
success,
|
||||
hasError,
|
||||
message, // can be error or success message
|
||||
});
|
||||
}
|
||||
|
||||
// Set a new password after having requested a password reset (GET route to input password)
|
||||
api.resetPasswordSetNewOne = {
|
||||
method: 'GET',
|
||||
@@ -24,63 +12,14 @@ api.resetPasswordSetNewOne = {
|
||||
middlewares: [locals],
|
||||
runCron: false,
|
||||
async handler (req, res) {
|
||||
let user = await validatePasswordResetCodeAndFindUser(req.query.code);
|
||||
let isValidCode = Boolean(user);
|
||||
const code = req.query.code;
|
||||
const user = await validatePasswordResetCodeAndFindUser(code);
|
||||
const isValidCode = Boolean(user);
|
||||
|
||||
return renderPasswordResetPage({
|
||||
res,
|
||||
hasError: !isValidCode,
|
||||
message: !isValidCode ? res.t('invalidPasswordResetCode') : null,
|
||||
});
|
||||
},
|
||||
};
|
||||
const hasError = !isValidCode;
|
||||
const message = !isValidCode ? res.t('invalidPasswordResetCode') : null;
|
||||
|
||||
// Set a new password after having requested a password reset (POST route to save password)
|
||||
api.resetPasswordSetNewOneSubmit = {
|
||||
method: 'POST',
|
||||
url: '/static/user/auth/local/reset-password-set-new-one',
|
||||
middlewares: [locals],
|
||||
runCron: false,
|
||||
async handler (req, res) {
|
||||
let user = await validatePasswordResetCodeAndFindUser(req.query.code);
|
||||
let isValidCode = Boolean(user);
|
||||
|
||||
if (!isValidCode) return renderPasswordResetPage({
|
||||
res,
|
||||
hasError: true,
|
||||
message: res.t('invalidPasswordResetCode'),
|
||||
});
|
||||
|
||||
let newPassword = req.body.newPassword;
|
||||
let confirmPassword = req.body.confirmPassword;
|
||||
|
||||
if (!newPassword) {
|
||||
return renderPasswordResetPage({
|
||||
res,
|
||||
hasError: true,
|
||||
message: res.t('missingNewPassword'),
|
||||
});
|
||||
}
|
||||
|
||||
if (newPassword !== confirmPassword) {
|
||||
return renderPasswordResetPage({
|
||||
res,
|
||||
hasError: true,
|
||||
message: res.t('passwordConfirmationMatch'),
|
||||
});
|
||||
}
|
||||
|
||||
// set new password and make sure it's using bcrypt for hashing
|
||||
await convertToBcrypt(user, String(newPassword));
|
||||
user.auth.local.passwordResetCode = undefined; // Reset saved password reset code
|
||||
await user.save();
|
||||
|
||||
return renderPasswordResetPage({
|
||||
res,
|
||||
hasError: false,
|
||||
success: true,
|
||||
message: res.t('passwordChangeSuccess'),
|
||||
});
|
||||
return res.redirect(`/reset-password?hasError=${hasError}&message=${message}&code=${code}`);
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user