Skip to content
Snippets Groups Projects
Commit fc75216f authored by Bart Chou's avatar Bart Chou
Browse files

add reset password feature

parent 29bb4a7c
No related branches found
No related tags found
2 merge requests!37Start AgreedMatchPage to show the ongoing matches and history matches for user...,!35add reset password feature
Showing
with 537 additions and 5 deletions
......@@ -20,10 +20,16 @@ repositories {
dependencies {
//implementation 'org.springframework.boot:spring-boot-starter-data-jdbc'
implementation 'org.springframework:spring-context-support:5.3.23'
implementation 'mysql:mysql-connector-java:8.0.30'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
// implementation 'org.springframework.boot:spring-boot-starter-data-mongodb'
implementation 'org.springframework.boot:spring-boot-starter-web'
// implementation 'org.springframework.boot:spring-boot-starter-security'
// implementation 'org.springframework.security:spring-security-test'
// implementation 'org.springframework.security:spring-security-core:5.7.3'
// implementation 'org.springframework.security.core.userdetails'
implementation 'org.springframework.boot:spring-boot-starter-mail:2.7.5'
implementation 'io.jsonwebtoken:jjwt:0.9.1'
implementation 'org.springframework.session:spring-session-core'
annotationProcessor 'io.jsonwebtoken:jjwt:0.9.1'
......
......@@ -4,15 +4,22 @@ import com.fasterxml.jackson.core.JsonProcessingException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.repository.query.Param;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import vt.CS5934.SwitchRoom.models.ResponseModel;
import vt.CS5934.SwitchRoom.models.UserModel;
import vt.CS5934.SwitchRoom.services.Token;
import vt.CS5934.SwitchRoom.services.UserService;
import vt.CS5934.SwitchRoom.utility.UsefulTools;
import javax.mail.MessagingException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.security.NoSuchAlgorithmException;
/**
......@@ -150,4 +157,85 @@ public class UserController {
response = userService.getProfile(userId);
return response;
}
@PostMapping("/forgotPassword")
public ResponseModel processForgotPassword(
HttpServletRequest request,
@RequestBody String payload
) throws MessagingException, UnsupportedEncodingException, JsonProcessingException {
ResponseModel response = new ResponseModel();
boolean result = userService.updateResetPasswordToken(payload, request);
if (result) {
response.setStatus(HttpStatus.OK);
response.setMessage("We have sent a reset password link to your email. Please check.");
} else {
response.setMessage("Couldn't find an account matching the email you entered");
response.setStatus(HttpStatus.FORBIDDEN);
}
return response;
}
@GetMapping("/forgotPassword_verify")
public ResponseEntity<Void> showResetPasswordForm(
@Param(value = "token") String token,
HttpServletRequest request,
HttpServletResponse servletResponse
) {
UserModel existUser = userService.getByResetPasswordToken(token);
String baseURL = UsefulTools.getSiteURL(request);
// deployed
// if (existUser != null) {
// return ResponseEntity.status(HttpStatus.FOUND)
// .location(URI.create(baseURL + "/forgotPasswordForm"))
// .build();
// } else {
// return ResponseEntity.status(HttpStatus.FOUND)
// .location(URI.create(baseURL))
// .build();
// }
// development
if (existUser != null) {
Cookie theCookie = new Cookie("resetToken", token);
theCookie.setHttpOnly(false);
theCookie.setSecure(false);
theCookie.setPath("/");
theCookie.setMaxAge(60*60); // 1 hour
servletResponse.addCookie(theCookie);
return ResponseEntity.status(HttpStatus.FOUND)
.location(URI.create("http://localhost:8080/forgotPasswordForm"))
.build();
} else {
return ResponseEntity.status(HttpStatus.FOUND)
.location(URI.create("http://localhost:8080"))
.build();
}
}
@PostMapping("/forgotPasswordCreateNew")
public ResponseModel forgotPasswordCreateNew(
@CookieValue(value = "resetToken", required = false) String token,
@RequestBody String payload) throws JsonProcessingException, NoSuchAlgorithmException {
ResponseModel response = new ResponseModel();
if (token == null) {
response.setMessage("Login session expired or invalid");
response.setStatus(HttpStatus.FORBIDDEN);
return response;
}
boolean result = userService.forgotPasswordCreateNew(token, payload);
if (result) {
response.setMessage("Successfully reset your password");
response.setStatus(HttpStatus.OK);
} else {
response.setMessage("Couldn't find an account matching your login session");
response.setStatus(HttpStatus.FORBIDDEN);
}
return response;
}
}
......@@ -39,9 +39,22 @@ public class UserModel {
@Column(name="gender")
private String gender;
@Column(name="token")
private String token;
public UserModel(String name, String password, String email, String firstname, String lastname, String gender, String token) {
@Column(name="reset_password_token")
private String resetPasswordToken;
public UserModel(
String name,
String password,
String email,
String firstname,
String lastname,
String gender,
String token,
String resetPasswordToken) {
this.username = name;
this.password = password;
this.email = email;
......@@ -49,6 +62,7 @@ public class UserModel {
this.lastname = lastname;
this.gender = gender;
this.token = token;
this.resetPasswordToken = resetPasswordToken;
}
@Override
......@@ -62,6 +76,7 @@ public class UserModel {
", lastname='" + lastname + '\'' +
", gender='" + gender + '\'' +
", token='" + token + '\'' +
", resetPasswordToken='" + resetPasswordToken + '\'' +
'}';
}
......
......@@ -24,6 +24,10 @@ public interface UserRepository extends JpaRepository<UserModel, Integer> {
UserModel findByUsername(String username);
UserModel findByEmail(String email);
UserModel findByResetPasswordToken(String token);
List<UserModel> findAll();
void deleteByUserId(Long userId);
......
......@@ -7,19 +7,30 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.stereotype.Service;
import vt.CS5934.SwitchRoom.hash.SHAModel;
import vt.CS5934.SwitchRoom.models.ExampleModel;
import vt.CS5934.SwitchRoom.models.ResponseModel;
import vt.CS5934.SwitchRoom.models.UserModel;
import vt.CS5934.SwitchRoom.repositories.UserRepository;
import vt.CS5934.SwitchRoom.utility.UsefulTools;
import javax.mail.MessagingException;
import javax.mail.internet.MimeMessage;
import javax.servlet.http.HttpServletRequest;
import javax.transaction.Transactional;
import java.io.UnsupportedEncodingException;
import java.security.NoSuchAlgorithmException;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.springframework.mail.javamail.JavaMailSender;
import vt.CS5934.SwitchRoom.utility.UsefulTools;
@Service
public class UserService {
......@@ -35,6 +46,8 @@ public class UserService {
* It usually uses on Repository class, Service class, or some globe object in the class.
*/
@Autowired
private JavaMailSender mailSender;
@Autowired
UserRepository userRepository;
public UserService() throws NoSuchAlgorithmException {
......@@ -42,7 +55,7 @@ public class UserService {
public UserModel addUserToDB(String username, String password, String email, String firstname, String lastname, String gender){
logger.info("Reached addNewExampleModelToDB()");
UserModel newUser = new UserModel(username, password, email, firstname, lastname, gender, null);
UserModel newUser = new UserModel(username, password, email, firstname, lastname, gender, null, null);
userRepository.save(newUser);
return newUser;
}
......@@ -138,4 +151,70 @@ public class UserService {
return response;
}
public boolean updateResetPasswordToken(String payload, HttpServletRequest request) throws MessagingException, UnsupportedEncodingException, JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
String email = mapper.readTree(payload)
.get("email")
.asText();
UserModel existUser = userRepository.findByEmail(email);
if (existUser != null) {
String token = Token.of(existUser.getUserId(), 10L, "secret").getToken();
existUser.setResetPasswordToken(token);
userRepository.save(existUser);
String resetPasswordLink = UsefulTools.getSiteURL(request) + "/user/forgotPassword_verify?token=" + token;
sendEmail(email, resetPasswordLink, existUser.getUsername());
return true;
} else {
logger.warn("checkLoginSession: FORBIDDEN (user not found)");
return false;
}
}
public void sendEmail(String recipientEmail, String link, String username)
throws MessagingException, UnsupportedEncodingException {
MimeMessage message = mailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(message);
helper.setFrom("switchroomvt@gmail.com", "Switch Room Support");
helper.setTo(recipientEmail);
String subject = "(Switch Room Support) Here's the link to reset your password";
String content = "<p>Hello, " + username + "</p>"
+ "<p>You have requested to reset your password.</p>"
+ "<p>Click the link below to change your password:</p>"
+ "<p><a href=\"" + link + "\">Change my password</a></p>"
+ "<br>"
+ "<p>Ignore this email if you do remember your password, "
+ "or you have not made the request.</p>";
helper.setSubject(subject);
helper.setText(content, true);
mailSender.send(message);
}
public UserModel getByResetPasswordToken(String token) {
return userRepository.findByResetPasswordToken(token);
}
public boolean forgotPasswordCreateNew(String token, String payload) throws NoSuchAlgorithmException, JsonProcessingException {
UserModel existUser = getByResetPasswordToken(token);
if (existUser != null) {
ObjectMapper mapper = new ObjectMapper();
String password = mapper.readTree(payload)
.get("password")
.asText();
String encodedPassword = hashPassword(password);
existUser.setPassword(encodedPassword);
existUser.setResetPasswordToken(null);
userRepository.save(existUser);
return true;
} else {
return false;
}
}
}
package vt.CS5934.SwitchRoom.utility;
import javax.servlet.http.HttpServletRequest;
import java.text.SimpleDateFormat;
public class UsefulTools {
public static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
public static String getSiteURL(HttpServletRequest request) {
String siteURL = request.getRequestURL().toString();
return siteURL.replace(request.getServletPath(), "");
}
}
......@@ -16,4 +16,14 @@ spring.task.scheduling.pool.size = 1
#SpringBoot Offer Wishlist matching job fields:
offer.wishlist.job.gap.seconds = 60
offer.wishlist.job.init.delay.seconds = 10
\ No newline at end of file
offer.wishlist.job.init.delay.seconds = 10
# spring mail
# zognlvyhizugupns
spring.mail.host=smtp.gmail.com
spring.mail.port=587
spring.mail.username=switchroomvt@gmail.com
spring.mail.password=zognlvyhizugupns
spring.mail.properties.mail.smtp.auth=true
spring.mail.properties.mail.smtp.starttls.enable=true
spring.mail.properties.mail.smtp.starttls.required=true
\ No newline at end of file
<template>
<div class="form_wrapper">
<div class="form_container">
<div class="title_container">
<h2>Reset Password</h2>
<div class="underline-title"></div>
</div>
<div class="row clearfix">
<div class="">
<form class="form">
<label for="user-password" style="padding-top: 13px">
&nbsp;Password
</label>
<input v-model="userInformation.password" id="user-password" class="form-content" type="password"
name="password" required />
<div class="form-border"></div>
<label for="user-retypePassword" style="padding-top: 13px">
&nbsp;Re-type Password
</label>
<input v-model="userInformation.retypePassword" id="user-password" class="form-content" type="password"
name="retypePassword" required />
<div class="form-border"></div>
<button id="submit-btn" @click.prevent="onSubmit" type="submit" value="Submit">
Submit
</button>
</form>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, reactive } from "vue";
import * as UserService from "../services/UserService";
import { useRouter } from "vue-router";
import { User } from "@/type/types";
const router = useRouter();
const userInformation = reactive({
password: "",
retypePassword: ""
});
const onSubmit = async () => {
const userInfo = {
password: userInformation.password,
};
if (userInformation.password != userInformation.retypePassword) {
alert(
"Please check the password and retype password, they are not the same"
);
} else {
const response = await UserService.forgotPasswordCreateNew(
JSON.stringify(userInfo)
);
alert(response.message)
userInformation.password = ""
userInformation.retypePassword = ""
router.push("/");
}
};
</script>
<style scoped>
body {
font-family: Verdana, Geneva, sans-serif;
font-size: 14px;
background: #f2f2f2;
}
.form_wrapper {
justify-content: center;
align-items: center;
display: flex;
}
.form_container {
background: #fbfbfb;
border-radius: 8px;
box-shadow: 1px 2px 8px rgba(0, 0, 0, 0.65);
height: auto;
width: 30%;
padding: 12px 44px;
}
.title_container {
font-family: "Raleway Thin", sans-serif;
letter-spacing: 4px;
padding-bottom: 23px;
padding-top: 13px;
text-align: center;
}
.form {
align-items: left;
display: flex;
flex-direction: column;
}
.form-content {
background: #fbfbfb;
border: none;
outline: none;
padding-top: 14px;
}
.form-border {
background: -webkit-linear-gradient(right, #a6f77b, #2ec06f);
height: 1px;
width: 100%;
}
#submit-btn {
background: -webkit-linear-gradient(right, #a6f77b, #2dbd6e);
border: none;
border-radius: 21px;
box-shadow: 0px 1px 8px #24c64f;
cursor: pointer;
color: white;
font-family: "Raleway SemiBold", sans-serif;
height: 42.3px;
margin: 0 auto;
margin-top: 50px;
transition: 0.25s;
width: 153px;
}
#submit-btn:hover {
box-shadow: 0px 1px 18px #24c64f;
}
.underline-title {
background: -webkit-linear-gradient(right, #a6f77b, #2ec06f);
height: 2px;
margin: -1.1rem auto 0 auto;
width: 200px;
}
.input_field radio_option {
margin-top: 10px;
}
</style>
<template>
<div class="form_wrapper">
<div class="form_container">
<div class="title_container">
<h2>Forgot Password</h2>
<div class="underline-title"></div>
</div>
<div class="row clearfix">
<div class="">
<form class="form">
<label for="user-email" style="padding-top: 13px">
&nbsp;Email
</label>
<input
v-model="userInformation.email"
id="user-email"
class="form-content"
type="email"
name="email"
required
/>
<div class="form-border"></div>
<button id="submit-btn" @click.prevent="onSubmit" type="submit" value="Submit">
Submit
</button>
</form>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, reactive } from "vue";
import * as UserService from "../services/UserService";
import { useRouter } from "vue-router";
import { User } from "@/type/types";
const router = useRouter();
const userInformation = reactive({
email: "",
});
const onSubmit = async () => {
const userInfo = {
email: userInformation.email,
};
const response = await UserService.forgotPassword(
JSON.stringify(userInfo)
);
alert(response.message)
userInformation.email = ""
};
</script>
<style scoped>
body {
font-family: Verdana, Geneva, sans-serif;
font-size: 14px;
background: #f2f2f2;
}
.form_wrapper {
justify-content: center;
align-items: center;
display: flex;
}
.form_container {
background: #fbfbfb;
border-radius: 8px;
box-shadow: 1px 2px 8px rgba(0, 0, 0, 0.65);
height: auto;
width: 30%;
padding: 12px 44px;
}
.title_container {
font-family: "Raleway Thin", sans-serif;
letter-spacing: 4px;
padding-bottom: 23px;
padding-top: 13px;
text-align: center;
}
.form {
align-items: left;
display: flex;
flex-direction: column;
}
.form-content {
background: #fbfbfb;
border: none;
outline: none;
padding-top: 14px;
}
.form-border {
background: -webkit-linear-gradient(right, #a6f77b, #2ec06f);
height: 1px;
width: 100%;
}
#submit-btn {
background: -webkit-linear-gradient(right, #a6f77b, #2dbd6e);
border: none;
border-radius: 21px;
box-shadow: 0px 1px 8px #24c64f;
cursor: pointer;
color: white;
font-family: "Raleway SemiBold", sans-serif;
height: 42.3px;
margin: 0 auto;
margin-top: 50px;
transition: 0.25s;
width: 153px;
}
#submit-btn:hover {
box-shadow: 0px 1px 18px #24c64f;
}
.underline-title {
background: -webkit-linear-gradient(right, #a6f77b, #2ec06f);
height: 2px;
margin: -1.1rem auto 0 auto;
width: 200px;
}
.input_field radio_option {
margin-top: 10px;
}
</style>
......@@ -48,9 +48,9 @@
required
/>
<div class="form-border"></div>
<a href="#">
<router-link to="/forgotPassword">
<legend id="forgot-pass">Forgot password?</legend>
</a>
</router-link>
<button type="button" class="btn btn-primary" @click="handleLogin()">
LOGIN
</button>
......
import { createRouter, createWebHistory, RouteRecordRaw } from "vue-router";
import HomeView from "../views/HomeView.vue";
import RegisterView from "../views/RegisterView.vue";
import ForgotPasswordView from "../views/ForgotPasswordView.vue";
import ForgotPasswordFormView from "../views/ForgotPasswordFormView.vue";
import ResetPasswordView from "../views/ResetPasswordView.vue";
import ProfileView from "../views/ProfileView.vue";
import MatchedView from "../views/MatchedView.vue";
......@@ -22,6 +24,23 @@ const routes: Array<RouteRecordRaw> = [
hideHeader: true,
},
},
{
path: "/forgotPassword",
name: "forgotPassword",
meta: {
hideHeader: true,
},
component: ForgotPasswordView,
},
{
path: "/forgotPasswordForm",
name: "forgotPasswordForm",
meta: {
requiresAuth: true,
hideHeader: true,
},
component: ForgotPasswordFormView,
},
{
path: "/resetPassword",
name: "resetPassword",
......
......@@ -19,6 +19,14 @@ function resetPassword(userData: any) {
const urlPath = "/resetPassword";
return serverHttpService.Post(baseUrl + urlPath, JSON.parse(userData));
}
function forgotPassword(userData: any) {
const urlPath = "/forgotPassword";
return serverHttpService.Post(baseUrl + urlPath, JSON.parse(userData));
}
function forgotPasswordCreateNew(userData: any) {
const urlPath = "/forgotPasswordCreateNew";
return serverHttpService.Post(baseUrl + urlPath, JSON.parse(userData));
}
function getProfile() {
const urlPath = "/profile";
return serverHttpService.Get(baseUrl + urlPath);
......@@ -29,5 +37,7 @@ export {
loginUser,
checkLoginSession,
resetPassword,
forgotPassword,
forgotPasswordCreateNew,
getProfile
};
<template>
<ForgotPasswordForm-Page></ForgotPasswordForm-Page>
</template>
<script setup lang="ts">
import ForgotPasswordFormPage from "@/components/ForgotPasswordFormPage.vue";
</script>
<template>
<ForgotPassword-Page></ForgotPassword-Page>
</template>
<script setup lang="ts">
import ForgotPasswordPage from "@/components/ForgotPasswordPage.vue";
</script>
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment