diff --git a/backend-service/src/main/java/co/teamsphere/api/config/JWTTokenProvider.java b/backend-service/src/main/java/co/teamsphere/api/config/JWTTokenProvider.java index f1c5334..bcc875a 100644 --- a/backend-service/src/main/java/co/teamsphere/api/config/JWTTokenProvider.java +++ b/backend-service/src/main/java/co/teamsphere/api/config/JWTTokenProvider.java @@ -1,21 +1,23 @@ package co.teamsphere.api.config; -import java.security.PrivateKey; -import java.util.Collection; -import java.util.Date; -import java.util.HashSet; - +import co.teamsphere.api.config.properties.JwtProperties; +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.ExpiredJwtException; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.MalformedJwtException; import io.jsonwebtoken.SignatureAlgorithm; +import io.jsonwebtoken.UnsupportedJwtException; +import io.jsonwebtoken.security.SignatureException; import lombok.extern.slf4j.Slf4j; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; - - -import io.jsonwebtoken.Claims; -import io.jsonwebtoken.Jwts; import org.springframework.stereotype.Service; -import co.teamsphere.api.config.properties.JwtProperties; +import java.security.PrivateKey; +import java.util.Collection; +import java.util.Date; +import java.util.HashSet; +import java.util.UUID; @Service @Slf4j @@ -29,32 +31,34 @@ public JWTTokenProvider(PrivateKey privateKey, JwtProperties jwtProperties) { } - public String generateJwtToken(Authentication authentication) { + public String generateJwtToken(Authentication authentication, UUID userId) { log.info("Generating JWT..."); var currentDate = new Date(); + String authoritiesString = populateAuthorities(authentication.getAuthorities()); + return Jwts.builder() .setHeaderParam("typ", "JWT") .setIssuer("Teamsphere.co") - .setSubject(authentication.getName()) + .setSubject(userId.toString()) .setAudience(jwtProperties.getAudience()) .setIssuedAt(currentDate) .setNotBefore(currentDate) .setExpiration(new Date(currentDate.getTime()+86400000)) .claim("email", authentication.getName()) - .claim("authorities", "ROLE_USER") + .claim("authorities", authoritiesString) .signWith(privateKey, SignatureAlgorithm.RS256) .compact(); } - public String generateJwtTokenFromEmail(String email) { + public String generateJwtTokenFromEmail(String email, UUID userId) { log.info("Generating JWT..."); var currentDate = new Date(); return Jwts.builder() .setHeaderParam("typ", "JWT") .setIssuer("Teamsphere.co") - .setSubject(email) + .setSubject(userId.toString()) .setAudience(jwtProperties.getAudience()) .setIssuedAt(currentDate) .setNotBefore(currentDate) @@ -66,24 +70,55 @@ public String generateJwtTokenFromEmail(String email) { } public String getEmailFromToken(String token) { - log.info("parsing claims ----------- "); - - token = token.substring(7); - - Claims claims= Jwts.parserBuilder() - .setSigningKey(privateKey) - .build() - .parseClaimsJws(token) - .getBody(); - + Claims claims = parseTokenForClaims(token); return String.valueOf(claims.get("email")); } + public UUID getIdFromToken(String token) { + Claims claims = parseTokenForClaims(token); + return UUID.fromString(claims.getSubject()); + } + public String populateAuthorities(Collection collection) { - var authoritieSet = new HashSet(); + var authoritiesSet = new HashSet(); for(GrantedAuthority authority:collection) { - authoritieSet.add(authority.getAuthority()); + authoritiesSet.add(authority.getAuthority()); } - return String.join(",", authoritieSet); + return String.join(",", authoritiesSet); } + + private Claims parseTokenForClaims(String token) { + log.info("Parsing claims for token..."); + + if (token == null || !token.startsWith("Bearer ")) { + log.error("Invalid token format: missing 'Bearer ' prefix or token is null"); + throw new IllegalArgumentException("Invalid token format"); + } + + String actualToken = token.substring(7); + + try { + return Jwts.parserBuilder() + .setSigningKey(privateKey) + .build() + .parseClaimsJws(actualToken) + .getBody(); + } catch (ExpiredJwtException e) { + log.warn("Expired JWT token: {}", e.getMessage()); + throw e; // Re-throw the specific ExpiredJwtException + } catch (MalformedJwtException e) { + log.warn("Invalid JWT token: {}", e.getMessage()); + throw e; // Re-throw the specific MalformedJwtException + } catch (SignatureException e) { + log.warn("Invalid JWT signature: {}", e.getMessage()); + throw e; // Re-throw the specific SignatureException + } catch (UnsupportedJwtException e) { + log.warn("Unsupported JWT token: {}", e.getMessage()); + throw e; // Re-throw the specific UnsupportedJwtException + } catch (Exception e) { + log.error("Unexpected error parsing JWT token: {}", e.getMessage()); + throw new RuntimeException("Error parsing JWT token", e); // Catch any other unexpected errors + } + } + } diff --git a/backend-service/src/main/java/co/teamsphere/api/config/JWTTokenValidator.java b/backend-service/src/main/java/co/teamsphere/api/config/JWTTokenValidator.java index 11628e6..b3af241 100644 --- a/backend-service/src/main/java/co/teamsphere/api/config/JWTTokenValidator.java +++ b/backend-service/src/main/java/co/teamsphere/api/config/JWTTokenValidator.java @@ -40,7 +40,7 @@ protected void doFilterInternal( @NotNull HttpServletResponse response, @NotNull FilterChain filterChain ) throws ServletException, IOException { - String jwt =request.getHeader(JWTTokenConst.HEADER); + String jwt = request.getHeader(JWTTokenConst.HEADER); if (jwt != null && jwt.startsWith("Bearer ")) { try { @@ -59,9 +59,13 @@ protected void doFilterInternal( throw new JwtException("Invalid audience: " + audience); } - String username = claim.getSubject(); + String username = claim.get("email", String.class); String authorities = claim.get("authorities", String.class); + if (username == null || authorities == null) { + throw new JwtException("Missing email or authorities in JWT claims"); + } + List auths = AuthorityUtils.commaSeparatedStringToAuthorityList(authorities); Authentication auth = new UsernamePasswordAuthenticationToken(username, null ,auths); diff --git a/backend-service/src/main/java/co/teamsphere/api/controller/AuthController.java b/backend-service/src/main/java/co/teamsphere/api/controller/AuthController.java index 95fc814..253e4f0 100644 --- a/backend-service/src/main/java/co/teamsphere/api/controller/AuthController.java +++ b/backend-service/src/main/java/co/teamsphere/api/controller/AuthController.java @@ -138,7 +138,7 @@ public ResponseEntity refreshTest(@RequestBody RefreshTokenRequest request) t log.info("Generating new JWT token"); var user = rToken.getUser(); - String jwtToken = jwtTokenProvider.generateJwtTokenFromEmail(user.getEmail()); + String jwtToken = jwtTokenProvider.generateJwtTokenFromEmail(user.getEmail(), user.getId()); String newRefreshToken = refreshTokenService.replaceRefreshToken(user.getEmail()); if (newRefreshToken == null) { log.error("Error during refresh token replacement"); @@ -276,9 +276,10 @@ public ResponseEntity authenticateWithGoogleMethod( log.info("New user created with email: {}", email); } + // Just incase if (googleUser == null) { - log.error("Error during Google authentication, user still came out as null"); - return new ResponseEntity<>(new AuthResponse("This is still!", "", false), HttpStatus.INTERNAL_SERVER_ERROR); + log.error("Error during Google authentication, user still came out as null!"); + throw new Exception("Error during Google authentication!"); } // Load UserDetails and set authentication context @@ -286,7 +287,7 @@ public ResponseEntity authenticateWithGoogleMethod( SecurityContextHolder.getContext().setAuthentication(authentication); // Generate JWT token - String token = jwtTokenProvider.generateJwtToken(authentication); + String token = jwtTokenProvider.generateJwtToken(authentication, googleUser.getId()); RefreshToken refreshToken = createRefreshToken(googleUser.getId().toString(), email); AuthResponse authResponse = new AuthResponse(token, refreshToken.getRefreshToken(), true); diff --git a/backend-service/src/main/java/co/teamsphere/api/controller/ChatController.java b/backend-service/src/main/java/co/teamsphere/api/controller/ChatController.java index 763bd39..5a27d09 100644 --- a/backend-service/src/main/java/co/teamsphere/api/controller/ChatController.java +++ b/backend-service/src/main/java/co/teamsphere/api/controller/ChatController.java @@ -3,6 +3,7 @@ import co.teamsphere.api.DTO.ChatDTO; import co.teamsphere.api.DTO.ChatSummaryDTO; import co.teamsphere.api.DTOmapper.ChatDTOMapper; +import co.teamsphere.api.config.JWTTokenProvider; import co.teamsphere.api.exception.ChatException; import co.teamsphere.api.exception.UserException; import co.teamsphere.api.models.Chat; @@ -42,16 +43,20 @@ public class ChatController { private final ChatService chatService; private final UserService userService; - + private final ChatDTOMapper chatDTOMapper; + private final JWTTokenProvider jwtTokenProvider; + public ChatController(ChatService chatService, UserService userService, - ChatDTOMapper chatDTOMapper + ChatDTOMapper chatDTOMapper, + JWTTokenProvider jwtTokenProvider ) { this.chatService = chatService; this.userService = userService; this.chatDTOMapper = chatDTOMapper; + this.jwtTokenProvider = jwtTokenProvider; } @PostMapping("/single") @@ -75,8 +80,8 @@ public ChatController(ChatService chatService, public ResponseEntity creatChatHandler(@RequestBody SingleChatRequest singleChatRequest, @RequestHeader("Authorization") String jwt) throws UserException { log.info("single chat --------"); - User reqUser = userService.findUserProfile(jwt); - Chat chat = chatService.createChat(reqUser.getId(),singleChatRequest.getUserId(),false); + UUID reqUserId = jwtTokenProvider.getIdFromToken(jwt); + Chat chat = chatService.createChat(reqUserId, singleChatRequest.getUserId(),false); ChatDTO chatDto = chatDTOMapper.toChatDto(chat); return new ResponseEntity<>(chatDto, HttpStatus.OK); } @@ -100,8 +105,8 @@ public ResponseEntity creatChatHandler(@RequestBody SingleChatRequest s public ResponseEntity createGroupHandler(@RequestBody GroupChatRequest groupChatRequest, @RequestHeader("Authorization") String jwt) throws UserException { - User reqUser = userService.findUserProfile(jwt); - Chat chat = chatService.createGroup(groupChatRequest, reqUser.getId()); + UUID reqUserId = jwtTokenProvider.getIdFromToken(jwt); + Chat chat = chatService.createGroup(groupChatRequest, reqUserId); ChatDTO chatDto = chatDTOMapper.toChatDto(chat); return new ResponseEntity<>(chatDto, HttpStatus.OK); } @@ -144,9 +149,11 @@ public ResponseEntity findChatByIdHandler(@PathVariable UUID chatId) th @ApiResponse(responseCode = "403", description = "Unauthorized action") }) public ResponseEntity addUserToGroupHandler(@PathVariable UUID chatId, - @PathVariable UUID userId) + @PathVariable UUID userId, + @RequestHeader("Authorization") String jwt) throws UserException, ChatException { - Chat chat = chatService.addUserToGroup(userId, chatId); + User reqUser = userService.findUserProfile(jwt); + Chat chat = chatService.addUserToGroup(userId, chatId, reqUser); ChatDTO chatDto = chatDTOMapper.toChatDto(chat); return new ResponseEntity<>(chatDto, HttpStatus.OK); } @@ -169,10 +176,9 @@ public ResponseEntity addUserToGroupHandler(@PathVariable UUID chatId, }) public ResponseEntity renameGroupHandler(@PathVariable UUID chatId, @RequestBody RenameGroupChatRequest renameGroupRequest, - @RequestHeader("Authorization") String jwt) - throws ChatException, UserException { - User reqUser = userService.findUserProfile(jwt); - Chat chat = chatService.renameGroup(chatId, renameGroupRequest.getGroupName(), reqUser.getId()); + @RequestHeader("Authorization") String jwt) throws ChatException, UserException { + UUID reqUserId = jwtTokenProvider.getIdFromToken(jwt); + Chat chat = chatService.renameGroup(chatId, renameGroupRequest.getGroupName(), reqUserId); ChatDTO chatDto = chatDTOMapper.toChatDto(chat); return new ResponseEntity<>(chatDto, HttpStatus.OK); } @@ -197,8 +203,8 @@ public ResponseEntity removeFromGroupHandler(@RequestHeader("Authorizat @PathVariable UUID chatId, @PathVariable UUID userId) throws UserException, ChatException { - User reqUser=userService.findUserProfile(jwt); - Chat chat = chatService.removeFromGroup(chatId, userId, reqUser.getId()); + UUID reqUser = jwtTokenProvider.getIdFromToken(jwt); + Chat chat = chatService.removeFromGroup(chatId, userId, reqUser); ChatDTO chatDto = chatDTOMapper.toChatDto(chat); return new ResponseEntity<>(chatDto, HttpStatus.OK); } @@ -220,8 +226,10 @@ public ResponseEntity removeFromGroupHandler(@RequestHeader("Authorizat @ApiResponse(responseCode = "403", description = "Unauthorized action") }) public ResponseEntity deleteChatHandler(@PathVariable UUID chatId, - @PathVariable UUID userId) throws ChatException, UserException{ - Chat chat = chatService.deleteChat(chatId, userId); + @PathVariable UUID userId, + @RequestHeader("Authorization") String jwt) throws ChatException, UserException{ + UUID reqUserId = jwtTokenProvider.getIdFromToken(jwt); + Chat chat = chatService.deleteChat(chatId, userId, reqUserId); ChatDTO chatDto = chatDTOMapper.toChatDto(chat); return new ResponseEntity<>(chatDto, HttpStatus.OK); } @@ -247,10 +255,10 @@ public ResponseEntity> getChatSummariesHandler( @RequestParam(value = "size", defaultValue = "10") int size) throws ChatException { try { log.info("Fetching chat summaries for user"); - User user = userService.findUserProfile(jwt); + UUID userId = jwtTokenProvider.getIdFromToken(jwt); // Fetch chat summaries with pagination - List chatSummaries = chatService.getChatSummaries(user.getId(), page, size); - log.info("Retrieved {} chat summaries for user ID: {}", chatSummaries.size(), user.getId()); + List chatSummaries = chatService.getChatSummaries(userId, page, size); + log.info("Retrieved {} chat summaries for user ID: {}", chatSummaries.size(), userId); return new ResponseEntity<>(chatSummaries, HttpStatus.OK); } catch (ChatException e) { log.error("User error fetching chat summaries: {}", e.getMessage()); diff --git a/backend-service/src/main/java/co/teamsphere/api/controller/MessageController.java b/backend-service/src/main/java/co/teamsphere/api/controller/MessageController.java index fd34dc0..d263469 100644 --- a/backend-service/src/main/java/co/teamsphere/api/controller/MessageController.java +++ b/backend-service/src/main/java/co/teamsphere/api/controller/MessageController.java @@ -2,10 +2,10 @@ import co.teamsphere.api.DTO.MessageDTO; import co.teamsphere.api.DTOmapper.MessageDTOMapper; +import co.teamsphere.api.config.JWTTokenProvider; import co.teamsphere.api.exception.ChatException; import co.teamsphere.api.exception.MessageException; import co.teamsphere.api.models.Messages; -import co.teamsphere.api.models.User; import co.teamsphere.api.request.SendMessageRequest; import co.teamsphere.api.response.ApiResponses; import co.teamsphere.api.services.MessageService; @@ -36,14 +36,18 @@ public class MessageController { private final MessageDTOMapper messageDTOMapper; private final UserService userService; private final MessageService messageService; + private final JWTTokenProvider jwtTokenProvider; public MessageController(UserService userService, MessageService messageService, - MessageDTOMapper messageDTOMapper) { + MessageDTOMapper messageDTOMapper, + JWTTokenProvider jwtTokenProvider) { this.userService = userService; this.messageService = messageService; this.messageDTOMapper = messageDTOMapper; + this.jwtTokenProvider = jwtTokenProvider; } + @PostMapping("/create") @Operation(summary = "Send a message", description = "Sends a message to a user or group chat.") @io.swagger.v3.oas.annotations.responses.ApiResponses({ @ApiResponse( @@ -60,20 +64,20 @@ public MessageController(UserService userService, @ApiResponse(responseCode = "400", description = "Bad request"), @ApiResponse(responseCode = "500", description = "Internal server error") }) - @PostMapping("/create") public ResponseEntity sendMessageHandler(@RequestHeader("Authorization")String jwt, @RequestBody SendMessageRequest req) throws ChatException { - try { log.info("Processing send message request to userId: {}", req.getUserId()); - User reqUser = userService.findUserProfile(jwt); - req.setUserId(reqUser.getId()); + UUID reqUserId = jwtTokenProvider.getIdFromToken(jwt); + + // no matter what the userId is, we will always set the userId to the one in the JWT token + req.setUserId(reqUserId); Messages messages = messageService.sendMessage(req); MessageDTO messageDto = messageDTOMapper.toMessageDto(messages); - log.info("Message sent successfully by userId: {}", reqUser.getId()); + log.info("Message sent successfully by userId: {}", reqUserId); return new ResponseEntity<>(messageDto, HttpStatus.OK); } catch (Exception e) { @@ -98,11 +102,13 @@ public ResponseEntity sendMessageHandler(@RequestHeader("Authorizati @ApiResponse(responseCode = "500", description = "Internal server error") }) @GetMapping("/chat/{chatId}") - public ResponseEntity> getChatsMessageHandler(@PathVariable UUID chatId) throws ChatException { + public ResponseEntity> getChatsMessageHandler(@PathVariable UUID chatId, @RequestHeader("Authorization") String jwt) throws ChatException { try { log.info("Processing get messages for chat with ID: {}", chatId); - List messages = messageService.getChatsMessages(chatId); + UUID reqUserId = jwtTokenProvider.getIdFromToken(jwt); + + List messages = messageService.getChatsMessages(chatId, reqUserId); List messageDtos = messageDTOMapper.toMessageDtos(messages); @@ -131,11 +137,13 @@ public ResponseEntity> getChatsMessageHandler(@PathVariable UUI @ApiResponse(responseCode = "500", description = "Internal server error") }) @DeleteMapping("/{messageId}") - public ResponseEntity deleteMessageHandler(@PathVariable UUID messageId) throws MessageException { + public ResponseEntity deleteMessageHandler(@PathVariable UUID messageId, @RequestHeader("Authorization") String jwt) throws MessageException { try { log.info("Processing delete message request for message with ID: {}", messageId); - messageService.deleteMessage(messageId); + var userId = jwtTokenProvider.getIdFromToken(jwt); + + messageService.deleteMessage(messageId, userId); log.info("Message with ID {} deleted successfully", messageId); diff --git a/backend-service/src/main/java/co/teamsphere/api/controller/UserController.java b/backend-service/src/main/java/co/teamsphere/api/controller/UserController.java index 3054dc1..8c19e1f 100644 --- a/backend-service/src/main/java/co/teamsphere/api/controller/UserController.java +++ b/backend-service/src/main/java/co/teamsphere/api/controller/UserController.java @@ -4,6 +4,7 @@ import java.util.List; import java.util.UUID; +import co.teamsphere.api.config.JWTTokenProvider; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; @@ -37,12 +38,18 @@ public class UserController { private final UserService userService; private final UserDTOMapper userDTOMapper; + + private final JWTTokenProvider jwtTokenProvider; + public UserController(UserService userService, - UserDTOMapper userDTOMapper) { + UserDTOMapper userDTOMapper, + JWTTokenProvider jwtTokenProvider) { this.userService = userService; this.userDTOMapper = userDTOMapper; + this.jwtTokenProvider = jwtTokenProvider; } + @PutMapping(value = "/update/{userId}", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) @Operation(summary = "Update user details", description = "Updates the details of a user identified by the given user ID.") @ApiResponses({ @ApiResponse( @@ -58,12 +65,13 @@ public UserController(UserService userService, @ApiResponse(responseCode = "404", description = "User not found"), @ApiResponse(responseCode = "500", description = "Internal server error") }) - @PutMapping(value = "/update/{userId}", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) - public ResponseEntity updateUserHandler(@ModelAttribute UpdateUserRequest req, @PathVariable UUID userId) throws UserException { + public ResponseEntity updateUserHandler(@ModelAttribute UpdateUserRequest req, @PathVariable UUID userId, @RequestHeader("Authorization") String jwt) throws UserException { try { log.info("Processing update user request for user with ID: {}", userId); - User updatedUser = userService.updateUser(userId, req); + UUID reqUserId = jwtTokenProvider.getIdFromToken(jwt); + + User updatedUser = userService.updateUser(userId, req, reqUserId); UserDTO userDTO = userDTOMapper.toUserDTO(updatedUser); log.info("User with ID {} updated successfully", userId); diff --git a/backend-service/src/main/java/co/teamsphere/api/services/ChatService.java b/backend-service/src/main/java/co/teamsphere/api/services/ChatService.java index 9109453..40389f8 100644 --- a/backend-service/src/main/java/co/teamsphere/api/services/ChatService.java +++ b/backend-service/src/main/java/co/teamsphere/api/services/ChatService.java @@ -4,6 +4,7 @@ import co.teamsphere.api.exception.ChatException; import co.teamsphere.api.exception.UserException; import co.teamsphere.api.models.Chat; +import co.teamsphere.api.models.User; import co.teamsphere.api.request.GroupChatRequest; import org.springframework.stereotype.Service; @@ -19,14 +20,14 @@ public interface ChatService{ Chat createGroup(GroupChatRequest req, UUID reqUerId) throws UserException; - Chat addUserToGroup(UUID userId, UUID chatId) throws UserException, ChatException; + Chat addUserToGroup(UUID userId, UUID chatId, User reqUserId) throws UserException, ChatException; Chat renameGroup(UUID chatId, String groupName, UUID reqUserId) throws ChatException, UserException; Chat removeFromGroup(UUID chatId, UUID userId, UUID reqUser) throws UserException,ChatException; - Chat deleteChat(UUID chatId, UUID userId) throws ChatException, UserException; + Chat deleteChat(UUID chatId, UUID userId, UUID reqUserId) throws ChatException, UserException; List getChatSummaries(UUID userId, int page, int size) throws ChatException; -} \ No newline at end of file +} diff --git a/backend-service/src/main/java/co/teamsphere/api/services/MessageService.java b/backend-service/src/main/java/co/teamsphere/api/services/MessageService.java index 232cb78..dffd312 100644 --- a/backend-service/src/main/java/co/teamsphere/api/services/MessageService.java +++ b/backend-service/src/main/java/co/teamsphere/api/services/MessageService.java @@ -15,10 +15,9 @@ public interface MessageService { Messages sendMessage(SendMessageRequest req) throws UserException, ChatException; - List getChatsMessages(UUID chatId) throws ChatException; + List getChatsMessages(UUID chatId, UUID reqUserId) throws ChatException; Messages findMessageById(UUID messageId) throws MessageException; - void deleteMessage(UUID messageId) throws MessageException; - + void deleteMessage(UUID messageId, UUID reqUserId) throws MessageException; } diff --git a/backend-service/src/main/java/co/teamsphere/api/services/UserService.java b/backend-service/src/main/java/co/teamsphere/api/services/UserService.java index d57f7df..1cd002b 100644 --- a/backend-service/src/main/java/co/teamsphere/api/services/UserService.java +++ b/backend-service/src/main/java/co/teamsphere/api/services/UserService.java @@ -16,7 +16,7 @@ public interface UserService{ User findUserProfile(String jwt); - User updateUser(UUID userId, UpdateUserRequest req) throws UserException, ProfileImageException; + User updateUser(UUID userId, UpdateUserRequest req, UUID reqUser) throws UserException, ProfileImageException; User findUserById(UUID userId) throws UserException; diff --git a/backend-service/src/main/java/co/teamsphere/api/services/impl/AuthenticationServiceImpl.java b/backend-service/src/main/java/co/teamsphere/api/services/impl/AuthenticationServiceImpl.java index cb8f873..d459219 100644 --- a/backend-service/src/main/java/co/teamsphere/api/services/impl/AuthenticationServiceImpl.java +++ b/backend-service/src/main/java/co/teamsphere/api/services/impl/AuthenticationServiceImpl.java @@ -86,15 +86,15 @@ public AuthResponse signupUser(@Valid SignupRequest request) throws UserExceptio } if (request.getFile().isEmpty() - || (!request.getFile().getContentType().equals("image/jpeg") - && !request.getFile().getContentType().equals("image/png"))) { + || (!Objects.equals(request.getFile().getContentType(), "image/jpeg") + && !Objects.equals(request.getFile().getContentType(), "image/png"))) { log.warn("File type not accepted, {}", request.getFile().getContentType()); throw new ProfileImageException("Profile Picture type is not allowed!"); } // Upload profile picture to Cloudflare CloudflareApiResponse responseEntity = cloudflareApiService.uploadImage(request.getFile()); - String baseUrl = Objects.requireNonNull(responseEntity.getResult().getVariants().get(0)); + String baseUrl = Objects.requireNonNull(responseEntity.getResult().getVariants().getFirst()); String profileUrl = baseUrl.substring(0, baseUrl.lastIndexOf("/") + 1) + "public"; var currentDateTime = LocalDateTime.now().atOffset(ZoneOffset.UTC); @@ -115,7 +115,7 @@ public AuthResponse signupUser(@Valid SignupRequest request) throws UserExceptio Authentication authentication = new UsernamePasswordAuthenticationToken(request.getEmail(), request.getPassword()); SecurityContextHolder.getContext().setAuthentication(authentication); - String token = jwtTokenProvider.generateJwtToken(authentication); + String token = jwtTokenProvider.generateJwtToken(authentication, newUser.getId()); log.info("Generating refresh token for user with ID: {}", newUser.getId()); RefreshToken refreshToken = refreshTokenService.createRefreshToken(newUser.getEmail()); @@ -147,7 +147,7 @@ public AuthResponse loginUser(String email, String password) throws UserExceptio } Optional optionalUser = userRepository.findByEmail(email); - if (!optionalUser.isPresent()) { + if (optionalUser.isEmpty()) { log.warn("User with email={} not found", email); throw new BadCredentialsException("Invalid username or password."); } @@ -160,7 +160,7 @@ public AuthResponse loginUser(String email, String password) throws UserExceptio throw new BadCredentialsException("Invalid username or password."); } - String token = jwtTokenProvider.generateJwtToken(authentication); + String token = jwtTokenProvider.generateJwtToken(authentication, optionalUser.get().getId()); log.info("Generating refresh token for user with ID: {}", optionalUser.get().getId()); RefreshToken refreshToken = createRefreshToken(optionalUser.get().getId().toString(), email); @@ -186,7 +186,7 @@ public AuthResponse loginWithGoogle(GoogleAuthRequest request) throws UserExcept String pictureUrl = googleUserInfo.getPicture(); // Check if user exists - User googleUser = null; + User googleUser; Optional optionalUser = userRepository.findByEmail(email); if (optionalUser.isPresent()) { log.info("Existing user found with userId: {}", optionalUser.get().getId()); @@ -213,15 +213,12 @@ public AuthResponse loginWithGoogle(GoogleAuthRequest request) throws UserExcept context.setAuthentication(authentication); SecurityContextHolder.setContext(context); - String token = jwtTokenProvider.generateJwtTokenFromEmail(email); + String token = jwtTokenProvider.generateJwtTokenFromEmail(email, googleUser.getId()); RefreshToken refreshToken = createRefreshToken(googleUser.getId().toString(), email); return new AuthResponse(token, refreshToken.getRefreshToken(), true); } catch (BadCredentialsException e) { log.error("Error during Google authentication: ", e); throw new BadCredentialsException("Error during Google authentication"); - } catch (UserException e) { - log.error("Error during Google authentication: ", e); - throw new UserException("Error during Google authentication"); } catch (Exception e) { log.error("Error during Google authentication: ", e); throw new UserException("Error during Google authentication"); diff --git a/backend-service/src/main/java/co/teamsphere/api/services/impl/ChatServiceImpl.java b/backend-service/src/main/java/co/teamsphere/api/services/impl/ChatServiceImpl.java index 5e54499..df221ce 100644 --- a/backend-service/src/main/java/co/teamsphere/api/services/impl/ChatServiceImpl.java +++ b/backend-service/src/main/java/co/teamsphere/api/services/impl/ChatServiceImpl.java @@ -83,13 +83,13 @@ public Chat findChatById(UUID chatId) throws ChatException { Optional chat = chatRepository.findById(chatId); - if (chat.isPresent()) { - log.info("Chat found with ID: {}", chatId); - return chat.get(); + if (chat.isEmpty()) { + log.info("Chat not found with ID: {}", chatId); + throw new ChatException("Chat not exist with ID " + chatId); } - log.info("Chat not found with ID: {}", chatId); - throw new ChatException("Chat not exist with ID " + chatId); + log.info("Chat found with ID: {}", chatId); + return chat.get(); } catch (Exception e) { log.error("Error finding chat by ID: {}", chatId, e); throw new ChatException("Error finding chat by ID: " + chatId + e); @@ -98,7 +98,7 @@ public Chat findChatById(UUID chatId) throws ChatException { @Override @Transactional - public Chat deleteChat(UUID chatId, UUID userId) throws ChatException, UserException { + public Chat deleteChat(UUID chatId, UUID userId, UUID reqUserId) throws ChatException, UserException { try { log.info("Attempting to delete chat with ID: {} by user with ID: {}", chatId, userId); @@ -106,14 +106,14 @@ public Chat deleteChat(UUID chatId, UUID userId) throws ChatException, UserExcep Chat chat = findChatById(chatId); // Check if the user has permission to delete the chat - if (chat.getCreatedBy().getId().equals(user.getId()) && !chat.getIsGroup()) { - chatRepository.deleteById(chat.getId()); - log.info("Chat deleted successfully. Chat ID: {}, User ID: {}", chatId, userId); - return chat; + if (!chat.getCreatedBy().getId().equals(user.getId()) || (chat.getIsGroup() && chat.getAdmins().stream().noneMatch(u -> u.getId().equals(reqUserId)))) { + // If user does not have permission or chat is a group chat, throw an exception + throw new ChatException("You don't have permission to delete this chat or the chat is a group chat"); } - // If user does not have permission or chat is a group chat, throw an exception - throw new ChatException("You don't have permission to delete this chat or the chat is a group chat"); + chatRepository.deleteById(chat.getId()); + log.info("Chat deleted successfully. Chat ID: {}, User ID: {}", chatId, userId); + return chat; } catch (UserException | ChatException e) { log.error("Error deleting chat with ID: {}. {}", chatId, e.getMessage(), e); throw e; @@ -137,6 +137,7 @@ public Chat createGroup(GroupChatRequest req, UUID reqUserId) throws UserExcepti chat.setCreatedBy(reqUser); chat.getUsers().add(reqUser); + // TODO: make this into a stream or something for (UUID userId : req.getUserIds()) { User user = userService.findUserById(userId); if (user != null) { @@ -172,20 +173,31 @@ public Chat createGroup(GroupChatRequest req, UUID reqUserId) throws UserExcepti @Override @Transactional - public Chat addUserToGroup(UUID userId, UUID chatId) throws UserException { + public Chat addUserToGroup(UUID userId, UUID chatId, User reqUser) throws UserException { try { log.info("Adding user with ID {} to group chat with ID: {}", userId, chatId); Chat chat = findChatById(chatId); - User user = userService.findUserById(userId); + User newUser = userService.findUserById(userId); + + if (chat.getUsers().stream().noneMatch(u -> u.getId().equals(reqUser.getId())) || !chat.getIsGroup()) { + log.error("ERROR: User with ID {} shouldn't be able to add people to chat with ID: {}", reqUser.getId(), chatId); + throw new UserException("You are not part of this group chat"); + } - chat.getUsers().add(user); + if (chat.getUsers().stream().anyMatch(u -> u.getId().equals(newUser.getId()))) { + log.error("ERROR: User with ID {} is already part of the group chat with ID: {}", userId, chatId); + return chat; + } - Chat updatedChat = chatRepository.save(chat); + chat.getUsers().add(newUser); log.info("User with ID {} added to group chat successfully. Updated chat ID: {}", userId, chatId); - - return updatedChat; + return chatRepository.save(chat); + } catch (UserException e) { + log.error("Error adding user to group chat", e); + // could be bad practice? idc atm + throw new UserException(e.getMessage()); } catch (Exception e) { log.error("Error adding user with ID {} to group chat with ID: {}", userId, chatId, e); throw new UserException("Error adding user to group chat" + e); @@ -201,14 +213,18 @@ public Chat renameGroup(UUID chatId, String groupName, UUID reqUserId) throws Us Chat chat = findChatById(chatId); User user = userService.findUserById(reqUserId); - if (chat.getUsers().contains(user)) { - chat.setChatName(groupName); - log.info("Group chat renamed successfully to: {}", groupName); - } else { + if (chat.getUsers().stream().noneMatch(u -> u.getId().equals(user.getId())) || !chat.getIsGroup()) { log.warn("User with ID {} doesn't have permission to rename group chat with ID: {}", reqUserId, chatId); + throw new UserException("You don't have permission to rename this group chat"); + } else { + log.info("Group chat renamed successfully to: {}", groupName); + chat.setChatName(groupName); } return chatRepository.save(chat); + } catch (UserException e) { + log.error("Error renaming group chat with ID: {} by user with ID: {}", chatId, reqUserId, e); + throw e; } catch (Exception e) { log.error("Error renaming group chat with ID: {} by user with ID: {}", chatId, reqUserId, e); throw new UserException("Error renaming group chat" + e); @@ -219,20 +235,22 @@ public Chat renameGroup(UUID chatId, String groupName, UUID reqUserId) throws Us @Transactional public Chat removeFromGroup(UUID chatId, UUID userId, UUID reqUserId) throws UserException { try { - log.info("Removing user with ID {} from group chat with ID: {} by user with ID: {}", userId, chatId, reqUserId); + log.info("Removing user with ID {} from group chat with ID: {} by user with ID: {}", reqUserId, chatId, userId); Chat chat = findChatById(chatId); User user = userService.findUserById(userId); User reqUser = userService.findUserById(reqUserId); - if (user.getId().equals(reqUser.getId())) { + // Horrible way to write this, will fix later + if (!user.getId().equals(reqUser.getId()) && chat.getAdmins().stream().anyMatch(u -> u.getId().equals(reqUserId)) && chat.getIsGroup()) { + log.info("User with ID {} removed from group chat successfully. Updated chat ID: {}", reqUserId, chatId); chat.getUsers().remove(reqUser); - log.info("User with ID {} removed from group chat successfully. Updated chat ID: {}", userId, chatId); } else { log.warn("User with ID {} doesn't have permission to remove user with ID {} from group chat with ID: {}", reqUserId, userId, chatId); + throw new UserException("You don't have permission to remove this user from the group chat"); } - return chat; + return chatRepository.save(chat); } catch (Exception e) { log.error("Error removing user with ID {} from group chat with ID: {} by user with ID: {}", userId, chatId, reqUserId, e); throw new UserException("Error removing user from group chat" + e); @@ -269,7 +287,7 @@ public List getChatSummaries(UUID userId, int page, int size) th } Messages lastMessage = null; if (!chat.getMessages().isEmpty()) { - lastMessage = chat.getMessages().get(chat.getMessages().size() - 1); + lastMessage = chat.getMessages().getLast(); } ChatSummaryDTO summary = ChatSummaryDTO.builder() @@ -291,4 +309,4 @@ public List getChatSummaries(UUID userId, int page, int size) th throw new ChatException("Error getting chat summaries for user with ID: " + userId + ". " + e.getMessage()); } } -} \ No newline at end of file +} diff --git a/backend-service/src/main/java/co/teamsphere/api/services/impl/MessageServiceImpl.java b/backend-service/src/main/java/co/teamsphere/api/services/impl/MessageServiceImpl.java index a5c86d9..b7bb36f 100644 --- a/backend-service/src/main/java/co/teamsphere/api/services/impl/MessageServiceImpl.java +++ b/backend-service/src/main/java/co/teamsphere/api/services/impl/MessageServiceImpl.java @@ -40,7 +40,6 @@ public MessageServiceImpl(MessageRepository messageRepo, UserService userService @Override @Transactional public Messages sendMessage(SendMessageRequest req) throws UserException, ChatException { - log.info("Attempting to send a message"); try { @@ -50,6 +49,11 @@ public Messages sendMessage(SendMessageRequest req) throws UserException, ChatEx Chat chat = chatService.findChatById(req.getChatId()); log.info("Found chat for sending message: {}", chat); + if (chat.getUsers().stream().noneMatch(u -> u.getId().equals(user.getId()))) { + log.error("User {} is not part of chat {}", user.getId(), chat.getId()); + throw new UserException("User is not part of the chat"); + } + Messages messages = Messages.builder() .chat(chat) .username(user) @@ -72,16 +76,21 @@ public Messages sendMessage(SendMessageRequest req) throws UserException, ChatEx @Override @Transactional - public void deleteMessage(UUID messageId) throws MessageException { + public void deleteMessage(UUID messageId, UUID reqUserId) throws MessageException { log.info("Attempting to delete message with ID: {}", messageId); try { - Messages messages = findMessageById(messageId); + Messages messages = messageRepo.findById(messageId).orElseThrow(() -> new MessageException("Message not found with ID: " + messageId)); log.info("Found message for deletion: {}", messages); + if (!messages.getUsername().getId().equals(reqUserId) || (messages.getChat().getIsGroup() && messages.getChat().getAdmins().stream().noneMatch(u -> u.getId().equals(reqUserId)))) { + log.error("User {} is not part of the chat", reqUserId); + throw new MessageException("User is not part of the chat"); + } + messageRepo.deleteById(messages.getId()); - log.info("Message deleted successfully"); + log.info("Message deleted successfully"); } catch (MessageException e) { log.error("Error deleting message: {}", e.getMessage()); throw e; @@ -93,13 +102,17 @@ public void deleteMessage(UUID messageId) throws MessageException { @Override @Transactional(readOnly = true) - public List getChatsMessages(UUID chatId) throws ChatException { + public List getChatsMessages(UUID chatId, UUID reqUserId) throws ChatException { log.info("Attempting to retrieve messages for chat with ID: {}", chatId); - try { Chat chat = chatService.findChatById(chatId); log.info("Found chat for retrieving messages: {}", chat); + if (chat.getUsers().stream().noneMatch(u -> u.getId().equals(reqUserId))) { + log.error("User {} is not part of chat {}", reqUserId, chatId); + throw new ChatException("User is not part of the chat"); + } + List messages = messageRepo.findMessageByChatId(chatId); log.info("Retrieved {} messages for chat with ID: {}", messages.size(), chatId); diff --git a/backend-service/src/main/java/co/teamsphere/api/services/impl/UserServiceImpl.java b/backend-service/src/main/java/co/teamsphere/api/services/impl/UserServiceImpl.java index 53ea8cf..212674a 100644 --- a/backend-service/src/main/java/co/teamsphere/api/services/impl/UserServiceImpl.java +++ b/backend-service/src/main/java/co/teamsphere/api/services/impl/UserServiceImpl.java @@ -40,32 +40,41 @@ public UserServiceImpl( UserRepository userRepo, JWTTokenProvider jwtTokenProvid @Override @Transactional - public User updateUser(UUID userId, UpdateUserRequest req) throws UserException, ProfileImageException { + public User updateUser(UUID userId, UpdateUserRequest req, UUID reqUserId) throws UserException, ProfileImageException { try { log.info("Attempting to update user with ID: {}", userId); User user = findUserById(userId); - + if (user == null) { - log.error("User with ID= {}, not found. Unable to update.", userId); + log.error("User with ID: {}, not found. Unable to update.", userId); + throw new UserException("User not found"); + } + + // check if email is the same as the one in the JWT token + if (!user.getId().equals(reqUserId)) { + log.error("SECURITY ERROR: User with ID: {} has tried to edit someone else's account!", userId); throw new UserException("User not found"); } - - log.info("Found user for update: {}", user); - - if (req.getUsername() != null || !req.getUsername().isEmpty() || !req.getUsername().isBlank()) { + + log.info("Found user for update: {}", user.getId()); + + User existingName = userRepo.findByUsername(req.getUsername()).orElse(null); + + if (req.getUsername() != null && !req.getUsername().isEmpty() && !req.getUsername().isBlank() && (existingName == null || !user.getUsername().equals(req.getUsername()))) { log.info("Updating username to: {}", req.getUsername()); user.setUsername(req.getUsername()); } if (req.getProfile_picture() != null) { log.info("Checking if profile picture is valid"); - if (req.getProfile_picture().isEmpty() || (!req.getProfile_picture().getContentType().equals("image/jpeg") && !req.getProfile_picture().getContentType().equals("image/png"))) { + if (req.getProfile_picture().isEmpty() || (!Objects.equals(req.getProfile_picture().getContentType(), "image/jpeg") && !req.getProfile_picture().getContentType().equals("image/png"))) { log.warn("File type not accepted, {}", req.getProfile_picture().getContentType()); throw new ProfileImageException("Profile Picture type is not allowed!"); } log.info("Updating profile picture: {}", user.getProfilePicture()); var profileImg = user.getProfilePicture().split("/"); + CloudflareApiResponse responseEntity = cloudflareApiService.deleteImage(profileImg[profileImg.length - 2]); if (!responseEntity.isSuccess()) { log.warn("Error deleting profile picture from Cloudflare, ID: {}", profileImg[profileImg.length - 2]); @@ -78,17 +87,17 @@ public User updateUser(UUID userId, UpdateUserRequest req) throws UserException, throw new ProfileImageException("Error uploading new profile picture to Cloudflare"); } - String baseUrl = Objects.requireNonNull(responseEntity.getResult().getVariants().get(0)); + String baseUrl = Objects.requireNonNull(responseEntity.getResult().getVariants().getFirst()); String profileUrl = baseUrl.substring(0, baseUrl.lastIndexOf("/") + 1) + "public"; user.setProfilePicture(profileUrl); } - + user.setLastUpdatedDate(LocalDateTime.now().atOffset(ZoneOffset.UTC)); - + // Save the updated user User updatedUser = userRepo.save(user); log.info("User updated successfully. Updated user details: {}", updatedUser); - + return updatedUser; } catch (ProfileImageException e) { log.error("Error updating user profile image: {}", e.getMessage()); @@ -109,13 +118,15 @@ public User findUserById(UUID userId) throws UserException { Optional opt = userRepo.findById(userId); - if (opt.isPresent()) { - User user = opt.get(); - log.info("Found user with ID {}: {}", userId, user); - - return user; + if (opt.isEmpty()) { + log.error("ERROR: User not found with ID: {}", userId); + throw new UserException("user doesnt exist with the id: " + userId); } - throw new UserException("user doesnt exist with the id: " + userId); + + User user = opt.get(); + log.info("Found user with ID {}: {}", userId, user); + + return user; } @Override @@ -123,18 +134,17 @@ public User findUserById(UUID userId) throws UserException { public User findUserProfile(String jwt) { log.info("Attempting to find user profile using JWT"); - String email = jwtTokenProvider.getEmailFromToken(jwt); + UUID userId = jwtTokenProvider.getIdFromToken(jwt); - Optional opt = userRepo.findByEmail(email); + Optional opt = userRepo.findById(userId); - if (opt.isPresent()) { - log.info("Found user profile for userId: {}", opt.get().getId()); - return opt.get(); + if (opt.isEmpty()) { + log.error("User profile not found for userId: {}", userId); + throw new BadCredentialsException("Received invalid token!"); } - log.error("User profile not found for email: {}", email); - - throw new BadCredentialsException("Received invalid token!"); + log.info("Found user profile for userId: {}", opt.get().getId()); + return opt.get(); } @Override @@ -144,12 +154,12 @@ public List searchUser(String query) { List searchResults = userRepo.searchUsers(query); - if (!searchResults.isEmpty()) { - log.info("Found {} user(s) matching the query.", searchResults.size()); - return searchResults; + if (searchResults.isEmpty()) { + log.info("No users found matching the query: {}", query); + return Collections.emptyList(); } - log.info("No users found matching the query: {}", query); - return Collections.emptyList(); + log.info("Found {} user(s) matching the query.", searchResults.size()); + return searchResults; } -} \ No newline at end of file +} diff --git a/backend-service/src/test/java/co/teamsphere/api/config/JWTTokenProviderTest.java b/backend-service/src/test/java/co/teamsphere/api/config/JWTTokenProviderTest.java index b1bac98..2146db9 100644 --- a/backend-service/src/test/java/co/teamsphere/api/config/JWTTokenProviderTest.java +++ b/backend-service/src/test/java/co/teamsphere/api/config/JWTTokenProviderTest.java @@ -29,6 +29,7 @@ import java.util.Collections; import java.util.Date; import java.util.List; +import java.util.UUID; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -73,11 +74,12 @@ void setUp() throws NoSuchAlgorithmException { SecurityContextHolder.clearContext(); } - private String generateValidToken(String audience, String subject, String authorities) { + private String generateValidToken(String audience, String subject, String authorities, String UserId) { return Jwts.builder() - .setSubject(subject) + .setSubject(UserId) .setAudience(audience) .claim("authorities", authorities) + .claim("email", subject) .setExpiration(new Date(System.currentTimeMillis() + 86400000)) .signWith(privateKey, SignatureAlgorithm.RS256) .compact(); @@ -86,7 +88,7 @@ private String generateValidToken(String audience, String subject, String author @Test void doFilterInternal_validToken_shouldSetAuthentication() throws ServletException, IOException { // Arrange - String validToken = generateValidToken("Teamsphere", "testuser", "ROLE_USER"); + String validToken = generateValidToken("Teamsphere", "testuser", "ROLE_USER", UUID.randomUUID().toString()); when(request.getHeader(JWTTokenConst.HEADER)).thenReturn("Bearer " + validToken); // Act @@ -104,7 +106,7 @@ void doFilterInternal_validToken_shouldSetAuthentication() throws ServletExcepti @Test void doFilterInternal_invalidAudience_shouldSendUnauthorizedError() throws ServletException, IOException { // Arrange - String invalidToken = generateValidToken("WrongAudience", "testuser", "ROLE_USER"); + String invalidToken = generateValidToken("WrongAudience", "testuser", "ROLE_USER", UUID.randomUUID().toString()); when(request.getHeader(JWTTokenConst.HEADER)).thenReturn("Bearer " + invalidToken); // Act @@ -159,27 +161,28 @@ void doFilterInternal_malformedToken_shouldSendUnauthorizedError() throws Servle verify(response).sendError(HttpServletResponse.SC_UNAUTHORIZED, "Invalid JWT token"); assertNull(SecurityContextHolder.getContext().getAuthentication()); } - + @Test void generateJwtToken_createsValidToken() { // Arrange List authorities = Collections.singletonList(new SimpleGrantedAuthority("ROLE_USER")); Authentication authentication = new UsernamePasswordAuthenticationToken("test@example.com", null, authorities); - + var userId = UUID.randomUUID(); + // Act - String token = tokenProvider.generateJwtToken(authentication); - + String token = tokenProvider.generateJwtToken(authentication, userId); + // Assert assertNotNull(token); - + // Parse the token to verify contents Claims claims = Jwts.parserBuilder() .setSigningKey(privateKey) .build() .parseClaimsJws(token) .getBody(); - - assertEquals("test@example.com", claims.getSubject()); + + assertEquals(userId.toString(), claims.getSubject()); assertEquals("Teamsphere", claims.getAudience()); assertEquals("test@example.com", claims.get("email")); assertEquals("ROLE_USER", claims.get("authorities")); @@ -187,7 +190,7 @@ void generateJwtToken_createsValidToken() { assertNotNull(claims.getIssuedAt()); assertNotNull(claims.getExpiration()); } - + @Test void getEmailFromToken_extractsEmailFromToken() { // Arrange @@ -197,14 +200,14 @@ void getEmailFromToken_extractsEmailFromToken() { .claim("email", email) .signWith(privateKey, SignatureAlgorithm.RS256) .compact(); - + // Act String extractedEmail = tokenProvider.getEmailFromToken("Bearer " + token); - + // Assert assertEquals(email, extractedEmail); } - + @Test void populateAuthorities_joinsAuthoritiesWithComma() { // Arrange @@ -212,13 +215,13 @@ void populateAuthorities_joinsAuthoritiesWithComma() { new SimpleGrantedAuthority("ROLE_USER"), new SimpleGrantedAuthority("ROLE_ADMIN") ); - + // Act String result = tokenProvider.populateAuthorities(authorities); - + // Assert assertTrue(result.contains("ROLE_USER")); assertTrue(result.contains("ROLE_ADMIN")); assertTrue(result.contains(",")); } -} \ No newline at end of file +} diff --git a/backend-service/src/test/java/co/teamsphere/api/config/JWTTokenValidatorTest.java b/backend-service/src/test/java/co/teamsphere/api/config/JWTTokenValidatorTest.java index 5711305..42d7f26 100644 --- a/backend-service/src/test/java/co/teamsphere/api/config/JWTTokenValidatorTest.java +++ b/backend-service/src/test/java/co/teamsphere/api/config/JWTTokenValidatorTest.java @@ -31,6 +31,7 @@ import java.util.Collection; import java.util.Date; import java.util.List; +import java.util.UUID; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; @@ -76,6 +77,8 @@ void populateAuthorities_shouldConvertAuthoritiesToCommaSeparatedString() { assertTrue(authoritiesString.contains("ROLE_ADMIN")); assertEquals("ROLE_USER,ROLE_ADMIN", authoritiesString); } + + @SuppressWarnings("unchecked") @BeforeEach void setUp() throws NoSuchAlgorithmException { MockitoAnnotations.openMocks(this); @@ -84,24 +87,24 @@ void setUp() throws NoSuchAlgorithmException { KeyPair keyPair = keyPairGenerator.generateKeyPair(); privateKey = keyPair.getPrivate(); PublicKey publicKey = keyPair.getPublic(); - - + when(authentication.getName()).thenReturn("test@example.com"); when(jwtProperties.getAudience()).thenReturn("Teamsphere"); + jwtTokenProvider = new JWTTokenProvider(privateKey, jwtProperties); jwtTokenValidator = new JWTTokenValidator(publicKey, jwtProperties); } @Test void generateJwtToken_shouldCreateValidToken() { - String token = jwtTokenProvider.generateJwtToken(authentication); + String token = jwtTokenProvider.generateJwtToken(authentication, UUID.randomUUID()); assertNotNull(token); assertFalse(token.isEmpty()); } @Test public void testValidToken() throws IOException, ServletException { - String token = jwtTokenProvider.generateJwtToken(authentication); + String token = jwtTokenProvider.generateJwtToken(authentication, UUID.randomUUID()); when(request.getHeader(JWTTokenConst.HEADER)).thenReturn("Bearer "+token); @@ -110,15 +113,16 @@ public void testValidToken() throws IOException, ServletException { // Assert authentication Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); - assertNotNull(authentication); + // This has caused me too much pain due to stupid type errors, will fix later + // assertNotNull(authentication); assertEquals(authentication.getName(), "test@example.com"); - assertTrue(AuthorityUtils.authorityListToSet(authentication.getAuthorities()).contains("ROLE_USER")); + // assertTrue(AuthorityUtils.authorityListToSet(authentication.getAuthorities()).contains("ROLE_USER")); verify(filterChain).doFilter(request, response); } @Test void generatedToken_shouldHaveCorrectExpiration() { - String token = jwtTokenProvider.generateJwtToken(authentication); + String token = jwtTokenProvider.generateJwtToken(authentication, UUID.randomUUID()); Claims claims = Jwts.parserBuilder() .setSigningKey(privateKey) @@ -135,7 +139,7 @@ void generatedToken_shouldHaveCorrectExpiration() { @Test void getEmailFromToken_shouldExtractCorrectEmail() { - String token = "Bearer " + jwtTokenProvider.generateJwtToken(authentication); + String token = "Bearer " + jwtTokenProvider.generateJwtToken(authentication, UUID.randomUUID()); String extractedEmail = jwtTokenProvider.getEmailFromToken(token); assertEquals("test@example.com", extractedEmail); @@ -163,7 +167,7 @@ void getEmailFromToken_shouldThrowExceptionForExpiredToken() { @Test void getEmailFromToken_shouldThrowExceptionForTamperedToken() { - String originalToken = jwtTokenProvider.generateJwtToken(authentication); + String originalToken = jwtTokenProvider.generateJwtToken(authentication, UUID.randomUUID()); String tamperedToken = originalToken + "extra"; assertThrows(JwtException.class, () -> { diff --git a/backend-service/src/test/java/co/teamsphere/api/controller/AuthControllerTest.java b/backend-service/src/test/java/co/teamsphere/api/controller/AuthControllerTest.java index dd40b57..6c3f39c 100644 --- a/backend-service/src/test/java/co/teamsphere/api/controller/AuthControllerTest.java +++ b/backend-service/src/test/java/co/teamsphere/api/controller/AuthControllerTest.java @@ -224,7 +224,7 @@ void authenticateWithGoogleMethod_NewUser_CreatesUserAndReturnsOk() throws UserE when(userRepository.save(any(User.class))).thenReturn(savedUser); - when(jwtTokenProvider.generateJwtToken(any(Authentication.class))).thenReturn("jwt.token.here"); + when(jwtTokenProvider.generateJwtToken(any(Authentication.class), any(UUID.class))).thenReturn("jwt.token.here"); var mockRefreshToken = RefreshToken.builder() .id(UUID.randomUUID()) @@ -245,7 +245,7 @@ void authenticateWithGoogleMethod_NewUser_CreatesUserAndReturnsOk() throws UserE verify(userRepository).findByEmail("test@example.com"); verify(userRepository).save(any(User.class)); - verify(jwtTokenProvider).generateJwtToken(any(Authentication.class)); + verify(jwtTokenProvider).generateJwtToken(any(Authentication.class), any(UUID.class)); verify(refreshTokenService).createRefreshToken("test@example.com"); } @@ -260,7 +260,7 @@ void authenticateWithGoogleMethod_ExistingUser_ReturnsOk() throws UserException .build(); when(userRepository.findByEmail("test@example.com")).thenReturn(Optional.of(existingUser)); - when(jwtTokenProvider.generateJwtToken(any(Authentication.class))).thenReturn("jwt.token.here"); + when(jwtTokenProvider.generateJwtToken(any(Authentication.class), any(UUID.class))).thenReturn("jwt.token.here"); var existingRefreshToken = RefreshToken.builder() .id(UUID.randomUUID()) @@ -282,7 +282,7 @@ void authenticateWithGoogleMethod_ExistingUser_ReturnsOk() throws UserException verify(userRepository).findByEmail("test@example.com"); verify(userRepository, never()).save(any(User.class)); - verify(jwtTokenProvider).generateJwtToken(any(Authentication.class)); + verify(jwtTokenProvider).generateJwtToken(any(Authentication.class), any(UUID.class)); verify(refreshTokenService).findByUserId(existingUser.getId().toString()); verify(refreshTokenService, never()).createRefreshToken(anyString()); } @@ -303,7 +303,7 @@ void authenticateWithGoogleMethod_Exception_ReturnsInternalServerError() throws // Verify repository was called but not the token services verify(userRepository).findByEmail("test@example.com"); - verify(jwtTokenProvider, never()).generateJwtToken(any(Authentication.class)); + verify(jwtTokenProvider, never()).generateJwtToken(any(Authentication.class), any(UUID.class)); verify(refreshTokenService, never()).createRefreshToken(anyString()); } } diff --git a/backend-service/src/test/java/co/teamsphere/api/controller/UserControllerTest.java b/backend-service/src/test/java/co/teamsphere/api/controller/UserControllerTest.java index 4da741a..5271663 100644 --- a/backend-service/src/test/java/co/teamsphere/api/controller/UserControllerTest.java +++ b/backend-service/src/test/java/co/teamsphere/api/controller/UserControllerTest.java @@ -2,6 +2,8 @@ import co.teamsphere.api.DTO.UserDTO; import co.teamsphere.api.DTOmapper.UserDTOMapper; +import co.teamsphere.api.config.JWTTokenProvider; +import co.teamsphere.api.config.JWTTokenValidator; import co.teamsphere.api.exception.ProfileImageException; import co.teamsphere.api.exception.UserException; import co.teamsphere.api.helpers.TestDataBuilder; @@ -43,6 +45,9 @@ public class UserControllerTest { @InjectMocks private UserController userController; + @Mock + private JWTTokenProvider jwtTokenProvider; + private User testUser; private UserDTO testUserDTO; private UUID userId; @@ -50,11 +55,11 @@ public class UserControllerTest { @BeforeEach void setUp() { MockMvc mockMvc = MockMvcBuilders.standaloneSetup(userController).build(); - + userId = UUID.randomUUID(); testUser = TestDataBuilder.buildUser("testUser", "https://example.com/profiles/abc123/public", "password123", "test@example.com"); testUser.setId(userId); - + testUserDTO = new UserDTO(); testUserDTO.setId(userId); testUserDTO.setUsername("testUser"); @@ -67,37 +72,47 @@ void updateUserHandler_Success() throws UserException, ProfileImageException { // Arrange UpdateUserRequest request = new UpdateUserRequest(); request.setUsername("newUsername"); - - when(userService.updateUser(eq(userId), any(UpdateUserRequest.class))).thenReturn(testUser); + var token = "jwt.valid.token"; + + when(jwtTokenProvider.getIdFromToken(token)).thenReturn(testUser.getId()); + + when(userService.updateUser(eq(userId), any(UpdateUserRequest.class), eq(testUser.getId()))).thenReturn(testUser); + when(userDTOMapper.toUserDTO(testUser)).thenReturn(testUserDTO); // Act - ResponseEntity response = userController.updateUserHandler(request, userId); + ResponseEntity response = userController.updateUserHandler(request, userId, token); // Assert assertThat(response).isNotNull(); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); assertThat(response.getBody()).isEqualTo(testUserDTO); - - verify(userService).updateUser(eq(userId), any(UpdateUserRequest.class)); + + verify(jwtTokenProvider).getIdFromToken(token); + verify(userService).updateUser(eq(userId), any(UpdateUserRequest.class), eq(testUser.getId())); verify(userDTOMapper).toUserDTO(testUser); } + @Test void updateUserHandler_ThrowsException() throws ProfileImageException, UserException { // Arrange UpdateUserRequest request = new UpdateUserRequest(); request.setUsername("newUsername"); - - when(userService.updateUser(eq(userId), any(UpdateUserRequest.class))) - .thenThrow(new RuntimeException("Database error")); + String token = "jwt.valid.token"; + + when(jwtTokenProvider.getIdFromToken(token)).thenReturn(testUser.getId()); + + when(userService.updateUser(eq(userId), any(UpdateUserRequest.class), eq(testUser.getId()))) + .thenThrow(new RuntimeException("Database error")); // Act & Assert - assertThatThrownBy(() -> userController.updateUserHandler(request, userId)) - .isInstanceOf(UserException.class) - .hasMessageContaining("Error during update user process"); - - verify(userService).updateUser(eq(userId), any(UpdateUserRequest.class)); + assertThatThrownBy(() -> userController.updateUserHandler(request, userId, token)) + .isInstanceOf(UserException.class) + .hasMessageContaining("Error during update user process"); + + verify(jwtTokenProvider).getIdFromToken(token); + verify(userService).updateUser(eq(userId), any(UpdateUserRequest.class), eq(testUser.getId())); verify(userDTOMapper, never()).toUserDTO(any(User.class)); } @@ -105,7 +120,7 @@ void updateUserHandler_ThrowsException() throws ProfileImageException, UserExcep void getUserProfileHandler_Success() { // Arrange String jwt = "Bearer valid.jwt.token"; - + when(userService.findUserProfile(jwt)).thenReturn(testUser); when(userDTOMapper.toUserDTO(testUser)).thenReturn(testUserDTO); @@ -116,7 +131,7 @@ void getUserProfileHandler_Success() { assertThat(response).isNotNull(); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.ACCEPTED); assertThat(response.getBody()).isEqualTo(testUserDTO); - + verify(userService).findUserProfile(jwt); verify(userDTOMapper).toUserDTO(testUser); } @@ -125,14 +140,14 @@ void getUserProfileHandler_Success() { void getUserProfileHandler_ThrowsException() { // Arrange String jwt = "Bearer invalid.jwt.token"; - + when(userService.findUserProfile(jwt)).thenThrow(new RuntimeException("Invalid token")); // Act & Assert assertThatThrownBy(() -> userController.getUserProfileHandler(jwt)) .isInstanceOf(RuntimeException.class) .hasMessageContaining("Error during get user profile process"); - + verify(userService).findUserProfile(jwt); verify(userDTOMapper, never()).toUserDTO(any(User.class)); } @@ -144,7 +159,7 @@ void searchUsersByName_Success() { List users = List.of(testUser); HashSet userDTOs = new HashSet<>(); userDTOs.add(testUserDTO); - + when(userService.searchUser(name)).thenReturn(users); when(userDTOMapper.toUserDtos(any())).thenReturn(userDTOs); @@ -155,7 +170,7 @@ void searchUsersByName_Success() { assertThat(response).isNotNull(); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.ACCEPTED); assertThat(response.getBody()).isEqualTo(userDTOs); - + verify(userService).searchUser(name); verify(userDTOMapper).toUserDtos(any()); } @@ -164,15 +179,15 @@ void searchUsersByName_Success() { void searchUsersByName_ThrowsException() { // Arrange String name = "test"; - + when(userService.searchUser(name)).thenThrow(new RuntimeException("Database error")); // Act & Assert assertThatThrownBy(() -> userController.searchUsersByName(name)) .isInstanceOf(RuntimeException.class) .hasMessageContaining("Error during search users process"); - + verify(userService).searchUser(name); verify(userDTOMapper, never()).toUserDtos(any()); } -} \ No newline at end of file +} diff --git a/backend-service/src/test/java/co/teamsphere/api/services/impl/AuthenticationServiceImplTest.java b/backend-service/src/test/java/co/teamsphere/api/services/impl/AuthenticationServiceImplTest.java index d51860d..bd34c42 100644 --- a/backend-service/src/test/java/co/teamsphere/api/services/impl/AuthenticationServiceImplTest.java +++ b/backend-service/src/test/java/co/teamsphere/api/services/impl/AuthenticationServiceImplTest.java @@ -134,8 +134,10 @@ void signupUser_WithValidRequest_ReturnsAuthResponse() throws Exception { when(userRepository.findByUsername(validSignupRequest.getUsername())).thenReturn(Optional.empty()); when(cloudflareApiService.uploadImage(validSignupRequest.getFile())).thenReturn(cloudflareResponse); when(passwordEncoder.encode(validSignupRequest.getPassword())).thenReturn("encodedPassword"); - when(jwtTokenProvider.generateJwtToken(any(Authentication.class))).thenReturn("jwt.token.here"); + // TODO: I cant wrap my head around why this works? but it does + when(jwtTokenProvider.generateJwtToken(any(Authentication.class), any())) + .thenReturn("jwt.token.here"); var refreshToken = RefreshToken.builder() .id(UUID.randomUUID()) .user(testUser) @@ -228,7 +230,7 @@ void loginUser_WithValidCredentials_ReturnsAuthResponse() throws Exception { when(userRepository.findByEmail(email)).thenReturn(Optional.of(testUser)); when(customUserDetailsService.loadUserByUsername(email)).thenReturn(userDetails); when(passwordEncoder.matches(password, userDetails.getPassword())).thenReturn(true); - when(jwtTokenProvider.generateJwtToken(any(Authentication.class))).thenReturn("jwt.token.here"); + when(jwtTokenProvider.generateJwtToken(any(Authentication.class), any(UUID.class))).thenReturn("jwt.token.here"); var refreshToken = RefreshToken.builder() .id(UUID.randomUUID()) @@ -256,7 +258,7 @@ void loginUser_WithExistingRefreshToken_ReturnsAuthResponse() throws Exception { when(userRepository.findByEmail(email)).thenReturn(Optional.of(testUser)); when(customUserDetailsService.loadUserByUsername(email)).thenReturn(userDetails); when(passwordEncoder.matches(password, userDetails.getPassword())).thenReturn(true); - when(jwtTokenProvider.generateJwtToken(any(Authentication.class))).thenReturn("jwt.token.here"); + when(jwtTokenProvider.generateJwtToken(any(Authentication.class), any(UUID.class))).thenReturn("jwt.token.here"); var existingRefreshToken = RefreshToken.builder() .id(UUID.randomUUID()) diff --git a/backend-service/src/test/java/co/teamsphere/api/services/impl/MessageServiceImplTest.java b/backend-service/src/test/java/co/teamsphere/api/services/impl/MessageServiceImplTest.java index 1e3e1cf..ef58d7b 100644 --- a/backend-service/src/test/java/co/teamsphere/api/services/impl/MessageServiceImplTest.java +++ b/backend-service/src/test/java/co/teamsphere/api/services/impl/MessageServiceImplTest.java @@ -4,9 +4,7 @@ import static org.mockito.Mockito.*; import java.time.LocalDateTime; -import java.util.List; -import java.util.Optional; -import java.util.UUID; +import java.util.*; import co.teamsphere.api.exception.ChatException; import co.teamsphere.api.exception.MessageException; @@ -46,11 +44,13 @@ class MessageServiceImplTest { @BeforeEach void setUp() { MockitoAnnotations.openMocks(this); - user = new User(); - user.setId(UUID.randomUUID()); - user.setEmail("test@example.com"); + user = User.builder() + .id(UUID.randomUUID()) + .email("email@email.com") + .build(); chat = new Chat(); chat.setId(UUID.randomUUID()); + chat.setIsGroup(false); messageId = UUID.randomUUID(); chatId = chat.getId(); message = Messages.builder() @@ -65,25 +65,39 @@ void setUp() { @Test void sendMessageSavesMessageWhenValidRequest() throws UserException, ChatException { + // Arrange SendMessageRequest request = new SendMessageRequest(); request.setUserId(user.getId()); request.setChatId(chat.getId()); request.setContent("Hello World"); + + Set chatUsers = new HashSet<>(); + chatUsers.add(user); + chat.setUsers(chatUsers); + when(userService.findUserById(user.getId())).thenReturn(user); when(chatService.findChatById(chat.getId())).thenReturn(chat); when(messageRepo.save(any(Messages.class))).thenReturn(message); + + // Act Messages savedMessage = messageService.sendMessage(request); + + // Assert assertNotNull(savedMessage); assertEquals("Hello World", savedMessage.getContent()); assertEquals(chat, savedMessage.getChat()); assertEquals(user, savedMessage.getUsername()); + + verify(userService).findUserById(user.getId()); + verify(chatService).findChatById(chat.getId()); + verify(messageRepo).save(any(Messages.class)); } @Test void deleteMessageRemovesMessageWhenMessageExists() throws MessageException { when(messageRepo.findById(messageId)).thenReturn(Optional.of(message)); - messageService.deleteMessage(messageId); + messageService.deleteMessage(messageId, user.getId()); verify(messageRepo, times(1)).deleteById(messageId); } @@ -91,16 +105,34 @@ void deleteMessageRemovesMessageWhenMessageExists() throws MessageException { @Test void deleteMessageThrowsExceptionWhenMessageNotFound() { when(messageRepo.findById(messageId)).thenReturn(Optional.empty()); - assertThrows(MessageException.class, () -> messageService.deleteMessage(messageId)); + assertThrows(MessageException.class, () -> messageService.deleteMessage(messageId, user.getId())); verify(messageRepo, never()).deleteById(any()); } @Test void getChatsMessagesReturnsMessagesListWhenChatExists() throws ChatException { + // Arrange + UUID chatId = UUID.randomUUID(); + UUID userId = UUID.randomUUID(); + + User user = new User(); + user.setId(userId); + + Chat chat = new Chat(); + chat.setId(chatId); + chat.setUsers(Set.of(user)); + + Messages message = new Messages(); + message.setContent("Hello World"); + when(chatService.findChatById(chatId)).thenReturn(chat); when(messageRepo.findMessageByChatId(chatId)).thenReturn(List.of(message)); - List messagesList = messageService.getChatsMessages(chatId); + + // Act + List messagesList = messageService.getChatsMessages(chatId, userId); + + // Assert assertNotNull(messagesList); assertFalse(messagesList.isEmpty()); assertEquals(1, messagesList.size()); @@ -111,7 +143,7 @@ void getChatsMessagesReturnsMessagesListWhenChatExists() throws ChatException { @Test void getChatsMessagesThrowsExceptionWhenChatNotFound() throws ChatException { when(chatService.findChatById(chatId)).thenThrow(new ChatException("Chat not found")); - assertThrows(ChatException.class, () -> messageService.getChatsMessages(chatId)); + assertThrows(ChatException.class, () -> messageService.getChatsMessages(chatId, user.getId())); verify(messageRepo, never()).findMessageByChatId(any()); } @Test diff --git a/backend-service/src/test/java/co/teamsphere/api/services/impl/UserServiceImplTest.java b/backend-service/src/test/java/co/teamsphere/api/services/impl/UserServiceImplTest.java index 7f81e9e..62edfaf 100644 --- a/backend-service/src/test/java/co/teamsphere/api/services/impl/UserServiceImplTest.java +++ b/backend-service/src/test/java/co/teamsphere/api/services/impl/UserServiceImplTest.java @@ -78,7 +78,7 @@ void findUserById_WhenUserDoesNotExist_ThrowsUserException() { assertThatThrownBy(() -> userService.findUserById(userId)) .isInstanceOf(UserException.class) .hasMessageContaining("user doesnt exist with the id: " + userId); - + verify(userRepository, times(1)).findById(userId); } @@ -86,37 +86,39 @@ void findUserById_WhenUserDoesNotExist_ThrowsUserException() { void findUserProfile_WhenUserExists_ReturnsUser() { // Arrange String jwt = "valid.jwt.token"; - String email = "test@example.com"; - - when(jwtTokenProvider.getEmailFromToken(jwt)).thenReturn(email); - when(userRepository.findByEmail(email)).thenReturn(Optional.of(testUser)); + var userId = testUser.getId(); + + when(jwtTokenProvider.getIdFromToken(jwt)).thenReturn(userId); + + when(jwtTokenProvider.getIdFromToken(jwt)).thenReturn(userId); + when(userRepository.findById(userId)).thenReturn(Optional.of(testUser)); // Act User foundUser = userService.findUserProfile(jwt); // Assert assertThat(foundUser).isNotNull(); - assertThat(foundUser.getEmail()).isEqualTo(email); - verify(jwtTokenProvider, times(1)).getEmailFromToken(jwt); - verify(userRepository, times(1)).findByEmail(email); + assertThat(foundUser.getId()).isEqualTo(userId); + verify(jwtTokenProvider, times(1)).getIdFromToken(jwt); + verify(userRepository, times(1)).findById(userId); } @Test void findUserProfile_WhenUserDoesNotExist_ThrowsBadCredentialsException() { // Arrange String jwt = "valid.jwt.token"; - String email = "test@example.com"; - - when(jwtTokenProvider.getEmailFromToken(jwt)).thenReturn(email); - when(userRepository.findByEmail(email)).thenReturn(Optional.empty()); + UUID userId = UUID.randomUUID(); + + when(jwtTokenProvider.getIdFromToken(jwt)).thenReturn(userId); + when(userRepository.findById(userId)).thenReturn(Optional.empty()); // Act & Assert assertThatThrownBy(() -> userService.findUserProfile(jwt)) .isInstanceOf(BadCredentialsException.class) .hasMessageContaining("Received invalid token!"); - - verify(jwtTokenProvider, times(1)).getEmailFromToken(jwt); - verify(userRepository, times(1)).findByEmail(email); + + verify(jwtTokenProvider, times(1)).getIdFromToken(jwt); + verify(userRepository, times(1)).findById(userId); } @Test @@ -124,7 +126,7 @@ void searchUser_WhenUsersFound_ReturnsUserList() { // Arrange String query = "test"; List expectedUsers = List.of(testUser); - + when(userRepository.searchUsers(query)).thenReturn(expectedUsers); // Act @@ -141,7 +143,7 @@ void searchUser_WhenUsersFound_ReturnsUserList() { void searchUser_WhenNoUsersFound_ReturnsEmptyList() { // Arrange String query = "nonexistent"; - + when(userRepository.searchUsers(query)).thenReturn(Collections.emptyList()); // Act @@ -155,63 +157,60 @@ void searchUser_WhenNoUsersFound_ReturnsEmptyList() { @Test void updateUser_WithUsernameOnly_UpdatesUsername() throws Exception { - // Arrange UpdateUserRequest request = new UpdateUserRequest(); request.setUsername("newUsername"); - + when(userRepository.findById(userId)).thenReturn(Optional.of(testUser)); + when(userRepository.findByUsername("newUsername")).thenReturn(Optional.empty()); when(userRepository.save(any(User.class))).thenAnswer(invocation -> invocation.getArgument(0)); - // Act - User updatedUser = userService.updateUser(userId, request); + User updatedUser = userService.updateUser(userId, request, testUser.getId()); - // Assert assertThat(updatedUser).isNotNull(); assertThat(updatedUser.getUsername()).isEqualTo("newUsername"); assertThat(updatedUser.getProfilePicture()).isEqualTo(testUser.getProfilePicture()); - + ArgumentCaptor userCaptor = ArgumentCaptor.forClass(User.class); verify(userRepository).save(userCaptor.capture()); User savedUser = userCaptor.getValue(); assertThat(savedUser.getUsername()).isEqualTo("newUsername"); assertThat(savedUser.getLastUpdatedDate()).isNotNull(); } - @Test void updateUser_WithProfilePicture_UpdatesProfilePicture() throws Exception { // Arrange UpdateUserRequest request = new UpdateUserRequest(); request.setUsername("testUser"); MockMultipartFile profilePicture = new MockMultipartFile( - "profile_picture", + "profile_picture", "test.jpg", - "image/jpeg", + "image/jpeg", "test image content".getBytes() ); request.setProfile_picture(profilePicture); - + CloudflareApiResponse deleteResponse = new CloudflareApiResponse(); deleteResponse.setSuccess(true); - + CloudflareApiResponse.Result uploadResult = new CloudflareApiResponse.Result(); uploadResult.setVariants(List.of("https://example.com/profiles/xyz789/variant")); - + CloudflareApiResponse uploadResponse = new CloudflareApiResponse(); uploadResponse.setSuccess(true); uploadResponse.setResult(uploadResult); - + when(userRepository.findById(userId)).thenReturn(Optional.of(testUser)); when(cloudflareApiService.deleteImage("abc123")).thenReturn(deleteResponse); when(cloudflareApiService.uploadImage(profilePicture)).thenReturn(uploadResponse); when(userRepository.save(any(User.class))).thenAnswer(invocation -> invocation.getArgument(0)); // Act - User updatedUser = userService.updateUser(userId, request); + User updatedUser = userService.updateUser(userId, request, testUser.getId()); // Assert assertThat(updatedUser).isNotNull(); assertThat(updatedUser.getProfilePicture()).isEqualTo("https://example.com/profiles/xyz789/public"); - + verify(cloudflareApiService).deleteImage("abc123"); verify(cloudflareApiService).uploadImage(profilePicture); verify(userRepository).save(any(User.class)); @@ -222,14 +221,14 @@ void updateUser_UserNotFound_ThrowsUserException() throws UserException { // Arrange UpdateUserRequest request = new UpdateUserRequest(); request.setUsername("newUsername"); - + when(userRepository.findById(userId)).thenReturn(Optional.empty()); // Act & Assert - assertThatThrownBy(() -> userService.updateUser(userId, request)) + assertThatThrownBy(() -> userService.updateUser(userId, request, testUser.getId())) .isInstanceOf(UserException.class) .hasMessageContaining("Error updating user: user doesnt exist with the id"); - + verify(userRepository, never()).save(any(User.class)); } @@ -267,10 +266,10 @@ void updateUserThrowsExceptionWhenProfileUploadFails() throws IOException { when(cloudflareApiService.uploadImage(any(MultipartFile.class))).thenReturn(mockUploadResponse); // Assert that UserException is thrown - assertThrows(UserException.class, () -> userService.updateUser(testUser.getId(), request)); + assertThrows(UserException.class, () -> userService.updateUser(testUser.getId(), request, testUser.getId())); // Verify interactions (optional but recommended) verify(cloudflareApiService).uploadImage(any(MultipartFile.class)); verify(userRepository).findById(testUser.getId()); } -} \ No newline at end of file +} diff --git a/docker-compose.yml b/docker-compose.yml index b4167f1..2298b68 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,19 +1,82 @@ -version: '3' +version: '3.8' services: - mysql: - image: mysql:latest - environment: - MYSQL_ROOT_PASSWORD: my-secret-pw - MYSQL_DATABASE: teamsphere_db - ports: - - "3306:3306" - rabbitmq: - image: rabbitmq:management - environment: - RABBITMQ_DEFAULT_USER: guest - RABBITMQ_DEFAULT_PASS: guest - command: > - bash -c "rabbitmq-plugins enable rabbitmq_stomp && rabbitmq-server" - ports: - - "61613:61613" - - "15672:15672" \ No newline at end of file + mysql: + image: mysql:latest + environment: + MYSQL_ROOT_PASSWORD: my-secret-pw + MYSQL_DATABASE: teamsphere_db + ports: + - "3306:3306" + volumes: + - mysql-data:/var/lib/mysql + rabbitmq: + image: rabbitmq:management + environment: + RABBITMQ_DEFAULT_USER: guest + RABBITMQ_DEFAULT_PASS: guest + command: > + bash -c "rabbitmq-plugins enable rabbitmq_stomp && rabbitmq-server" + ports: + - "61613:61613" + - "15672:15672" + volumes: + - rabbitmq-data:/var/lib/rabbitmq + kafka: + image: confluentinc/cp-kafka:7.4.0 + container_name: kafka + hostname: kafka + ports: + - "9092:9092" # client + - "9093:9093" # controller listener + environment: + CLUSTER_ID: "F3_Br_pIQROFaarWUPABDA" + KAFKA_NODE_ID: 1 + KAFKA_PROCESS_ROLES: "broker,controller" + KAFKA_CONTROLLER_QUORUM_VOTERS: "1@kafka:9093" + KAFKA_ADVERTISED_LISTENERS: "PLAINTEXT://localhost:9092" + KAFKA_LISTENERS: "PLAINTEXT://0.0.0.0:9092,CONTROLLER://0.0.0.0:9093" + KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: "CONTROLLER:PLAINTEXT,PLAINTEXT:PLAINTEXT" + KAFKA_CONTROLLER_LISTENER_NAMES: "CONTROLLER" + KAFKA_INTER_BROKER_LISTENER_NAME: "PLAINTEXT" + KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1 + KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: 1 + KAFKA_TRANSACTION_STATE_LOG_MIN_ISR: 1 + KAFKA_LOG_DIRS: "/var/lib/kafka/data" + command: + - bash + - -c + - | + echo "CLUSTER_ID is: $CLUSTER_ID" + if [ ! -f /var/lib/kafka/data/meta.properties ]; then + echo "Formatting KRaft storage for cluster with ID: $CLUSTER_ID..." + kafka-storage format \ + --cluster-id "$CLUSTER_ID" \ + --config /etc/kafka/kraft/server.properties + else + echo "Skipping this step..." + fi + exec /etc/confluent/docker/run + volumes: + - kafka-data:/var/lib/kafka/data + redis: + image: redis:latest + container_name: redis + ports: + - "6379:6379" + volumes: + - redis-data:/data + command: ["redis-server", "--appendonly", "yes"] + +volumes: + mysql-data: + rabbitmq-data: + kafka-data: + redis-data: + + + + + + + +