diff --git a/pom.xml b/pom.xml index 4961a2c..89d0a60 100644 --- a/pom.xml +++ b/pom.xml @@ -48,6 +48,27 @@ org.projectlombok lombok + + io.jsonwebtoken + jjwt-api + 0.11.5 + + + io.jsonwebtoken + jjwt-impl + 0.11.5 + + + io.jsonwebtoken + jjwt-jackson + 0.11.5 + + + org.projectlombok + lombok + 1.18.20 + provided + diff --git a/src/main/java/com/faf223/expensetrackerfaf/config/ApplicationConfig.java b/src/main/java/com/faf223/expensetrackerfaf/config/ApplicationConfig.java new file mode 100644 index 0000000..57d1823 --- /dev/null +++ b/src/main/java/com/faf223/expensetrackerfaf/config/ApplicationConfig.java @@ -0,0 +1,47 @@ +package com.faf223.expensetrackerfaf.config; + +import com.faf223.expensetrackerfaf.repository.CredentialRepository; +import com.faf223.expensetrackerfaf.repository.UserRepository; +import com.faf223.expensetrackerfaf.security.PersonDetails; +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.AuthenticationProvider; +import org.springframework.security.authentication.dao.DaoAuthenticationProvider; +import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; + +@Configuration +@RequiredArgsConstructor +public class ApplicationConfig { + + private final UserRepository userRepository; + private final CredentialRepository credentialRepository; + + @Bean + public UserDetailsService userDetailsService() { + return username -> new PersonDetails(credentialRepository.findByEmail(username).orElseThrow((() -> new UsernameNotFoundException("User not found")))); + } + + @Bean + public AuthenticationProvider authenticationProvider() { + DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider(); + authProvider.setUserDetailsService(userDetailsService()); + authProvider.setPasswordEncoder(passwordEncoder()); + return authProvider; + } + + @Bean + public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception { + return config.getAuthenticationManager(); + } + + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } +} diff --git a/src/main/java/com/faf223/expensetrackerfaf/config/JwtAuthenticationFilter.java b/src/main/java/com/faf223/expensetrackerfaf/config/JwtAuthenticationFilter.java new file mode 100644 index 0000000..1f91bc6 --- /dev/null +++ b/src/main/java/com/faf223/expensetrackerfaf/config/JwtAuthenticationFilter.java @@ -0,0 +1,57 @@ +package com.faf223.expensetrackerfaf.config; + +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.lang.NonNull; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +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.web.authentication.WebAuthenticationDetailsSource; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; + +import java.io.IOException; + +@Component +@RequiredArgsConstructor +public class JwtAuthenticationFilter extends OncePerRequestFilter { + + private final JwtService jwtService; + private final UserDetailsService userDetailsService; + + @Override + protected void doFilterInternal( + @NonNull HttpServletRequest request, + @NonNull HttpServletResponse response, + @NonNull FilterChain filterChain + ) throws ServletException, IOException { + if (request.getServletPath().contains("/api/v1/auth")) { + filterChain.doFilter(request, response); + return; + } + final String authHeader = request.getHeader("Authorization"); + final String jwt; + final String userEmail; + if (authHeader == null || !authHeader.startsWith("Bearer ")) { + filterChain.doFilter(request, response); + return; + } + jwt = authHeader.substring(7); + userEmail = jwtService.extractUsername(jwt); + if (userEmail != null && SecurityContextHolder.getContext().getAuthentication() == null) { + UserDetails userDetails = this.userDetailsService.loadUserByUsername(userEmail); + if (jwtService.isTokenValid(jwt, userDetails)) { + UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken( + userDetails, null, userDetails.getAuthorities()); + authToken.setDetails(new WebAuthenticationDetailsSource() + .buildDetails(request)); + SecurityContextHolder.getContext().setAuthentication(authToken); + } + } + filterChain.doFilter(request, response); + } +} \ No newline at end of file diff --git a/src/main/java/com/faf223/expensetrackerfaf/config/JwtService.java b/src/main/java/com/faf223/expensetrackerfaf/config/JwtService.java new file mode 100644 index 0000000..a9904f9 --- /dev/null +++ b/src/main/java/com/faf223/expensetrackerfaf/config/JwtService.java @@ -0,0 +1,85 @@ +package com.faf223.expensetrackerfaf.config; + +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; +import io.jsonwebtoken.io.Decoders; +import io.jsonwebtoken.security.Keys; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.stereotype.Service; + +import java.security.Key; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import java.util.function.Function; + +@Service +public class JwtService { + + @Value("${application.security.jwt.secret-key}") + private String secretKey; + @Value("${application.security.jwt.expiration}") + private long jwtExpiration; + @Value("${application.security.jwt.refresh-token.expiration}") + private long refreshExpiration; + + public String extractUsername(String token) { + return extractClaim(token, Claims::getSubject); + } + + public T extractClaim(String token, Function claimsResolver) { + final Claims claims = extractAllClaims(token); + return claimsResolver.apply(claims); + } + + public String generateToken(UserDetails userDetails) { + return generateToken(new HashMap<>(), userDetails); + } + + public String generateToken( + Map extraClaims, + UserDetails userDetails + ) { + return buildToken(extraClaims, userDetails, jwtExpiration); + } + + private String buildToken(Map extraClaims, UserDetails userDetails, long expiration) { + return Jwts + .builder() + .setClaims(extraClaims) + .setSubject(userDetails.getUsername()) + .setIssuedAt(new Date(System.currentTimeMillis())) + .setExpiration(new Date(System.currentTimeMillis() + expiration)) + .signWith(getSignInKey(), SignatureAlgorithm.HS256) + .compact(); + } + + public boolean isTokenValid(String token, UserDetails userDetails) { + final String username = extractUsername(token); + return (username.equals(userDetails.getUsername())) && !isTokenExpired(token); + } + + private boolean isTokenExpired(String token) { + return extractExpiration(token).before(new Date()); + } + + private Date extractExpiration(String token) { + return extractClaim(token, Claims::getExpiration); + } + + private Claims extractAllClaims(String token) { + return Jwts + .parserBuilder() + .setSigningKey(getSignInKey()) + .build() + .parseClaimsJws(token) + .getBody(); + } + + private Key getSignInKey() { + byte[] keyBytes = Decoders.BASE64.decode(secretKey); + return Keys.hmacShaKeyFor(keyBytes); + } +} diff --git a/src/main/java/com/faf223/expensetrackerfaf/config/SecurityConfiguration.java b/src/main/java/com/faf223/expensetrackerfaf/config/SecurityConfiguration.java new file mode 100644 index 0000000..ea7f8b9 --- /dev/null +++ b/src/main/java/com/faf223/expensetrackerfaf/config/SecurityConfiguration.java @@ -0,0 +1,36 @@ +package com.faf223.expensetrackerfaf.config; + +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.authentication.AuthenticationProvider; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; + +@Configuration +@EnableWebSecurity +@RequiredArgsConstructor +public class SecurityConfiguration { + + private final JwtAuthenticationFilter jwtAuthFilter; + private final AuthenticationProvider authenticationProvider; + + @Bean + public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + http + .csrf(csrf -> csrf.disable()) + .authorizeHttpRequests(auth -> auth + .requestMatchers("/api/v1/auth/**").permitAll() + .requestMatchers("/expenses").hasRole("ADMIN") + .anyRequest().authenticated() + ) + .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) + .authenticationProvider(authenticationProvider) + .addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class); // will be executed before UsernamePasswordAuthenticationFilter + + return http.build(); + } +} diff --git a/src/main/java/com/faf223/expensetrackerfaf/controller/ExpenseController.java b/src/main/java/com/faf223/expensetrackerfaf/controller/ExpenseController.java index d2298e5..7f8fa2d 100644 --- a/src/main/java/com/faf223/expensetrackerfaf/controller/ExpenseController.java +++ b/src/main/java/com/faf223/expensetrackerfaf/controller/ExpenseController.java @@ -5,6 +5,7 @@ import com.faf223.expensetrackerfaf.dto.ExpenseDTO; import com.faf223.expensetrackerfaf.dto.mappers.ExpenseMapper; import com.faf223.expensetrackerfaf.model.Expense; import com.faf223.expensetrackerfaf.service.ExpenseService; +import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.validation.BindingResult; @@ -15,17 +16,12 @@ import java.util.stream.Collectors; @RestController @RequestMapping("/expenses") +@RequiredArgsConstructor public class ExpenseController { private final ExpenseService expenseService; private final ExpenseMapper expenseMapper; - @Autowired - public ExpenseController(ExpenseService expenseService, ExpenseMapper expenseMapper) { - this.expenseService = expenseService; - this.expenseMapper = expenseMapper; - } - @GetMapping() public ResponseEntity> getAllExpenses() { List expenses = expenseService.getExpenses().stream().map(expenseMapper::toDto).collect(Collectors.toList()); @@ -35,7 +31,7 @@ public class ExpenseController { @PostMapping() public ResponseEntity createNewExpense(@RequestBody ExpenseCreationDTO expenseDTO, - BindingResult bindingResult) { + BindingResult bindingResult) { Expense expense = expenseMapper.toExpense(expenseDTO); if (!bindingResult.hasErrors()) { expenseService.createOrUpdateExpense(expense); @@ -47,7 +43,7 @@ public class ExpenseController { @PatchMapping() public ResponseEntity updateExpense(@RequestBody ExpenseCreationDTO expenseDTO, - BindingResult bindingResult) { + BindingResult bindingResult) { Expense expense = expenseMapper.toExpense(expenseDTO); if (!bindingResult.hasErrors()) { expenseService.createOrUpdateExpense(expense); diff --git a/src/main/java/com/faf223/expensetrackerfaf/controller/IncomeController.java b/src/main/java/com/faf223/expensetrackerfaf/controller/IncomeController.java index ad342f7..582062e 100644 --- a/src/main/java/com/faf223/expensetrackerfaf/controller/IncomeController.java +++ b/src/main/java/com/faf223/expensetrackerfaf/controller/IncomeController.java @@ -5,6 +5,7 @@ import com.faf223.expensetrackerfaf.dto.IncomeDTO; import com.faf223.expensetrackerfaf.dto.mappers.IncomeMapper; import com.faf223.expensetrackerfaf.model.Income; import com.faf223.expensetrackerfaf.service.IncomeService; +import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.validation.BindingResult; @@ -15,17 +16,12 @@ import java.util.stream.Collectors; @RestController @RequestMapping("/incomes") +@RequiredArgsConstructor public class IncomeController { private final IncomeService incomeService; private final IncomeMapper incomeMapper; - @Autowired - public IncomeController(IncomeService incomeService, IncomeMapper incomeMapper) { - this.incomeService = incomeService; - this.incomeMapper = incomeMapper; - } - @GetMapping() public ResponseEntity> getAllIncomes() { List incomes = incomeService.getIncomes().stream().map(incomeMapper::toDto).collect(Collectors.toList()); @@ -35,7 +31,7 @@ public class IncomeController { @PostMapping() public ResponseEntity createNewIncome(@RequestBody IncomeCreationDTO incomeDTO, - BindingResult bindingResult) { + BindingResult bindingResult) { Income income = incomeMapper.toIncome(incomeDTO); if (!bindingResult.hasErrors()) { incomeService.createOrUpdateIncome(income); @@ -47,7 +43,7 @@ public class IncomeController { @PatchMapping() public ResponseEntity updateIncome(@RequestBody IncomeCreationDTO incomeDTO, - BindingResult bindingResult) { + BindingResult bindingResult) { Income income = incomeMapper.toIncome(incomeDTO); if (!bindingResult.hasErrors()) { incomeService.createOrUpdateIncome(income); diff --git a/src/main/java/com/faf223/expensetrackerfaf/controller/UserController.java b/src/main/java/com/faf223/expensetrackerfaf/controller/UserController.java index 97b3648..542ccbc 100644 --- a/src/main/java/com/faf223/expensetrackerfaf/controller/UserController.java +++ b/src/main/java/com/faf223/expensetrackerfaf/controller/UserController.java @@ -5,37 +5,23 @@ import com.faf223.expensetrackerfaf.dto.UserDTO; import com.faf223.expensetrackerfaf.dto.mappers.UserMapper; import com.faf223.expensetrackerfaf.model.User; import com.faf223.expensetrackerfaf.service.UserService; +import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.validation.BindingResult; import org.springframework.web.bind.annotation.*; -import java.util.List; -import java.util.stream.Collectors; - @RestController @RequestMapping("/users") +@RequiredArgsConstructor public class UserController { private final UserService userService; private final UserMapper userMapper; - @Autowired - public UserController(UserService userService, UserMapper userMapper) { - this.userService = userService; - this.userMapper = userMapper; - } - - @GetMapping() - public ResponseEntity> getAllUsers() { - List users = userService.getUsers().stream().map(userMapper::toDto).collect(Collectors.toList()); - if (!users.isEmpty()) return ResponseEntity.ok(users); - else return ResponseEntity.notFound().build(); - } - @PostMapping() public ResponseEntity createNewUser(@RequestBody UserCreationDTO userDTO, - BindingResult bindingResult) { + BindingResult bindingResult) { User user = userMapper.toUser(userDTO); if (!bindingResult.hasErrors()) { userService.createOrUpdateUser(user); @@ -47,7 +33,7 @@ public class UserController { @PatchMapping() public ResponseEntity updateUser(@RequestBody UserCreationDTO userDTO, - BindingResult bindingResult) { + BindingResult bindingResult) { User user = userMapper.toUser(userDTO); if (!bindingResult.hasErrors()) { userService.createOrUpdateUser(user); diff --git a/src/main/java/com/faf223/expensetrackerfaf/controller/auth/AuthenticationController.java b/src/main/java/com/faf223/expensetrackerfaf/controller/auth/AuthenticationController.java new file mode 100644 index 0000000..1f375ea --- /dev/null +++ b/src/main/java/com/faf223/expensetrackerfaf/controller/auth/AuthenticationController.java @@ -0,0 +1,26 @@ +package com.faf223.expensetrackerfaf.controller.auth; + +import com.faf223.expensetrackerfaf.service.AuthenticationService; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping("api/v1/auth") +public class AuthenticationController { + + private final AuthenticationService service; + + public AuthenticationController(AuthenticationService service) { + this.service = service; + } + + @PostMapping("/register") + public ResponseEntity register(@RequestBody RegisterRequest request) { + return ResponseEntity.ok(service.register(request)); + } + + @PostMapping("/authenticate") + public ResponseEntity authenticate(@RequestBody AuthenticationRequest request) { + return ResponseEntity.ok(service.authenticate(request)); + } +} diff --git a/src/main/java/com/faf223/expensetrackerfaf/controller/auth/AuthenticationRequest.java b/src/main/java/com/faf223/expensetrackerfaf/controller/auth/AuthenticationRequest.java new file mode 100644 index 0000000..63f7b1c --- /dev/null +++ b/src/main/java/com/faf223/expensetrackerfaf/controller/auth/AuthenticationRequest.java @@ -0,0 +1,16 @@ +package com.faf223.expensetrackerfaf.controller.auth; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class AuthenticationRequest { + + private String email; + private String password; +} diff --git a/src/main/java/com/faf223/expensetrackerfaf/controller/auth/AuthenticationResponse.java b/src/main/java/com/faf223/expensetrackerfaf/controller/auth/AuthenticationResponse.java new file mode 100644 index 0000000..bc92552 --- /dev/null +++ b/src/main/java/com/faf223/expensetrackerfaf/controller/auth/AuthenticationResponse.java @@ -0,0 +1,15 @@ +package com.faf223.expensetrackerfaf.controller.auth; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class AuthenticationResponse { + + private String token; +} diff --git a/src/main/java/com/faf223/expensetrackerfaf/controller/auth/RegisterRequest.java b/src/main/java/com/faf223/expensetrackerfaf/controller/auth/RegisterRequest.java new file mode 100644 index 0000000..ca50bad --- /dev/null +++ b/src/main/java/com/faf223/expensetrackerfaf/controller/auth/RegisterRequest.java @@ -0,0 +1,22 @@ +package com.faf223.expensetrackerfaf.controller.auth; + + +import com.faf223.expensetrackerfaf.model.Role; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class RegisterRequest { + + private String firstname; // Change field name to match JSON + private String lastname; // Change field name to match JSON + private String username; // Change field name to match JSON + private String email; // Change field name to match JSON + private String password; + private Role role; +} diff --git a/src/main/java/com/faf223/expensetrackerfaf/dto/UserCreationDTO.java b/src/main/java/com/faf223/expensetrackerfaf/dto/UserCreationDTO.java index fefb21b..edd0663 100644 --- a/src/main/java/com/faf223/expensetrackerfaf/dto/UserCreationDTO.java +++ b/src/main/java/com/faf223/expensetrackerfaf/dto/UserCreationDTO.java @@ -6,8 +6,10 @@ import lombok.Data; @Data @AllArgsConstructor public class UserCreationDTO { - private String uuid; - private String name; - private String surname; + + private String firstname; + private String lastname; private String username; + private String email; + private String password; } diff --git a/src/main/java/com/faf223/expensetrackerfaf/dto/UserDTO.java b/src/main/java/com/faf223/expensetrackerfaf/dto/UserDTO.java index 9f79253..ff95f52 100644 --- a/src/main/java/com/faf223/expensetrackerfaf/dto/UserDTO.java +++ b/src/main/java/com/faf223/expensetrackerfaf/dto/UserDTO.java @@ -6,8 +6,9 @@ import lombok.Data; @Data @AllArgsConstructor public class UserDTO { - private String uuid; + private String name; private String surname; private String username; + } diff --git a/src/main/java/com/faf223/expensetrackerfaf/dto/mappers/UserMapper.java b/src/main/java/com/faf223/expensetrackerfaf/dto/mappers/UserMapper.java index 8e94aff..90af4a2 100644 --- a/src/main/java/com/faf223/expensetrackerfaf/dto/mappers/UserMapper.java +++ b/src/main/java/com/faf223/expensetrackerfaf/dto/mappers/UserMapper.java @@ -10,21 +10,16 @@ import org.springframework.stereotype.Component; @Component public class UserMapper { - private final UserService userService; - - @Autowired - public UserMapper(UserService userService) { - this.userService = userService; - } - public UserDTO toDto(User user) { - return new UserDTO(user.getUuid(), user.getName(), user.getSurname(), user.getUsername()); + return new UserDTO(user.getFirstName(), user.getLastName(), user.getUsername()); } public User toUser(UserCreationDTO userDTO) { - User user = userService.getUserById(userDTO.getUuid()); - if(user == null) return new User(userDTO.getUuid(), userDTO.getName(), - userDTO.getSurname(), userDTO.getUsername()); + + User user = new User(); + user.setFirstName(userDTO.getFirstname()); + user.setLastName(userDTO.getLastname()); + user.setUsername(userDTO.getUsername()); return user; } diff --git a/src/main/java/com/faf223/expensetrackerfaf/model/Credential.java b/src/main/java/com/faf223/expensetrackerfaf/model/Credential.java index aa2b4bd..f993e9f 100644 --- a/src/main/java/com/faf223/expensetrackerfaf/model/Credential.java +++ b/src/main/java/com/faf223/expensetrackerfaf/model/Credential.java @@ -1,20 +1,35 @@ -package com.faf223.expensetrackerfaf.model; + package com.faf223.expensetrackerfaf.model; -import jakarta.persistence.*; -import lombok.Data; + import jakarta.persistence.*; + import lombok.AllArgsConstructor; + import lombok.Data; + import lombok.NoArgsConstructor; -@Data -@Entity(name = "credentials") -public class Credential { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long credentialId; + @Data + @Entity(name = "credentials") + @NoArgsConstructor + @AllArgsConstructor + public class Credential { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long credentialId; - @ManyToOne - @JoinColumn(name = "user_uuid") - private User user; + @ManyToOne + @JoinColumn(name = "user_uuid") + private User user; - private String email; - private String password; -} + private String email; + private String password; + + @Enumerated(EnumType.STRING) + private Role role; + + public Credential(User user, String email, String password) { + this.user = user; + this.email = email; + this.password = password; + + this.role = Role.ROLE_USER; + } + } diff --git a/src/main/java/com/faf223/expensetrackerfaf/model/Expense.java b/src/main/java/com/faf223/expensetrackerfaf/model/Expense.java index cf558e7..6bdccb1 100644 --- a/src/main/java/com/faf223/expensetrackerfaf/model/Expense.java +++ b/src/main/java/com/faf223/expensetrackerfaf/model/Expense.java @@ -1,7 +1,9 @@ package com.faf223.expensetrackerfaf.model; +import com.fasterxml.jackson.annotation.JsonIgnore; import jakarta.persistence.*; import lombok.AllArgsConstructor; import lombok.Data; +import lombok.ToString; import lombok.NoArgsConstructor; import java.math.BigDecimal; @@ -16,8 +18,10 @@ public class Expense { @GeneratedValue(strategy = GenerationType.IDENTITY) private Long expenseId; - @ManyToOne + @ManyToOne() @JoinColumn(name = "user_uuid") + @ToString.Exclude + @JsonIgnore private User user; @ManyToOne diff --git a/src/main/java/com/faf223/expensetrackerfaf/model/Income.java b/src/main/java/com/faf223/expensetrackerfaf/model/Income.java index cd9b7bb..a4c88f1 100644 --- a/src/main/java/com/faf223/expensetrackerfaf/model/Income.java +++ b/src/main/java/com/faf223/expensetrackerfaf/model/Income.java @@ -1,10 +1,11 @@ package com.faf223.expensetrackerfaf.model; +import com.fasterxml.jackson.annotation.JsonIgnore; import jakarta.persistence.*; import lombok.AllArgsConstructor; import lombok.Data; +import lombok.ToString; import lombok.NoArgsConstructor; - import java.math.BigDecimal; import java.time.LocalDate; @@ -19,6 +20,8 @@ public class Income { @ManyToOne @JoinColumn(name = "user_uuid") + @ToString.Exclude + @JsonIgnore private User user; @ManyToOne diff --git a/src/main/java/com/faf223/expensetrackerfaf/model/Role.java b/src/main/java/com/faf223/expensetrackerfaf/model/Role.java index d9393e8..ea04328 100644 --- a/src/main/java/com/faf223/expensetrackerfaf/model/Role.java +++ b/src/main/java/com/faf223/expensetrackerfaf/model/Role.java @@ -2,5 +2,5 @@ package com.faf223.expensetrackerfaf.model; public enum Role { - UNREGISTERED, REGISTERED, ADMIN; + ROLE_USER, ROLE_ADMIN } diff --git a/src/main/java/com/faf223/expensetrackerfaf/model/User.java b/src/main/java/com/faf223/expensetrackerfaf/model/User.java index 85b876b..339416f 100644 --- a/src/main/java/com/faf223/expensetrackerfaf/model/User.java +++ b/src/main/java/com/faf223/expensetrackerfaf/model/User.java @@ -1,22 +1,39 @@ + package com.faf223.expensetrackerfaf.model; -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.Id; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; +import jakarta.persistence.*; +import lombok.*; + +import java.util.List; -@Data -@AllArgsConstructor -@NoArgsConstructor @Entity(name = "users") +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor public class User { @Id - @Column(table = "users", name = "user_uuid") - private String uuid; + @Column(name = "user_uuid") + @GeneratedValue(strategy = GenerationType.UUID) + private String userUuid; - private String name; - private String surname; + @Column(name = "name") + private String firstName; + + @Column(name = "surname") + private String lastName; + + @Column(name = "username") private String username; + + @Transient + private String password; + + @OneToMany(mappedBy = "user", fetch = FetchType.LAZY) + @ToString.Exclude + private List expenses; + + @OneToMany(mappedBy = "user", fetch = FetchType.LAZY) + @ToString.Exclude + private List incomes; } diff --git a/src/main/java/com/faf223/expensetrackerfaf/repository/CredentialRepository.java b/src/main/java/com/faf223/expensetrackerfaf/repository/CredentialRepository.java index 346136a..065212d 100644 --- a/src/main/java/com/faf223/expensetrackerfaf/repository/CredentialRepository.java +++ b/src/main/java/com/faf223/expensetrackerfaf/repository/CredentialRepository.java @@ -4,6 +4,9 @@ import com.faf223.expensetrackerfaf.model.Credential; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; +import java.util.Optional; + @Repository public interface CredentialRepository extends JpaRepository { + Optional findByEmail(String email); } \ No newline at end of file diff --git a/src/main/java/com/faf223/expensetrackerfaf/repository/ExpenseRepository.java b/src/main/java/com/faf223/expensetrackerfaf/repository/ExpenseRepository.java index 38fb2fb..f96f68e 100644 --- a/src/main/java/com/faf223/expensetrackerfaf/repository/ExpenseRepository.java +++ b/src/main/java/com/faf223/expensetrackerfaf/repository/ExpenseRepository.java @@ -1,6 +1,7 @@ package com.faf223.expensetrackerfaf.repository; import com.faf223.expensetrackerfaf.model.Expense; +import com.faf223.expensetrackerfaf.model.User; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; @@ -8,5 +9,5 @@ import java.util.List; @Repository public interface ExpenseRepository extends JpaRepository { - List findByUserUuid(String userUuid); + List findByUser(User user); } diff --git a/src/main/java/com/faf223/expensetrackerfaf/repository/IncomeRepository.java b/src/main/java/com/faf223/expensetrackerfaf/repository/IncomeRepository.java index 2f199af..3f4f94d 100644 --- a/src/main/java/com/faf223/expensetrackerfaf/repository/IncomeRepository.java +++ b/src/main/java/com/faf223/expensetrackerfaf/repository/IncomeRepository.java @@ -1,6 +1,7 @@ package com.faf223.expensetrackerfaf.repository; import com.faf223.expensetrackerfaf.model.Income; +import com.faf223.expensetrackerfaf.model.User; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; @@ -8,5 +9,5 @@ import java.util.List; @Repository public interface IncomeRepository extends JpaRepository { - List findByUserUuid(String userUuid); + List findByUser(User user); } diff --git a/src/main/java/com/faf223/expensetrackerfaf/repository/UserRepository.java b/src/main/java/com/faf223/expensetrackerfaf/repository/UserRepository.java index 9e4e015..d370b0a 100644 --- a/src/main/java/com/faf223/expensetrackerfaf/repository/UserRepository.java +++ b/src/main/java/com/faf223/expensetrackerfaf/repository/UserRepository.java @@ -2,8 +2,9 @@ package com.faf223.expensetrackerfaf.repository; import com.faf223.expensetrackerfaf.model.User; import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.stereotype.Repository; -@Repository +import java.util.Optional; + public interface UserRepository extends JpaRepository { -} \ No newline at end of file + Optional getUserByUserUuid(String userUuid); +} diff --git a/src/main/java/com/faf223/expensetrackerfaf/security/PersonDetails.java b/src/main/java/com/faf223/expensetrackerfaf/security/PersonDetails.java index cb18bc5..f805e79 100644 --- a/src/main/java/com/faf223/expensetrackerfaf/security/PersonDetails.java +++ b/src/main/java/com/faf223/expensetrackerfaf/security/PersonDetails.java @@ -1,40 +1,38 @@ package com.faf223.expensetrackerfaf.security; -import com.faf223.expensetrackerfaf.model.Role; -import jakarta.persistence.EnumType; -import jakarta.persistence.Enumerated; +import com.faf223.expensetrackerfaf.model.Credential; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; -import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; import java.util.Collection; -import java.util.List; +import java.util.Collections; +@Data +@Builder +@NoArgsConstructor(force = true) +@AllArgsConstructor public class PersonDetails implements UserDetails { - private final User user; - - @Enumerated(EnumType.STRING) - private Role role; - - public PersonDetails(User user) { - this.user = user; - } + private final Credential credential; @Override public Collection getAuthorities() { - return List.of(new SimpleGrantedAuthority(role.name())); + return Collections.singletonList(new SimpleGrantedAuthority(credential.getRole().toString())); } @Override public String getPassword() { - return user.getPassword(); + return credential.getPassword(); } @Override public String getUsername() { - return user.getUsername(); + return credential.getEmail(); } @Override diff --git a/src/main/java/com/faf223/expensetrackerfaf/service/AuthenticationService.java b/src/main/java/com/faf223/expensetrackerfaf/service/AuthenticationService.java new file mode 100644 index 0000000..96cb4a4 --- /dev/null +++ b/src/main/java/com/faf223/expensetrackerfaf/service/AuthenticationService.java @@ -0,0 +1,58 @@ +package com.faf223.expensetrackerfaf.service; + +import com.faf223.expensetrackerfaf.config.JwtService; +import com.faf223.expensetrackerfaf.controller.auth.AuthenticationRequest; +import com.faf223.expensetrackerfaf.controller.auth.AuthenticationResponse; +import com.faf223.expensetrackerfaf.controller.auth.RegisterRequest; +import com.faf223.expensetrackerfaf.model.Credential; +import com.faf223.expensetrackerfaf.model.User; +import com.faf223.expensetrackerfaf.repository.CredentialRepository; +import com.faf223.expensetrackerfaf.repository.UserRepository; +import com.faf223.expensetrackerfaf.security.PersonDetails; +import lombok.RequiredArgsConstructor; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class AuthenticationService { + + private final UserRepository userRepository; + private final CredentialRepository credentialRepository; + private final PasswordEncoder passwordEncoder; + private final JwtService jwtService; + private final AuthenticationManager authenticationManager; + + public AuthenticationResponse register(RegisterRequest request) { + + User user = User.builder() + .firstName(request.getFirstname()) + .lastName(request.getLastname()) + .username(request.getUsername()) + .build(); + + userRepository.save(user); + Credential credential = new Credential(user, request.getEmail(), passwordEncoder.encode(request.getPassword())); + credentialRepository.save(credential); + + String jwtToken = jwtService.generateToken(new PersonDetails(credential)); + return AuthenticationResponse.builder() + .token(jwtToken) + .build(); + } + + public AuthenticationResponse authenticate(AuthenticationRequest request) { + authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(request.getEmail(), request.getPassword())); + + Credential credential = credentialRepository.findByEmail(request.getEmail()).orElseThrow((() -> new UsernameNotFoundException("User not found"))); + + String jwtToken = jwtService.generateToken(new PersonDetails(credential)); + return AuthenticationResponse.builder() + .token(jwtToken) + .build(); + } + +} diff --git a/src/main/java/com/faf223/expensetrackerfaf/service/ExpenseService.java b/src/main/java/com/faf223/expensetrackerfaf/service/ExpenseService.java index 942c5a1..0683998 100644 --- a/src/main/java/com/faf223/expensetrackerfaf/service/ExpenseService.java +++ b/src/main/java/com/faf223/expensetrackerfaf/service/ExpenseService.java @@ -1,28 +1,36 @@ package com.faf223.expensetrackerfaf.service; import com.faf223.expensetrackerfaf.model.Expense; +import com.faf223.expensetrackerfaf.model.User; import com.faf223.expensetrackerfaf.repository.ExpenseRepository; +import com.faf223.expensetrackerfaf.repository.UserRepository; +import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import java.util.ArrayList; import java.util.List; +import java.util.Optional; @Service +@RequiredArgsConstructor public class ExpenseService { private final ExpenseRepository expenseRepository; - - @Autowired - public ExpenseService(ExpenseRepository expenseRepository) { - this.expenseRepository = expenseRepository; - } + private final UserRepository userRepository; public void createOrUpdateExpense(Expense expense) { expenseRepository.save(expense); } public List getExpensesByUserId(String userUuid) { - return expenseRepository.findByUserUuid(userUuid); + + Optional user = userRepository.getUserByUserUuid(userUuid); + if (user.isPresent()) { + return expenseRepository.findByUser(user.get()); + } + + return new ArrayList<>(); } public List getExpenses() { diff --git a/src/main/java/com/faf223/expensetrackerfaf/service/IncomeService.java b/src/main/java/com/faf223/expensetrackerfaf/service/IncomeService.java index cde77d0..63e1bab 100644 --- a/src/main/java/com/faf223/expensetrackerfaf/service/IncomeService.java +++ b/src/main/java/com/faf223/expensetrackerfaf/service/IncomeService.java @@ -1,21 +1,23 @@ package com.faf223.expensetrackerfaf.service; import com.faf223.expensetrackerfaf.model.Income; +import com.faf223.expensetrackerfaf.model.User; import com.faf223.expensetrackerfaf.repository.IncomeRepository; +import com.faf223.expensetrackerfaf.repository.UserRepository; +import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import java.util.ArrayList; import java.util.List; +import java.util.Optional; @Service +@RequiredArgsConstructor public class IncomeService { private final IncomeRepository incomeRepository; - - @Autowired - public IncomeService(IncomeRepository incomeRepository) { - this.incomeRepository = incomeRepository; - } + private final UserRepository userRepository; public void createOrUpdateIncome(Income income) { incomeRepository.save(income); @@ -26,7 +28,13 @@ public class IncomeService { } public List getIncomesByUserId(String userUuid) { - return incomeRepository.findByUserUuid(userUuid); + + Optional user = userRepository.getUserByUserUuid(userUuid); + if (user.isPresent()) { + return incomeRepository.findByUser(user.get()); + } + + return new ArrayList<>(); } public Income getIncomeById(long id) { diff --git a/src/main/java/com/faf223/expensetrackerfaf/service/UserService.java b/src/main/java/com/faf223/expensetrackerfaf/service/UserService.java index 956dfe1..c3de777 100644 --- a/src/main/java/com/faf223/expensetrackerfaf/service/UserService.java +++ b/src/main/java/com/faf223/expensetrackerfaf/service/UserService.java @@ -2,21 +2,18 @@ package com.faf223.expensetrackerfaf.service; import com.faf223.expensetrackerfaf.model.User; import com.faf223.expensetrackerfaf.repository.UserRepository; +import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.List; @Service +@RequiredArgsConstructor public class UserService { private final UserRepository userRepository; - @Autowired - public UserService(UserRepository userRepository) { - this.userRepository = userRepository; - } - public void createOrUpdateUser(User user) { userRepository.save(user); }