Merge branch 'email-feature' into 'dev'
Email feature See merge request sa4-2020/the-sanmarinoes/backend!15
This commit is contained in:
commit
44d17c1cdd
15 changed files with 341 additions and 16 deletions
|
@ -18,6 +18,7 @@ dependencies {
|
|||
implementation 'org.springframework.boot:spring-boot-starter'
|
||||
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
|
||||
implementation 'org.springframework.boot:spring-boot-starter-security'
|
||||
implementation 'org.springframework.boot:spring-boot-starter-mail'
|
||||
implementation 'io.jsonwebtoken:jjwt:0.9.1'
|
||||
implementation 'org.springframework.security:spring-security-web'
|
||||
implementation 'org.postgresql:postgresql'
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
package ch.usi.inf.sa4.sanmarinoes.smarthut.service;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.mail.SimpleMailMessage;
|
||||
import org.springframework.mail.javamail.JavaMailSender;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service("emailSenderService")
|
||||
public class EmailSenderService {
|
||||
|
||||
private JavaMailSender javaMailSender;
|
||||
|
||||
@Autowired
|
||||
public EmailSenderService(JavaMailSender javaMailSender) {
|
||||
this.javaMailSender = javaMailSender;
|
||||
}
|
||||
|
||||
@Async
|
||||
public void sendEmail(SimpleMailMessage email) {
|
||||
javaMailSender.send(email);
|
||||
}
|
||||
}
|
|
@ -57,7 +57,7 @@ public class SpringFoxConfig {
|
|||
* @return A predicate that tests whether a path must be included or not
|
||||
*/
|
||||
private Predicate<String> paths() {
|
||||
return regex("/auth.*")::apply;
|
||||
return ((Predicate<String>) regex("/auth.*")::apply).or(regex("/register.*")::apply);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -54,6 +54,8 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
|
|||
"/auth/login",
|
||||
"/auth/register",
|
||||
"/swagger-ui.html",
|
||||
"/register",
|
||||
"/register/confirm-account",
|
||||
"/v2/api-docs",
|
||||
"/webjars/**",
|
||||
"/swagger-resources/**",
|
||||
|
|
|
@ -29,8 +29,6 @@ public class AuthenticationController {
|
|||
|
||||
private final JWTUserDetailsService userDetailsService;
|
||||
|
||||
private final UserRepository users;
|
||||
|
||||
private BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
|
||||
|
||||
public AuthenticationController(
|
||||
|
@ -43,7 +41,6 @@ public class AuthenticationController {
|
|||
this.userRepository = userRepository;
|
||||
this.jwtTokenUtil = jwtTokenUtil;
|
||||
this.userDetailsService = userDetailsService;
|
||||
this.users = users;
|
||||
}
|
||||
|
||||
@PostMapping("/login")
|
||||
|
@ -55,13 +52,6 @@ public class AuthenticationController {
|
|||
return new JWTResponse(token);
|
||||
}
|
||||
|
||||
@PostMapping("/register")
|
||||
public User register(@Valid @RequestBody User user) {
|
||||
user.setPassword(encoder.encode(user.getPassword()));
|
||||
users.save(user);
|
||||
return user;
|
||||
}
|
||||
|
||||
@Authorization(value = "Bearer")
|
||||
@PatchMapping("/update")
|
||||
public User update(
|
||||
|
|
|
@ -0,0 +1,98 @@
|
|||
package ch.usi.inf.sa4.sanmarinoes.smarthut.controller;
|
||||
|
||||
import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.OkResponse;
|
||||
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.models.ConfirmationToken;
|
||||
import ch.usi.inf.sa4.sanmarinoes.smarthut.models.ConfirmationTokenRepository;
|
||||
import ch.usi.inf.sa4.sanmarinoes.smarthut.models.User;
|
||||
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;
|
||||
|
||||
@RestController
|
||||
@EnableAutoConfiguration
|
||||
@RequestMapping("/register")
|
||||
public class UserAccountController {
|
||||
|
||||
@Autowired private UserRepository userRepository;
|
||||
|
||||
@Autowired private ConfirmationTokenRepository confirmationTokenRepository;
|
||||
|
||||
@Autowired private EmailSenderService emailSenderService;
|
||||
|
||||
@Autowired private BCryptPasswordEncoder encoder;
|
||||
|
||||
@PostMapping
|
||||
public OkResponse registerUser(@Valid @RequestBody UserRegistrationRequest registrationData)
|
||||
throws DuplicateRegistrationException {
|
||||
final User existingEmailUser =
|
||||
userRepository.findByEmailIgnoreCase(registrationData.getEmail());
|
||||
final User existingUsernameUser =
|
||||
userRepository.findByUsername(registrationData.getUsername());
|
||||
|
||||
// Check if an User with the same email already exists
|
||||
if (existingEmailUser != null || existingUsernameUser != null) {
|
||||
throw new DuplicateRegistrationException();
|
||||
} else {
|
||||
final User toSave = new User();
|
||||
// disable the user (it will be enabled on email confiration)
|
||||
toSave.setEnabled(false);
|
||||
|
||||
// encode user's password
|
||||
toSave.setPassword(encoder.encode(registrationData.getPassword()));
|
||||
|
||||
// set other fields
|
||||
toSave.setName(registrationData.getName());
|
||||
toSave.setUsername(registrationData.getUsername());
|
||||
toSave.setEmail(registrationData.getEmail());
|
||||
userRepository.save(toSave);
|
||||
|
||||
ConfirmationToken confirmationToken = new ConfirmationToken(toSave);
|
||||
|
||||
confirmationTokenRepository.save(confirmationToken);
|
||||
|
||||
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);
|
||||
|
||||
return new OkResponse();
|
||||
}
|
||||
}
|
||||
|
||||
@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);
|
||||
// TODO: redirect to frontend
|
||||
return new OkResponse();
|
||||
} else {
|
||||
throw new EmailTokenNotFoundException();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
package ch.usi.inf.sa4.sanmarinoes.smarthut.dto;
|
||||
|
||||
/** A dummy DTO to return when there is no data to return */
|
||||
public class OkResponse {
|
||||
private boolean success = true;
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
package ch.usi.inf.sa4.sanmarinoes.smarthut.dto;
|
||||
|
||||
import javax.validation.constraints.*;
|
||||
|
||||
public class UserRegistrationRequest {
|
||||
|
||||
/** The full name of the user */
|
||||
@NotNull
|
||||
@NotEmpty(message = "Please provide a full name")
|
||||
private String name;
|
||||
|
||||
/** The full name of the user */
|
||||
@NotNull
|
||||
@NotEmpty(message = "Please provide a username")
|
||||
private String username;
|
||||
|
||||
/** 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;
|
||||
|
||||
/**
|
||||
* The user's email (validated according to criteria used in <code>>input type="email"<>
|
||||
* </code>, technically not RFC 5322 compliant
|
||||
*/
|
||||
@NotNull
|
||||
@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 getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
public void setUsername(String username) {
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
public String getPassword() {
|
||||
return password;
|
||||
}
|
||||
|
||||
public void setPassword(String password) {
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
public String getEmail() {
|
||||
return email;
|
||||
}
|
||||
|
||||
public void setEmail(String email) {
|
||||
this.email = email;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
package ch.usi.inf.sa4.sanmarinoes.smarthut.error;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
|
||||
@ResponseStatus(code = HttpStatus.BAD_REQUEST)
|
||||
public class DuplicateRegistrationException extends Exception {
|
||||
public DuplicateRegistrationException() {
|
||||
super("Email or username already belonging to another user");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
package ch.usi.inf.sa4.sanmarinoes.smarthut.error;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
|
||||
@ResponseStatus(code = HttpStatus.BAD_REQUEST)
|
||||
public class EmailTokenNotFoundException extends Exception {
|
||||
public EmailTokenNotFoundException() {
|
||||
super("Email verification token not found in DB");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
package ch.usi.inf.sa4.sanmarinoes.smarthut.models;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.UUID;
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.FetchType;
|
||||
import javax.persistence.GeneratedValue;
|
||||
import javax.persistence.GenerationType;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.JoinColumn;
|
||||
import javax.persistence.OneToOne;
|
||||
import javax.persistence.Temporal;
|
||||
import javax.persistence.TemporalType;
|
||||
|
||||
@Entity
|
||||
public class ConfirmationToken {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.AUTO)
|
||||
@Column(name = "id", updatable = false, nullable = false)
|
||||
private Long id;
|
||||
|
||||
@Column(name = "confirmation_token")
|
||||
private String confirmationToken;
|
||||
|
||||
@Temporal(TemporalType.TIMESTAMP)
|
||||
private Date createdDate;
|
||||
|
||||
@OneToOne(targetEntity = User.class, fetch = FetchType.EAGER)
|
||||
@JoinColumn(nullable = false, name = "user_id")
|
||||
private User user;
|
||||
|
||||
public ConfirmationToken(User user) {
|
||||
this.user = user;
|
||||
createdDate = new Date();
|
||||
confirmationToken = UUID.randomUUID().toString();
|
||||
}
|
||||
|
||||
/** Constructor for hibernate reflective stuff things whatever */
|
||||
public ConfirmationToken() {}
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getConfirmationToken() {
|
||||
return confirmationToken;
|
||||
}
|
||||
|
||||
public Date getCreatedDate() {
|
||||
return createdDate;
|
||||
}
|
||||
|
||||
public User getUser() {
|
||||
return user;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public void setConfirmationToken(String confirmationToken) {
|
||||
this.confirmationToken = confirmationToken;
|
||||
}
|
||||
|
||||
public void setCreatedDate(Date createdDate) {
|
||||
this.createdDate = createdDate;
|
||||
}
|
||||
|
||||
public void setUser(User user) {
|
||||
this.user = user;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
package ch.usi.inf.sa4.sanmarinoes.smarthut.models;
|
||||
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
|
||||
public interface ConfirmationTokenRepository extends CrudRepository<ConfirmationToken, String> {
|
||||
ConfirmationToken findByConfirmationToken(String confirmationToken);
|
||||
}
|
|
@ -3,7 +3,11 @@ package ch.usi.inf.sa4.sanmarinoes.smarthut.models;
|
|||
import io.swagger.annotations.ApiModelProperty;
|
||||
import java.util.Set;
|
||||
import javax.persistence.*;
|
||||
import javax.validation.constraints.*;
|
||||
import javax.validation.constraints.Email;
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import javax.validation.constraints.Pattern;
|
||||
import javax.validation.constraints.Size;
|
||||
|
||||
/** A user of the Smarthut application */
|
||||
@Entity(name = "smarthutuser")
|
||||
|
@ -34,8 +38,7 @@ public class User {
|
|||
@Size(
|
||||
min = 6,
|
||||
max = 255,
|
||||
message =
|
||||
"Your password should be at least 6 characters long and at most 255 chars long")
|
||||
message = "Your password should be at least 6 characters long and up to 255 chars long")
|
||||
private String password;
|
||||
|
||||
/**
|
||||
|
@ -54,6 +57,10 @@ public class User {
|
|||
@ApiModelProperty(hidden = true)
|
||||
private Set<Room> rooms;
|
||||
|
||||
@Column(nullable = false)
|
||||
@ApiModelProperty(hidden = true)
|
||||
private Boolean isEnabled = false;
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
@ -98,6 +105,14 @@ public class User {
|
|||
return rooms;
|
||||
}
|
||||
|
||||
public Boolean getEnabled() {
|
||||
return isEnabled;
|
||||
}
|
||||
|
||||
public void setEnabled(Boolean enabled) {
|
||||
isEnabled = enabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "User{"
|
||||
|
@ -106,11 +121,17 @@ public class User {
|
|||
+ ", name='"
|
||||
+ name
|
||||
+ '\''
|
||||
+ ", username='"
|
||||
+ username
|
||||
+ '\''
|
||||
+ ", password='"
|
||||
+ password
|
||||
+ '\''
|
||||
+ ", email='"
|
||||
+ email
|
||||
+ "\'}";
|
||||
+ '\''
|
||||
+ ", isEnabled="
|
||||
+ isEnabled
|
||||
+ '}';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,4 +5,6 @@ import org.springframework.data.repository.CrudRepository;
|
|||
|
||||
public interface UserRepository extends CrudRepository<User, Long> {
|
||||
User findByUsername(String username);
|
||||
|
||||
User findByEmailIgnoreCase(String email);
|
||||
}
|
||||
|
|
|
@ -10,4 +10,16 @@ spring.jpa.hibernate.ddl-auto=update
|
|||
spring.jpa.hibernate.naming.implicit-strategy=org.hibernate.boot.model.naming.ImplicitNamingStrategyJpaCompliantImpl
|
||||
spring.jpa.properties.hibernate.format_sql=true
|
||||
|
||||
jwt.secret=thiskeymustbeverylongorthethingcomplainssoiamjustgoingtowritehereabunchofgarbageciaomamma
|
||||
jwt.secret=thiskeymustbeverylongorthethingcomplainssoiamjustgoingtowritehereabunchofgarbageciaomamma
|
||||
|
||||
spring.mail.test-connection=true
|
||||
spring.mail.host=smtp.gmail.com
|
||||
spring.mail.port=587
|
||||
spring.mail.properties.mail.smtp.starttls.enable=true
|
||||
spring.mail.username=smarthut.sm@gmail.com
|
||||
spring.mail.password=dcadvbagqfkwbfts
|
||||
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
|
Loading…
Reference in a new issue