Skip to content

Commit

Permalink
fix():multiple fixes + init unit tests
Browse files Browse the repository at this point in the history
  • Loading branch information
Thibaut-Mouton committed Apr 26, 2024
1 parent 4a22937 commit bbfa516
Show file tree
Hide file tree
Showing 77 changed files with 910 additions and 651 deletions.
18 changes: 18 additions & 0 deletions .github/workflows/coverage-back.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
name: test-back

on: push

jobs:
build:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v3
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
cache: maven
- name: build
run: mvn clean test -f backend/pom.xml
14 changes: 14 additions & 0 deletions .github/workflows/coverage-front.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
name: test-front

on: push

jobs:
build:
runs-on: ubuntu-latest

steps:
- uses: actions/setup-node@v4
with:
node-version: 20.x
- name: build
run: npm run test --prefix ./frontend-web
2 changes: 1 addition & 1 deletion .github/workflows/npm.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,4 @@ jobs:
with:
node-version: 20.x
- name: build
run: npm run build --prefix ./frontend-web
run: npm run --prefix ./frontend-web build
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
![maven build status](https://github.com/Thibaut-Mouton/react-spring-messenger-project/workflows/build-back/badge.svg?branch=master)
![npm build status](https://github.com/Thibaut-Mouton/react-spring-messenger-project/workflows/build-front/badge.svg?branch=master)
![maven build status](https://github.com/Thibaut-Mouton/react-spring-messenger-project/workflows/build-back/badge.svg?branch=develop)
![npm build status](https://github.com/Thibaut-Mouton/react-spring-messenger-project/workflows/build-front/badge.svg?branch=develop)
![coverage back](https://github.com/Thibaut-Mouton/react-spring-messenger-project/workflows/test-back/badge.svg?branch=develop)
![coverage front](https://github.com/Thibaut-Mouton/react-spring-messenger-project/workflows/test-front/badge.svg?branch=develop)

<p align="center">
<img src="/assets/react.png" alt="React logo"/>
Expand Down
18 changes: 18 additions & 0 deletions backend/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,24 @@
<artifactId>liquibase-maven-plugin</artifactId>
<version>4.27.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-inline</artifactId>
<version>5.2.0</version>
<scope>compile</scope>
</dependency>

<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-junit-jupiter</artifactId>
<version>5.2.0</version>
<scope>compile</scope>
</dependency>
</dependencies>

<build>
Expand Down
3 changes: 2 additions & 1 deletion backend/src/main/java/com/mercure/config/JwtWebConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.NonNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
Expand All @@ -28,7 +29,7 @@ public class JwtWebConfig extends OncePerRequestFilter {
private JwtUtil jwtUtil;

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws IOException, ServletException {
protected void doFilterInternal(@NonNull HttpServletRequest request,@NonNull HttpServletResponse response,@NonNull FilterChain filterChain) throws IOException, ServletException {
String jwtToken = null;
String username;
Cookie cookie = WebUtils.getCookie(request, StaticVariable.SECURE_COOKIE);
Expand Down
47 changes: 34 additions & 13 deletions backend/src/main/java/com/mercure/config/SecurityConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,53 +4,74 @@
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.csrf.CookieCsrfTokenRepository;
import org.springframework.security.web.csrf.CsrfTokenRepository;

@Configuration
public class SecurityConfig {

@Autowired
public JwtWebConfig jwtWebConfig;

@Autowired
public CustomUserDetailsService customUserDetailsService;

@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}

// @Bean
// public CsrfTokenRepository csrfTokenRepository() {
// CookieCsrfTokenRepository repository = CookieCsrfTokenRepository.withHttpOnlyFalse();
// repository.setCookiePath("/");
// repository.setCookieName("X-CSRF-TOKEN");
// repository.setHeaderName("X-CSRF-TOKEN");
// return repository;
// }

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf(AbstractHttpConfigurer::disable)
// .csrf(httpSecurityCsrfConfigurer -> httpSecurityCsrfConfigurer.ignoringRequestMatchers("/api/csrf"))
.cors(Customizer.withDefaults())
.authorizeHttpRequests((request) -> request
.requestMatchers("/api").permitAll()
.requestMatchers("/api/csrf").permitAll()
.requestMatchers("/api/auth").permitAll()
.requestMatchers("/api/**").authenticated())
// .csrf(csrf -> csrf.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()))
.cors(Customizer.withDefaults()).authorizeHttpRequests((request) -> request
.requestMatchers("/messenger", "/websocket", "/ws").permitAll()
.requestMatchers("/csrf").permitAll()
.requestMatchers("/auth").permitAll()
.requestMatchers("/health-check").permitAll()
.anyRequest().authenticated())
.sessionManagement(httpSecuritySessionManagementConfigurer -> httpSecuritySessionManagementConfigurer.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authenticationProvider(authenticationProvider())
.addFilterBefore(jwtWebConfig, UsernamePasswordAuthenticationFilter.class);
return http.build();
}

@Bean
public AuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
authenticationProvider.setUserDetailsService(new CustomUserDetailsService());
authenticationProvider.setPasswordEncoder(passwordEncoder());
return authenticationProvider;
public DaoAuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
authProvider.setUserDetailsService(customUserDetailsService);
authProvider.setPasswordEncoder(passwordEncoder());
return authProvider;
}

@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
return authenticationConfiguration.getAuthenticationManager();
}


// @Bean
// public CorsConfigurationSource corsConfigurationSource() {
// CorsConfiguration configuration = new CorsConfiguration();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.mercure.config;

import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.Message;
Expand All @@ -9,6 +10,7 @@

@Configuration
@EnableWebSocketSecurity
@ConditionalOnProperty(name = "websocket.csrf.enable", havingValue = "1")
public class WebSocketSecurityConfig {

@Bean
Expand Down
26 changes: 14 additions & 12 deletions backend/src/main/java/com/mercure/controller/ApiController.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
import java.util.*;

@RestController
@CrossOrigin
@CrossOrigin(allowCredentials = "true", origins = "http://localhost:3000")
public class ApiController {

private final Logger log = LoggerFactory.getLogger(ApiController.class);
Expand Down Expand Up @@ -136,17 +136,19 @@ private ResponseEntity<?> doUserAction(HttpServletRequest request, Integer userI
}
if (userService.checkIfUserIsAdmin(adminUserId, groupId)) {
try {
if (action.equals("grant")) {
groupUserJoinService.grantUserAdminInConversation(userId, groupId);
return ResponseEntity.ok().body(userToChange + " has been granted administrator to " + groupService.getGroupName(groupUrl));
}
if (action.equals("delete")) {
groupUserJoinService.removeUserFromConversation(userId, groupId);
return ResponseEntity.ok().body(userToChange + " has been removed from " + groupService.getGroupName(groupUrl));
}
if (action.equals("removeAdmin")) {
groupUserJoinService.removeUserAdminFromConversation(userId, groupId);
return ResponseEntity.ok().body(userToChange + " has been removed from administrators of " + groupService.getGroupName(groupUrl));
switch (action) {
case "grant" -> {
groupUserJoinService.grantUserAdminInConversation(userId, groupId);
return ResponseEntity.ok().body(userToChange + " has been granted administrator to " + groupService.getGroupName(groupUrl));
}
case "delete" -> {
groupUserJoinService.removeUserFromConversation(userId, groupId);
return ResponseEntity.ok().body(userToChange + " has been removed from " + groupService.getGroupName(groupUrl));
}
case "removeAdmin" -> {
groupUserJoinService.removeUserAdminFromConversation(userId, groupId);
return ResponseEntity.ok().body(userToChange + " has been removed from administrators of " + groupService.getGroupName(groupUrl));
}
}
} catch (Exception e) {
log.warn("Error during performing {} : {}", action, e.getMessage());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,17 @@
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.DisabledException;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.web.csrf.CsrfToken;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.util.WebUtils;


@RestController
@CrossOrigin(allowCredentials = "true", origins = "http://localhost:3000")
@CrossOrigin(allowCredentials = "true", origins = "http://localhost:3000", methods = {RequestMethod.GET, RequestMethod.POST})
public class AuthenticationController {

private final Logger log = LoggerFactory.getLogger(AuthenticationController.class);
Expand All @@ -53,22 +54,29 @@ public class AuthenticationController {
@Autowired
private GroupMapper groupMapper;

@Autowired
private AuthenticationManager authenticationManager;

@PostMapping(value = "/auth")
public AuthUserDTO createAuthenticationToken(@RequestBody JwtDTO authenticationRequest, HttpServletResponse response) throws Exception {
authenticate(authenticationRequest.getUsername(), authenticationRequest.getPassword());
UserDetails userDetails = userDetailsService.loadUserByUsername(authenticationRequest.getUsername());
UserEntity user = userService.findByNameOrEmail(authenticationRequest.getUsername(), authenticationRequest.getUsername());
String token = jwtTokenUtil.generateToken(userDetails);
Cookie jwtAuthToken = new Cookie(StaticVariable.SECURE_COOKIE, token);
jwtAuthToken.setHttpOnly(true);
jwtAuthToken.setSecure(false);
jwtAuthToken.setPath("/");
public AuthUserDTO createAuthenticationToken(@RequestBody JwtDTO authenticationRequest, HttpServletResponse response) {
Authentication authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(authenticationRequest.getUsername(), authenticationRequest.getPassword()));
if (authentication.isAuthenticated()) {
UserDetails userDetails = userDetailsService.loadUserByUsername(authenticationRequest.getUsername());
UserEntity user = userService.findByNameOrEmail(authenticationRequest.getUsername(), authenticationRequest.getUsername());
String token = jwtTokenUtil.generateToken(userDetails);
Cookie jwtAuthToken = new Cookie(StaticVariable.SECURE_COOKIE, token);
jwtAuthToken.setHttpOnly(true);
jwtAuthToken.setSecure(false);
jwtAuthToken.setPath("/");
// cookie.setDomain("http://localhost");
// 7 days
jwtAuthToken.setMaxAge(7 * 24 * 60 * 60);
response.addCookie(jwtAuthToken);
log.debug("User authenticated successfully");
return userMapper.toLightUserDTO(user);
// TODO add to env vars
jwtAuthToken.setMaxAge(2 * 60 * 60); // 2 hours
response.addCookie(jwtAuthToken);
log.debug("User authenticated successfully");
return userMapper.toLightUserDTO(user);
} else {
throw new UsernameNotFoundException("invalid user request");
}
}

@GetMapping(value = "/logout")
Expand All @@ -92,16 +100,6 @@ public InitUserDTO fetchInformation(HttpServletRequest request) {
return userMapper.toUserDTO(getUserEntity(request));
}

private void authenticate(String username, String password) throws Exception {
try {
//authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));
} catch (DisabledException e) {
throw new Exception("USER_DISABLED", e);
} catch (BadCredentialsException e) {
throw new Exception("INVALID_CREDENTIALS", e);
}
}

@PostMapping(value = "/create")
public GroupDTO createGroupChat(HttpServletRequest request, @RequestBody String payload) {
UserEntity user = getUserEntity(request);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ public class MessageController {
@Autowired
private MessageService messageService;

@GetMapping(value = "/group/{groupUrl}")
public WrapperMessageDTO fetchGroupMessages(@PathVariable String groupUrl) {
@GetMapping(value = "{offset}/group/{groupUrl}")
public WrapperMessageDTO fetchGroupMessages(@PathVariable String groupUrl, @PathVariable int offset) {
this.log.debug("Fetching messages from conversation");
return this.messageService.getConversationMessage(groupUrl, -1);
return this.messageService.getConversationMessage(groupUrl, offset);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ public class PingController {
private final Logger log = LoggerFactory.getLogger(PingController.class);


@GetMapping
@GetMapping("health-check")
public String testRoute() {
log.debug("Ping base route");
return "Server status OK";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,13 +87,14 @@ public void mainChannel(InputTransportDTO dto, @Header("simpSessionId") String s
if (!"".equals(dto.getGroupUrl())) {
int messageId = messageService.findLastMessageIdByGroupId(groupService.findGroupByUrl(dto.getGroupUrl()));
MessageUserEntity messageUserEntity = seenMessageService.findByMessageId(messageId, dto.getUserId());
if (messageUserEntity == null) break;
messageUserEntity.setSeen(true);
if (messageUserEntity == null) {
break;
};
seenMessageService.saveMessageUserEntity(messageUserEntity);
}
break;
case LEAVE_GROUP:
if (!dto.getGroupUrl().equals("")) {
if (!dto.getGroupUrl().isEmpty()) {
log.info("User id {} left group {}", dto.getUserId(), dto.getGroupUrl());
int groupId = groupService.findGroupByUrl(dto.getGroupUrl());
groupUserJoinService.removeUserFromConversation(dto.getUserId(), groupId);
Expand Down Expand Up @@ -183,7 +184,7 @@ public void webRtcChannel(@DestinationVariable String roomUrl, RtcTransportDTO d
String key = roomUrl + "_" + dto.getGroupUrl();
List<Integer> userIds = groupService.getAllUsersIdByGroupUrl(dto.getGroupUrl());
HashMap<String, ArrayList<Integer>> hostListsIndexedByRoomUrl = roomCacheService.getRoomByKey(key);
if (hostListsIndexedByRoomUrl.size() == 0) {
if (hostListsIndexedByRoomUrl.isEmpty()) {
log.info("All users left the call, removing room from list");
OutputTransportDTO outputTransportDTO = new OutputTransportDTO();
outputTransportDTO.setAction(TransportActionEnum.END_CALL);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,11 @@

import java.util.List;

/**
* API controller to handle file upload
*/
@RestController
@CrossOrigin(allowCredentials = "true", origins = "http://localhost:3000")
public class WsFileController {

private static Logger log = LoggerFactory.getLogger(WsFileController.class);
private static final Logger log = LoggerFactory.getLogger(WsFileController.class);

@Autowired
private MessageService messageService;
Expand Down
4 changes: 4 additions & 0 deletions backend/src/main/java/com/mercure/dto/WrapperMessageDTO.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,9 @@ public class WrapperMessageDTO {

private String groupName;

private boolean isActiveCall;

private String callUrl;

private List<MessageDTO> messages;
}
Loading

0 comments on commit bbfa516

Please sign in to comment.