完成忘记密码重置功能

This commit is contained in:
yang.xie 2022-10-12 10:07:40 +08:00
parent d4d7c7626e
commit d90beedf52
7 changed files with 486 additions and 20 deletions

View File

@ -11,7 +11,6 @@ import lombok.Data;
*/
@Data
public class ChangePasswordRequest {
private String userId;
private String oldPassword;
private String newPassword;
private String confirmPassword;

View File

@ -42,7 +42,7 @@ public class UserChangePassword extends BaseEntity<Long> {
/**
* User change password time
*/
@Column(name = "change_password_time", columnDefinition = "DATE COMMENT 'User change password time'")
@Column(name = "change_password_time", columnDefinition = "DATETIME COMMENT 'User change password time'")
private LocalDateTime changePasswordTime;
@ -55,7 +55,8 @@ public class UserChangePassword extends BaseEntity<Long> {
/**
* User change password request time
*/
@Column(name = "request_date_change_password", columnDefinition = "DATE COMMENT 'User change password request " +
@Column(name = "request_date_change_password", columnDefinition = "DATETIME COMMENT 'User change password request" +
" " +
"time'")
private LocalDateTime requestDateChangePassword;

View File

@ -17,6 +17,6 @@ public interface UserChangePasswordRepository extends CrudRepository<UserChangeP
@Query(value = "select u from UserChangePassword u" +
" where u.userId = ?1" +
" and u.changed = false" +
" and u.changePasswordCode = ?2")
" and u.changePasswordCode = ?2 order by u.requestDateChangePassword desc")
Optional<UserChangePassword> findByUserIdAndCode(long userId, String changePasswordCode);
}

View File

@ -2,7 +2,6 @@ package com.northtecom.visatrack.api.service.impl;
import com.northtecom.visatrack.api.base.util.EmailUtils;
import com.northtecom.visatrack.api.data.entity.User;
import com.northtecom.visatrack.api.data.entity.UserChangePassword;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@ -64,15 +63,17 @@ public class EmailService {
emailUtils.sendEmail(user.getUserEmail(), subject, htmlContent);
}
public void sendChangePasswordEmailToUser(User user, UserChangePassword savedUserChangePassword) {
public void sendChangePasswordEmailToUser(User user, String changePasswordCode) {
log.info("Send change password email to user: {}", user);
HashMap<String, Object> map = new HashMap<>();
map.put("SiteName", "UsVisaTrack");
map.put("SiteHost", "www.usvisatrack.com");
map.put("SiteUrl", "https://www.usvisatrack.com/");
map.put("UserName", user.getUserName());
map.put("UserEmail", user.getUserEmail());
map.put("UserId", user.getId());
map.put("ChangePasswordCode", savedUserChangePassword.getChangePasswordCode());
map.put("ChangePasswordCode", changePasswordCode);
String htmlContent = this.templateService.createHtml("email/ChangePassword.html", map);
@ -87,6 +88,7 @@ public class EmailService {
HashMap<String, Object> map = new HashMap<>();
map.put("SiteName", "UsVisaTrack");
map.put("SiteHost", "www.usvisatrack.com");
map.put("SiteUrl", "https://www.usvisatrack.com/");
map.put("UserName", user.getUserName());
map.put("UserId", user.getId());

View File

@ -15,7 +15,9 @@ import com.northtecom.visatrack.api.data.repository.UserRepository;
import com.northtecom.visatrack.api.service.enums.UserStatus;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
@ -105,8 +107,8 @@ public class UserService implements UserDetailsService {
emailService.sendWelcomeEmailToUser(savedUser);
}
public boolean checkPasswordIsMatch(String userPassword, String password) {
return passwordEncoder.matches(userPassword, password);
public boolean checkPasswordIsMatch(String userEncodePassword, String checkPassword) {
return passwordEncoder.matches(checkPassword, userEncodePassword);
}
public String encodePassword(String password) {
@ -152,6 +154,20 @@ public class UserService implements UserDetailsService {
}
public void forgotPassword(String userEmail) {
User user = userRepository.findByUserEmail(userEmail).orElseThrow(() -> new BaseException(Status.BAD_REQUEST,
"User is not exist"));
UserChangePassword userChangePassword = new UserChangePassword();
userChangePassword.setUserId(user.getId());
userChangePassword.setOldPassword(user.getUserPassword());
userChangePassword.setChangePasswordCode(RandomUtil.randomString(18));
userChangePassword.setChanged(false);
userChangePassword.setRequestDateChangePassword(LocalDateTime.now());
UserChangePassword savedUserChangePassword = userChangePasswordRepository.save(userChangePassword);
emailService.sendChangePasswordEmailToUser(user, savedUserChangePassword.getChangePasswordCode());
}
@ -161,9 +177,14 @@ public class UserService implements UserDetailsService {
(Status.BAD_REQUEST, "User is not exist"));
UserChangePassword userChangePassword =
userChangePasswordRepository.findByUserIdAndCode(Long.parseLong(changePasswordForgotRequest.getUserId()), changePasswordForgotRequest.getNewPassword()).orElseThrow(() -> new BaseException
userChangePasswordRepository.findByUserIdAndCode(Long.parseLong(changePasswordForgotRequest.getUserId()), changePasswordForgotRequest.getChangePasswordCode()).orElseThrow(() -> new BaseException
(Status.BAD_REQUEST, "User is not exist"));
//更改密码过期
if (userChangePassword.getRequestDateChangePassword().plusHours(24).isBefore(LocalDateTime.now())) {
throw new BaseException(Status.BAD_REQUEST, "Change password code is expired");
}
userChangePassword.setChanged(true);
userChangePassword.setNewPassword(passwordEncoder.encode(changePasswordForgotRequest.getNewPassword()));
userChangePassword.setChangePasswordTime(LocalDateTime.now());
@ -175,19 +196,34 @@ public class UserService implements UserDetailsService {
emailService.sendChangePasswordSuccessEmailToUser(user);
}
public void changePassword(ChangePasswordRequest changePasswordRequest) {
User user =
userRepository.findById(Long.parseLong(changePasswordRequest.getUserId())).orElseThrow(() -> new BaseException
(Status.BAD_REQUEST, "User is not exist"));
if (!checkPasswordIsMatch(user.getUserPassword(), changePasswordRequest.getOldPassword())) {
@PreAuthorize("authentication.getPrincipal().toString()!=\"anonymousUser\"")
public void changePassword(ChangePasswordRequest changePasswordRequest) {
if (changePasswordRequest.getNewPassword().equals(changePasswordRequest.getOldPassword())) {
throw new BaseException(Status.BAD_REQUEST, "New password is same as old password");
}
if (!changePasswordRequest.getNewPassword().equals(changePasswordRequest.getConfirmPassword())) {
throw new BaseException(Status.BAD_REQUEST, "New password and confirm password is not match");
}
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
VisaTrackUserDetail userDetail = (VisaTrackUserDetail) authentication.getPrincipal();
User dbUser =
userRepository.findById(userDetail.getId()).orElseThrow(() -> new BaseException(Status.BAD_REQUEST,
"User is not exist"));
if (!checkPasswordIsMatch(dbUser.getUserPassword(), changePasswordRequest.getOldPassword())) {
throw new BaseException(Status.BAD_REQUEST, "Old password is not correct");
}
UserChangePassword userChangePassword = new UserChangePassword();
userChangePassword.setChanged(true);
userChangePassword.setUserId(user.getId());
userChangePassword.setOldPassword(encodePassword(changePasswordRequest.getOldPassword()));
userChangePassword.setUserId(dbUser.getId());
//使用数据库中的原始加密密码
userChangePassword.setOldPassword(dbUser.getUserPassword());
userChangePassword.setNewPassword(encodePassword(changePasswordRequest.getNewPassword()));
userChangePassword.setRequestDateChangePassword(LocalDateTime.now());
userChangePassword.setChangePasswordCode("");
@ -195,9 +231,9 @@ public class UserService implements UserDetailsService {
userChangePassword.setChangePasswordTime(LocalDateTime.now());
UserChangePassword savedUserChangePassword = userChangePasswordRepository.save(userChangePassword);
user.setUserPassword(userChangePassword.getNewPassword());
userRepository.save(user);
dbUser.setUserPassword(userChangePassword.getNewPassword());
userRepository.save(dbUser);
emailService.sendChangePasswordSuccessEmailToUser(user);
emailService.sendChangePasswordSuccessEmailToUser(dbUser);
}
}

View File

@ -0,0 +1,239 @@
<!DOCTYPE html>
<html>
<head>
<meta content="text/html; charset=UTF-8" http-equiv="Content-Type">
<title>[(${SiteName})] - Track Your Visa Status</title>
<style>
b {
font-family: 'Open Sans', sans-serif;
font-weight: 600;
}
body {
font-family: 'Open Sans', sans-serif;
font-weight: 400;
}
/* cyrillic-ext */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 400;
src: local('Open Sans Regular'), local('OpenSans-Regular'), url(https://fonts.gstatic.com/s/opensans/v15/K88pR3goAWT7BTt32Z01mxJtnKITppOI_IvcXXDNrsc.woff2) format('woff2');
unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F;
}
/* cyrillic */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 400;
src: local('Open Sans Regular'), local('OpenSans-Regular'), url(https://fonts.gstatic.com/s/opensans/v15/RjgO7rYTmqiVp7vzi-Q5URJtnKITppOI_IvcXXDNrsc.woff2) format('woff2');
unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* greek-ext */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 400;
src: local('Open Sans Regular'), local('OpenSans-Regular'), url(https://fonts.gstatic.com/s/opensans/v15/LWCjsQkB6EMdfHrEVqA1KRJtnKITppOI_IvcXXDNrsc.woff2) format('woff2');
unicode-range: U+1F00-1FFF;
}
/* greek */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 400;
src: local('Open Sans Regular'), local('OpenSans-Regular'), url(https://fonts.gstatic.com/s/opensans/v15/xozscpT2726on7jbcb_pAhJtnKITppOI_IvcXXDNrsc.woff2) format('woff2');
unicode-range: U+0370-03FF;
}
/* vietnamese */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 400;
src: local('Open Sans Regular'), local('OpenSans-Regular'), url(https://fonts.gstatic.com/s/opensans/v15/59ZRklaO5bWGqF5A9baEERJtnKITppOI_IvcXXDNrsc.woff2) format('woff2');
unicode-range: U+0102-0103, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 400;
src: local('Open Sans Regular'), local('OpenSans-Regular'), url(https://fonts.gstatic.com/s/opensans/v15/u-WUoqrET9fUeobQW7jkRRJtnKITppOI_IvcXXDNrsc.woff2) format('woff2');
unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 400;
src: local('Open Sans Regular'), local('OpenSans-Regular'), url(https://fonts.gstatic.com/s/opensans/v15/cJZKeOuBrn4kERxqtaUH3VtXRa8TVwTICgirnJhmVJw.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215;
}
/* cyrillic-ext */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 600;
src: local('Open Sans SemiBold'), local('OpenSans-SemiBold'), url(https://fonts.gstatic.com/s/opensans/v15/MTP_ySUJH_bn48VBG8sNSq-j2U0lmluP9RWlSytm3ho.woff2) format('woff2');
unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F;
}
/* cyrillic */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 600;
src: local('Open Sans SemiBold'), local('OpenSans-SemiBold'), url(https://fonts.gstatic.com/s/opensans/v15/MTP_ySUJH_bn48VBG8sNSpX5f-9o1vgP2EXwfjgl7AY.woff2) format('woff2');
unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* greek-ext */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 600;
src: local('Open Sans SemiBold'), local('OpenSans-SemiBold'), url(https://fonts.gstatic.com/s/opensans/v15/MTP_ySUJH_bn48VBG8sNShWV49_lSm1NYrwo-zkhivY.woff2) format('woff2');
unicode-range: U+1F00-1FFF;
}
/* greek */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 600;
src: local('Open Sans SemiBold'), local('OpenSans-SemiBold'), url(https://fonts.gstatic.com/s/opensans/v15/MTP_ySUJH_bn48VBG8sNSqaRobkAwv3vxw3jMhVENGA.woff2) format('woff2');
unicode-range: U+0370-03FF;
}
/* vietnamese */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 600;
src: local('Open Sans SemiBold'), local('OpenSans-SemiBold'), url(https://fonts.gstatic.com/s/opensans/v15/MTP_ySUJH_bn48VBG8sNSv8zf_FOSsgRmwsS7Aa9k2w.woff2) format('woff2');
unicode-range: U+0102-0103, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 600;
src: local('Open Sans SemiBold'), local('OpenSans-SemiBold'), url(https://fonts.gstatic.com/s/opensans/v15/MTP_ySUJH_bn48VBG8sNSj0LW-43aMEzIO6XUTLjad8.woff2) format('woff2');
unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 600;
src: local('Open Sans SemiBold'), local('OpenSans-SemiBold'), url(https://fonts.gstatic.com/s/opensans/v15/MTP_ySUJH_bn48VBG8sNSugdm0LZdjqr5-oayXSOefg.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215;
}
</style>
</head>
<body bgcolor="#F7F7F7" contenteditable="false" data-gr-c-s-loaded="true" style="margin:0px; padding:0px; color:#333;">
<table border="0" cellpadding="0" cellspacing="0" width="100%">
<tbody>
<tr style="background:#F7F7F7;">
<td>
<!-- main table stats-->
<!--[if gte mso 9]>
<table id="tableForOutlook" align="center">
<tr>
<td>
<![endif]-->
<div style="max-width:100%; margin:0 auto; padding: 10px;">
<table border="0" cellpadding="0" cellspacing="0" class="content"
style="width: 100%; border-collapse: separate; border-spacing: 0;">
<tbody>
<tr>
<td
style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; color: #333333; font-size: 15px; font-weight: 400; line-height: 1.4; padding: 15px 5px;">
<h1 align="center"
style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; color: #333333; font-size: 18px; font-weight: 400; line-height: 1.4; margin: 0; padding: 0;">
You (or someone else) has requested to reset your password.</h1>
<p>
This password reset request is valid for the next 24 hours. Don't worry you can always
ask for a new password using the following link
[(@{${SiteUrl}})]forgotuserpassword.action
</p>
<p>
If you did not initiate this change, please contact your administrator immediately.
</p>
</td>
</tr>
<tr>
<td
class="email-content-main mobile-expand last-row-padding"
>
<table border="0" cellpadding="0" cellspacing="0" class="aui-button-email-container"
style="border-collapse: collapse; mso-table-lspace: 0pt; mso-table-rspace: 0pt; color: #333333">
<tbody>
<tr>
<td class="aui-button-email"
style="padding: 0px; border-collapse: collapse; border-radius: 3px; padding: 5px; margin: 0; background: #3572b0; -moz-box-sizing: border-box; -webkit-font-smoothing: antialiased">
<a class="aui-button-email-link"
rel="noopener"
style="color: #3b73af; text-decoration: none; color: #ffffff; font-weight: bold; padding: 6px; font-size: 14px; line-height: 1.429; font-family: Arial, sans-serif"
target="_blank"
th:href="@{https://www.usvisatrack.com/user/forgotPassword(id=${UserId},code=${ChangePasswordCode})}">Change password</a></td>
</tr>
</tbody>
</table>
</td>
</tr>
<tr>
<td
style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; color: #333333; font-size: 15px; font-weight: 400; line-height: 1.4; padding: 15px 5px;">
<p>
Here are the details of your account:
</p>
<p>
Username [(@{${UserName}})]
</p>
<p>
Email [(@{${UserEmail}})]
</p>
</td>
</tr>
</tbody>
</table>
</div>
<!--[if gte mso 9]>
</td></tr></table>
<![endif]-->
<!-- main table ends-->
</td>
</tr>
</tbody>
</table>
</body>
</html>

View File

@ -0,0 +1,189 @@
<!DOCTYPE html>
<html>
<head>
<meta content="text/html; charset=UTF-8" http-equiv="Content-Type">
<title>[(${SiteName})] - Track Your Visa Status</title>
<style>
b {
font-family: 'Open Sans', sans-serif;
font-weight: 600;
}
body {
font-family: 'Open Sans', sans-serif;
font-weight: 400;
}
/* cyrillic-ext */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 400;
src: local('Open Sans Regular'), local('OpenSans-Regular'), url(https://fonts.gstatic.com/s/opensans/v15/K88pR3goAWT7BTt32Z01mxJtnKITppOI_IvcXXDNrsc.woff2) format('woff2');
unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F;
}
/* cyrillic */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 400;
src: local('Open Sans Regular'), local('OpenSans-Regular'), url(https://fonts.gstatic.com/s/opensans/v15/RjgO7rYTmqiVp7vzi-Q5URJtnKITppOI_IvcXXDNrsc.woff2) format('woff2');
unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* greek-ext */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 400;
src: local('Open Sans Regular'), local('OpenSans-Regular'), url(https://fonts.gstatic.com/s/opensans/v15/LWCjsQkB6EMdfHrEVqA1KRJtnKITppOI_IvcXXDNrsc.woff2) format('woff2');
unicode-range: U+1F00-1FFF;
}
/* greek */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 400;
src: local('Open Sans Regular'), local('OpenSans-Regular'), url(https://fonts.gstatic.com/s/opensans/v15/xozscpT2726on7jbcb_pAhJtnKITppOI_IvcXXDNrsc.woff2) format('woff2');
unicode-range: U+0370-03FF;
}
/* vietnamese */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 400;
src: local('Open Sans Regular'), local('OpenSans-Regular'), url(https://fonts.gstatic.com/s/opensans/v15/59ZRklaO5bWGqF5A9baEERJtnKITppOI_IvcXXDNrsc.woff2) format('woff2');
unicode-range: U+0102-0103, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 400;
src: local('Open Sans Regular'), local('OpenSans-Regular'), url(https://fonts.gstatic.com/s/opensans/v15/u-WUoqrET9fUeobQW7jkRRJtnKITppOI_IvcXXDNrsc.woff2) format('woff2');
unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 400;
src: local('Open Sans Regular'), local('OpenSans-Regular'), url(https://fonts.gstatic.com/s/opensans/v15/cJZKeOuBrn4kERxqtaUH3VtXRa8TVwTICgirnJhmVJw.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215;
}
/* cyrillic-ext */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 600;
src: local('Open Sans SemiBold'), local('OpenSans-SemiBold'), url(https://fonts.gstatic.com/s/opensans/v15/MTP_ySUJH_bn48VBG8sNSq-j2U0lmluP9RWlSytm3ho.woff2) format('woff2');
unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F;
}
/* cyrillic */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 600;
src: local('Open Sans SemiBold'), local('OpenSans-SemiBold'), url(https://fonts.gstatic.com/s/opensans/v15/MTP_ySUJH_bn48VBG8sNSpX5f-9o1vgP2EXwfjgl7AY.woff2) format('woff2');
unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* greek-ext */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 600;
src: local('Open Sans SemiBold'), local('OpenSans-SemiBold'), url(https://fonts.gstatic.com/s/opensans/v15/MTP_ySUJH_bn48VBG8sNShWV49_lSm1NYrwo-zkhivY.woff2) format('woff2');
unicode-range: U+1F00-1FFF;
}
/* greek */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 600;
src: local('Open Sans SemiBold'), local('OpenSans-SemiBold'), url(https://fonts.gstatic.com/s/opensans/v15/MTP_ySUJH_bn48VBG8sNSqaRobkAwv3vxw3jMhVENGA.woff2) format('woff2');
unicode-range: U+0370-03FF;
}
/* vietnamese */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 600;
src: local('Open Sans SemiBold'), local('OpenSans-SemiBold'), url(https://fonts.gstatic.com/s/opensans/v15/MTP_ySUJH_bn48VBG8sNSv8zf_FOSsgRmwsS7Aa9k2w.woff2) format('woff2');
unicode-range: U+0102-0103, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 600;
src: local('Open Sans SemiBold'), local('OpenSans-SemiBold'), url(https://fonts.gstatic.com/s/opensans/v15/MTP_ySUJH_bn48VBG8sNSj0LW-43aMEzIO6XUTLjad8.woff2) format('woff2');
unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 600;
src: local('Open Sans SemiBold'), local('OpenSans-SemiBold'), url(https://fonts.gstatic.com/s/opensans/v15/MTP_ySUJH_bn48VBG8sNSugdm0LZdjqr5-oayXSOefg.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215;
}
</style>
</head>
<body bgcolor="#F7F7F7" contenteditable="false" data-gr-c-s-loaded="true" style="margin:0px; padding:0px; color:#333;">
<table border="0" cellpadding="0" cellspacing="0" width="100%">
<tbody>
<tr style="background:#F7F7F7;">
<td>
<!-- main table stats-->
<!--[if gte mso 9]>
<table id="tableForOutlook" align="center">
<tr>
<td>
<![endif]-->
<div style="max-width:100%; margin:0 auto; padding: 10px;">
<table border="0" cellpadding="0" cellspacing="0" class="content"
style="width: 100%; border-collapse: separate; border-spacing: 0;">
<tbody>
<tr>
<td align="center"
style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; color: #333333; font-size: 15px; font-weight: 400; line-height: 1.4; padding: 15px 5px;">
<h1 align="center"
style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; color: #333333; font-size: 18px; font-weight: 400; line-height: 1.4; margin: 0; padding: 0;">
Hello, [(${UserName})]!</h1>
<p>
The password for your [(${SiteName})] account on <a rel="noopener"
style="color: #3777b0; text-decoration: none;"
target="_blank"
th:href="@{${SiteUrl}}">[(@{${SiteUrl}})]</a>
has successfully been changed.
</p>
<p>
If you did not initiate this change, please contact your administrator immediately.
</p>
</td>
</tr>
</tbody>
</table>
</div>
<!--[if gte mso 9]>
</td></tr></table>
<![endif]-->
<!-- main table ends-->
</td>
</tr>
</tbody>
</table>
</body>
</html>