Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added a verification email on registration #27

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 5 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,11 @@
<version>2.17.2</version>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>

</dependencies>

<build>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@
import org.apache.logging.log4j.MarkerManager;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;

@SpringBootApplication
@EnableScheduling
public class TutoringPlatformApplication {


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,13 @@
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
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.RestController;
import org.springframework.web.bind.annotation.*;
import pl.simpleascoding.tutoringplatform.dto.ChangeUserPasswordDTO;
import pl.simpleascoding.tutoringplatform.dto.CreateUserDTO;
import pl.simpleascoding.tutoringplatform.service.user.UserFacade;

import javax.servlet.http.HttpServletRequest;

import java.security.Principal;

@RestController
Expand All @@ -21,8 +20,13 @@ class UserController {
private final UserFacade userFacade;

@PostMapping
ResponseEntity<String> createUser(@RequestBody CreateUserDTO dto) {
return new ResponseEntity<>(userFacade.createUser(dto), HttpStatus.OK);
ResponseEntity<String> createUser(@RequestBody CreateUserDTO dto, HttpServletRequest request) {
return new ResponseEntity<>(userFacade.createUser(dto, request.getRequestURL().toString()), HttpStatus.OK);
}

@GetMapping("/confirm-registration")
ResponseEntity<String> confirmRegistration(@RequestParam String tokenValue) {
return new ResponseEntity<>(userFacade.confirmUserRegistration(tokenValue), HttpStatus.OK);
}

@PostMapping("/change-password")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package pl.simpleascoding.tutoringplatform.domain.token;

import lombok.Data;
import lombok.NoArgsConstructor;
import pl.simpleascoding.tutoringplatform.domain.user.User;

import javax.persistence.*;
import java.time.LocalDateTime;
import java.util.UUID;

@Data
@Entity
@Table(name = "token")
@NoArgsConstructor
public class Token {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

private TokenType type;

private String value = UUID.randomUUID().toString();

@ManyToOne(cascade = {CascadeType.PERSIST, CascadeType.MERGE})
private User user;

private LocalDateTime createdAt = LocalDateTime.now();
private LocalDateTime expiresAt = createdAt.plusMinutes(15);
private LocalDateTime confirmedAt;

public Token(TokenType type) {
this.type = type;
}

public void confirm() {
this.confirmedAt = LocalDateTime.now();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package pl.simpleascoding.tutoringplatform.domain.token;

public enum TokenType {
REGISTER, PASSWORD, EMAIL
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import pl.simpleascoding.tutoringplatform.domain.token.Token;

import javax.persistence.*;
import java.util.ArrayList;
Expand All @@ -24,16 +25,25 @@ public class User implements UserDetails {

private String name;
private String surname;
private String email;

@OneToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE}, fetch = FetchType.EAGER)
private boolean enabled = false;
private boolean locked = false;

@OneToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REMOVE}, fetch = FetchType.EAGER)
@JoinColumn(name = "user_id")
private List<Role> roles = new ArrayList<>();

public User(String username, String password, String name, String surname) {
@OneToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE})
@JoinColumn(name = "user_id")
private List<Token> tokens = new ArrayList<>();

public User(String username, String password, String name, String surname, String email) {
this.username = username;
this.password = password;
this.name = name;
this.surname = surname;
this.email = email;
}

@Override
Expand All @@ -48,16 +58,12 @@ public boolean isAccountNonExpired() {

@Override
public boolean isAccountNonLocked() {
return true;
return !locked;
}

@Override
public boolean isCredentialsNonExpired() {
return true;
}

@Override
public boolean isEnabled() {
return true;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@
import org.hibernate.annotations.Immutable;

@Immutable
public record CreateUserDTO(String username, String password, String name, String surname) {
public record CreateUserDTO(String username, String password, String name, String surname, String email) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package pl.simpleascoding.tutoringplatform.exception;

public class EmailTakenException extends RuntimeException {
Copy link
Member

@dawciobiel dawciobiel Jul 6, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Proponowana nazwa klasy:

EmailAddressAlreadyTakenException
EmailAaddressAlreadyInUseException

public EmailTakenException(String email) {
super("Email \"" + email + "\" is already in use");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tutaj jest konkatencja 3ch obiektów klasy String. Więc wychodzi, że JVM wykonuje takie operacje aby utworzyć wynikowy obiekt:

  • stworzenie obiektu: "Email" // p.s. Chyba miało być: E-mail

  • stworzenie obiektu: " is already in use" // p.s. is jest tutaj w zasadzie nie potrzebne, bo i tak zdanie będzie poprawne

  • stworzenie obiektu "Email adres@domena.com"

  • stworzenie obiektu "Email adres@domena.com is already in use"

  • garbage collector za jakiś okres czasu będzie musiał zwolnić zarezerwowaną pamięć dla obiektów: "Email", "is already in use". A więc będzie musiał mieć dodatkową robotę.

JVM też musi wykonać dodatkową robotę aby obiekty String stworzyć - właściwie na darmo, bo za chwile nie są one już potrzebne.

Więc może zamiast tak łączyć i rzeźbić warto użyć klasy StringBuffer bądź StringBuilder. Zależnie od wymaganego bezpieczeństwa wielowątkowości przy tej operacji.
string-vs-stringbuffer-vs-stringbuilder

p.s. W tym miejscu będzie też w przyszłości użyta metoda do pobrania odpowiedniej wersji językowej komunikatu. Ale przeróbka będzie banalnie prosta w pszyszłości.

StringBuilder sb = new StringBuilder("E-mail [");
sb.append(email);
sb.append("] already in use");

A w przyszłości całość będzie taka:
super(LanguageBundle.getMessage("EMAIL_ADDRESS_ALREADY_IN_USE_EXCEPTION", email);

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package pl.simpleascoding.tutoringplatform.exception;

public class TokenAlreadyConfirmedException extends RuntimeException {
public TokenAlreadyConfirmedException() {
super("Token already confirmed");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package pl.simpleascoding.tutoringplatform.exception;

public class TokenNotFoundException extends RuntimeException {
public TokenNotFoundException() {
super("Token not found");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package pl.simpleascoding.tutoringplatform.exception;

public class UserAlreadyEnabledException extends RuntimeException {
public UserAlreadyEnabledException(String username) {
super("User \"" + username + "\" is already enabled");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Taka sama uwaga jak przy klasie:
src/main/java/pl/simpleascoding/tutoringplatform/exception/EmailTakenException.java

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package pl.simpleascoding.tutoringplatform.repository;

import org.springframework.data.jpa.repository.JpaRepository;
import pl.simpleascoding.tutoringplatform.domain.token.Token;

import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;

public interface TokenRepository extends JpaRepository<Token, Long> {
Optional<Token> findTokenByValue(String value);

List<Token> findTokensByExpiresAtBefore(LocalDateTime localDateTime);
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,7 @@
import java.util.Optional;

public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByUsername(String username);
Optional<User> findUserByUsername(String username);

Optional<User> findUserByEmail(String email);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package pl.simpleascoding.tutoringplatform.service.token;

public interface ExpiredTokensCleanerService {
void cleanExpiredTokens();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package pl.simpleascoding.tutoringplatform.service.token;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import pl.simpleascoding.tutoringplatform.domain.token.Token;
import pl.simpleascoding.tutoringplatform.domain.token.TokenType;
import pl.simpleascoding.tutoringplatform.domain.user.User;
import pl.simpleascoding.tutoringplatform.repository.TokenRepository;
import pl.simpleascoding.tutoringplatform.repository.UserRepository;

import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;

@Service
@RequiredArgsConstructor
@Slf4j
public class ExpiredTokensCleanerServiceImpl implements ExpiredTokensCleanerService {

private final TokenRepository tokenRepository;
private final UserRepository userRepository;

@Override
@Scheduled(cron = "0 0 * ? * *")
public void cleanExpiredTokens() {
List<Token> expiredTokens = tokenRepository.findTokensByExpiresAtBefore(LocalDateTime.now());
List<User> inactiveUsers = expiredTokens
.stream()
.filter(token -> TokenType.REGISTER.equals(token.getType()) && Optional.ofNullable(token.getConfirmedAt()).isEmpty())
.map(Token::getUser)
.toList();

tokenRepository.deleteAll(expiredTokens);
log.info("Deleted expired tokens from the database.");
userRepository.deleteAll(inactiveUsers);
inactiveUsers.forEach(user -> log.info("Deleted user \"" + user.getUsername() + "\". Reason: Account has not been been activated."));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,23 @@
@RequiredArgsConstructor
public class UserFacade {

private final UserServiceImpl userServiceImpl;
private final UserService userService;

public String createUser(CreateUserDTO dto) {
public String createUser(CreateUserDTO dto, String rootUrl) {

return userServiceImpl.createUser(dto);
return userService.createUser(dto, rootUrl);

}

public String confirmUserRegistration(String tokenValue) {

return userService.confirmUserRegistration(tokenValue);

}

public String changeUserPassword(ChangeUserPasswordDTO dto, String username) {

return userServiceImpl.changeUserPassword(dto, username);
return userService.changeUserPassword(dto, username);

}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@
@Service
interface UserService {

String createUser(CreateUserDTO dto);
String createUser(CreateUserDTO dto, String rootUrl);

String confirmUserRegistration(String token);

String changeUserPassword(ChangeUserPasswordDTO dto, String username);

Expand Down