Merge branch 'dev' into '21-test-endpoints'
# Conflicts: # build.gradle
This commit is contained in:
commit
ca63f0d7df
10 changed files with 316 additions and 35 deletions
|
@ -26,7 +26,8 @@ dependencies {
|
|||
implementation 'com.google.code.gson:gson'
|
||||
compile 'io.springfox:springfox-swagger2:2.9.2'
|
||||
compile 'io.springfox:springfox-swagger-ui:2.9.2'
|
||||
|
||||
compile "org.springframework.boot:spring-boot-configuration-processor"
|
||||
|
||||
implementation('org.springframework.boot:spring-boot-starter-web') {
|
||||
exclude group: 'org.springframework.boot', module: 'spring-boot-starter-json'
|
||||
}
|
||||
|
|
|
@ -0,0 +1,92 @@
|
|||
package ch.usi.inf.sa4.sanmarinoes.smarthut.config;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
/**
|
||||
* Class to interface with `email.*` properties in application.properties. This properties are used
|
||||
* for generating the email to send on password reset or registration
|
||||
*
|
||||
* @see ch.usi.inf.sa4.sanmarinoes.smarthut.controller.UserAccountController
|
||||
*/
|
||||
@Component
|
||||
@Validated
|
||||
@EnableConfigurationProperties
|
||||
@ConfigurationProperties(prefix = "email")
|
||||
public class EmailConfigurationService {
|
||||
|
||||
/** The email subject for a registration email */
|
||||
@NotNull private String registrationSubject;
|
||||
|
||||
/** The text in the email body preceding the confirmation URL for a registration email */
|
||||
@NotNull private String registration;
|
||||
|
||||
/**
|
||||
* The URL to follow for registration email confirmation. Has to end with the start of a query
|
||||
* parameter
|
||||
*/
|
||||
@NotNull private String registrationPath;
|
||||
|
||||
/** The email subject for a reset password email */
|
||||
@NotNull private String resetPasswordSubject;
|
||||
|
||||
/** The text in the email body preceding the confirmation URL for a reset password email */
|
||||
@NotNull private String resetPassword;
|
||||
|
||||
/**
|
||||
* The URL to follow for password reset email confirmation. Has to end with the start of a query
|
||||
* parameter
|
||||
*/
|
||||
@NotNull private String resetPasswordPath;
|
||||
|
||||
public String getRegistrationSubject() {
|
||||
return registrationSubject;
|
||||
}
|
||||
|
||||
public void setRegistrationSubject(String registrationSubject) {
|
||||
this.registrationSubject = registrationSubject;
|
||||
}
|
||||
|
||||
public String getRegistration() {
|
||||
return registration;
|
||||
}
|
||||
|
||||
public void setRegistration(String registration) {
|
||||
this.registration = registration;
|
||||
}
|
||||
|
||||
public String getRegistrationPath() {
|
||||
return registrationPath;
|
||||
}
|
||||
|
||||
public void setRegistrationPath(String registrationPath) {
|
||||
this.registrationPath = registrationPath;
|
||||
}
|
||||
|
||||
public String getResetPasswordSubject() {
|
||||
return resetPasswordSubject;
|
||||
}
|
||||
|
||||
public void setResetPasswordSubject(String resetPasswordSubject) {
|
||||
this.resetPasswordSubject = resetPasswordSubject;
|
||||
}
|
||||
|
||||
public String getResetPassword() {
|
||||
return resetPassword;
|
||||
}
|
||||
|
||||
public void setResetPassword(String resetPassword) {
|
||||
this.resetPassword = resetPassword;
|
||||
}
|
||||
|
||||
public String getResetPasswordPath() {
|
||||
return resetPasswordPath;
|
||||
}
|
||||
|
||||
public void setResetPasswordPath(String resetPasswordPath) {
|
||||
this.resetPasswordPath = resetPasswordPath;
|
||||
}
|
||||
}
|
|
@ -8,6 +8,7 @@ import org.springframework.context.annotation.Bean;
|
|||
import org.springframework.context.annotation.ComponentScan;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import springfox.documentation.builders.ApiInfoBuilder;
|
||||
import springfox.documentation.builders.PathSelectors;
|
||||
import springfox.documentation.builders.RequestHandlerSelectors;
|
||||
import springfox.documentation.service.ApiInfo;
|
||||
import springfox.documentation.service.ApiKey;
|
||||
|
@ -67,10 +68,7 @@ public class SpringFoxConfig {
|
|||
* @return A predicate that tests whether a path must be included or not
|
||||
*/
|
||||
private Predicate<String> paths() {
|
||||
return regexPredicate("/auth.*")
|
||||
.or(regexPredicate("/room.*"))
|
||||
.or(regexPredicate("/register.*"))
|
||||
.or(regexPredicate("/"));
|
||||
return PathSelectors.any()::apply;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -52,10 +52,11 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
|
|||
.authorizeRequests()
|
||||
.antMatchers(
|
||||
"/auth/login",
|
||||
"/auth/register",
|
||||
"/swagger-ui.html",
|
||||
"/register",
|
||||
"/register/confirm-account",
|
||||
"/register/init-reset-password",
|
||||
"/register/reset-password",
|
||||
"/v2/api-docs",
|
||||
"/webjars/**",
|
||||
"/swagger-resources/**",
|
||||
|
|
|
@ -1,9 +1,13 @@
|
|||
package ch.usi.inf.sa4.sanmarinoes.smarthut.controller;
|
||||
|
||||
import ch.usi.inf.sa4.sanmarinoes.smarthut.config.EmailConfigurationService;
|
||||
import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.InitPasswordResetRequest;
|
||||
import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.OkResponse;
|
||||
import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.PasswordResetRequest;
|
||||
import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.UserRegistrationRequest;
|
||||
import ch.usi.inf.sa4.sanmarinoes.smarthut.error.DuplicateRegistrationException;
|
||||
import ch.usi.inf.sa4.sanmarinoes.smarthut.error.EmailTokenNotFoundException;
|
||||
import ch.usi.inf.sa4.sanmarinoes.smarthut.error.UserNotFoundException;
|
||||
import ch.usi.inf.sa4.sanmarinoes.smarthut.models.ConfirmationToken;
|
||||
import ch.usi.inf.sa4.sanmarinoes.smarthut.models.ConfirmationTokenRepository;
|
||||
import ch.usi.inf.sa4.sanmarinoes.smarthut.models.User;
|
||||
|
@ -11,30 +15,66 @@ import ch.usi.inf.sa4.sanmarinoes.smarthut.models.UserRepository;
|
|||
import ch.usi.inf.sa4.sanmarinoes.smarthut.service.EmailSenderService;
|
||||
import javax.validation.Valid;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.mail.SimpleMailMessage;
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
/** Unauthenticated set of endpoints to handle registration and password reset */
|
||||
@RestController
|
||||
@EnableAutoConfiguration
|
||||
@RequestMapping("/register")
|
||||
public class UserAccountController {
|
||||
|
||||
@Autowired private UserRepository userRepository;
|
||||
private final UserRepository userRepository;
|
||||
|
||||
@Autowired private ConfirmationTokenRepository confirmationTokenRepository;
|
||||
private final ConfirmationTokenRepository confirmationTokenRepository;
|
||||
|
||||
@Autowired private EmailSenderService emailSenderService;
|
||||
private final EmailSenderService emailSenderService;
|
||||
|
||||
@Autowired private BCryptPasswordEncoder encoder;
|
||||
private final BCryptPasswordEncoder encoder;
|
||||
|
||||
private final EmailConfigurationService emailConfig;
|
||||
|
||||
public UserAccountController(
|
||||
UserRepository userRepository,
|
||||
ConfirmationTokenRepository confirmationTokenRepository,
|
||||
EmailSenderService emailSenderService,
|
||||
BCryptPasswordEncoder encoder,
|
||||
EmailConfigurationService emailConfig) {
|
||||
this.userRepository = userRepository;
|
||||
this.confirmationTokenRepository = confirmationTokenRepository;
|
||||
this.emailSenderService = emailSenderService;
|
||||
this.encoder = encoder;
|
||||
this.emailConfig = emailConfig;
|
||||
}
|
||||
|
||||
private void sendEmail(String email, ConfirmationToken token, boolean isRegistration) {
|
||||
SimpleMailMessage mailMessage = new SimpleMailMessage();
|
||||
mailMessage.setTo(email);
|
||||
mailMessage.setSubject(
|
||||
isRegistration
|
||||
? emailConfig.getRegistrationSubject()
|
||||
: emailConfig.getResetPasswordSubject());
|
||||
mailMessage.setFrom("smarthut.sm@gmail.com");
|
||||
mailMessage.setText(
|
||||
(isRegistration ? emailConfig.getRegistration() : emailConfig.getResetPassword())
|
||||
+ " "
|
||||
+ (isRegistration
|
||||
? emailConfig.getRegistrationPath()
|
||||
: emailConfig.getResetPasswordPath())
|
||||
+ token.getConfirmationToken());
|
||||
|
||||
emailSenderService.sendEmail(mailMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unauthenticated endpoint to call to send a password reset email
|
||||
*
|
||||
* @param registrationData registration data of the new user
|
||||
* @return success
|
||||
* @throws DuplicateRegistrationException if a user exists with same email or username
|
||||
*/
|
||||
@PostMapping
|
||||
public OkResponse registerUser(@Valid @RequestBody UserRegistrationRequest registrationData)
|
||||
throws DuplicateRegistrationException {
|
||||
|
@ -60,35 +100,101 @@ public class UserAccountController {
|
|||
toSave.setEmail(registrationData.getEmail());
|
||||
userRepository.save(toSave);
|
||||
|
||||
ConfirmationToken confirmationToken = new ConfirmationToken(toSave);
|
||||
ConfirmationToken token;
|
||||
do {
|
||||
token = new ConfirmationToken(toSave);
|
||||
} while (confirmationTokenRepository.findByConfirmationToken(
|
||||
token.getConfirmationToken())
|
||||
!= null);
|
||||
|
||||
confirmationTokenRepository.save(confirmationToken);
|
||||
confirmationTokenRepository.save(token);
|
||||
|
||||
SimpleMailMessage mailMessage = new SimpleMailMessage();
|
||||
mailMessage.setTo(registrationData.getEmail());
|
||||
mailMessage.setSubject("Complete Registration!");
|
||||
mailMessage.setFrom("smarthut.sm@gmail.com");
|
||||
mailMessage.setText(
|
||||
"To confirm your account, please click here : "
|
||||
+ "http://localhost:8080/register/confirm-account?token="
|
||||
+ confirmationToken.getConfirmationToken());
|
||||
|
||||
emailSenderService.sendEmail(mailMessage);
|
||||
sendEmail(toSave.getEmail(), token, true);
|
||||
|
||||
return new OkResponse();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unauthenticated endpoint to call to send a password reset email
|
||||
*
|
||||
* @param resetRequest a JSON object containing the email of the user to reset
|
||||
* @return success
|
||||
* @throws UserNotFoundException if given email does not belong to any user
|
||||
*/
|
||||
@PostMapping("/init-reset-password")
|
||||
public OkResponse initResetPassword(@Valid @RequestBody InitPasswordResetRequest resetRequest)
|
||||
throws UserNotFoundException {
|
||||
final User toReset = userRepository.findByEmailIgnoreCase(resetRequest.getEmail());
|
||||
|
||||
// Check if an User with the same email already exists
|
||||
if (toReset == null) {
|
||||
throw new UserNotFoundException();
|
||||
}
|
||||
|
||||
ConfirmationToken token;
|
||||
do {
|
||||
token = new ConfirmationToken(toReset);
|
||||
token.setResetPassword(true);
|
||||
} while (confirmationTokenRepository.findByConfirmationToken(token.getConfirmationToken())
|
||||
!= null);
|
||||
|
||||
// Delete existing email password reset tokens
|
||||
confirmationTokenRepository.deleteByUserAndResetPassword(toReset, true);
|
||||
|
||||
// Save new token
|
||||
confirmationTokenRepository.save(token);
|
||||
|
||||
sendEmail(toReset.getEmail(), token, false);
|
||||
|
||||
return new OkResponse();
|
||||
}
|
||||
|
||||
/**
|
||||
* Unauthenticated endpoint to call with token sent by email to reset password
|
||||
*
|
||||
* @param resetRequest the token given via email and the new password
|
||||
* @return success
|
||||
* @throws EmailTokenNotFoundException if given token is not a valid token for password reset
|
||||
*/
|
||||
@PutMapping("/reset-password")
|
||||
public OkResponse resetPassword(@Valid @RequestBody PasswordResetRequest resetRequest)
|
||||
throws EmailTokenNotFoundException {
|
||||
final ConfirmationToken token =
|
||||
confirmationTokenRepository.findByConfirmationToken(
|
||||
resetRequest.getConfirmationToken());
|
||||
|
||||
if (token == null || !token.getResetPassword()) {
|
||||
throw new EmailTokenNotFoundException();
|
||||
}
|
||||
|
||||
final User user = token.getUser();
|
||||
user.setPassword(encoder.encode(resetRequest.getPassword()));
|
||||
userRepository.save(user);
|
||||
|
||||
// Delete token to prevent further password changes
|
||||
confirmationTokenRepository.delete(token);
|
||||
|
||||
return new OkResponse();
|
||||
}
|
||||
|
||||
/**
|
||||
* Unauthenticated endpoint to call with token sent by email to enable user
|
||||
*
|
||||
* @param confirmationToken the token given via email
|
||||
* @return success
|
||||
* @throws EmailTokenNotFoundException if given token is not a valid token for email
|
||||
* confirmation
|
||||
*/
|
||||
@GetMapping(value = "/confirm-account")
|
||||
public OkResponse confirmUserAccount(@RequestParam("token") @NotNull String confirmationToken)
|
||||
throws EmailTokenNotFoundException {
|
||||
final ConfirmationToken token =
|
||||
confirmationTokenRepository.findByConfirmationToken(confirmationToken);
|
||||
|
||||
if (token != null) {
|
||||
final User user = userRepository.findByEmailIgnoreCase(token.getUser().getEmail());
|
||||
user.setEnabled(true);
|
||||
userRepository.save(user);
|
||||
if (token != null && !token.getResetPassword()) {
|
||||
token.getUser().setEnabled(true);
|
||||
userRepository.save(token.getUser());
|
||||
// TODO: redirect to frontend
|
||||
return new OkResponse();
|
||||
} else {
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
package ch.usi.inf.sa4.sanmarinoes.smarthut.dto;
|
||||
|
||||
import javax.validation.constraints.Email;
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
import javax.validation.constraints.Pattern;
|
||||
|
||||
/** DTO for password reset request */
|
||||
public class InitPasswordResetRequest {
|
||||
/**
|
||||
* The user's email (validated according to criteria used in <code>>input type="email"<>
|
||||
* </code>, technically not RFC 5322 compliant
|
||||
*/
|
||||
@NotEmpty(message = "Please provide an email")
|
||||
@Email(message = "Please provide a valid email address")
|
||||
@Pattern(regexp = ".+@.+\\..+", message = "Please provide a valid email address")
|
||||
private String email;
|
||||
|
||||
public String getEmail() {
|
||||
return email;
|
||||
}
|
||||
|
||||
public void setEmail(String email) {
|
||||
this.email = email;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
package ch.usi.inf.sa4.sanmarinoes.smarthut.dto;
|
||||
|
||||
import javax.validation.constraints.*;
|
||||
|
||||
/** DTO for password reset request */
|
||||
public class PasswordResetRequest {
|
||||
|
||||
@NotNull private String confirmationToken;
|
||||
|
||||
/** A properly salted way to store the password */
|
||||
@NotNull
|
||||
@NotEmpty(message = "Please provide a password")
|
||||
@Size(
|
||||
min = 6,
|
||||
max = 255,
|
||||
message = "Your password should be at least 6 characters long and up to 255 chars long")
|
||||
private String password;
|
||||
|
||||
public String getConfirmationToken() {
|
||||
return confirmationToken;
|
||||
}
|
||||
|
||||
public void setConfirmationToken(String confirmationToken) {
|
||||
this.confirmationToken = confirmationToken;
|
||||
}
|
||||
|
||||
public String getPassword() {
|
||||
return password;
|
||||
}
|
||||
|
||||
public void setPassword(String password) {
|
||||
this.password = password;
|
||||
}
|
||||
}
|
|
@ -21,7 +21,7 @@ public class ConfirmationToken {
|
|||
@Column(name = "id", updatable = false, nullable = false)
|
||||
private Long id;
|
||||
|
||||
@Column(name = "confirmation_token")
|
||||
@Column(name = "confirmation_token", unique = true)
|
||||
private String confirmationToken;
|
||||
|
||||
@Temporal(TemporalType.TIMESTAMP)
|
||||
|
@ -31,10 +31,14 @@ public class ConfirmationToken {
|
|||
@JoinColumn(nullable = false, name = "user_id")
|
||||
private User user;
|
||||
|
||||
@Column(nullable = false)
|
||||
private Boolean resetPassword;
|
||||
|
||||
public ConfirmationToken(User user) {
|
||||
this.user = user;
|
||||
createdDate = new Date();
|
||||
confirmationToken = UUID.randomUUID().toString();
|
||||
resetPassword = false;
|
||||
}
|
||||
|
||||
/** Constructor for hibernate reflective stuff things whatever */
|
||||
|
@ -71,4 +75,12 @@ public class ConfirmationToken {
|
|||
public void setUser(User user) {
|
||||
this.user = user;
|
||||
}
|
||||
|
||||
public Boolean getResetPassword() {
|
||||
return resetPassword;
|
||||
}
|
||||
|
||||
public void setResetPassword(Boolean resetPassword) {
|
||||
this.resetPassword = resetPassword;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,11 @@
|
|||
package ch.usi.inf.sa4.sanmarinoes.smarthut.models;
|
||||
|
||||
import javax.transaction.Transactional;
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
|
||||
public interface ConfirmationTokenRepository extends CrudRepository<ConfirmationToken, String> {
|
||||
ConfirmationToken findByConfirmationToken(String confirmationToken);
|
||||
|
||||
@Transactional
|
||||
void deleteByUserAndResetPassword(User user, boolean resetPassword);
|
||||
}
|
||||
|
|
|
@ -22,4 +22,12 @@ spring.mail.properties.mail.smtp.starttls.required=true
|
|||
spring.mail.properties.mail.smtp.auth=true
|
||||
spring.mail.properties.mail.smtp.connectiontimeout=5000
|
||||
spring.mail.properties.mail.smtp.timeout=5000
|
||||
spring.mail.properties.mail.smtp.writetimeout=5000
|
||||
spring.mail.properties.mail.smtp.writetimeout=5000
|
||||
|
||||
email.registrationSubject=Complete your SmartHut.sm registration
|
||||
email.registration=To confirm your registration, please click here:
|
||||
email.registrationPath=http://localhost:8080/register/confirm-account?token=
|
||||
|
||||
email.resetpasswordSubject=SmartHut.sm password reset
|
||||
email.resetpassword=To reset your password, please click here:
|
||||
email.resetpasswordPath=http://localhost:3000/password-reset?token=
|
Loading…
Reference in a new issue