+1
-7
@@ -3,8 +3,6 @@
|
||||
services:
|
||||
client:
|
||||
container_name: tasknote
|
||||
profiles:
|
||||
- "dev"
|
||||
image: node:20.17-bullseye-slim
|
||||
ports:
|
||||
- "5000:5000"
|
||||
@@ -20,8 +18,6 @@ services:
|
||||
|
||||
java-api:
|
||||
container_name: java-api
|
||||
profiles:
|
||||
- "dev"
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_started
|
||||
@@ -49,8 +45,6 @@ services:
|
||||
|
||||
postgres:
|
||||
container_name: postgres
|
||||
profiles:
|
||||
- "dev"
|
||||
image: postgres:15.8-bookworm
|
||||
environment:
|
||||
POSTGRES_DB: tasknote
|
||||
@@ -59,7 +53,7 @@ services:
|
||||
POSTGRES_PASSWORD: default
|
||||
POSTGRES_PORT: 5432
|
||||
volumes:
|
||||
- "./localpgdata:/pgdata"
|
||||
- "/pgdata"
|
||||
ports:
|
||||
- "5432:5432"
|
||||
healthcheck:
|
||||
|
||||
@@ -102,6 +102,25 @@
|
||||
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
|
||||
<version>2.6.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Authentication -->
|
||||
<dependency>
|
||||
<groupId>io.jsonwebtoken</groupId>
|
||||
<artifactId>jjwt-api</artifactId>
|
||||
<version>0.12.6</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.jsonwebtoken</groupId>
|
||||
<artifactId>jjwt-impl</artifactId>
|
||||
<version>0.12.6</version>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.jsonwebtoken</groupId>
|
||||
<artifactId>jjwt-jackson</artifactId>
|
||||
<version>0.12.6</version>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
||||
@@ -3,11 +3,16 @@ package br.com.tasknoteapp.java_api;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
/** Entrypoint of the Java API service application. */
|
||||
@SpringBootApplication
|
||||
public class JavaApiApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(JavaApiApplication.class, args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Main method of the application.
|
||||
*
|
||||
* @param args Additional arguments, if any.
|
||||
*/
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(JavaApiApplication.class, args);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
package br.com.tasknoteapp.java_api.auth;
|
||||
|
||||
public record JwtAuthenticationResponse(String token) {}
|
||||
@@ -0,0 +1,11 @@
|
||||
package br.com.tasknoteapp.java_api.auth;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.Email;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
|
||||
/** This record represents a login request with user email and password. */
|
||||
@Schema(description = "Login request with user email and password.")
|
||||
public record LoginRequest(
|
||||
@Schema(description = "User email.") @Email @NotNull String email,
|
||||
@Schema(description = "User password.") @NotNull String password) {}
|
||||
@@ -1,22 +1,35 @@
|
||||
package br.com.tasknoteapp.java_api.config;
|
||||
|
||||
import br.com.tasknoteapp.java_api.filter.JwtAuthenticationFilter;
|
||||
import br.com.tasknoteapp.java_api.service.UserService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.authentication.AuthenticationProvider;
|
||||
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
|
||||
import org.springframework.security.config.Customizer;
|
||||
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
|
||||
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
|
||||
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
import org.springframework.security.web.csrf.CookieCsrfTokenRepository;
|
||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||
|
||||
/** This class contains security configurations. */
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
@EnableMethodSecurity
|
||||
@RequiredArgsConstructor
|
||||
public class SecurityConfig {
|
||||
|
||||
private final UserService userService;
|
||||
|
||||
private final JwtAuthenticationFilter jwtAuthenticationFilter;
|
||||
|
||||
/**
|
||||
* Filters a request to add security checks and configurations.
|
||||
*
|
||||
@@ -27,18 +40,44 @@ public class SecurityConfig {
|
||||
@Bean
|
||||
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||
http.cors(Customizer.withDefaults())
|
||||
.csrf(custom -> custom.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()))
|
||||
.csrf(AbstractHttpConfigurer::disable)
|
||||
.authorizeHttpRequests(
|
||||
custom ->
|
||||
custom
|
||||
request ->
|
||||
request
|
||||
.requestMatchers("/auth/**")
|
||||
.permitAll()
|
||||
.requestMatchers("/rest/**")
|
||||
.authenticated()
|
||||
.requestMatchers(HttpMethod.OPTIONS, "/**")
|
||||
.permitAll()
|
||||
.anyRequest()
|
||||
.permitAll())
|
||||
.sessionManagement(
|
||||
manager -> manager.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
|
||||
.httpBasic(AbstractHttpConfigurer::disable)
|
||||
.formLogin(AbstractHttpConfigurer::disable);
|
||||
.formLogin(AbstractHttpConfigurer::disable)
|
||||
.authenticationProvider(authenticationProvider())
|
||||
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
|
||||
|
||||
return http.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public PasswordEncoder passwordEncoder() {
|
||||
return new BCryptPasswordEncoder();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public AuthenticationProvider authenticationProvider() {
|
||||
DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
|
||||
authProvider.setUserDetailsService(userService.userDetailsService());
|
||||
authProvider.setPasswordEncoder(passwordEncoder());
|
||||
return authProvider;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public AuthenticationManager authenticationManager(AuthenticationConfiguration config)
|
||||
throws Exception {
|
||||
return config.getAuthenticationManager();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,6 +55,6 @@ public class SwaggerConfig {
|
||||
openApi.addSecurityItem(new SecurityRequirement().addList("bearerAuth"));
|
||||
openApi.setComponents(components);
|
||||
|
||||
return new OpenAPI().info(info);
|
||||
return openApi;
|
||||
}
|
||||
}
|
||||
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
package br.com.tasknoteapp.java_api.controller;
|
||||
|
||||
import br.com.tasknoteapp.java_api.auth.JwtAuthenticationResponse;
|
||||
import br.com.tasknoteapp.java_api.auth.LoginRequest;
|
||||
import br.com.tasknoteapp.java_api.exception.UserAlreadyExistsException;
|
||||
import br.com.tasknoteapp.java_api.service.AuthService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/auth")
|
||||
@Tag(name = "Authentication", description = "Authentication controller.")
|
||||
@AllArgsConstructor
|
||||
public class AuthenticationController {
|
||||
|
||||
private final AuthService authService;
|
||||
|
||||
/**
|
||||
* Authenticate a user given his email and password.
|
||||
*
|
||||
* @param loginRequest User data containing email and password.
|
||||
* @return OK if authenticated, 401 - Unauthorized otherwise
|
||||
*/
|
||||
@PostMapping(path = "/signin", consumes = "application/json", produces = "application/json")
|
||||
@Operation(
|
||||
summary = "SigIn an existing user",
|
||||
description = "SigIn an existing user given his email and password",
|
||||
responses = {
|
||||
@ApiResponse(responseCode = "200", description = "User successfully logged in"),
|
||||
@ApiResponse(responseCode = "400", description = "Wrong or missing information"),
|
||||
})
|
||||
public JwtAuthenticationResponse signin(@RequestBody @Valid LoginRequest loginRequest) {
|
||||
String token = authService.signin(loginRequest);
|
||||
return new JwtAuthenticationResponse(token);
|
||||
}
|
||||
|
||||
/**
|
||||
* Signup a new user.
|
||||
*
|
||||
* @param loginRequest User data with email and password.
|
||||
* @return JwtAuthenticationResponse containing user token
|
||||
* @throws UserAlreadyExistsException
|
||||
*/
|
||||
@PutMapping(path = "/signup", consumes = "application/json", produces = "application/json")
|
||||
@Operation(
|
||||
summary = "Signup a new user",
|
||||
description = "Signup a new user given his email and password",
|
||||
responses = {
|
||||
@ApiResponse(responseCode = "201", description = "User successfully created and saved"),
|
||||
@ApiResponse(responseCode = "400", description = "Wrong or missing information"),
|
||||
@ApiResponse(responseCode = "409", description = "User already exists")
|
||||
})
|
||||
public ResponseEntity<JwtAuthenticationResponse> signup(
|
||||
@RequestBody @Valid LoginRequest loginRequest) {
|
||||
String token = authService.create(loginRequest);
|
||||
return ResponseEntity.status(HttpStatus.CREATED).body(new JwtAuthenticationResponse(token));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package br.com.tasknoteapp.java_api.controller;
|
||||
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/rest/some")
|
||||
public class SomeController {
|
||||
|
||||
@GetMapping
|
||||
public String some() {
|
||||
return "Some";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package br.com.tasknoteapp.java_api.entity;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.GenerationType;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.Table;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import lombok.Data;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
|
||||
/** This class represents a User in the database. */
|
||||
@Data
|
||||
@Entity
|
||||
@Table(name = "users")
|
||||
public class UserEntity implements UserDetails {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
@Column(unique = true, nullable = false)
|
||||
private String email;
|
||||
|
||||
@Column(nullable = false)
|
||||
private String password;
|
||||
|
||||
@Column(nullable = false)
|
||||
private Boolean admin;
|
||||
|
||||
@Column(name = "created_at", nullable = false)
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
@Column(name = "inactivated_at", nullable = true)
|
||||
private LocalDateTime inactivatedAt;
|
||||
|
||||
@Override
|
||||
public Collection<? extends GrantedAuthority> getAuthorities() {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsername() {
|
||||
// email in our case
|
||||
return email;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAccountNonExpired() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAccountNonLocked() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCredentialsNonExpired() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabled() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
package br.com.tasknoteapp.java_api.exception;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
|
||||
@ResponseStatus(code = HttpStatus.CONFLICT)
|
||||
public class UserAlreadyExistsException extends ResponseStatusException {
|
||||
|
||||
public UserAlreadyExistsException() {
|
||||
super(HttpStatus.CONFLICT, "User already exists");
|
||||
}
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
package br.com.tasknoteapp.java_api.exception;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
|
||||
@ResponseStatus(code = HttpStatus.NOT_FOUND)
|
||||
public class UserNotFoundException extends ResponseStatusException {
|
||||
|
||||
public UserNotFoundException() {
|
||||
super(HttpStatus.NOT_FOUND, "User not found");
|
||||
}
|
||||
}
|
||||
+74
@@ -0,0 +1,74 @@
|
||||
package br.com.tasknoteapp.java_api.filter;
|
||||
|
||||
import br.com.tasknoteapp.java_api.service.JwtService;
|
||||
import br.com.tasknoteapp.java_api.service.UserService;
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.util.Objects;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.context.SecurityContext;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.filter.OncePerRequestFilter;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
public class JwtAuthenticationFilter extends OncePerRequestFilter {
|
||||
|
||||
@Autowired private UserService userService;
|
||||
|
||||
@Autowired private JwtService jwtService;
|
||||
|
||||
@Override
|
||||
protected void doFilterInternal(
|
||||
@NonNull HttpServletRequest request,
|
||||
@NonNull HttpServletResponse response,
|
||||
@NonNull FilterChain filterChain)
|
||||
throws ServletException, IOException {
|
||||
final String authorizationHeader = request.getHeader("Authorization");
|
||||
|
||||
log.info("-1-authorizationHeader {}", authorizationHeader);
|
||||
|
||||
if (Objects.isNull(authorizationHeader) || authorizationHeader.isBlank()) {
|
||||
log.info("-2-authorizationHeader {}", authorizationHeader);
|
||||
filterChain.doFilter(request, response);
|
||||
return;
|
||||
}
|
||||
|
||||
log.info("-3-authorizationHeader {}", authorizationHeader);
|
||||
|
||||
String jwtToken = authorizationHeader.substring(7);
|
||||
String email = jwtService.getEmailFromToken(jwtToken);
|
||||
|
||||
log.info("-4-email {}", email);
|
||||
|
||||
if (!Objects.isNull(email)
|
||||
&& Objects.isNull(SecurityContextHolder.getContext().getAuthentication())) {
|
||||
log.info("-5-email {}", email);
|
||||
UserDetails user = userService.userDetailsService().loadUserByUsername(email);
|
||||
|
||||
if (jwtService.validateTokenAndUser(jwtToken, user)) {
|
||||
log.info("-6-jwtToken {}", jwtToken);
|
||||
SecurityContext context = SecurityContextHolder.createEmptyContext();
|
||||
|
||||
UsernamePasswordAuthenticationToken authToken =
|
||||
new UsernamePasswordAuthenticationToken(
|
||||
user.getUsername(), user.getPassword(), user.getAuthorities());
|
||||
|
||||
authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
|
||||
context.setAuthentication(authToken);
|
||||
SecurityContextHolder.setContext(context);
|
||||
}
|
||||
}
|
||||
|
||||
filterChain.doFilter(request, response);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package br.com.tasknoteapp.java_api.repository;
|
||||
|
||||
import br.com.tasknoteapp.java_api.entity.UserEntity;
|
||||
import java.util.Optional;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
/** This interface contains methods to access the user table in the database. */
|
||||
public interface UserRepository extends JpaRepository<UserEntity, Long> {
|
||||
|
||||
Optional<UserEntity> findByEmail(String email);
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package br.com.tasknoteapp.java_api.service;
|
||||
|
||||
import br.com.tasknoteapp.java_api.auth.LoginRequest;
|
||||
import br.com.tasknoteapp.java_api.entity.UserEntity;
|
||||
import java.util.Optional;
|
||||
import org.springframework.security.core.userdetails.User;
|
||||
|
||||
public interface AuthService {
|
||||
|
||||
/**
|
||||
* Create a new user in the app.
|
||||
*
|
||||
* @param login User details with email and password.
|
||||
* @return Token
|
||||
*/
|
||||
public String create(LoginRequest login);
|
||||
|
||||
/**
|
||||
* Find a user by email in the database.
|
||||
*
|
||||
* @param email The user email.
|
||||
* @return Optional of a UserEntity instance.
|
||||
*/
|
||||
public Optional<UserEntity> findByEmail(String email);
|
||||
|
||||
/**
|
||||
* Load a user from the database given his email.
|
||||
*
|
||||
* @param email The user email.
|
||||
* @return User with found record.
|
||||
*/
|
||||
public User loadUserByUsername(String email);
|
||||
|
||||
/**
|
||||
* SignIn a user given his email and password.
|
||||
*
|
||||
* @param login User details with email and password.
|
||||
* @return Token
|
||||
*/
|
||||
public String signin(LoginRequest login);
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package br.com.tasknoteapp.java_api.service;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Map;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
|
||||
public interface JwtService {
|
||||
|
||||
public String getEmailFromToken(String token);
|
||||
|
||||
public LocalDateTime extractExpiration(String token);
|
||||
|
||||
public String generateToken(String username);
|
||||
|
||||
public String createToken(Map<String, Object> claims, String email);
|
||||
|
||||
public boolean isTokenExpired(String token);
|
||||
|
||||
public boolean validateTokenAndUser(String token, UserDetails user);
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package br.com.tasknoteapp.java_api.service;
|
||||
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
|
||||
public interface UserService {
|
||||
UserDetailsService userDetailsService();
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
package br.com.tasknoteapp.java_api.service.impl;
|
||||
|
||||
import br.com.tasknoteapp.java_api.auth.LoginRequest;
|
||||
import br.com.tasknoteapp.java_api.entity.UserEntity;
|
||||
import br.com.tasknoteapp.java_api.exception.UserAlreadyExistsException;
|
||||
import br.com.tasknoteapp.java_api.exception.UserNotFoundException;
|
||||
import br.com.tasknoteapp.java_api.repository.UserRepository;
|
||||
import br.com.tasknoteapp.java_api.service.AuthService;
|
||||
import br.com.tasknoteapp.java_api.service.JwtService;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Optional;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.userdetails.User;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
@AllArgsConstructor
|
||||
class AuthServiceImpl implements AuthService {
|
||||
|
||||
private final UserRepository userRepository;
|
||||
|
||||
private final PasswordEncoder passwordEncoder;
|
||||
|
||||
private final JwtService jwtService;
|
||||
|
||||
private final AuthenticationManager authenticationManager;
|
||||
|
||||
/**
|
||||
* Create a new user in the app.
|
||||
*
|
||||
* @param login User details with email and password.
|
||||
* @return Token
|
||||
*/
|
||||
@Override
|
||||
public String create(LoginRequest login) {
|
||||
log.info("Creating user! {}", login.email());
|
||||
|
||||
if (findByEmail(login.email()).isPresent()) {
|
||||
throw new UserAlreadyExistsException();
|
||||
}
|
||||
|
||||
UserEntity user = new UserEntity();
|
||||
user.setEmail(login.email());
|
||||
user.setPassword(passwordEncoder.encode(login.password()));
|
||||
user.setAdmin(login.email().equals("ricardompcampos@gmail.com"));
|
||||
user.setCreatedAt(LocalDateTime.now());
|
||||
userRepository.save(user);
|
||||
|
||||
String token = jwtService.generateToken(user.getEmail());
|
||||
|
||||
log.info("User created! Token {}", token);
|
||||
return token;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a user by email in the database.
|
||||
*
|
||||
* @param email The user email.
|
||||
* @return Optional of a UserEntity instance.
|
||||
*/
|
||||
@Override
|
||||
public Optional<UserEntity> findByEmail(String email) {
|
||||
return userRepository.findByEmail(email);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a user from the database given his email.
|
||||
*
|
||||
* @param email The user email.
|
||||
* @return User with found record.
|
||||
*/
|
||||
@Override
|
||||
public User loadUserByUsername(String email) {
|
||||
Optional<UserEntity> user = userRepository.findByEmail(email);
|
||||
if (user.isEmpty()) {
|
||||
throw new UserNotFoundException();
|
||||
}
|
||||
|
||||
return new User(user.get().getEmail(), user.get().getPassword(), new ArrayList<>());
|
||||
}
|
||||
|
||||
/**
|
||||
* SignIn a user given his email and password.
|
||||
*
|
||||
* @param login User details with email and password.
|
||||
* @return Token
|
||||
*/
|
||||
@Override
|
||||
public String signin(LoginRequest login) {
|
||||
log.info("Creating user! {}", login.email());
|
||||
|
||||
Optional<UserEntity> user = findByEmail(login.email());
|
||||
if (user.isEmpty()) {
|
||||
throw new UserNotFoundException();
|
||||
}
|
||||
|
||||
authenticationManager.authenticate(
|
||||
new UsernamePasswordAuthenticationToken(login.email(), login.password()));
|
||||
|
||||
String token = jwtService.generateToken(user.get().getEmail());
|
||||
|
||||
log.info("User authenticated! Token {}", token);
|
||||
return token;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package br.com.tasknoteapp.java_api.service.impl;
|
||||
|
||||
import br.com.tasknoteapp.java_api.service.JwtService;
|
||||
import io.jsonwebtoken.Claims;
|
||||
import io.jsonwebtoken.Jwts;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import javax.crypto.SecretKey;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class JwtServiceImpl implements JwtService {
|
||||
|
||||
private final long SECOND = 1000;
|
||||
private final long MINUTE = SECOND * 60;
|
||||
private final long EXPIRATION_TIME = MINUTE * 30;
|
||||
private final SecretKey KEY = Jwts.SIG.HS256.key().build();
|
||||
|
||||
@Override
|
||||
public String getEmailFromToken(String token) {
|
||||
return extractClaim(token, Claims::getSubject);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocalDateTime extractExpiration(String token) {
|
||||
Date date = extractClaim(token, Claims::getExpiration);
|
||||
return date.toInstant().atZone(java.time.ZoneId.systemDefault()).toLocalDateTime();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String generateToken(String username) {
|
||||
Map<String, Object> claims = new HashMap<>();
|
||||
return createToken(claims, username);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String createToken(Map<String, Object> claims, String email) {
|
||||
return Jwts.builder()
|
||||
.issuer("Java-API")
|
||||
.subject(email)
|
||||
.issuedAt(new Date(System.currentTimeMillis()))
|
||||
.expiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME))
|
||||
.signWith(KEY)
|
||||
.compact();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTokenExpired(String token) {
|
||||
return extractExpiration(token).isBefore(LocalDateTime.now());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean validateTokenAndUser(String token, UserDetails user) {
|
||||
final String email = user.getUsername();
|
||||
return !isTokenExpired(token) && getEmailFromToken(token).equals(email);
|
||||
}
|
||||
|
||||
private <T> T extractClaim(String token, Function<Claims, T> claimsResolver) {
|
||||
final Claims claims = extractAllClaims(token);
|
||||
return claimsResolver.apply(claims);
|
||||
}
|
||||
|
||||
private Claims extractAllClaims(String token) {
|
||||
return Jwts.parser().verifyWith(KEY).build().parseSignedClaims(token).getPayload();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package br.com.tasknoteapp.java_api.service.impl;
|
||||
|
||||
import br.com.tasknoteapp.java_api.entity.UserEntity;
|
||||
import br.com.tasknoteapp.java_api.repository.UserRepository;
|
||||
import br.com.tasknoteapp.java_api.service.UserService;
|
||||
import java.util.Optional;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class UserServiceImpl implements UserService {
|
||||
|
||||
private final UserRepository userRepository;
|
||||
|
||||
@Override
|
||||
public UserDetailsService userDetailsService() {
|
||||
return new UserDetailsService() {
|
||||
@Override
|
||||
public UserDetails loadUserByUsername(String email) {
|
||||
Optional<UserEntity> user = userRepository.findByEmail(email);
|
||||
if (user.isEmpty()) {
|
||||
throw new RuntimeException("User not found: " + email);
|
||||
}
|
||||
|
||||
return user.get();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
CREATE SCHEMA IF NOT EXISTS tasknote;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS tasknote.users (
|
||||
id SERIAL,
|
||||
email VARCHAR(100) NOT NULL,
|
||||
password VARCHAR(255) NOT NULL,
|
||||
admin BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
created_at TIMESTAMP NOT NULL,
|
||||
inactivated_at TIMESTAMP,
|
||||
CONSTRAINT users_pk PRIMARY KEY (id),
|
||||
CONSTRAINT users_email_uk UNIQUE (email)
|
||||
);
|
||||
Reference in New Issue
Block a user