Skip to content
Snippets Groups Projects
Commit 5d11dc08 authored by fz2907's avatar fz2907
Browse files

Merge branch 'dev_chou' into 'sprint_3'

add reset password feature

See merge request !35
parents 63ae5ae4 cc1566db
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 538 additions and 6 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,
getProfile,
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