Skip to content

Commit

Permalink
Back-end:
Browse files Browse the repository at this point in the history
	Updated reset password routes:
	1. authentication is no longer required.
	2. email needs to be sent as part of request body.
Front-end:
	Added reset password mechanism. Includes 2 forms:
	1. "forgot-password": send a reset mail.
	2. "reset": reset to a new password.
issues: #203 #300
  • Loading branch information
alonttal committed Apr 19, 2018
1 parent f1e22c7 commit f3d3f2e
Show file tree
Hide file tree
Showing 13 changed files with 252 additions and 37 deletions.
47 changes: 33 additions & 14 deletions server/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -611,11 +611,13 @@ app.post('/users/verify', async (req, res) => {
*
* This route is used to send an email upon reset password request.
* To perform the action we use the Password-Reset service.
* Authentication is first required for security enhancement.
* Authentication is not required, but the designated user email
* should be supplied via the request body.
*/
app.post('/users/forgot-password', authenticate, (req, res) => {
app.post('/users/reset', async (req, res) => {
try {
passwordReset.sendResetPasswordMail(req.user);
const user = await User.findOne({ email: req.body.email });
passwordReset.sendResetPasswordMail(user);
res.send('forgot email was sent');
} catch (err) {
res.status(BAD_REQUEST).send(err);
Expand All @@ -628,12 +630,17 @@ app.post('/users/forgot-password', authenticate, (req, res) => {
*
* Handles the GET request which is sent to the server when a user
* clicks on the Password-Reset URL attach to the mail sent by
* "/users/forgot-password" route.
* "POST /users/reset" route.
* This route is important because of it supplies verification. The server
* will not display the change password page unless the "token" is valid.
* Authentication is required.
*
* @updatedBy: Alon Talmor
* @date: 19/04/18
* !! DEPRECATED !! Please do not use.
* The method of reset password was changed, authentication is no more required!
*/
app.get('/users/reset-password/:token', authenticate, (req, res) => {
app.get('/users/reset/:token', (req, res) => {
try {
passwordReset.verifyResetToken(req.user, req.params.token);
res.send('Reset verification successful');
Expand All @@ -646,22 +653,34 @@ app.get('/users/reset-password/:token', authenticate, (req, res) => {
* @author: Alon Talmor
* @date: 2/4/18
*
* After the user chooses his/her new password, it send to the server in the request's body.
* After the user chooses his/her new password, it sends it to the server in the request's body.
* In addition, he/she must add email address - this is required for fetching the account details from the db.
* It is assumed that the password is sent under the property "password".
* First the token is revarified, so we know that no malicious user is trying to change the
* First the token is verified, so we know that no malicious user is trying to change the
* password without acctually recieving a token!
* Next, the user's password is reset and is changed to the new password. It is assumed that
* the resetPassword method performs checks on the password (on error an exception might be thrown).
* If everything went well, updated user object is returned.
* The user should not be authenticated afterwards (he/she is required to login in again).
*
* @updatedBy: Alon Talmor
* @date: 19/04/18
* Property email is also assumed to be sent in the request body.
*/
app.patch('/users/reset-password/:token', authenticate, async (req, res) => {
app.patch('/users/reset/:token', async (req, res) => {
try {
passwordReset.verifyResetToken(req.user, req.params.token);
const user = await req.user.resetPassword(req.body.password);
console.log(req.body);
const user = await User.findOne({ email: req.body.email });
console.log(user);
passwordReset.verifyResetToken(user, req.params.token);
console.log("3");
await user.resetPassword(req.body.password);
console.log("5");

//TODO: disable using this same link after password change.
res.send({ user });
} catch (err) {
console.log(err);
res.status(BAD_REQUEST).send(err);
}
});
Expand All @@ -682,9 +701,9 @@ app.patch('/users/notifications/:id', authenticate, async (req, res) => {
const { id } = req.params;

const notificationData = _.pick(req.body,
[
'wasRead'
]);
[
'wasRead'
]);

const curNotification = JSON.parse(JSON.stringify(req.user.getNotificationById(id)));
const newNotification = updateNotificationByJson(curNotification, notificationData);
Expand All @@ -694,7 +713,7 @@ app.patch('/users/notifications/:id', authenticate, async (req, res) => {
res.send({ user });

} catch (err) {
return res.status(BAD_REQUEST).send(errors.unknownError);
return res.status(BAD_REQUEST).send(errors.unknownError);
}
});

Expand Down
2 changes: 1 addition & 1 deletion server/services/password-reset/password-reset.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ const sendResetPasswordMail = (user) => {
if (err) {
throw PasswordResetFailure;
}
const resetPasswordURL = `http://localhost:3000/users/reset-password/${ResetToken}`; //TODO: user CORS
const resetPasswordURL = `http://localhost:8080/users/reset-password/${ResetToken}`; //TODO: user CORS

const msg = {
to: user.email,
Expand Down
2 changes: 1 addition & 1 deletion views/src/components/AppApartmentAd.vue
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@
<v-card-text v-show="show" class="pt-0">
<v-divider class="mb-3"></v-divider>

<strong>Enterance date:</strong> {{ new Date(apartment.enteranceDate).toDateString() }}
<strong>Entrance date:</strong> {{ new Date(apartment.entranceDate).toDateString() }}
<v-card class="mt-3">
<v-card-title>
<h4>Attributes</h4>
Expand Down
8 changes: 2 additions & 6 deletions views/src/components/AppCalendarForm.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<template>
<v-menu ref="menu" lazy :close-on-content-click="false" v-model="menu" transition="scale-transition" offset-y full-width :nudge-right="40" min-width="290px" :return-value.sync="date">
<v-text-field :label="label" slot="activator" v-model="date" :prepend-icon="no-icon? '' : 'event'" readonly :single-line="true" :required="required"></v-text-field>
<v-text-field :label="label" slot="activator" v-model="date" :prepend-icon="noIcon? '' : 'event'" readonly :single-line="true" :required="required"></v-text-field>
<v-date-picker v-model="date" no-title scrollable>
<v-spacer></v-spacer>
<v-btn flat color="primary" @click="menu = false">Cancel</v-btn>
Expand All @@ -24,11 +24,7 @@
type: String,
default: ''
},
'no-form': {
type: Boolean,
default: false
},
'no-icon': {
noIcon: {
type: Boolean,
default: false
}
Expand Down
2 changes: 1 addition & 1 deletion views/src/components/AppDrawer.vue
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
<v-flex xs12>
<v-list-tile>
<v-list-tile-action>
Enterance date
Entrance date
</v-list-tile-action>
<v-list-tile-content>
<v-list-tile-title class="grey--text">
Expand Down
7 changes: 5 additions & 2 deletions views/src/components/AppLogin.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@
<v-flex>
<v-form v-model="valid">
<v-text-field label="E-mail" type="email" prepend-icon="email" v-model="payload.email" :rules="rules.email" required></v-text-field>
<v-text-field label="Password" type="password" prepend-icon="lock" v-model="payload.password" :rules="rules.password" required></v-text-field>
<v-text-field @keyup.enter="login" label="Password" type="password" prepend-icon="lock" v-model="payload.password" :rules="rules.password" required></v-text-field>
<router-link :to="{ name: 'AppResetPassword' }" class="" style="text-decoration:none;">
<span class="text-xs-right d-block">Forgot password?</span>
</router-link>
<v-btn @click="login" :disabled="!valid || loading" :loading="loading">
Login
</v-btn>
Expand Down Expand Up @@ -45,7 +48,7 @@
message: '',
type: 'error'
},
loading: false,
loading: false
}),
methods: {
showBadAlert() {
Expand Down
2 changes: 1 addition & 1 deletion views/src/components/AppMainSearchForm.vue
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
<v-card-text v-show="extendSearch">
<v-layout wrap row mt-3>
<v-flex xs12 sm4>
<app-calendar-form @dateUpdated="payload.latestEntranceDate = $event" label="Enterance Date"></app-calendar-form>
<app-calendar-form @dateUpdated="payload.latestEntranceDate = $event" label="Entrance Date"></app-calendar-form>
</v-flex>
<v-flex xs12 sm4 v-for="(slider,i) in sliders" :key="`slider-${i}`">
<v-slider :label="slider.label" v-model="payload[slider.ref]" thumb-label :step="slider.interval" :min="slider.value.min" :max="slider.value.max" ticks></v-slider>
Expand Down
2 changes: 1 addition & 1 deletion views/src/components/AppPublishApartment.vue
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
<v-slider v-model="requiredRoommatesSlider" label="" thumb-label step="1" min="0" max="10" ticks required></v-slider>
</v-flex>
<v-flex xs12 sm12 md2 order-sm2 order-md1>
<v-subheader v-text="'Enterance date'"></v-subheader>
<v-subheader v-text="'Entrance date'"></v-subheader>
</v-flex>
<v-flex xs12 sm12 md3 order-sm2 order-md1>
<app-calendar-form @dateUpdated="payload.brithdate = $event" label="when" single-line required />
Expand Down
163 changes: 163 additions & 0 deletions views/src/components/AppResetPassword.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
<template>
<v-container>
<v-layout row wrap>
<v-flex>
<v-card class="pb-3">
<v-card-title primary-title>
<div>
<h3 class="headline mb-2 secondary--text"> Password Reset</h3>
<v-divider></v-divider>
<div v-if="!showResetForm">
<p class="mt-3 mb-1">
Enter your email address below to reset your password. You will be sent an email which you will need to open to continue.
</p>
<p class="mb-4">
This procedure is required in order to assure you are the rightful owner of the account.
</p>
<v-text-field label="Email" type="email" prepend-icon="mail" v-model="payload.email" :error-messages="emailMessages" :disabled="emailSent" required></v-text-field>
</div>
<div v-else>
<p class="mt-3 mb-1">
Enter your new account password below. Once confirmed, your new password will be active.
</p>
<v-container>
<v-layout row wrap>
<v-flex xs12>
<v-form>
<v-text-field label="E-mail" type="email" prepend-icon="email" v-model="payload.email" :error-messages="emailMessages" required></v-text-field>
<v-text-field label="Password" type="password" prepend-icon="lock" v-model="payload.password" :rules="rules.password" required></v-text-field>
<v-text-field label="Password (again)" type="password" prepend-icon="lock" v-model="passwordAgain" :rules="rules.passwordAgain" required></v-text-field>
</v-form>
</v-flex>
</v-layout>
</v-container>
</div>
</div>
</v-card-title>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn v-if="!emailSent" @click="showResetForm? resetPassword() : sendResetMail()" color="primary" :disabled="loading" :loading="loading">
Submit
<v-icon right>send</v-icon>
</v-btn>
<div v-else class="accept--text mr-1 mt-1 subheading">
Email was sent
<v-icon right color="accept">check_circle</v-icon>
</div>
</v-card-actions>
</v-card>

</v-flex>
</v-layout>
</v-container>
</template>

<script>
export default {
data() {
return {
payload: {
email: null,
password: ''
},
passwordAgain: '',
loading: false,
emailMessages: [],
emailSent: false,
showResetForm: false,
rules: {
password: [
v => !!v || 'Password is required',
v => v.length >= 6 || 'Password must be at least 6 characters'
],
passwordAgain: [
v => !!v || 'Please re-enter your password',
v => v === this.payload.password || 'Passwords do not match'
]
}
};
},
methods: {
showSentSnackbar() {
this.$store.commit(
'showSnackbar',
'Password reset was sent to your email, please check your inbox to continue'
);
},
showResetSnackbar() {
this.$store.commit(
'showSnackbar',
'Your password was reset! You can now login to your account using your new password'
);
},
showInvalidEmail() {
this.emailMessages = 'This is not a valid email';
},
showFailureSnackbar() {
this.$store.commit(
'showSnackbar',
"Oops.. something didn't go well, please retry again or contact us"
);
},
sendResetMail() {
if (!this.isValidEmail) {
this.showInvalidEmail();
return;
}
this.errorMessages = [];
this.loading = true;
this.$store
.dispatch('sendResetMail', this.payload)
.then(() => {
this.emailSent = true;
this.showSentSnackbar();
})
.catch(() => {
this.showInvalidEmail();
})
.then(() => {
this.loading = false;
});
},
resetPassword() {
if (!this.isValidEmail) {
this.showInvalidEmail();
return;
}
this.errorMessages = [];
this.loading = true;
this.$store
.dispatch('resetPassword', {
jwt: this.$route.params.token,
payload: this.payload
})
.then(() => {
this.$router.push({ name: 'AppIdentification' });
this.showResetSnackbar();
})
.catch(() => {
this.showFailureSnackbar();
})
.then(() => {
this.loading = false;
});
}
},
computed: {
isValidEmail() {
return /^\w+([.-]?\w+)*@\w+([.-]?\w+)*(\.\w{2,3})+$/.test(
this.payload.email
);
}
},
created() {
if (this.$route.params.token) {
this.showResetForm = true;
}
}
};
</script>

<style>
</style>
2 changes: 1 addition & 1 deletion views/src/components/AppToolbar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@
{
title: 'Account',
icon: 'settings',
to: '##',
to: 'AppResetPassword',
do: () => {}
},
{
Expand Down
Loading

0 comments on commit f3d3f2e

Please sign in to comment.