diff --git a/java/services/auth/src/main/java/com/uva/authentication/api/UserAPI.java b/java/services/auth/src/main/java/com/uva/authentication/api/UserAPI.java index d61a328f4353b10ee58bec766bb2ba24b047cd82..6239c85caa1aeca7f4fa838df9a940e11d0907a0 100644 --- a/java/services/auth/src/main/java/com/uva/authentication/api/UserAPI.java +++ b/java/services/auth/src/main/java/com/uva/authentication/api/UserAPI.java @@ -53,7 +53,7 @@ public class UserAPI { */ public User registerUser(RegisterRequest registerRequest) { String url = USER_API_URL; - System.out.println(registerRequest + " " + registerRequest.getPassword()); + System.out.println(url + " " + registerRequest); ResponseEntity<User> userResponse = restTemplate.postForEntity(url, registerRequest, User.class); if (!userResponse.getStatusCode().is2xxSuccessful()) { String errorMessage = "Failed to register user: " + userResponse.getStatusCode() + ". " + userResponse.getBody(); @@ -80,4 +80,10 @@ public class UserAPI { restTemplate.put(url, body, id); } + public void deleteUser(User user) { + String url = USER_API_URL + "/{id}"; + + restTemplate.delete(url, user.getId()); + } + } diff --git a/java/services/auth/src/main/java/com/uva/authentication/config/RestTemplateConfig.java b/java/services/auth/src/main/java/com/uva/authentication/config/RestTemplateConfig.java index 9ab4fa43b1587585cfdf4c1cd6d80b9556a78606..dbcefe7ec34f65cca38b60db09be7355a98b73a7 100644 --- a/java/services/auth/src/main/java/com/uva/authentication/config/RestTemplateConfig.java +++ b/java/services/auth/src/main/java/com/uva/authentication/config/RestTemplateConfig.java @@ -7,19 +7,16 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.client.RestTemplate; -import com.uva.authentication.interceptor.AuthHttpInterceptor; - @Configuration public class RestTemplateConfig { @Autowired - private AuthHttpInterceptor jwtInterceptor; + private RestTemplateInterceptor interceptor; @Bean RestTemplate restTemplate() { RestTemplate restTemplate = new RestTemplate(); - restTemplate.setInterceptors(List.of(jwtInterceptor)); - + restTemplate.setInterceptors(List.of(interceptor)); return restTemplate; } diff --git a/java/services/auth/src/main/java/com/uva/authentication/config/RestTemplateInterceptor.java b/java/services/auth/src/main/java/com/uva/authentication/config/RestTemplateInterceptor.java new file mode 100644 index 0000000000000000000000000000000000000000..25b59946b262e6e8026f5f05ebd098e7dc9674be --- /dev/null +++ b/java/services/auth/src/main/java/com/uva/authentication/config/RestTemplateInterceptor.java @@ -0,0 +1,33 @@ +package com.uva.authentication.config; + +import org.springframework.http.HttpRequest; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpHeaders; +import org.springframework.http.client.ClientHttpRequestExecution; +import org.springframework.http.client.ClientHttpRequestInterceptor; +import org.springframework.http.client.ClientHttpResponse; +import org.springframework.stereotype.Component; + +import com.uva.authentication.utils.JwtUtil; + +import java.io.IOException; + +@Component +public class RestTemplateInterceptor implements ClientHttpRequestInterceptor { + + @Autowired + private JwtUtil jwtUtil; + + @Override + public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) + throws IOException { + + // Añadir el encabezado "Authorization" con el valor "Bearer <token>" + HttpHeaders headers = request.getHeaders(); + headers.add("Authorization", + "Bearer " + jwtUtil.getOwnInternalToken()); + + // Continuar con la solicitud + return execution.execute(request, body); + } +} diff --git a/java/services/auth/src/main/java/com/uva/authentication/controllers/AuthController.java b/java/services/auth/src/main/java/com/uva/authentication/controllers/AuthController.java index 83041cc56f5019ea047e41c56dae8224828f792a..7dfa56acc48bad46f862f259c15f90b8e172ad01 100644 --- a/java/services/auth/src/main/java/com/uva/authentication/controllers/AuthController.java +++ b/java/services/auth/src/main/java/com/uva/authentication/controllers/AuthController.java @@ -14,6 +14,7 @@ import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; @RestController +@RequestMapping("auth") @CrossOrigin(origins = "*") public class AuthController { @@ -23,55 +24,52 @@ public class AuthController { @PostMapping("/login") public ResponseEntity<?> login(@RequestBody LoginRequest loginRequest) { try { - String token = authService.login(loginRequest); - return ResponseEntity.ok(new JwtAuthResponse(token)); + return authService.login(loginRequest); } catch (HttpClientErrorException e) { if (e.getStatusCode() == HttpStatus.FORBIDDEN) { - return new ResponseEntity<String>(e.getMessage(), HttpStatus.FORBIDDEN); + return new ResponseEntity<>(e.getMessage(), HttpStatus.FORBIDDEN); } } - return new ResponseEntity<String>("Algo no fue bien", HttpStatus.UNAUTHORIZED); + return new ResponseEntity<>("Algo no fue bien", HttpStatus.UNAUTHORIZED); } @PostMapping("/register") public ResponseEntity<?> register(@RequestBody RegisterRequest registerRequest) { try { - String token = authService.register(registerRequest); - return ResponseEntity.ok(new JwtAuthResponse(token)); + return authService.register(registerRequest); } catch (HttpClientErrorException e) { - if (e.getStatusCode() == HttpStatus.CONFLICT) { - return new ResponseEntity<String>(e.getMessage(), HttpStatus.CONFLICT); - } - e.printStackTrace(System.err); + if (e.getStatusCode() == HttpStatus.CONFLICT) + return new ResponseEntity<>(e.getMessage(), HttpStatus.CONFLICT); } - return new ResponseEntity<String>("Algo no fue bien", HttpStatus.UNAUTHORIZED); + return new ResponseEntity<>("Algo no fue bien", HttpStatus.UNAUTHORIZED); } - private boolean validStrings(String... args) { - for (String arg : args) { - if (arg == null || arg.isBlank()) - return false; - } - return true; + @PostMapping("/password") + public ResponseEntity<?> changePassword(@RequestBody Map<String, String> json, + @RequestHeader(value = "Authorization", required = false) String authorization) { + if (authorization == null || !authorization.startsWith("Bearer ")) + return new ResponseEntity<>(HttpStatus.FORBIDDEN); + + String token = authorization.substring(7); + + String actualPassword = json.get("password"); + String newPassword = json.get("newPassword"); + + return authService.changePassword(token, actualPassword, newPassword); } - @PostMapping("/password") - public ResponseEntity<?> postMethodName(@RequestBody Map<String, String> json) { - // TODO adaptar a comportamiento de admin - String email = json.get("email"); - String actualPassword = json.get("actual"); - String newPassword = json.get("new"); + @PostMapping("/delete/{id}") + public Object postMethodName(@PathVariable int id, @RequestBody Map<String, String> json, + @RequestHeader(value = "Authorization", required = false) String authorization) { + if (authorization == null || !authorization.startsWith("Bearer ")) + return new ResponseEntity<>(HttpStatus.FORBIDDEN); - if (!validStrings(email, actualPassword, newPassword)) - return new ResponseEntity<Void>(HttpStatus.BAD_REQUEST); + String token = authorization.substring(7); - try { - // TODO extraer información del token? - String token = authService.changePassword(email, actualPassword, newPassword); - return ResponseEntity.ok(new JwtAuthResponse(token)); - } catch (Exception e) { - return new ResponseEntity<Void>(HttpStatus.BAD_REQUEST); - } + String actualPassword = json.get("password"); + + return authService.deleteUser(token, id, actualPassword); } + } diff --git a/java/services/auth/src/main/java/com/uva/authentication/controllers/TokenController.java b/java/services/auth/src/main/java/com/uva/authentication/controllers/TokenController.java new file mode 100644 index 0000000000000000000000000000000000000000..3e33376dd18629f48bd6863bd8beb06465455901 --- /dev/null +++ b/java/services/auth/src/main/java/com/uva/authentication/controllers/TokenController.java @@ -0,0 +1,47 @@ +package com.uva.authentication.controllers; + +import org.springframework.beans.factory.annotation.Autowired; +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 com.fasterxml.jackson.databind.JsonNode; +import com.uva.authentication.models.JwtAuth; +import com.uva.authentication.services.TokenService; + +@RestController +@RequestMapping("/token") +public class TokenController { + + @Autowired + private TokenService tokenService; + + @PostMapping("/validate") + public ResponseEntity<?> validateToken(@RequestBody JwtAuth tokenRequest) { + boolean isValid = tokenService.validateToken(tokenRequest.getToken()); + if (isValid) { + return ResponseEntity.ok("Token is valid"); + } else { + return new ResponseEntity<>("Token not valid or expired", HttpStatus.UNAUTHORIZED); + } + } + + @PostMapping("/info") + public ResponseEntity<?> getTokenInfo(@RequestBody JwtAuth tokenRequest) { + return tokenService.getTokenInf(tokenRequest.getToken()); + } + + @PostMapping("/service") + public ResponseEntity<?> identifyService(@RequestBody JsonNode request) { + JsonNode name = request.get("service"); + + if (name == null) + return new ResponseEntity<>("Missing required fields", HttpStatus.BAD_REQUEST); + + return tokenService.identifyService(name.asText()); + } + +} diff --git a/java/services/auth/src/main/java/com/uva/authentication/exceptions/GlobalExceptionHandler.java b/java/services/auth/src/main/java/com/uva/authentication/exceptions/GlobalExceptionHandler.java deleted file mode 100644 index 6d4b8d58dbb5560de50b5b2149cbe3f181687446..0000000000000000000000000000000000000000 --- a/java/services/auth/src/main/java/com/uva/authentication/exceptions/GlobalExceptionHandler.java +++ /dev/null @@ -1,51 +0,0 @@ -package com.uva.authentication.exceptions; - -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.ControllerAdvice; -import org.springframework.web.bind.annotation.ExceptionHandler; - -import java.time.LocalDateTime; -import java.util.HashMap; -import java.util.Map; - -@ControllerAdvice -public class GlobalExceptionHandler { - - @ExceptionHandler(HotelNotFoundException.class) - public ResponseEntity<Map<String, Object>> handleHotelNotFound(HotelNotFoundException ex) { - Map<String, Object> body = new HashMap<>(); - body.put("timestamp", LocalDateTime.now()); - body.put("message", ex.getMessage()); - - return new ResponseEntity<>(body, HttpStatus.NOT_FOUND); - } - - @ExceptionHandler(InvalidRequestException.class) - public ResponseEntity<Map<String, Object>> handleInvalidRequest(InvalidRequestException ex) { - Map<String, Object> body = new HashMap<>(); - body.put("timestamp", LocalDateTime.now()); - body.put("message", ex.getMessage()); - - return new ResponseEntity<>(body, HttpStatus.BAD_REQUEST); - } - - @ExceptionHandler(InvalidDateRangeException.class) - public ResponseEntity<Map<String, Object>> handleInvalidDateRange(InvalidDateRangeException ex) { - Map<String, Object> body = new HashMap<>(); - body.put("timestamp", LocalDateTime.now()); - body.put("message", ex.getMessage()); - - return new ResponseEntity<>(body, HttpStatus.BAD_REQUEST); - } - - // @ExceptionHandler(Exception.class) - // public ResponseEntity<Map<String, Object>> handleGeneralException(Exception - // ex) { - // Map<String, Object> body = new HashMap<>(); - // body.put("timestamp", LocalDateTime.now()); - // body.put("message", "An unexpected error occurred: " + ex.getMessage()); - - // return new ResponseEntity<>(body, HttpStatus.INTERNAL_SERVER_ERROR); - // } -} diff --git a/java/services/auth/src/main/java/com/uva/authentication/exceptions/HotelNotFoundException.java b/java/services/auth/src/main/java/com/uva/authentication/exceptions/HotelNotFoundException.java deleted file mode 100644 index c642139b421a5cf864218fa2d0063f955335c5b7..0000000000000000000000000000000000000000 --- a/java/services/auth/src/main/java/com/uva/authentication/exceptions/HotelNotFoundException.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.uva.authentication.exceptions; - -import org.springframework.http.HttpStatus; -import org.springframework.web.bind.annotation.ResponseStatus; - -@ResponseStatus(HttpStatus.NOT_FOUND) // Devuelve un 404 cuando se lanza la excepción -public class HotelNotFoundException extends RuntimeException { - public HotelNotFoundException(int id) { - super("Hotel not found with id: " + id); - } -} diff --git a/java/services/auth/src/main/java/com/uva/authentication/exceptions/InvalidDateRangeException.java b/java/services/auth/src/main/java/com/uva/authentication/exceptions/InvalidDateRangeException.java deleted file mode 100644 index c3dc917fb03495480007365b117e185521cf7bf2..0000000000000000000000000000000000000000 --- a/java/services/auth/src/main/java/com/uva/authentication/exceptions/InvalidDateRangeException.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.uva.authentication.exceptions; - -public class InvalidDateRangeException extends RuntimeException { - public InvalidDateRangeException(String message) { - super(message); - } -} diff --git a/java/services/auth/src/main/java/com/uva/authentication/exceptions/InvalidRequestException.java b/java/services/auth/src/main/java/com/uva/authentication/exceptions/InvalidRequestException.java deleted file mode 100644 index 499a320e58ecd7576cbfff39101db14395f0edbe..0000000000000000000000000000000000000000 --- a/java/services/auth/src/main/java/com/uva/authentication/exceptions/InvalidRequestException.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.uva.authentication.exceptions; - -import org.springframework.http.HttpStatus; -import org.springframework.web.bind.annotation.ResponseStatus; - -@ResponseStatus(HttpStatus.BAD_REQUEST) -public class InvalidRequestException extends RuntimeException { - public InvalidRequestException(String message) { - super(message); - } -} diff --git a/java/services/auth/src/main/java/com/uva/authentication/interceptor/AuthHttpInterceptor.java b/java/services/auth/src/main/java/com/uva/authentication/interceptor/AuthHttpInterceptor.java deleted file mode 100644 index b64a0f69a673dcac07e93616c3affad5f6be3be5..0000000000000000000000000000000000000000 --- a/java/services/auth/src/main/java/com/uva/authentication/interceptor/AuthHttpInterceptor.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.uva.authentication.interceptor; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.HttpRequest; -import org.springframework.http.client.ClientHttpRequestExecution; -import org.springframework.http.client.ClientHttpResponse; -import org.springframework.http.client.ClientHttpRequestInterceptor; -import org.springframework.stereotype.Component; - -import com.uva.authentication.models.remote.User; -import com.uva.authentication.models.remote.UserRol; -import com.uva.authentication.utils.JwtUtil; - -import java.io.IOException; - -@Component -public class AuthHttpInterceptor implements ClientHttpRequestInterceptor { - - @Autowired - private JwtUtil jwtUtil; - - private String token; - private final User USER = new User(-1, "auth", "auth@dev.com", null, UserRol.ADMIN); - - private String getAccessToken() { - if (token == null || token.isEmpty()) { - // TODO cambiar también si el token ha caducado - token = jwtUtil.generateToken(USER); - } - return token; - - } - - @Override - public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) - throws IOException { - // Generar o cargar el JWT token desde el bean JwtUtil - String jwtToken = getAccessToken(); - - // System.out.println("Using token " + jwtToken); - - // Agregar el token al encabezado Authorization - request.getHeaders().add("Authorization", "Bearer " + jwtToken); - - // Continuar con la ejecución de la solicitud - return execution.execute(request, body); - } -} diff --git a/java/services/auth/src/main/java/com/uva/authentication/models/AuthResponse.java b/java/services/auth/src/main/java/com/uva/authentication/models/JwtAuth.java similarity index 51% rename from java/services/auth/src/main/java/com/uva/authentication/models/AuthResponse.java rename to java/services/auth/src/main/java/com/uva/authentication/models/JwtAuth.java index 148730189d6c5a29df507bc3cd10eef7a9b274e6..1736439a1b93980155336707350f695a9f56c0ed 100644 --- a/java/services/auth/src/main/java/com/uva/authentication/models/AuthResponse.java +++ b/java/services/auth/src/main/java/com/uva/authentication/models/JwtAuth.java @@ -1,20 +1,16 @@ package com.uva.authentication.models; -import com.uva.authentication.models.remote.UserRol; - import lombok.AllArgsConstructor; +import lombok.Data; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; -@AllArgsConstructor -@NoArgsConstructor -@Setter @Getter -public class AuthResponse { - private int id; - private String name; - private String email; - private String password; - private UserRol rol; +@Setter +@Data +@NoArgsConstructor +@AllArgsConstructor +public class JwtAuth { + private String token; } diff --git a/java/services/auth/src/main/java/com/uva/authentication/models/JwtAuthResponse.java b/java/services/auth/src/main/java/com/uva/authentication/models/JwtAuthResponse.java deleted file mode 100644 index a9566954c35ce71864881e320843e270a998b9da..0000000000000000000000000000000000000000 --- a/java/services/auth/src/main/java/com/uva/authentication/models/JwtAuthResponse.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.uva.authentication.models; - -public class JwtAuthResponse { - private String token; - - public JwtAuthResponse(String token) { - this.token = token; - } - - // Getter - public String getToken() { - return token; - } -} diff --git a/java/services/auth/src/main/java/com/uva/authentication/models/TokenData.java b/java/services/auth/src/main/java/com/uva/authentication/models/TokenData.java new file mode 100644 index 0000000000000000000000000000000000000000..d503f9193f58a44dee2a0b184ee654af4893999f --- /dev/null +++ b/java/services/auth/src/main/java/com/uva/authentication/models/TokenData.java @@ -0,0 +1,55 @@ +package com.uva.authentication.models; + +import java.lang.reflect.Field; + +import com.auth0.jwt.interfaces.Claim; +import com.auth0.jwt.interfaces.DecodedJWT; + +import lombok.Data; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@Data +public class TokenData { + private Integer id; + private String name; + private String email; + private String rol; + private String service; + + private String subject; + private String audience; + private Long ttl; + + public TokenData(DecodedJWT decoded, long ttl) { + + subject = decoded.getSubject(); + audience = decoded.getAudience().get(0); + this.ttl = ttl; + + for (Field field : this.getClass().getDeclaredFields()) { + field.setAccessible(true); + + // Verificamos si el campo está en el mapa y asignamos el valor + Claim claim = decoded.getClaim(field.getName()); + if (!claim.isMissing()) { + try { + // Dependiendo del tipo de campo, asignamos el valor + if (field.getType() == Integer.class) { + field.set(this, Integer.parseInt(claim.asString())); + } else if (field.getType() == String.class) { + field.set(this, claim.asString()); + } + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + } + } + } + + public boolean isAdmin() { + return rol != null && rol == "ADMIN"; + } +} \ No newline at end of file diff --git a/java/services/auth/src/main/java/com/uva/authentication/models/TokenRequest.java b/java/services/auth/src/main/java/com/uva/authentication/models/TokenRequest.java new file mode 100644 index 0000000000000000000000000000000000000000..e63522e1a8660e71573fdb018aaae5f5596517ce --- /dev/null +++ b/java/services/auth/src/main/java/com/uva/authentication/models/TokenRequest.java @@ -0,0 +1,16 @@ +package com.uva.authentication.models; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Getter +@Setter +@Data +@NoArgsConstructor +@AllArgsConstructor +public class TokenRequest { + private String token; +} diff --git a/java/services/auth/src/main/java/com/uva/authentication/models/remote/User.java b/java/services/auth/src/main/java/com/uva/authentication/models/remote/User.java index ae8de366340950b1bb5af476600408446a2d3f32..10049fc82e340fab5885cbd523f0962fc03292f5 100644 --- a/java/services/auth/src/main/java/com/uva/authentication/models/remote/User.java +++ b/java/services/auth/src/main/java/com/uva/authentication/models/remote/User.java @@ -16,6 +16,7 @@ public class User extends RegisterRequest { public User(int id, String email, String password, String name, UserRol rol) { super(); + this.id = id; setEmail(email); setName(name); setPassword(password); diff --git a/java/services/auth/src/main/java/com/uva/authentication/models/remote/UserRol.java b/java/services/auth/src/main/java/com/uva/authentication/models/remote/UserRol.java index 2cb39bb4f6174faf20c7174269c835f7c6b80cc2..fe4d90dd1dd595f4c09ec699b452910352b406d5 100644 --- a/java/services/auth/src/main/java/com/uva/authentication/models/remote/UserRol.java +++ b/java/services/auth/src/main/java/com/uva/authentication/models/remote/UserRol.java @@ -1,5 +1,5 @@ package com.uva.authentication.models.remote; public enum UserRol { - ADMIN, AUTH, HOTEL_ADMIN, CLIENT + ADMIN, HOTEL_ADMIN, CLIENT } diff --git a/java/services/auth/src/main/java/com/uva/authentication/services/AuthService.java b/java/services/auth/src/main/java/com/uva/authentication/services/AuthService.java index 67cf0b7308c3979274697da16bc0f0f103227aa9..03f2b6c132f4bf9531f891d34471d6484197a9dd 100644 --- a/java/services/auth/src/main/java/com/uva/authentication/services/AuthService.java +++ b/java/services/auth/src/main/java/com/uva/authentication/services/AuthService.java @@ -3,12 +3,16 @@ package com.uva.authentication.services; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Service; import org.springframework.web.client.HttpClientErrorException; +import org.springframework.web.servlet.function.EntityResponse; import com.uva.authentication.api.UserAPI; +import com.uva.authentication.models.JwtAuth; import com.uva.authentication.models.LoginRequest; import com.uva.authentication.models.RegisterRequest; +import com.uva.authentication.models.TokenData; import com.uva.authentication.models.remote.User; import com.uva.authentication.utils.JwtUtil; import com.uva.authentication.utils.SecurityUtils; @@ -35,17 +39,17 @@ public class AuthService { * @return token for identify the user * @throws HttpClientErrorException(FORBIDDEN) if the credentials are invalid */ - public String login(LoginRequest loginRequest) { + public ResponseEntity<?> login(LoginRequest loginRequest) { User user = userAPI.getUserByEmail(loginRequest.getEmail()); - if (!authenticateUser(loginRequest, user)) { + if (!authenticateUser(loginRequest, user)) throw new HttpClientErrorException(HttpStatus.FORBIDDEN, "Invalid credentials"); - } - return jwtUtil.generateToken(user); + String token = jwtUtil.generateToken(user); + return ResponseEntity.ok(new JwtAuth(token)); } - public String register(RegisterRequest registerRequest) { + public ResponseEntity<?> register(RegisterRequest registerRequest) { String plainTextPassword = registerRequest.getPassword(); // Ciframos la contraseña String hashPass = SecurityUtils.encrypt(plainTextPassword); @@ -56,21 +60,66 @@ public class AuthService { BeanUtils.copyProperties(user, logReq); // Recuperamos la contraseña y lo loggeamos logReq.setPassword(plainTextPassword); - System.err.println(logReq); return login(logReq); } - public String changePassword(String email, String actualPass, String newPass) { + private boolean validStrings(String... args) { + for (String arg : args) { + if (arg == null || arg.isBlank()) + return false; + } + return true; + } + + private User getUser(String email, String password) { User user = userAPI.getUserByEmail(email); - // Validamos la anterior contraseña - if (SecurityUtils.checkPassword(actualPass, user.getPassword())) { + boolean correctPassword = SecurityUtils.checkPassword(password, user.getPassword()); + return correctPassword ? user : null; + } + + public ResponseEntity<?> changePassword(String token, String actualPass, String newPass) { + TokenData decoded = jwtUtil.decodeToken(token); + if (decoded == null) + return new ResponseEntity<>(HttpStatus.FORBIDDEN); + + String email = decoded.getEmail(); + User user = getUser(email, actualPass); + + boolean changePasswordAllowed = decoded.isAdmin() || user != null; + + if (user != null && !validStrings(actualPass, newPass)) + return new ResponseEntity<>(HttpStatus.BAD_REQUEST); + + if (changePasswordAllowed) { // Actualizamos la nueva String hashPass = SecurityUtils.encrypt(newPass); userAPI.changePassword(user, hashPass); // Hacemos un login con los nuevos datos return login(new LoginRequest(email, newPass)); } else { - throw new HttpClientErrorException(HttpStatus.FORBIDDEN, "Invalid credentials"); + return new ResponseEntity<>("Invalid credentials", HttpStatus.FORBIDDEN); + } + } + + public ResponseEntity<?> deleteUser(String token, int id, String password) { + TokenData decoded = jwtUtil.decodeToken(token); + if (decoded == null) + return new ResponseEntity<>(HttpStatus.FORBIDDEN); + + String email = decoded.getEmail(); + User user = getUser(email, password); + + boolean changePasswordAllowed = decoded.isAdmin() + || (user != null && user.getId() == id); + + if (user != null && !validStrings(password)) + return new ResponseEntity<>(HttpStatus.BAD_REQUEST); + + if (changePasswordAllowed) { + userAPI.deleteUser(user); + return new ResponseEntity<>(HttpStatus.ACCEPTED); + } else { + return new ResponseEntity<>("Invalid credentials", HttpStatus.FORBIDDEN); } } } diff --git a/java/services/auth/src/main/java/com/uva/authentication/services/TokenService.java b/java/services/auth/src/main/java/com/uva/authentication/services/TokenService.java new file mode 100644 index 0000000000000000000000000000000000000000..a2a00805f914c4aa95e29e244ece3826db25796c --- /dev/null +++ b/java/services/auth/src/main/java/com/uva/authentication/services/TokenService.java @@ -0,0 +1,35 @@ +package com.uva.authentication.services; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Service; + +import com.uva.authentication.models.JwtAuth; +import com.uva.authentication.models.TokenData; +import com.uva.authentication.utils.JwtUtil; + +@Service +public class TokenService { + + @Autowired + private JwtUtil jwtUtil; + + public boolean validateToken(String token) { + return jwtUtil.validate(token) != null; + } + + public ResponseEntity<?> identifyService(String name) { + if (name == null) + return new ResponseEntity<>("Token has expire or is malformed", HttpStatus.FORBIDDEN); + String token = jwtUtil.generateInternalToken(name); + return ResponseEntity.ok(new JwtAuth(token)); + } + + public ResponseEntity<?> getTokenInf(String token) { + TokenData decoded = jwtUtil.decodeToken(token); + if (decoded == null) + return new ResponseEntity<>("Token has expire or is malformed", HttpStatus.FORBIDDEN); + return ResponseEntity.ok(decoded); + } +} diff --git a/java/services/auth/src/main/java/com/uva/authentication/utils/JwtUtil.java b/java/services/auth/src/main/java/com/uva/authentication/utils/JwtUtil.java index 7b5bbf50066d94d8de2fced81890d5ce8ea93569..d633043cce2da7d19a75b7bf32db38bd52905e5f 100644 --- a/java/services/auth/src/main/java/com/uva/authentication/utils/JwtUtil.java +++ b/java/services/auth/src/main/java/com/uva/authentication/utils/JwtUtil.java @@ -1,44 +1,117 @@ package com.uva.authentication.utils; import java.util.Date; - import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpStatus; import org.springframework.stereotype.Component; +import org.springframework.web.client.HttpClientErrorException; import com.auth0.jwt.JWT; import com.auth0.jwt.algorithms.Algorithm; +import com.uva.authentication.models.TokenData; import com.uva.authentication.models.remote.User; +import com.auth0.jwt.interfaces.DecodedJWT; + +import java.time.Instant; + @Component public class JwtUtil { + @Value("${security.jwt.kid}") + private String kid; + @Value("${security.jwt.secret-key}") private String secretKey; - @Value("${security.jwt.kid}") - private String kid; + @Value("${security.jwt.internal.expiration}") + private long intJwtExpiration; + + @Value("${security.jwt.external.expiration}") + private long extJwtExpiration; + + private String token; + + private static final String SERVICE = "AUTH_SERVICES"; - @Value("${security.jwt.expiration-time}") - private long jwtExpiration; + public String getOwnInternalToken() { + + // Si no hay token, no es valido o quedan 10 seg para caducar se genera otro + if (token == null || validate(token) == null || + decodeToken(token).getTtl() <= 10) { + token = generateInternalToken(SERVICE); + } + + return token; - public long getExpirationTime() { - return jwtExpiration; + } + + public String generateInternalToken(String service) { + String email = service.toLowerCase() + "@internal.com"; + Algorithm algorithm = Algorithm.HMAC256(secretKey); + + return JWT + .create() + + .withKeyId(kid) + .withIssuedAt(new Date()) + .withExpiresAt(new Date(System.currentTimeMillis() + intJwtExpiration * 1000)) + + .withSubject(service) + .withAudience("INTERNAL") + + // DATA + .withClaim("service", service) + .withClaim("email", email) + + .sign(algorithm); } public String generateToken(User user) { Algorithm algorithm = Algorithm.HMAC256(secretKey); + return JWT .create() + .withKeyId(kid) + .withIssuedAt(new Date()) + .withExpiresAt(new Date(System.currentTimeMillis() + extJwtExpiration * 1000)) + + .withSubject(SERVICE) + .withAudience("EXTERNAL") + + // DATA .withClaim("id", user.getId()) .withClaim("name", user.getName()) .withClaim("email", user.getEmail()) .withClaim("rol", user.getRol().toString()) - .withIssuedAt(new Date()) - .withExpiresAt(new Date(System.currentTimeMillis() + jwtExpiration * 1000)) + .sign(algorithm); } - // TODO estaría guapo recuperar métodos de validación para el token de petición - // para este servicio + public DecodedJWT validate(String token) { + try { + return JWT.require(Algorithm.HMAC256(secretKey)).build().verify(token); + } catch (Exception e) { + return null; + } + } + + public TokenData decodeToken(String token) { + DecodedJWT decoded = validate(token); + if (decoded == null) + return null; + return new TokenData(decoded, calculateTTL(decoded)); + } + + private long calculateTTL(DecodedJWT decodedJWT) { + if (decodedJWT == null) + throw new HttpClientErrorException(HttpStatus.FORBIDDEN); + + long exp = decodedJWT.getExpiresAt().toInstant().getEpochSecond(); + long now = Instant.now().getEpochSecond(); + + return exp - now; + } + } diff --git a/java/services/auth/src/main/resources/application.properties b/java/services/auth/src/main/resources/application.properties index c9d72501a6b64ab7f91a39562614adfa601ef98e..ad4a9a515522af4efcda2a87f59dc1902879136f 100644 --- a/java/services/auth/src/main/resources/application.properties +++ b/java/services/auth/src/main/resources/application.properties @@ -3,7 +3,8 @@ server.port=8101 security.jwt.secret-key=MiClaveDeSeguridadMuyLargaParaQueNoFalleSpringBoot # 1h in millisecond -security.jwt.expiration-time=3600000 +security.jwt.external.expiration=3600 +security.jwt.internal.expiration=20 security.jwt.kid=cYz3kNRLAirxVhHXQ5rh5xKrOwHwZVui external.services.users.url=http://localhost:8201/users \ No newline at end of file