diff --git a/test/api/v3/integration/user/auth/GET-auth_reset-password-set-new-one.js b/test/api/v3/integration/user/auth/GET-auth_reset-password-set-new-one.js index 29d3807e879..760041b5292 100644 --- a/test/api/v3/integration/user/auth/GET-auth_reset-password-set-new-one.js +++ b/test/api/v3/integration/user/auth/GET-auth_reset-password-set-new-one.js @@ -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); }); }); 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 a963ced5d7c..92960a31eab 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 @@ -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}`); - - 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(), + }); + + 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); diff --git a/website/client/components/auth/registerLogin.vue b/website/client/components/auth/registerLoginReset.vue similarity index 76% rename from website/client/components/auth/registerLogin.vue rename to website/client/components/auth/registerLoginReset.vue index 541c5235d46..5b587430d55 100644 --- a/website/client/components/auth/registerLogin.vue +++ b/website/client/components/auth/registerLoginReset.vue @@ -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; } @@ -385,11 +445,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; } @@ -399,6 +464,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'}); + }, }, }; diff --git a/website/client/components/settings/api.vue b/website/client/components/settings/api.vue index 84b17cbf1e3..720da63368c 100644 --- a/website/client/components/settings/api.vue +++ b/website/client/components/settings/api.vue @@ -23,7 +23,7 @@ br | {{ $t('chromeChatExtensionDesc') }} li - a(target='_blank' :href='`http://data.habitrpg.com?uuid= + user._id`') {{ $t('dataTool') }} + a(target='_blank' :href='`https://oldgods.net/habitica/habitrpg_user_data_display.html?uuid=` + user._id') {{ $t('dataTool') }} br | {{ $t('dataToolDesc') }} li(v-html="$t('otherExtensions')") diff --git a/website/client/router.js b/website/client/router.js index ff6c8f94168..005693fc7bc 100644 --- a/website/client/router.js +++ b/website/client/router.js @@ -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', diff --git a/website/common/locales/en/front.json b/website/common/locales/en/front.json index 1f863a5ca2a..160aa5b2fdc 100644 --- a/website/common/locales/en/front.json +++ b/website/common/locales/en/front.json @@ -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", diff --git a/website/server/controllers/api-v3/auth.js b/website/server/controllers/api-v3/auth.js index 3f03cc835f3..96c52d67192 100644 --- a/website/server/controllers/api-v3/auth.js +++ b/website/server/controllers/api-v3/auth.js @@ -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 diff --git a/website/server/controllers/top-level/auth.js b/website/server/controllers/top-level/auth.js index d547c281e81..b75786ab78f 100644 --- a/website/server/controllers/top-level/auth.js +++ b/website/server/controllers/top-level/auth.js @@ -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); - - return renderPasswordResetPage({ - res, - hasError: !isValidCode, - 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'), - }); - } + const code = req.query.code; + const user = await validatePasswordResetCodeAndFindUser(code); + const isValidCode = Boolean(user); - // 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(); + const hasError = !isValidCode; + const message = !isValidCode ? res.t('invalidPasswordResetCode') : null; - return renderPasswordResetPage({ - res, - hasError: false, - success: true, - message: res.t('passwordChangeSuccess'), - }); + return res.redirect(`/reset-password?hasError=${hasError}&message=${message}&code=${code}`); }, };