From a86a2ac51acd8dc74960baabd772a51f23e9d2d8 Mon Sep 17 00:00:00 2001 From: JJerome-NM Date: Mon, 15 May 2023 20:28:13 +0300 Subject: [PATCH 1/8] CodeNames #7: Added authorization and started adding Redis --- .../configurations/RestControllersConfig.java | 24 ---- .../properties/SecurityProperties.java | 14 +++ .../src/main/resources/application.yml | 16 +++ .../com/codenames/config/PasswordConfig.java | 15 +++ .../com/codenames/config/RedisConfig.java | 40 ++++++ .../config/RestControllersConfig.java | 24 ++++ .../codenames/config/WebSecurityConfig.java | 65 ++++++++++ .../com/codenames/dao/RedisPlayerDao.java | 27 ++++ .../java/com/codenames/dto/CredentialDto.java | 14 +++ .../main/java/com/codenames/dto/ErrorDto.java | 11 ++ .../java/com/codenames/dto/SignUpDto.java | 21 ++++ .../java/com/codenames/dto/UserAuthDto.java | 19 +++ .../java/com/codenames/entity/UserEntity.java | 31 +++++ .../exception/UserAlreadyExistsException.java | 15 +++ .../exception/UserAuthEntryPoint.java | 36 ++++++ .../exception/UserNotFoundException.java | 13 ++ .../UserParametersNoValidException.java | 15 +++ .../com/codenames/filter/JwtAuthFilter.java | 44 +++++++ .../java/com/codenames/mapper/UserMapper.java | 22 ++++ .../com/codenames/models/game/Player.java | 9 ++ .../codenames/provider/UserAuthProvider.java | 70 +++++++++++ .../codenames/redis_entity/HashedPlayer.java | 26 ++++ .../codenames/redis_entity/HashedUser.java | 18 +++ .../codenames/repository/UserRepository.java | 11 ++ .../com/codenames/services/RoomService.java | 21 +--- .../com/codenames/services/TeamService.java | 30 ++--- .../com/codenames/services/UserService.java | 67 ++++++++++ codenames-front-end/package-lock.json | 55 ++++---- codenames-front-end/package.json | 3 +- codenames-front-end/src/App.tsx | 85 +++++++++---- .../menu/CNStopGameMenu/CNStopGameMenu.tsx | 2 + .../ui/CNBlurredFrom/CNBluredFrom.tsx | 29 +++++ .../ui/CNBlurredFrom/StyledCNBlurredFrom.ts | 28 +++++ .../src/components/ui/CNBlurredFrom/index.ts | 3 + ...faultImport.ts => StyledCNDefaultInput.ts} | 3 +- .../src/components/ui/CNDefaultInput/index.ts | 2 +- .../src/components/ui/index.ts | 3 +- codenames-front-end/src/config/RestConfig.ts | 8 +- codenames-front-end/src/helper/AuthRequest.ts | 28 +++++ codenames-front-end/src/helper/index.ts | 3 + .../src/hooks/useCodeNamesRestRequests.ts | 9 +- .../src/hooks/useCodeNamesWsRoomConnect.ts | 40 +++--- .../Authentication/AuthenticationStyles.ts | 46 +++++++ .../Authentication/SignIn/SignInStyles.ts | 13 ++ .../pages/Authentication/SignIn/SingIn.tsx | 95 ++++++++++++++ .../SignIn}/index.ts | 0 .../Authentication/SignUp/SignUpStyles.ts | 12 ++ .../pages/Authentication/SignUp/SingUp.tsx | 118 ++++++++++++++++++ .../{ => Authentication}/SignUp/index.ts | 0 .../src/pages/Authentication/index.ts | 4 + .../src/pages/Connect/Connect.tsx | 4 +- .../src/pages/Connect/ConnectStyles.ts | 4 +- .../src/pages/SignUp/SingUp.tsx | 13 -- .../src/pages/SingIn/SingIn.tsx | 11 -- codenames-front-end/src/pages/index.ts | 3 +- codenames-front-end/src/router/index.ts | 15 +-- .../GameRoomsWebSocketController.java | 9 +- .../controllers/UsersController.java | 51 ++++++++ .../filters/connect/BannedUserFilter.java | 1 + .../connect/UserIsAuthorizedFilter.java | 40 ++++++ .../filters/method/UserAuthorizedFilter.java | 3 +- pom.xml | 44 +++++-- 62 files changed, 1323 insertions(+), 182 deletions(-) delete mode 100644 codenames-common/src/main/java/com/codenames/configurations/RestControllersConfig.java create mode 100644 codenames-common/src/main/java/com/codenames/properties/SecurityProperties.java create mode 100644 codenames-domain/src/main/java/com/codenames/config/PasswordConfig.java create mode 100644 codenames-domain/src/main/java/com/codenames/config/RedisConfig.java create mode 100644 codenames-domain/src/main/java/com/codenames/config/RestControllersConfig.java create mode 100644 codenames-domain/src/main/java/com/codenames/config/WebSecurityConfig.java create mode 100644 codenames-domain/src/main/java/com/codenames/dao/RedisPlayerDao.java create mode 100644 codenames-domain/src/main/java/com/codenames/dto/CredentialDto.java create mode 100644 codenames-domain/src/main/java/com/codenames/dto/ErrorDto.java create mode 100644 codenames-domain/src/main/java/com/codenames/dto/SignUpDto.java create mode 100644 codenames-domain/src/main/java/com/codenames/dto/UserAuthDto.java create mode 100644 codenames-domain/src/main/java/com/codenames/entity/UserEntity.java create mode 100644 codenames-domain/src/main/java/com/codenames/exception/UserAlreadyExistsException.java create mode 100644 codenames-domain/src/main/java/com/codenames/exception/UserAuthEntryPoint.java create mode 100644 codenames-domain/src/main/java/com/codenames/exception/UserNotFoundException.java create mode 100644 codenames-domain/src/main/java/com/codenames/exception/UserParametersNoValidException.java create mode 100644 codenames-domain/src/main/java/com/codenames/filter/JwtAuthFilter.java create mode 100644 codenames-domain/src/main/java/com/codenames/mapper/UserMapper.java create mode 100644 codenames-domain/src/main/java/com/codenames/provider/UserAuthProvider.java create mode 100644 codenames-domain/src/main/java/com/codenames/redis_entity/HashedPlayer.java create mode 100644 codenames-domain/src/main/java/com/codenames/redis_entity/HashedUser.java create mode 100644 codenames-domain/src/main/java/com/codenames/repository/UserRepository.java create mode 100644 codenames-domain/src/main/java/com/codenames/services/UserService.java create mode 100644 codenames-front-end/src/components/ui/CNBlurredFrom/CNBluredFrom.tsx create mode 100644 codenames-front-end/src/components/ui/CNBlurredFrom/StyledCNBlurredFrom.ts create mode 100644 codenames-front-end/src/components/ui/CNBlurredFrom/index.ts rename codenames-front-end/src/components/ui/CNDefaultInput/{StyledCNDefaultImport.ts => StyledCNDefaultInput.ts} (77%) create mode 100644 codenames-front-end/src/helper/AuthRequest.ts create mode 100644 codenames-front-end/src/helper/index.ts create mode 100644 codenames-front-end/src/pages/Authentication/AuthenticationStyles.ts create mode 100644 codenames-front-end/src/pages/Authentication/SignIn/SignInStyles.ts create mode 100644 codenames-front-end/src/pages/Authentication/SignIn/SingIn.tsx rename codenames-front-end/src/pages/{SingIn => Authentication/SignIn}/index.ts (100%) create mode 100644 codenames-front-end/src/pages/Authentication/SignUp/SignUpStyles.ts create mode 100644 codenames-front-end/src/pages/Authentication/SignUp/SingUp.tsx rename codenames-front-end/src/pages/{ => Authentication}/SignUp/index.ts (100%) create mode 100644 codenames-front-end/src/pages/Authentication/index.ts delete mode 100644 codenames-front-end/src/pages/SignUp/SingUp.tsx delete mode 100644 codenames-front-end/src/pages/SingIn/SingIn.tsx create mode 100644 codenames-web-services/src/main/java/com/codenames/controllers/UsersController.java create mode 100644 codenames-web-services/src/main/java/com/codenames/filters/connect/UserIsAuthorizedFilter.java diff --git a/codenames-common/src/main/java/com/codenames/configurations/RestControllersConfig.java b/codenames-common/src/main/java/com/codenames/configurations/RestControllersConfig.java deleted file mode 100644 index 75d1b77..0000000 --- a/codenames-common/src/main/java/com/codenames/configurations/RestControllersConfig.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.codenames.configurations; - -import com.codenames.properties.CodeNamesProperties; -import lombok.RequiredArgsConstructor; -import org.springframework.context.annotation.Configuration; -import org.springframework.web.servlet.config.annotation.CorsRegistry; -import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; - - -@Configuration -@RequiredArgsConstructor -public class RestControllersConfig implements WebMvcConfigurer { - - private final CodeNamesProperties codeNamesProperties; - - - @Override - public void addCorsMappings(CorsRegistry registry) { - registry.addMapping("/**") - .allowedOrigins(codeNamesProperties.getRestController().getCrossOrigin()) - .allowedMethods("GET", "POST", "PUT", "DELETE") - .allowedHeaders("*"); - } -} diff --git a/codenames-common/src/main/java/com/codenames/properties/SecurityProperties.java b/codenames-common/src/main/java/com/codenames/properties/SecurityProperties.java new file mode 100644 index 0000000..49624f5 --- /dev/null +++ b/codenames-common/src/main/java/com/codenames/properties/SecurityProperties.java @@ -0,0 +1,14 @@ +package com.codenames.properties; + + +import lombok.Getter; +import lombok.Setter; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +@Component +@Setter @Getter +@ConfigurationProperties(prefix = "security") +public class SecurityProperties { + String jwt; +} diff --git a/codenames-common/src/main/resources/application.yml b/codenames-common/src/main/resources/application.yml index 0f6a0e9..188fa35 100644 --- a/codenames-common/src/main/resources/application.yml +++ b/codenames-common/src/main/resources/application.yml @@ -16,3 +16,19 @@ codenames: cross-origin: - http://localhost:3000 + +security: + jwt: I don't understand this stupid thing. + +spring: + datasource: + url: jdbc:mysql://localhost:3306/codenames + username: root + password: 1234 + driver-class-name: com.mysql.cj.jdbc.Driver + + jpa: + hibernate: + ddl-auto: update + generate-ddl: true + show-sql: true diff --git a/codenames-domain/src/main/java/com/codenames/config/PasswordConfig.java b/codenames-domain/src/main/java/com/codenames/config/PasswordConfig.java new file mode 100644 index 0000000..9bbb12a --- /dev/null +++ b/codenames-domain/src/main/java/com/codenames/config/PasswordConfig.java @@ -0,0 +1,15 @@ +package com.codenames.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Component; + +@Component +public class PasswordConfig { + + @Bean + public PasswordEncoder passwordEncoder(){ + return new BCryptPasswordEncoder(); + } +} diff --git a/codenames-domain/src/main/java/com/codenames/config/RedisConfig.java b/codenames-domain/src/main/java/com/codenames/config/RedisConfig.java new file mode 100644 index 0000000..f16c9c5 --- /dev/null +++ b/codenames-domain/src/main/java/com/codenames/config/RedisConfig.java @@ -0,0 +1,40 @@ +package com.codenames.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.connection.RedisStandaloneConfiguration; +import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.repository.configuration.EnableRedisRepositories; +import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer; +import org.springframework.data.redis.serializer.StringRedisSerializer; + +@Configuration +@EnableRedisRepositories +public class RedisConfig { + + + @Bean + public JedisConnectionFactory jedisConnectionFactory() { + RedisStandaloneConfiguration configuration = new RedisStandaloneConfiguration(); + + configuration.setHostName("localhost"); + configuration.setPort(6379); + + return new JedisConnectionFactory(configuration); + } + + @Bean + public RedisTemplate redisTemplate(){ + RedisTemplate redisTemplate = new RedisTemplate<>(); + + redisTemplate.setConnectionFactory(jedisConnectionFactory()); + redisTemplate.setKeySerializer(new StringRedisSerializer()); + redisTemplate.setHashKeySerializer(new StringRedisSerializer()); + redisTemplate.setValueSerializer(new JdkSerializationRedisSerializer()); + redisTemplate.setEnableTransactionSupport(true); + redisTemplate.afterPropertiesSet(); + + return redisTemplate; + } +} diff --git a/codenames-domain/src/main/java/com/codenames/config/RestControllersConfig.java b/codenames-domain/src/main/java/com/codenames/config/RestControllersConfig.java new file mode 100644 index 0000000..9249035 --- /dev/null +++ b/codenames-domain/src/main/java/com/codenames/config/RestControllersConfig.java @@ -0,0 +1,24 @@ +package com.codenames.config; + +import com.codenames.properties.CodeNamesProperties; +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.CorsRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + + +//@Configuration +//@RequiredArgsConstructor +//public class RestControllersConfig implements WebMvcConfigurer { +// +// private final CodeNamesProperties codeNamesProperties; +// +// +// @Override +// public void addCorsMappings(CorsRegistry registry) { +// registry.addMapping("/**") +// .allowedOrigins(codeNamesProperties.getRestController().getCrossOrigin()) +// .allowedMethods("GET", "POST", "PUT", "DELETE") +// .allowedHeaders("*"); +// } +//} diff --git a/codenames-domain/src/main/java/com/codenames/config/WebSecurityConfig.java b/codenames-domain/src/main/java/com/codenames/config/WebSecurityConfig.java new file mode 100644 index 0000000..1ce6a31 --- /dev/null +++ b/codenames-domain/src/main/java/com/codenames/config/WebSecurityConfig.java @@ -0,0 +1,65 @@ +package com.codenames.config; + + +import com.codenames.exception.UserAuthEntryPoint; +import com.codenames.filter.JwtAuthFilter; +import com.codenames.properties.CodeNamesProperties; +import com.codenames.provider.UserAuthProvider; +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpMethod; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.CorsConfigurationSource; +import org.springframework.web.cors.UrlBasedCorsConfigurationSource; + +import java.util.List; + +@Configuration +@EnableWebSecurity +@RequiredArgsConstructor +public class WebSecurityConfig { + + private final CodeNamesProperties codeNamesProperties; + + private final UserAuthProvider userAuthProvider; + + private final UserAuthEntryPoint userAuthEntryPoint; + + @Bean + public SecurityFilterChain filterChain (HttpSecurity http) throws Exception { + http + .cors().and() + .exceptionHandling().authenticationEntryPoint(userAuthEntryPoint) + .and() + .addFilterBefore(new JwtAuthFilter(userAuthProvider), UsernamePasswordAuthenticationFilter.class) + .csrf().disable() + .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) + .and() + .authorizeHttpRequests(auth -> auth + .requestMatchers(HttpMethod.POST , "/login", "/register").permitAll() + .requestMatchers("/socket/**").permitAll() + .anyRequest().authenticated() + ); + + return http.build(); + } + + @Bean + CorsConfigurationSource corsConfigurationSource(){ + CorsConfiguration configuration = new CorsConfiguration(); + configuration.setAllowedOrigins(List.of(codeNamesProperties.getRestController().getCrossOrigin())); + configuration.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE")); + configuration.setAllowedHeaders(List.of("*")); + + UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + source.registerCorsConfiguration("/**", configuration); + + return source; + } +} diff --git a/codenames-domain/src/main/java/com/codenames/dao/RedisPlayerDao.java b/codenames-domain/src/main/java/com/codenames/dao/RedisPlayerDao.java new file mode 100644 index 0000000..f2291ae --- /dev/null +++ b/codenames-domain/src/main/java/com/codenames/dao/RedisPlayerDao.java @@ -0,0 +1,27 @@ +package com.codenames.dao; + +import com.codenames.redis_entity.HashedPlayer; +import lombok.RequiredArgsConstructor; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Repository; + +@Repository +@RequiredArgsConstructor +public class RedisPlayerDao { + + private static final String HASH_KEY = "authorize_player"; + + private RedisTemplate template; + + public void save(HashedPlayer hashedPlayer){ + template.opsForHash().put(HASH_KEY, hashedPlayer.getId(), hashedPlayer); + } + + public void removeHashedPlayerById(int id){ + template.opsForHash().delete(HASH_KEY, id); + } + + public HashedPlayer findHashedPlayerById(int id){ + return (HashedPlayer) template.opsForHash().get(HASH_KEY, id); + } +} diff --git a/codenames-domain/src/main/java/com/codenames/dto/CredentialDto.java b/codenames-domain/src/main/java/com/codenames/dto/CredentialDto.java new file mode 100644 index 0000000..cc726bd --- /dev/null +++ b/codenames-domain/src/main/java/com/codenames/dto/CredentialDto.java @@ -0,0 +1,14 @@ +package com.codenames.dto; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.Setter; + +@RequiredArgsConstructor +@Getter @Setter +public class CredentialDto { + + private String login; + + private String password; +} diff --git a/codenames-domain/src/main/java/com/codenames/dto/ErrorDto.java b/codenames-domain/src/main/java/com/codenames/dto/ErrorDto.java new file mode 100644 index 0000000..1b84bef --- /dev/null +++ b/codenames-domain/src/main/java/com/codenames/dto/ErrorDto.java @@ -0,0 +1,11 @@ +package com.codenames.dto; + +import lombok.*; + +@AllArgsConstructor +@NoArgsConstructor +@Getter @Setter +public class ErrorDto { + + private String message; +} diff --git a/codenames-domain/src/main/java/com/codenames/dto/SignUpDto.java b/codenames-domain/src/main/java/com/codenames/dto/SignUpDto.java new file mode 100644 index 0000000..6f8713c --- /dev/null +++ b/codenames-domain/src/main/java/com/codenames/dto/SignUpDto.java @@ -0,0 +1,21 @@ +package com.codenames.dto; + + +import jakarta.validation.constraints.NotBlank; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@AllArgsConstructor +@NoArgsConstructor +@Getter @Setter +public class SignUpDto { + + @NotBlank + private String email; + + private String nickname; + + private String password; +} diff --git a/codenames-domain/src/main/java/com/codenames/dto/UserAuthDto.java b/codenames-domain/src/main/java/com/codenames/dto/UserAuthDto.java new file mode 100644 index 0000000..df94e65 --- /dev/null +++ b/codenames-domain/src/main/java/com/codenames/dto/UserAuthDto.java @@ -0,0 +1,19 @@ +package com.codenames.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@AllArgsConstructor +@NoArgsConstructor +@Getter @Setter +public class UserAuthDto { + private int id; + + private String nickname; + + private String login; + + private String jwtToken; +} diff --git a/codenames-domain/src/main/java/com/codenames/entity/UserEntity.java b/codenames-domain/src/main/java/com/codenames/entity/UserEntity.java new file mode 100644 index 0000000..f6031df --- /dev/null +++ b/codenames-domain/src/main/java/com/codenames/entity/UserEntity.java @@ -0,0 +1,31 @@ +package com.codenames.entity; + + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Entity +@Table(name = "users") +@Getter @Setter +@NoArgsConstructor +public class UserEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name = "nickname") + private String nickname; + + @Column(name = "login") + private String login; + + @Column(name = "password") + private String password; +} diff --git a/codenames-domain/src/main/java/com/codenames/exception/UserAlreadyExistsException.java b/codenames-domain/src/main/java/com/codenames/exception/UserAlreadyExistsException.java new file mode 100644 index 0000000..8a223b2 --- /dev/null +++ b/codenames-domain/src/main/java/com/codenames/exception/UserAlreadyExistsException.java @@ -0,0 +1,15 @@ +package com.codenames.exception; + +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@Getter +public class UserAlreadyExistsException extends RuntimeException { + + private final HttpStatus status; + + public UserAlreadyExistsException(String message, HttpStatus status){ + super(message); + this.status = status; + } +} diff --git a/codenames-domain/src/main/java/com/codenames/exception/UserAuthEntryPoint.java b/codenames-domain/src/main/java/com/codenames/exception/UserAuthEntryPoint.java new file mode 100644 index 0000000..4bc2215 --- /dev/null +++ b/codenames-domain/src/main/java/com/codenames/exception/UserAuthEntryPoint.java @@ -0,0 +1,36 @@ +package com.codenames.exception; + +import com.codenames.dto.ErrorDto; +import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.AuthenticationEntryPoint; +import org.springframework.stereotype.Component; + +import java.io.IOException; + + +@Component +public class UserAuthEntryPoint implements AuthenticationEntryPoint { + + private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + + @Override + public void commence(HttpServletRequest request, + HttpServletResponse response, + AuthenticationException authException) throws IOException { + + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + response.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE); + response.setHeader("Access-Control-Allow-Origin", "*"); +// response.setHeader("Access-Control-Allow-Methods", "GET, POST, DELETE, PUT, OPTIONS"); + response.setHeader("Access-Control-Allow-Headers", "*"); +// response.setHeader("Access-Control-Allow-Credentials", String.valueOf(true)); +// response.setHeader("Access-Control-Max-Age", String.valueOf(3600)); + + OBJECT_MAPPER.writeValue(response.getOutputStream(), new ErrorDto("Unauthorized path")); + } +} diff --git a/codenames-domain/src/main/java/com/codenames/exception/UserNotFoundException.java b/codenames-domain/src/main/java/com/codenames/exception/UserNotFoundException.java new file mode 100644 index 0000000..00d0a88 --- /dev/null +++ b/codenames-domain/src/main/java/com/codenames/exception/UserNotFoundException.java @@ -0,0 +1,13 @@ +package com.codenames.exception; + +import org.springframework.http.HttpStatus; + +public class UserNotFoundException extends RuntimeException{ + + private HttpStatus code; + + public UserNotFoundException(String message, HttpStatus code){ + super(message); + this.code = code; + } +} diff --git a/codenames-domain/src/main/java/com/codenames/exception/UserParametersNoValidException.java b/codenames-domain/src/main/java/com/codenames/exception/UserParametersNoValidException.java new file mode 100644 index 0000000..756be91 --- /dev/null +++ b/codenames-domain/src/main/java/com/codenames/exception/UserParametersNoValidException.java @@ -0,0 +1,15 @@ +package com.codenames.exception; + +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@Getter +public class UserParametersNoValidException extends RuntimeException{ + + private HttpStatus status; + + public UserParametersNoValidException(String message, HttpStatus status){ + super(message); + this.status = status; + } +} diff --git a/codenames-domain/src/main/java/com/codenames/filter/JwtAuthFilter.java b/codenames-domain/src/main/java/com/codenames/filter/JwtAuthFilter.java new file mode 100644 index 0000000..a1c7619 --- /dev/null +++ b/codenames-domain/src/main/java/com/codenames/filter/JwtAuthFilter.java @@ -0,0 +1,44 @@ +package com.codenames.filter; + +import com.codenames.provider.UserAuthProvider; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpHeaders; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.web.filter.OncePerRequestFilter; + +import java.io.IOException; + +@RequiredArgsConstructor +public class JwtAuthFilter extends OncePerRequestFilter { + + private final UserAuthProvider userAuthProvider; + + @Override + protected void doFilterInternal(HttpServletRequest request, + HttpServletResponse response, + FilterChain filterChain) throws ServletException, IOException { + + String header = request.getHeader(HttpHeaders.AUTHORIZATION); + + if (header != null){ + String[] elements = header.split(" "); + + if (elements.length == 2 && "Bearer".equals(elements[0])){ + try { + SecurityContextHolder.getContext().setAuthentication( + userAuthProvider.validateToken(elements[1]) + ); + + } catch (RuntimeException e){ + SecurityContextHolder.clearContext(); + throw e; + } + } + } + filterChain.doFilter(request, response); + } +} diff --git a/codenames-domain/src/main/java/com/codenames/mapper/UserMapper.java b/codenames-domain/src/main/java/com/codenames/mapper/UserMapper.java new file mode 100644 index 0000000..e00a211 --- /dev/null +++ b/codenames-domain/src/main/java/com/codenames/mapper/UserMapper.java @@ -0,0 +1,22 @@ +package com.codenames.mapper; + +import com.codenames.dto.SignUpDto; +import com.codenames.dto.UserAuthDto; +import com.codenames.dto.UserDto; +import com.codenames.entity.UserEntity; +import com.codenames.models.game.User; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; + +@Mapper(componentModel = "spring") +public interface UserMapper { + + UserDto userEntityToUserDto(UserEntity userEntity); + + @Mapping(target = "login", source = "email") + UserEntity signUpToUserEntity(SignUpDto userEntity); + + UserAuthDto userEntityToUserAuthDto(UserEntity userEntity); + + User userEntityToUser(UserEntity userEntity); +} diff --git a/codenames-domain/src/main/java/com/codenames/models/game/Player.java b/codenames-domain/src/main/java/com/codenames/models/game/Player.java index edcff80..3fc7628 100644 --- a/codenames-domain/src/main/java/com/codenames/models/game/Player.java +++ b/codenames-domain/src/main/java/com/codenames/models/game/Player.java @@ -11,4 +11,13 @@ public class Player { private String wsSessionId; private PlayerRole playerRole; private final User user; + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof Player sPlayer)) { + return false; + } else { + return sPlayer.getUser().id() == this.getUser().id(); + } + } } diff --git a/codenames-domain/src/main/java/com/codenames/provider/UserAuthProvider.java b/codenames-domain/src/main/java/com/codenames/provider/UserAuthProvider.java new file mode 100644 index 0000000..b72193c --- /dev/null +++ b/codenames-domain/src/main/java/com/codenames/provider/UserAuthProvider.java @@ -0,0 +1,70 @@ +package com.codenames.provider; + +import com.auth0.jwt.JWT; +import com.auth0.jwt.JWTVerifier; +import com.auth0.jwt.algorithms.Algorithm; +import com.auth0.jwt.interfaces.DecodedJWT; +import com.codenames.entity.UserEntity; +import com.codenames.properties.SecurityProperties; +import com.codenames.services.UserService; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.stereotype.Component; + +import java.util.Base64; +import java.util.Collections; +import java.util.Date; +import java.util.Optional; + +@Component +public class UserAuthProvider { + + private static final int TOKEN_LIFETIME = 60 * 60 * 60 * 24 * 30; + + private SecurityProperties securityProperties; + + private String secretKey; + + private final UserService userService; + + UserAuthProvider(UserService userService, + SecurityProperties securityProperties) { + this.secretKey = securityProperties.getJwt(); + + if (this.secretKey == null){ + this.secretKey = "secret-key"; + } + + this.secretKey = Base64.getEncoder().encodeToString(this.secretKey.getBytes()); + this.userService = userService; + } + + public String createToken(String login) { + Date now = new Date(); + Date validity = new Date(now.getTime() + TOKEN_LIFETIME); + + return JWT.create() + .withIssuer(login) + .withIssuedAt(now) + .withExpiresAt(validity) + .sign(Algorithm.HMAC256(secretKey)); + } + + public Authentication validateToken(String token) { + JWTVerifier verifier = JWT.require(Algorithm.HMAC256(secretKey)).build(); + + DecodedJWT decodedJWT = verifier.verify(token); + + UserEntity userEntity = userService.findByNickname(UserEntity.class, decodedJWT.getIssuer()); + + return new UsernamePasswordAuthenticationToken(userEntity, null, Collections.emptyList()); + } + + public Optional verifyToken(String token){ + JWTVerifier verifier = JWT.require(Algorithm.HMAC256(secretKey)).build(); + + DecodedJWT decodedJWT = verifier.verify(token); + + return Optional.of(userService.findByNickname(UserEntity.class, decodedJWT.getIssuer())); + } +} diff --git a/codenames-domain/src/main/java/com/codenames/redis_entity/HashedPlayer.java b/codenames-domain/src/main/java/com/codenames/redis_entity/HashedPlayer.java new file mode 100644 index 0000000..5bb06f3 --- /dev/null +++ b/codenames-domain/src/main/java/com/codenames/redis_entity/HashedPlayer.java @@ -0,0 +1,26 @@ +package com.codenames.redis_entity; + +import com.codenames.enums.PlayerRole; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.springframework.data.annotation.Id; +import org.springframework.data.redis.core.RedisHash; + +import java.io.Serializable; + +@Getter @Setter +@AllArgsConstructor +@NoArgsConstructor +@RedisHash("authorize_player") +public class HashedPlayer implements Serializable { + @Id + private int id; + + private String wsSessionId; + + private PlayerRole playerRole; + + private HashedUser user; +} diff --git a/codenames-domain/src/main/java/com/codenames/redis_entity/HashedUser.java b/codenames-domain/src/main/java/com/codenames/redis_entity/HashedUser.java new file mode 100644 index 0000000..5f016ff --- /dev/null +++ b/codenames-domain/src/main/java/com/codenames/redis_entity/HashedUser.java @@ -0,0 +1,18 @@ +package com.codenames.redis_entity; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.springframework.data.annotation.Id; + +import java.io.Serializable; + +@AllArgsConstructor +@NoArgsConstructor +@Getter @Setter +public class HashedUser implements Serializable { + @Id + private int id; + private String nickname; +} diff --git a/codenames-domain/src/main/java/com/codenames/repository/UserRepository.java b/codenames-domain/src/main/java/com/codenames/repository/UserRepository.java new file mode 100644 index 0000000..948cbfb --- /dev/null +++ b/codenames-domain/src/main/java/com/codenames/repository/UserRepository.java @@ -0,0 +1,11 @@ +package com.codenames.repository; + +import com.codenames.entity.UserEntity; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Optional; + +public interface UserRepository extends JpaRepository { + + Optional findByLogin(Class type, String nickname); +} diff --git a/codenames-domain/src/main/java/com/codenames/services/RoomService.java b/codenames-domain/src/main/java/com/codenames/services/RoomService.java index 2eb6c0f..f33534e 100644 --- a/codenames-domain/src/main/java/com/codenames/services/RoomService.java +++ b/codenames-domain/src/main/java/com/codenames/services/RoomService.java @@ -9,7 +9,6 @@ import com.codenames.models.room.Room; import com.codenames.models.room.Team; import lombok.RequiredArgsConstructor; -import org.mapstruct.factory.Mappers; import org.springframework.stereotype.Service; @@ -100,21 +99,11 @@ public void removeUserFromAllRoles(Room room, Player player){ final Team blueTeam = room.getBlueTeam(); final Team yellowTeam = room.getYellowTeam(); - if (teamService.checkUserInTeam(blueTeam, player)){ - teamService.removeUserFormTeam(blueTeam, player); - - } else if (teamService.checkUserInMaster(blueTeam, player)){ - teamService.removeUserFromMaster(blueTeam, player); - - } else if (teamService.checkUserInTeam(yellowTeam, player)){ - teamService.removeUserFormTeam(yellowTeam, player); - - } else if (teamService.checkUserInMaster(yellowTeam, player)){ - teamService.removeUserFromMaster(yellowTeam, player); - - } else { - room.getSpectators().remove(player); - } + teamService.removeUserFromTeam(blueTeam, player); + teamService.removeUserFromMaster(blueTeam, player); + teamService.removeUserFromTeam(yellowTeam, player); + teamService.removeUserFromMaster(yellowTeam, player); + room.getSpectators().removeIf(player1 -> player1.equals(player)); } public void selectRole(Room room, Player player, PlayerRole selectedRole){ diff --git a/codenames-domain/src/main/java/com/codenames/services/TeamService.java b/codenames-domain/src/main/java/com/codenames/services/TeamService.java index 52cf960..fb5cf0d 100644 --- a/codenames-domain/src/main/java/com/codenames/services/TeamService.java +++ b/codenames-domain/src/main/java/com/codenames/services/TeamService.java @@ -9,56 +9,56 @@ @Service public class TeamService { - public boolean compareTeamPlayers(Team team, List secondPlayers){ - if (team.getPlayers().size() != secondPlayers.size()){ + public boolean compareTeamPlayers(Team team, List secondPlayers) { + if (team.getPlayers().size() != secondPlayers.size()) { return false; } - for (Player player : secondPlayers){ - if (!team.getPlayers().containsKey(player.getUser().id())){ + for (Player player : secondPlayers) { + if (!team.getPlayers().containsKey(player.getUser().id())) { return false; } } return true; } - public void addMessage(Team team, String message){ + public void addMessage(Team team, String message) { team.getMessages().add(message); } - public void addUserToTeam(Team team, Player player){ + public void addUserToTeam(Team team, Player player) { team.getPlayers().put(player.getUser().id(), player); } - public boolean checkUserInTeam(Team team, Player player){ + public boolean checkUserInTeam(Team team, Player player) { return team.getPlayers().containsValue(player); } - public void removeUserFormTeam(Team team, Player player){ + public void removeUserFromTeam(Team team, Player player) { team.getPlayers().remove(player.getUser().id()); } - public boolean checkUserInMaster(Team team, Player player){ + public boolean checkUserInMaster(Team team, Player player) { return team.getMaster() == player; } - public void removeUserFromMaster(Team team, Player player){ - if (player == team.getMaster()){ + public void removeUserFromMaster(Team team, Player player) { + if (team.getMaster() != null && team.getMaster().equals(player)) { team.setMaster(null); } } - public void selectMaster(Team team, Player player){ - if (team.getMaster() == null){ + public void selectMaster(Team team, Player player) { + if (team.getMaster() == null) { team.setMaster(player); } } - public boolean masterRoleAvailable(Team team){ + public boolean masterRoleAvailable(Team team) { return team.getMaster() == null; } - public boolean playerRoleAvailable(Team team){ + public boolean playerRoleAvailable(Team team) { return team.getPlayers().size() < 10; } } diff --git a/codenames-domain/src/main/java/com/codenames/services/UserService.java b/codenames-domain/src/main/java/com/codenames/services/UserService.java new file mode 100644 index 0000000..8789cd4 --- /dev/null +++ b/codenames-domain/src/main/java/com/codenames/services/UserService.java @@ -0,0 +1,67 @@ +package com.codenames.services; + +import com.codenames.dto.CredentialDto; +import com.codenames.dto.SignUpDto; +import com.codenames.entity.UserEntity; +import com.codenames.exception.UserAlreadyExistsException; +import com.codenames.exception.UserNotFoundException; +import com.codenames.exception.UserParametersNoValidException; +import com.codenames.mapper.UserMapper; +import com.codenames.repository.UserRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Controller; + +import java.nio.CharBuffer; +import java.util.Optional; + +@Controller +@RequiredArgsConstructor +public class UserService { + + private final UserRepository userRepository; + + private final PasswordEncoder passwordEncoder; + + private final UserMapper userMapper; + + public R findByNickname(Class rClass, String login){ + return userRepository.findByLogin(rClass, login) + .orElseThrow(() -> new UserNotFoundException("User not found", HttpStatus.NOT_FOUND)); + } + + public UserEntity login(CredentialDto credentialDto){ + UserEntity userEntity = userRepository.findByLogin(UserEntity.class, credentialDto.getLogin()) + .orElseThrow(() -> new UserNotFoundException("User not found on database", HttpStatus.NOT_FOUND)); + + System.out.println(userEntity); + + if (passwordEncoder.matches(CharBuffer.wrap(credentialDto.getPassword()), userEntity.getPassword())){ + return userEntity; + } + + throw new UserNotFoundException("Bad password", HttpStatus.BAD_REQUEST); + } + + public UserEntity register(SignUpDto signUpDto){ + if (signUpDto.getPassword().length() < 8 + || signUpDto.getEmail().length() < 4 + || signUpDto.getNickname().length() < 4){ + throw new UserParametersNoValidException("User parameters not valid", HttpStatus.BAD_REQUEST); + } + + Optional optionalUser = userRepository.findByLogin(UserEntity.class, signUpDto.getEmail()); + + if (optionalUser.isPresent()){ + throw new UserAlreadyExistsException( + optionalUser.get().getLogin() + " - is already exists", HttpStatus.BAD_REQUEST); + } + + UserEntity user = userMapper.signUpToUserEntity(signUpDto); + + user.setPassword(passwordEncoder.encode(CharBuffer.wrap(signUpDto.getPassword()))); + + return userRepository.save(user); + } +} diff --git a/codenames-front-end/package-lock.json b/codenames-front-end/package-lock.json index 3632890..80c2bfd 100644 --- a/codenames-front-end/package-lock.json +++ b/codenames-front-end/package-lock.json @@ -18,6 +18,8 @@ "@types/styled-components": "^5.1.26", "axios": "^1.3.6", "react": "^18.2.0", + "react-app-rewire-alias": "^1.1.7", + "react-app-rewired": "^2.2.1", "react-dom": "^18.2.0", "react-router-dom": "^6.10.0", "react-scripts": "5.0.1", @@ -26,7 +28,8 @@ "styled-components": "^5.3.10", "typescript": "^4.9.5", "uuid": "^9.0.0", - "web-vitals": "^2.1.4" + "web-vitals": "^2.1.4", + "ws": "^8.13.0" }, "devDependencies": { "@types/react-transition-group": "^4.4.5" @@ -11496,6 +11499,26 @@ } } }, + "node_modules/jsdom/node_modules/ws": { + "version": "7.5.9", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", + "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/jsesc": { "version": "2.5.2", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", @@ -16689,26 +16712,6 @@ "url": "https://opencollective.com/webpack" } }, - "node_modules/webpack-dev-server/node_modules/ws": { - "version": "8.13.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz", - "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, "node_modules/webpack-manifest-plugin": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/webpack-manifest-plugin/-/webpack-manifest-plugin-4.1.1.tgz", @@ -17262,15 +17265,15 @@ } }, "node_modules/ws": { - "version": "7.5.9", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", - "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", + "version": "8.13.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz", + "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==", "engines": { - "node": ">=8.3.0" + "node": ">=10.0.0" }, "peerDependencies": { "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" + "utf-8-validate": ">=5.0.2" }, "peerDependenciesMeta": { "bufferutil": { diff --git a/codenames-front-end/package.json b/codenames-front-end/package.json index cdaf25f..cf980e2 100644 --- a/codenames-front-end/package.json +++ b/codenames-front-end/package.json @@ -23,7 +23,8 @@ "styled-components": "^5.3.10", "typescript": "^4.9.5", "uuid": "^9.0.0", - "web-vitals": "^2.1.4" + "web-vitals": "^2.1.4", + "ws": "^8.13.0" }, "scripts": { "start": "react-scripts start", diff --git a/codenames-front-end/src/App.tsx b/codenames-front-end/src/App.tsx index 68bb996..25993b6 100644 --- a/codenames-front-end/src/App.tsx +++ b/codenames-front-end/src/App.tsx @@ -1,33 +1,66 @@ -import React from 'react'; +import React, {useEffect, useRef, useState} from 'react'; import {privateRouters, publicRouters} from "./router"; import {BrowserRouter, Navigate, Route, Routes} from "react-router-dom"; +import useFetching from "./hooks/useFetching"; +import {authRequest, getAuthToken} from "./helper"; +import {RestConfig} from "./config"; +import {AxiosResponse} from "axios"; function App() { - return ( - - - {publicRouters.map(route => - } - /> - )} - {privateRouters.map(route => - } - /> - )} - - } - /> - - - ); + const isAuthorized = useRef(!!getAuthToken()) + const [redirectPath, setRedirectPath] = useState("/sign_in") + const [checkUserIsAuth] = useFetching(async () => { + try { + const response: AxiosResponse = await authRequest("GET", RestConfig.paths.request.userIsAuth, {}) + + if (response.data) { + isAuthorized.current = true + } + } catch (e) { + console.log(e) + } + }) + + + useEffect(() => { + checkUserIsAuth() + }, []) + + useEffect(() => { + + if (isAuthorized.current){ + setRedirectPath("/room") + } + }, [isAuthorized]) + + + return ( + + + {publicRouters.map(route => + } + /> + )} + {isAuthorized.current && privateRouters.map(route => { + return (} + /> + ) + } + )} + + } + /> + + + ); } export default App; diff --git a/codenames-front-end/src/components/game/stopped/menu/CNStopGameMenu/CNStopGameMenu.tsx b/codenames-front-end/src/components/game/stopped/menu/CNStopGameMenu/CNStopGameMenu.tsx index 56cf932..9a06c8d 100644 --- a/codenames-front-end/src/components/game/stopped/menu/CNStopGameMenu/CNStopGameMenu.tsx +++ b/codenames-front-end/src/components/game/stopped/menu/CNStopGameMenu/CNStopGameMenu.tsx @@ -21,6 +21,8 @@ const CNStopGameMenu = ({ const [timeoutId, setTimeoutId] = React.useState(0); useEffect(() => { + console.log(timeoutId) + clearTimeout(timeoutId); if (room?.status && room?.status !== Status.STOPPED) { diff --git a/codenames-front-end/src/components/ui/CNBlurredFrom/CNBluredFrom.tsx b/codenames-front-end/src/components/ui/CNBlurredFrom/CNBluredFrom.tsx new file mode 100644 index 0000000..99dde77 --- /dev/null +++ b/codenames-front-end/src/components/ui/CNBlurredFrom/CNBluredFrom.tsx @@ -0,0 +1,29 @@ +import React, {FormEvent} from "react"; + + +type CNBlurredFromProps = { + action?: string; + method?: string; + onSubmit?: (e: FormEvent) => void; + className?: string; + children?: React.ReactNode; +} + +const CNBlurredFrom = ({ + action, + method, + onSubmit, + className, + children + }: CNBlurredFromProps) => ( +
+ {children} +
+) + +export default CNBlurredFrom; \ No newline at end of file diff --git a/codenames-front-end/src/components/ui/CNBlurredFrom/StyledCNBlurredFrom.ts b/codenames-front-end/src/components/ui/CNBlurredFrom/StyledCNBlurredFrom.ts new file mode 100644 index 0000000..2846424 --- /dev/null +++ b/codenames-front-end/src/components/ui/CNBlurredFrom/StyledCNBlurredFrom.ts @@ -0,0 +1,28 @@ +import styled from "styled-components"; +import CNBlurredFrom from "./CNBluredFrom"; + + +type StyledProps = { + width?: string; + height?: string; + borderRadius?: string; +} + +export const StyledCNBlurredFrom = styled(CNBlurredFrom)` + display: flex; + + justify-content: center; + align-content: center; + + width: ${props => props.width ? props.width : "max-content"}; + height: ${props => props.height ? props.height : "max-content"}; + + color: #ffffff; + + background: rgba(0, 0, 0, 0.65); + box-shadow: 0 0 10px 1px rgba(0, 0, 0, 0.25); + backdrop-filter: blur(5px); + + border-radius: ${props => props.borderRadius ? props.borderRadius : "20px"}; + padding: ${props => props.borderRadius ? props.borderRadius : "20px"}; +`; \ No newline at end of file diff --git a/codenames-front-end/src/components/ui/CNBlurredFrom/index.ts b/codenames-front-end/src/components/ui/CNBlurredFrom/index.ts new file mode 100644 index 0000000..0f5dbff --- /dev/null +++ b/codenames-front-end/src/components/ui/CNBlurredFrom/index.ts @@ -0,0 +1,3 @@ + +export * from "./CNBluredFrom" +export * from "./StyledCNBlurredFrom" \ No newline at end of file diff --git a/codenames-front-end/src/components/ui/CNDefaultInput/StyledCNDefaultImport.ts b/codenames-front-end/src/components/ui/CNDefaultInput/StyledCNDefaultInput.ts similarity index 77% rename from codenames-front-end/src/components/ui/CNDefaultInput/StyledCNDefaultImport.ts rename to codenames-front-end/src/components/ui/CNDefaultInput/StyledCNDefaultInput.ts index cf9bf11..ad6b3ca 100644 --- a/codenames-front-end/src/components/ui/CNDefaultInput/StyledCNDefaultImport.ts +++ b/codenames-front-end/src/components/ui/CNDefaultInput/StyledCNDefaultInput.ts @@ -2,8 +2,9 @@ import styled from "styled-components"; import CNDefaultInput from "./CNDefaultInput"; -export const StyledCNDefaultImport = styled(CNDefaultInput)` +export const StyledCNDefaultInput = styled(CNDefaultInput)` display: flex; + flex-direction: row; align-items: center; justify-content: center; diff --git a/codenames-front-end/src/components/ui/CNDefaultInput/index.ts b/codenames-front-end/src/components/ui/CNDefaultInput/index.ts index c813b01..dee8249 100644 --- a/codenames-front-end/src/components/ui/CNDefaultInput/index.ts +++ b/codenames-front-end/src/components/ui/CNDefaultInput/index.ts @@ -1,4 +1,4 @@ -export * from "./StyledCNDefaultImport" +export * from "./StyledCNDefaultInput" export * from "./CNDefaultInput" \ No newline at end of file diff --git a/codenames-front-end/src/components/ui/index.ts b/codenames-front-end/src/components/ui/index.ts index dfd581f..ef46a3a 100644 --- a/codenames-front-end/src/components/ui/index.ts +++ b/codenames-front-end/src/components/ui/index.ts @@ -8,4 +8,5 @@ export * from "./PauseButton" export * from "./CNDefaultInput" export * from "./BlueYellowBG" export * from "./CNDefautBtn" -export * from "./CNStartButton" \ No newline at end of file +export * from "./CNStartButton" +export * from "./CNBlurredFrom" \ No newline at end of file diff --git a/codenames-front-end/src/config/RestConfig.ts b/codenames-front-end/src/config/RestConfig.ts index 764ac4f..020d318 100644 --- a/codenames-front-end/src/config/RestConfig.ts +++ b/codenames-front-end/src/config/RestConfig.ts @@ -1,3 +1,4 @@ +import {getAuthToken} from "../helper"; const restIP = "localhost"; const restPort = 8080; @@ -12,7 +13,10 @@ export const RestConfig = { response: {}, request: { createRoom: `http://${restIP}:${restPort}/room/create`, - connectToRoom: `http://${restIP}:${restPort}/room/connect` + connectToRoom: `http://${restIP}:${restPort}/room/connect`, + userIsAuth: `http://${restIP}:${restPort}/check_user_is_auth`, + signIn: `http://${restIP}:${restPort}/login`, + signUp: `http://${restIP}:${restPort}/register` } } } @@ -20,7 +24,7 @@ export const RestConfig = { export const WebSocketConfig = { ip: wsIP, port: wsPort, - connectPath: `ws://${wsIP}:${wsPort}/socket`, + connectPath: `ws://${wsIP}:${wsPort}/socket?auth_token=${getAuthToken()}`, paths: { response: { newRoomInfo: "/room/new/info" diff --git a/codenames-front-end/src/helper/AuthRequest.ts b/codenames-front-end/src/helper/AuthRequest.ts new file mode 100644 index 0000000..494b82e --- /dev/null +++ b/codenames-front-end/src/helper/AuthRequest.ts @@ -0,0 +1,28 @@ +import axios, {Method} from "axios"; + + +export const getAuthToken = (): string | null => { + return window.localStorage.getItem("auth_token") +} + +export const removeAuthToken = (): void => { + window.localStorage.removeItem("auth_token") +} + +export const setAuthToken = (jwtToken: string): void => { + window.localStorage.setItem("auth_token", jwtToken) +} +export const authRequest = async (method: Method, url: string, data: any) => { + let headers = {} + + if (getAuthToken() != null){ + headers = {"Authorization": `Bearer ${getAuthToken()}`} + } + + return axios({ + method: method, + headers: headers, + url: url, + data: data + }) +} \ No newline at end of file diff --git a/codenames-front-end/src/helper/index.ts b/codenames-front-end/src/helper/index.ts new file mode 100644 index 0000000..a35a806 --- /dev/null +++ b/codenames-front-end/src/helper/index.ts @@ -0,0 +1,3 @@ + + +export * from "./AuthRequest" diff --git a/codenames-front-end/src/hooks/useCodeNamesRestRequests.ts b/codenames-front-end/src/hooks/useCodeNamesRestRequests.ts index d0bea1a..af1a8d5 100644 --- a/codenames-front-end/src/hooks/useCodeNamesRestRequests.ts +++ b/codenames-front-end/src/hooks/useCodeNamesRestRequests.ts @@ -1,5 +1,6 @@ -import axios, {AxiosResponse} from "axios"; -import {RestConfig} from "../config/RestConfig"; +import {AxiosResponse} from "axios"; +import {RestConfig} from "../config"; +import {authRequest} from "../helper"; export const useCodeNamesRestRequests = (): [ () => Promise>, @@ -9,11 +10,11 @@ export const useCodeNamesRestRequests = (): [ const restPaths = RestConfig.paths; const createRoom = async () => { - return await axios.get(restPaths.request.createRoom) + return await authRequest("GET", restPaths.request.createRoom, {}) } const tryConnectToRoom = async (roomID: number) => { - return await axios.get(restPaths.request.connectToRoom + `/${roomID ? roomID : "0"}`) + return await authRequest("GET", restPaths.request.connectToRoom + `/${roomID ? roomID : "0"}`, {}) } return [createRoom, tryConnectToRoom] diff --git a/codenames-front-end/src/hooks/useCodeNamesWsRoomConnect.ts b/codenames-front-end/src/hooks/useCodeNamesWsRoomConnect.ts index 2c10863..7f460c9 100644 --- a/codenames-front-end/src/hooks/useCodeNamesWsRoomConnect.ts +++ b/codenames-front-end/src/hooks/useCodeNamesWsRoomConnect.ts @@ -1,13 +1,11 @@ import {MutableRefObject, useEffect, useRef, useState} from "react"; -import {WebSocketConfig} from "../config/RestConfig"; -import {Color} from "../models/CodeNames/Color"; +import {WebSocketConfig} from "../config"; +import {Color, notify, IGameRoom} from "../models"; import WebSocketRequest from "../models/CodeNames/WebSocketRequest"; -import {IGameRoom} from "../models/CodeNames/IGameRoom"; import WebSocketResponse from "../models/CodeNames/WebSocketResponse"; import useFetching from "./useFetching"; import {useCodeNamesRestRequests} from "./useCodeNamesRestRequests"; import {useNavigate} from "react-router-dom"; -import {notify} from "../models/notifications/Notifications"; export interface CodeNameWsRoomRequests { @@ -62,7 +60,7 @@ export const useCodeNamesWsRoomConnect = ( }, onSocketMessage: (message: MessageEvent) => void = () => { }, - onSocketClose: (event: Event) => void = () => { + onSocketClose: (event: CloseEvent) => void = () => { }, onSocketError: (event: Event) => void = () => { } @@ -82,11 +80,11 @@ export const useCodeNamesWsRoomConnect = ( try { const response = await tryConnectToRoom(roomID); if (response.data === -1) { - navigate("/room/Connect"); + navigate("/room"); } } catch (e) { if (reconnectCount.current === 10) { - navigate("/room/Connect"); + navigate("/room"); } notify.error("Something went wrong while connecting to the server, perhaps the server is down.") @@ -97,28 +95,37 @@ export const useCodeNamesWsRoomConnect = ( return fetchConnectToRoom(); } - webSocket.current = new WebSocket(WebSocketConfig.connectPath) + webSocket.current = new WebSocket(WebSocketConfig.connectPath); webSocketRequests.current = buildRequestMethods(webSocket.current) + webSocket.current.onopen = (event: Event) => { setIsConnected(true); - webSocketRequests.current?.sendSocketRequest(WebSocketConfig.paths.request.connect, roomID); + setTimeout(() => { + webSocketRequests.current?.sendSocketRequest(WebSocketConfig.paths.request.connect, roomID); + }, 10) onSocketConnect(event) } + webSocket.current.onmessage = (message: MessageEvent) => { - const messageData: WebSocketResponse = JSON.parse(message.data) + if (typeof message.data === "string") { + const messageData: WebSocketResponse = JSON.parse(message.data) - console.log(messageData) + console.log(messageData) - if (messageData.responsePath === WebSocketConfig.paths.response.newRoomInfo) { - onNewRoomInfo(messageData.responseBody) + if (messageData.responsePath === WebSocketConfig.paths.response.newRoomInfo) { + onNewRoomInfo(messageData.responseBody) + } + onSocketMessage(message) } - onSocketMessage(message) } + webSocket.current.onclose = onSocketClose.bind(this); + + webSocket.current.onerror = async (event: Event) => { onSocketError(event) @@ -129,6 +136,11 @@ export const useCodeNamesWsRoomConnect = ( useEffect(() => { fetchConnectToRoom() + + return () => { + webSocket.current = undefined; + setIsConnected(false); + } }, []) return [webSocket, webSocketRequests.current, isConnected] diff --git a/codenames-front-end/src/pages/Authentication/AuthenticationStyles.ts b/codenames-front-end/src/pages/Authentication/AuthenticationStyles.ts new file mode 100644 index 0000000..55affcb --- /dev/null +++ b/codenames-front-end/src/pages/Authentication/AuthenticationStyles.ts @@ -0,0 +1,46 @@ +import styled from "styled-components"; +import {StyledCNBlurredFrom, StyledCNDefaultButton, StyledCNDefaultInput} from "../../components"; + + +export const StyledAuthenticationFrom = styled(StyledCNBlurredFrom)` + display: flex; + + flex-direction: column; + justify-content: center; + align-items: center; + + flex-wrap: wrap; + + padding: 20px 60px; + border-radius: 40px; +`; + +type FormTitleProps = { + marginBottom?: string; +} + +export const StyledAuthenticationFormTitle = styled.div` + + margin-bottom: ${props => props.marginBottom ? props.marginBottom : "0"}; + + font-size: 1.8rem; +`; + +export const StyledAuthenticationInputBlock = styled.div` + display: flex; + + align-items: end; + flex-direction: column; +`; + +export const StyledAuthenticationInput = styled(StyledCNDefaultInput)` + + margin: 5px; +`; + +export const StyledAuthenticationButton = styled(StyledCNDefaultButton)` + + width: 90%; + + margin-top: 2vh; +`; \ No newline at end of file diff --git a/codenames-front-end/src/pages/Authentication/SignIn/SignInStyles.ts b/codenames-front-end/src/pages/Authentication/SignIn/SignInStyles.ts new file mode 100644 index 0000000..059752f --- /dev/null +++ b/codenames-front-end/src/pages/Authentication/SignIn/SignInStyles.ts @@ -0,0 +1,13 @@ +import styled from "styled-components"; + + + +export const StyledSignIn = styled.div` + display: flex; + + align-items: center; + justify-content: center; + + min-width: 100vw; + min-height: 100vh; +`; \ No newline at end of file diff --git a/codenames-front-end/src/pages/Authentication/SignIn/SingIn.tsx b/codenames-front-end/src/pages/Authentication/SignIn/SingIn.tsx new file mode 100644 index 0000000..29c9d04 --- /dev/null +++ b/codenames-front-end/src/pages/Authentication/SignIn/SingIn.tsx @@ -0,0 +1,95 @@ +import React, {useState} from 'react'; +import {StyledSignIn} from "./SignInStyles"; +import {StyledBlueYellowBG} from "../../../components"; +import { + StyledAuthenticationButton, + StyledAuthenticationFormTitle, + StyledAuthenticationFrom, StyledAuthenticationInput, + StyledAuthenticationInputBlock +} from "../AuthenticationStyles"; +import useFetching from "../../../hooks/useFetching"; +import {Flip, ToastContainer} from "react-toastify"; +import {authRequest, removeAuthToken, setAuthToken} from "../../../helper"; +import {RestConfig} from "../../../config"; +import {notify} from "../../../models"; +import {AxiosResponse} from "axios"; +import {useNavigate} from "react-router-dom"; + +type SignInResponse = { + nickname: string; + jwtToken: string; + login: string; +} + +const SingIn = () => { + const navigate = useNavigate(); + const [login, setLogin] = useState(""); + const [password, setPassword] = useState(""); + + const [tryLogin] = useFetching(async () => { + try { + removeAuthToken() + const response: AxiosResponse = await authRequest("POST", RestConfig.paths.request.signIn, { + login: login, + password: password + }) + + setAuthToken(response.data.jwtToken); + + if (window.history.state && window.history.state.idx > 0) { + navigate(-1); + } else { + navigate('/room', { replace: true }); + } + } catch (e){ + notify.error("Check the data you entered") + } + }) + + return ( + + + { + e.preventDefault() + tryLogin() + }}> + + + Sign In + + + + ) => setLogin(e.target.value)} + /> + ) => setPassword(e.target.value)} + /> + + Sign In + + + + + + + + ); +}; + +export default SingIn; \ No newline at end of file diff --git a/codenames-front-end/src/pages/SingIn/index.ts b/codenames-front-end/src/pages/Authentication/SignIn/index.ts similarity index 100% rename from codenames-front-end/src/pages/SingIn/index.ts rename to codenames-front-end/src/pages/Authentication/SignIn/index.ts diff --git a/codenames-front-end/src/pages/Authentication/SignUp/SignUpStyles.ts b/codenames-front-end/src/pages/Authentication/SignUp/SignUpStyles.ts new file mode 100644 index 0000000..0872284 --- /dev/null +++ b/codenames-front-end/src/pages/Authentication/SignUp/SignUpStyles.ts @@ -0,0 +1,12 @@ +import styled from "styled-components"; + + +export const StyledSingUp = styled.div` + display: flex; + + align-items: center; + justify-content: center; + + min-width: 100vw; + min-height: 100vh; +`; \ No newline at end of file diff --git a/codenames-front-end/src/pages/Authentication/SignUp/SingUp.tsx b/codenames-front-end/src/pages/Authentication/SignUp/SingUp.tsx new file mode 100644 index 0000000..45a630f --- /dev/null +++ b/codenames-front-end/src/pages/Authentication/SignUp/SingUp.tsx @@ -0,0 +1,118 @@ +import React, {FormEvent, useState} from 'react'; +import {StyledBlueYellowBG} from "../../../components"; +import {StyledSingUp} from "./SignUpStyles"; +import { + StyledAuthenticationButton, + StyledAuthenticationFormTitle, + StyledAuthenticationFrom, StyledAuthenticationInput, + StyledAuthenticationInputBlock +} from "../AuthenticationStyles"; +import useFetching from "../../../hooks/useFetching"; +import {AxiosResponse} from "axios"; +import {notify} from "../../../models"; +import {Flip, ToastContainer} from "react-toastify"; +import {authRequest, removeAuthToken, setAuthToken} from "../../../helper"; +import {RestConfig} from "../../../config"; + +type SignUpResponse = { + nickname: string; + jwtToken: string; + login: string; +} + +const SingUp = () => { + const [email, setEmail] = useState("") + const [nickname, setNickname] = useState("") + const [password, setPassword] = useState("") + const [repeatedPassword, setRepeatedPassword] = useState("") + + const [tryRegister] = useFetching(async () => { + removeAuthToken() + const response: AxiosResponse = await authRequest( + "POST", + RestConfig.paths.request.signUp, + { + email: email, + nickname: nickname, + password: password + }) + + + setAuthToken(response.data.jwtToken) + console.log(response.data) + }) + + const validateFormAndTryRegister = (e: FormEvent) => { + e.preventDefault() + + if (email.length < 4){ + return notify.error("Login length must be exceed 4 symbols") + } else if (password.length < 8){ + return notify.error("Password length must be exceed 8 symbols") + } else if (password !== repeatedPassword){ + return notify.error("Passwords must match") + } else { + tryRegister() + } + tryRegister() + } + + + return ( + + + + + To get started, you need to sing up + + + + ) => setNickname(e.target.value)} + /> + ) => setEmail(e.target.value)} + /> + ) => setPassword(e.target.value)} + /> + ) => setRepeatedPassword(e.target.value)} + /> + + + Sign Up + + + + + + + + + ); +}; + +export default SingUp; \ No newline at end of file diff --git a/codenames-front-end/src/pages/SignUp/index.ts b/codenames-front-end/src/pages/Authentication/SignUp/index.ts similarity index 100% rename from codenames-front-end/src/pages/SignUp/index.ts rename to codenames-front-end/src/pages/Authentication/SignUp/index.ts diff --git a/codenames-front-end/src/pages/Authentication/index.ts b/codenames-front-end/src/pages/Authentication/index.ts new file mode 100644 index 0000000..9ff73a3 --- /dev/null +++ b/codenames-front-end/src/pages/Authentication/index.ts @@ -0,0 +1,4 @@ + + +export * from "./SignUp" +export * from "./SignIn" \ No newline at end of file diff --git a/codenames-front-end/src/pages/Connect/Connect.tsx b/codenames-front-end/src/pages/Connect/Connect.tsx index e0a2b1c..9b2eed3 100644 --- a/codenames-front-end/src/pages/Connect/Connect.tsx +++ b/codenames-front-end/src/pages/Connect/Connect.tsx @@ -31,7 +31,7 @@ export const Connect = () => { const [createRoom, connectToRoom] = useCodeNamesRestRequests(); - const [fetchConnectToRoom, isLoadingConnectToRoom, errorConnectToRoom] = useFetching(async () => { + const [fetchConnectToRoom] = useFetching(async () => { const response = await connectToRoom(Number(roomID)); if (response.data === -1) { @@ -41,7 +41,7 @@ export const Connect = () => { } }); - const [fetchCreateRoom, isLoadingCreateRoom, errorCreateRoom] = useFetching(async () => { + const [fetchCreateRoom] = useFetching(async () => { const response = await createRoom(); if (response.data === -1) { diff --git a/codenames-front-end/src/pages/Connect/ConnectStyles.ts b/codenames-front-end/src/pages/Connect/ConnectStyles.ts index 53edde9..5a18efd 100644 --- a/codenames-front-end/src/pages/Connect/ConnectStyles.ts +++ b/codenames-front-end/src/pages/Connect/ConnectStyles.ts @@ -1,5 +1,5 @@ import styled, {css} from "styled-components"; -import {StyledCNDefaultImport, StyledCNDefaultButton} from "../../components"; +import {StyledCNDefaultInput, StyledCNDefaultButton} from "../../components"; export const StyledConnectPage = styled.div` @@ -95,7 +95,7 @@ export const StyledTitle = styled.h1` margin-bottom: 30px; `; -export const StyledConnectInput = styled(StyledCNDefaultImport)` +export const StyledConnectInput = styled(StyledCNDefaultInput)` & input{ width: 220px; } diff --git a/codenames-front-end/src/pages/SignUp/SingUp.tsx b/codenames-front-end/src/pages/SignUp/SingUp.tsx deleted file mode 100644 index 5792faa..0000000 --- a/codenames-front-end/src/pages/SignUp/SingUp.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import React from 'react'; -import {StyledBlueYellowBG} from "../../components/ui/BlueYellowBG/StyledBlueYellowBG"; - -const SingUp = () => { - return ( -
- - -
- ); -}; - -export default SingUp; \ No newline at end of file diff --git a/codenames-front-end/src/pages/SingIn/SingIn.tsx b/codenames-front-end/src/pages/SingIn/SingIn.tsx deleted file mode 100644 index c6a4f42..0000000 --- a/codenames-front-end/src/pages/SingIn/SingIn.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import React from 'react'; - -const SingIn = () => { - return ( -
- -
- ); -}; - -export default SingIn; \ No newline at end of file diff --git a/codenames-front-end/src/pages/index.ts b/codenames-front-end/src/pages/index.ts index 2c33b72..02451b3 100644 --- a/codenames-front-end/src/pages/index.ts +++ b/codenames-front-end/src/pages/index.ts @@ -1,6 +1,5 @@ export * from "./Connect" -export * from "./SignUp" -export * from "./SingIn" +export * from "./Authentication" export * from "./GameRoom" \ No newline at end of file diff --git a/codenames-front-end/src/router/index.ts b/codenames-front-end/src/router/index.ts index da1c372..6cd34c5 100644 --- a/codenames-front-end/src/router/index.ts +++ b/codenames-front-end/src/router/index.ts @@ -1,14 +1,15 @@ import {ComponentType} from "react"; import {Connect} from "../pages"; -import SingUp from "../pages/SignUp/SingUp"; -import SingIn from "../pages/SingIn/SingIn"; import GameRoom from "../pages/GameRoom/GameRoom"; +import SingUp from "../pages/Authentication/SignUp/SingUp"; +import SingIn from "../pages/Authentication/SignIn/SingIn"; export const publicRouters: {path: string, component: ComponentType}[] = [ - { path: '/room/Connect', component: Connect }, - { path: '/room/:id', component: GameRoom }, - { path: '/SignUp', component: SingUp}, - { path: '/SignIn', component: SingIn} + { path: '/sign_up', component: SingUp}, + { path: '/sign_in', component: SingIn} ] -export const privateRouters: {path: string, component: ComponentType}[] = [] \ No newline at end of file +export const privateRouters: {path: string, component: ComponentType}[] = [ + { path: '/room', component: Connect }, + { path: '/room/:id', component: GameRoom }, +] \ No newline at end of file diff --git a/codenames-web-services/src/main/java/com/codenames/controllers/GameRoomsWebSocketController.java b/codenames-web-services/src/main/java/com/codenames/controllers/GameRoomsWebSocketController.java index 9da77ef..4661a7d 100644 --- a/codenames-web-services/src/main/java/com/codenames/controllers/GameRoomsWebSocketController.java +++ b/codenames-web-services/src/main/java/com/codenames/controllers/GameRoomsWebSocketController.java @@ -1,6 +1,7 @@ package com.codenames.controllers; +import com.codenames.dao.RedisPlayerDao; import com.codenames.enums.PlayerRole; import com.codenames.filters.method.AvailableRoomFilter; import com.codenames.filters.method.GameRunningFilter; @@ -13,7 +14,6 @@ import com.codenames.models.game.CodeNamesGame; import com.codenames.models.game.Player; import com.codenames.models.room.Room; -import com.codenames.models.game.User; import com.codenames.services.PlayerService; import com.codenames.services.RoomService; import com.codenames.services.GameService; @@ -45,13 +45,12 @@ public class GameRoomsWebSocketController { private final AuthorizedUsers authorizedUsers; + private final RedisPlayerDao redisPlayerDao; + @SocketConnectMapping public void userConnect(WebSocketSession session) { - // TODO: 18.04.2023 authorizedUsers - used temporarily, after adding the database will be recycled - authorizedUsers.addUserRoomSession(session.getId(), -1, new User(100, "100", "random")); - - LOGGER.info(session.getId() + " - connected"); + LOGGER.info(authorizedUsers.getUserRoomSession(session.getId()).getPlayer().getUser().nickname() + " - connected"); } @SocketDisconnectMapping diff --git a/codenames-web-services/src/main/java/com/codenames/controllers/UsersController.java b/codenames-web-services/src/main/java/com/codenames/controllers/UsersController.java new file mode 100644 index 0000000..71bfde0 --- /dev/null +++ b/codenames-web-services/src/main/java/com/codenames/controllers/UsersController.java @@ -0,0 +1,51 @@ +package com.codenames.controllers; + +import com.codenames.dto.CredentialDto; +import com.codenames.dto.SignUpDto; +import com.codenames.dto.UserAuthDto; +import com.codenames.dto.UserDto; +import com.codenames.mapper.UserMapper; +import com.codenames.provider.UserAuthProvider; +import com.codenames.services.UserService; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + + +@RestController +@RequiredArgsConstructor +public class UsersController { + + private final UserService userService; + + private final UserAuthProvider userAuthProvider; + + private final UserMapper userMapper; + + @PostMapping("/login") + public ResponseEntity loginUser(@RequestBody CredentialDto credentialDto){ + UserAuthDto user = userMapper.userEntityToUserAuthDto(userService.login(credentialDto)); + + user.setJwtToken(userAuthProvider.createToken(user.getLogin())); + + return ResponseEntity.ok(user); + } + + @PostMapping("/register") + public ResponseEntity registerUser(@RequestBody SignUpDto signUpDto){ + UserAuthDto user = userMapper.userEntityToUserAuthDto(userService.register(signUpDto)); + + user.setJwtToken(userAuthProvider.createToken(user.getLogin())); + + return ResponseEntity.ok(user); + } + + @GetMapping("/check_user_is_auth") + public boolean checkUserIsAuth(){ + return true; + } +} diff --git a/codenames-web-services/src/main/java/com/codenames/filters/connect/BannedUserFilter.java b/codenames-web-services/src/main/java/com/codenames/filters/connect/BannedUserFilter.java index ed14812..f07b9b1 100644 --- a/codenames-web-services/src/main/java/com/codenames/filters/connect/BannedUserFilter.java +++ b/codenames-web-services/src/main/java/com/codenames/filters/connect/BannedUserFilter.java @@ -5,6 +5,7 @@ import com.codenames.services.GameService; import com.jjerome.filters.SocketConnectionFilter; import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpHeaders; import org.springframework.stereotype.Component; import org.springframework.web.socket.WebSocketSession; diff --git a/codenames-web-services/src/main/java/com/codenames/filters/connect/UserIsAuthorizedFilter.java b/codenames-web-services/src/main/java/com/codenames/filters/connect/UserIsAuthorizedFilter.java new file mode 100644 index 0000000..5b56026 --- /dev/null +++ b/codenames-web-services/src/main/java/com/codenames/filters/connect/UserIsAuthorizedFilter.java @@ -0,0 +1,40 @@ +package com.codenames.filters.connect; + +import com.codenames.dao.RedisPlayerDao; +import com.codenames.entity.UserEntity; +import com.codenames.exception.UserNotFoundException; +import com.codenames.mapper.UserMapper; +import com.codenames.models.game.AuthorizedUsers; +import com.codenames.provider.UserAuthProvider; +import com.jjerome.filters.SocketConnectionFilter; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Component; +import org.springframework.util.MultiValueMap; +import org.springframework.web.socket.WebSocketSession; +import org.springframework.web.util.UriComponentsBuilder; + +@Component +@RequiredArgsConstructor +public class UserIsAuthorizedFilter implements SocketConnectionFilter { + + private final UserAuthProvider userAuthProvider; + + private final AuthorizedUsers authorizedUsers; + + private final UserMapper userMapper; + + @Override + public boolean doFilter(WebSocketSession session) { + + MultiValueMap params = UriComponentsBuilder.fromUri(session.getUri()).build().getQueryParams(); + + UserEntity userEntity = userAuthProvider.verifyToken(params.getFirst("auth_token")) + .orElseThrow(() -> new UserNotFoundException("User not found", HttpStatus.NOT_FOUND)); + + authorizedUsers.addUserRoomSession(session.getId(), -1, userMapper.userEntityToUser(userEntity)); + + return true; + } +} diff --git a/codenames-web-services/src/main/java/com/codenames/filters/method/UserAuthorizedFilter.java b/codenames-web-services/src/main/java/com/codenames/filters/method/UserAuthorizedFilter.java index 9eb4276..45a1097 100644 --- a/codenames-web-services/src/main/java/com/codenames/filters/method/UserAuthorizedFilter.java +++ b/codenames-web-services/src/main/java/com/codenames/filters/method/UserAuthorizedFilter.java @@ -18,7 +18,6 @@ public class UserAuthorizedFilter implements SocketMethodFilter { @Override public boolean doFilter(WebSocketSession session, TextMessage message, Request request) { - // TODO: 18.04.2023 Temporarily empty method it will be finalized - return true; + return authorizedUsers.getUserRoomSession(session.getId()) != null; } } diff --git a/pom.xml b/pom.xml index 1fd78cb..2691718 100644 --- a/pom.xml +++ b/pom.xml @@ -47,24 +47,54 @@ spring-boot-starter-websocket + + org.springframework.boot + spring-boot-starter-security + + + + com.auth0 + java-jwt + 4.4.0 + + + + + org.springframework.boot + spring-boot-starter-data-jpa + + + + mysql + mysql-connector-java + 8.0.33 + + - - + + + org.springframework.boot - spring-boot-configuration-processor - true + spring-boot-starter-data-redis + 3.0.6 - io.jsonwebtoken - jjwt - 0.9.1 + redis.clients + jedis + 4.4.0 + + org.springframework.boot + spring-boot-configuration-processor + true + + org.projectlombok lombok From fcfa73e2d8c1cc2e60a2df2d6fe6888dce34631e Mon Sep 17 00:00:00 2001 From: JJerome-NM Date: Tue, 16 May 2023 15:31:24 +0300 Subject: [PATCH 2/8] CodeNames #7: Added Spring security and Redis --- .../properties/CodeNamesProperties.java | 14 +++-- .../codenames/properties/RedisProperties.java | 15 +++++ .../properties/SecurityProperties.java | 2 +- .../src/main/resources/application.yml | 3 + .../config/RestControllersConfig.java | 24 -------- .../com/codenames/dao/RedisPlayerDao.java | 27 --------- .../dao/RedisUserRoomSessionDao.java | 34 +++++++++++ .../game/CodeNamesGame.java | 4 +- .../{models => domain}/game/Player.java | 2 +- .../java/com/codenames/domain/game/User.java | 4 ++ .../game/UserRoomSession.java | 2 +- .../{models => domain}/room/Room.java | 6 +- .../{models => domain}/room/Settings.java | 2 +- .../{models => domain}/room/Team.java | 4 +- .../{models => domain}/room/Timer.java | 2 +- .../{models => domain}/room/Word.java | 2 +- .../java/com/codenames/entity/UserEntity.java | 2 +- .../UserRoomSessionNotFoundExeption.java | 8 +++ .../com/codenames/mapper/PlayerMapper.java | 2 +- .../java/com/codenames/mapper/RoomMapper.java | 2 +- .../java/com/codenames/mapper/TeamMapper.java | 2 +- .../java/com/codenames/mapper/UserMapper.java | 2 +- .../mapper/UserRoomSessionMapper.java | 14 +++++ .../com/codenames/mapper/WordsMapper.java | 2 +- .../models/game/AuthorizedUsers.java | 40 ------------- .../java/com/codenames/models/game/User.java | 4 -- .../codenames/provider/UserAuthProvider.java | 2 +- .../codenames/redis_entity/HashedPlayer.java | 2 - .../redis_entity/HashedUserRoomSession.java | 20 +++++++ .../services/AuthorizedUsersService.java | 57 +++++++++++++++++++ .../com/codenames/services/GameService.java | 8 +-- .../com/codenames/services/PlayerService.java | 9 ++- .../com/codenames/services/RoomService.java | 6 +- .../com/codenames/services/TeamService.java | 4 +- .../com/codenames/services/UserService.java | 12 ++++ .../com/codenames/services/WordsService.java | 6 +- codenames-front-end/src/App.tsx | 14 +++++ .../src/hooks/useCodeNamesWsRoomConnect.ts | 2 - .../pages/Authentication/SignIn/SingIn.tsx | 20 +------ .../pages/Authentication/SignUp/SingUp.tsx | 15 ----- .../src/pages/Connect/Connect.tsx | 34 +++++------ .../src/pages/GameRoom/GameRoom.tsx | 16 ------ .../controllers/AdminWebSocketController.java | 20 +++---- .../GameRoomsWebSocketController.java | 21 +++---- .../controllers/RoomsRestController.java | 26 ++++++--- .../filters/connect/BannedUserFilter.java | 7 +-- .../connect/UserIsAuthorizedFilter.java | 7 +-- .../filters/message/UserAuthorizedFilter.java | 20 +++++++ .../method/AllowedToChangeRoleFilter.java | 8 +-- .../filters/method/AvailableRoomFilter.java | 2 +- .../filters/method/GameRunningFilter.java | 8 +-- .../filters/method/GameStoppedFilter.java | 8 +-- .../method/SelectWordAvailableFilter.java | 10 ++-- .../filters/method/SendMessageFilter.java | 10 ++-- .../filters/method/SkipGameTurnFilter.java | 10 ++-- .../filters/method/UserAuthorizedFilter.java | 23 -------- .../filters/method/UserIsRoomAdminFilter.java | 6 +- 57 files changed, 330 insertions(+), 308 deletions(-) create mode 100644 codenames-common/src/main/java/com/codenames/properties/RedisProperties.java delete mode 100644 codenames-domain/src/main/java/com/codenames/config/RestControllersConfig.java delete mode 100644 codenames-domain/src/main/java/com/codenames/dao/RedisPlayerDao.java create mode 100644 codenames-domain/src/main/java/com/codenames/dao/RedisUserRoomSessionDao.java rename codenames-domain/src/main/java/com/codenames/{models => domain}/game/CodeNamesGame.java (89%) rename codenames-domain/src/main/java/com/codenames/{models => domain}/game/Player.java (93%) create mode 100644 codenames-domain/src/main/java/com/codenames/domain/game/User.java rename codenames-domain/src/main/java/com/codenames/{models => domain}/game/UserRoomSession.java (86%) rename codenames-domain/src/main/java/com/codenames/{models => domain}/room/Room.java (91%) rename codenames-domain/src/main/java/com/codenames/{models => domain}/room/Settings.java (92%) rename codenames-domain/src/main/java/com/codenames/{models => domain}/room/Team.java (88%) rename codenames-domain/src/main/java/com/codenames/{models => domain}/room/Timer.java (69%) rename codenames-domain/src/main/java/com/codenames/{models => domain}/room/Word.java (91%) create mode 100644 codenames-domain/src/main/java/com/codenames/exception/UserRoomSessionNotFoundExeption.java create mode 100644 codenames-domain/src/main/java/com/codenames/mapper/UserRoomSessionMapper.java delete mode 100644 codenames-domain/src/main/java/com/codenames/models/game/AuthorizedUsers.java delete mode 100644 codenames-domain/src/main/java/com/codenames/models/game/User.java create mode 100644 codenames-domain/src/main/java/com/codenames/redis_entity/HashedUserRoomSession.java create mode 100644 codenames-domain/src/main/java/com/codenames/services/AuthorizedUsersService.java create mode 100644 codenames-web-services/src/main/java/com/codenames/filters/message/UserAuthorizedFilter.java delete mode 100644 codenames-web-services/src/main/java/com/codenames/filters/method/UserAuthorizedFilter.java diff --git a/codenames-common/src/main/java/com/codenames/properties/CodeNamesProperties.java b/codenames-common/src/main/java/com/codenames/properties/CodeNamesProperties.java index 5e04660..2995d6b 100644 --- a/codenames-common/src/main/java/com/codenames/properties/CodeNamesProperties.java +++ b/codenames-common/src/main/java/com/codenames/properties/CodeNamesProperties.java @@ -9,8 +9,14 @@ @ConfigurationProperties(prefix = "codenames") @Getter @Setter public class CodeNamesProperties { - WordsProperties words; - GameProperties gameRooms; - WSResponsePathProperties wsResponsePaths; - RestControllerProperties restController; + + private WordsProperties words; + + private GameProperties gameRooms; + + private WSResponsePathProperties wsResponsePaths; + + private RestControllerProperties restController; + + private RedisProperties redis; } diff --git a/codenames-common/src/main/java/com/codenames/properties/RedisProperties.java b/codenames-common/src/main/java/com/codenames/properties/RedisProperties.java new file mode 100644 index 0000000..51211be --- /dev/null +++ b/codenames-common/src/main/java/com/codenames/properties/RedisProperties.java @@ -0,0 +1,15 @@ +package com.codenames.properties; + + +import lombok.Getter; +import lombok.Setter; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +@Component +@ConfigurationProperties(prefix = "codenames-redis") +@Getter @Setter +public class RedisProperties { + + private String userRoomSessionsHashKey; +} diff --git a/codenames-common/src/main/java/com/codenames/properties/SecurityProperties.java b/codenames-common/src/main/java/com/codenames/properties/SecurityProperties.java index 49624f5..16ecdc0 100644 --- a/codenames-common/src/main/java/com/codenames/properties/SecurityProperties.java +++ b/codenames-common/src/main/java/com/codenames/properties/SecurityProperties.java @@ -7,7 +7,7 @@ import org.springframework.stereotype.Component; @Component -@Setter @Getter +@Getter @Setter @ConfigurationProperties(prefix = "security") public class SecurityProperties { String jwt; diff --git a/codenames-common/src/main/resources/application.yml b/codenames-common/src/main/resources/application.yml index 188fa35..18abfc3 100644 --- a/codenames-common/src/main/resources/application.yml +++ b/codenames-common/src/main/resources/application.yml @@ -16,6 +16,9 @@ codenames: cross-origin: - http://localhost:3000 + redis: + user-room-sessions-hash-key: user_room_sessions + security: jwt: I don't understand this stupid thing. diff --git a/codenames-domain/src/main/java/com/codenames/config/RestControllersConfig.java b/codenames-domain/src/main/java/com/codenames/config/RestControllersConfig.java deleted file mode 100644 index 9249035..0000000 --- a/codenames-domain/src/main/java/com/codenames/config/RestControllersConfig.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.codenames.config; - -import com.codenames.properties.CodeNamesProperties; -import lombok.RequiredArgsConstructor; -import org.springframework.context.annotation.Configuration; -import org.springframework.web.servlet.config.annotation.CorsRegistry; -import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; - - -//@Configuration -//@RequiredArgsConstructor -//public class RestControllersConfig implements WebMvcConfigurer { -// -// private final CodeNamesProperties codeNamesProperties; -// -// -// @Override -// public void addCorsMappings(CorsRegistry registry) { -// registry.addMapping("/**") -// .allowedOrigins(codeNamesProperties.getRestController().getCrossOrigin()) -// .allowedMethods("GET", "POST", "PUT", "DELETE") -// .allowedHeaders("*"); -// } -//} diff --git a/codenames-domain/src/main/java/com/codenames/dao/RedisPlayerDao.java b/codenames-domain/src/main/java/com/codenames/dao/RedisPlayerDao.java deleted file mode 100644 index f2291ae..0000000 --- a/codenames-domain/src/main/java/com/codenames/dao/RedisPlayerDao.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.codenames.dao; - -import com.codenames.redis_entity.HashedPlayer; -import lombok.RequiredArgsConstructor; -import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.stereotype.Repository; - -@Repository -@RequiredArgsConstructor -public class RedisPlayerDao { - - private static final String HASH_KEY = "authorize_player"; - - private RedisTemplate template; - - public void save(HashedPlayer hashedPlayer){ - template.opsForHash().put(HASH_KEY, hashedPlayer.getId(), hashedPlayer); - } - - public void removeHashedPlayerById(int id){ - template.opsForHash().delete(HASH_KEY, id); - } - - public HashedPlayer findHashedPlayerById(int id){ - return (HashedPlayer) template.opsForHash().get(HASH_KEY, id); - } -} diff --git a/codenames-domain/src/main/java/com/codenames/dao/RedisUserRoomSessionDao.java b/codenames-domain/src/main/java/com/codenames/dao/RedisUserRoomSessionDao.java new file mode 100644 index 0000000..a8ffff4 --- /dev/null +++ b/codenames-domain/src/main/java/com/codenames/dao/RedisUserRoomSessionDao.java @@ -0,0 +1,34 @@ +package com.codenames.dao; + +import com.codenames.properties.CodeNamesProperties; +import com.codenames.redis_entity.HashedUserRoomSession; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Repository; + +import java.util.Optional; + +@Repository +public class RedisUserRoomSessionDao { + + private final String hashKey; + + private final RedisTemplate template; + + RedisUserRoomSessionDao(CodeNamesProperties codeNamesProperties, + RedisTemplate template){ + this.template = template; + this.hashKey = codeNamesProperties.getRedis().getUserRoomSessionsHashKey(); + } + + public void save(HashedUserRoomSession hashedPlayer){ + template.opsForHash().put(hashKey, hashedPlayer.getPlayer().getWsSessionId(), hashedPlayer); + } + + public void removeByWsSessionID(String wsSessionId){ + template.opsForHash().delete(hashKey, wsSessionId); + } + + public Optional findByWsSessionID(String wsSessionId){ + return Optional.of((HashedUserRoomSession) template.opsForHash().get(hashKey, wsSessionId)); + } +} diff --git a/codenames-domain/src/main/java/com/codenames/models/game/CodeNamesGame.java b/codenames-domain/src/main/java/com/codenames/domain/game/CodeNamesGame.java similarity index 89% rename from codenames-domain/src/main/java/com/codenames/models/game/CodeNamesGame.java rename to codenames-domain/src/main/java/com/codenames/domain/game/CodeNamesGame.java index fa101e5..b80ca7e 100644 --- a/codenames-domain/src/main/java/com/codenames/models/game/CodeNamesGame.java +++ b/codenames-domain/src/main/java/com/codenames/domain/game/CodeNamesGame.java @@ -1,6 +1,6 @@ -package com.codenames.models.game; +package com.codenames.domain.game; -import com.codenames.models.room.Room; +import com.codenames.domain.room.Room; import lombok.Getter; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; diff --git a/codenames-domain/src/main/java/com/codenames/models/game/Player.java b/codenames-domain/src/main/java/com/codenames/domain/game/Player.java similarity index 93% rename from codenames-domain/src/main/java/com/codenames/models/game/Player.java rename to codenames-domain/src/main/java/com/codenames/domain/game/Player.java index 3fc7628..5708a65 100644 --- a/codenames-domain/src/main/java/com/codenames/models/game/Player.java +++ b/codenames-domain/src/main/java/com/codenames/domain/game/Player.java @@ -1,4 +1,4 @@ -package com.codenames.models.game; +package com.codenames.domain.game; import com.codenames.enums.PlayerRole; import lombok.AllArgsConstructor; diff --git a/codenames-domain/src/main/java/com/codenames/domain/game/User.java b/codenames-domain/src/main/java/com/codenames/domain/game/User.java new file mode 100644 index 0000000..3b113a7 --- /dev/null +++ b/codenames-domain/src/main/java/com/codenames/domain/game/User.java @@ -0,0 +1,4 @@ +package com.codenames.domain.game; + +public record User(int id, String nickname) { +} diff --git a/codenames-domain/src/main/java/com/codenames/models/game/UserRoomSession.java b/codenames-domain/src/main/java/com/codenames/domain/game/UserRoomSession.java similarity index 86% rename from codenames-domain/src/main/java/com/codenames/models/game/UserRoomSession.java rename to codenames-domain/src/main/java/com/codenames/domain/game/UserRoomSession.java index 46aa7b5..b9640ef 100644 --- a/codenames-domain/src/main/java/com/codenames/models/game/UserRoomSession.java +++ b/codenames-domain/src/main/java/com/codenames/domain/game/UserRoomSession.java @@ -1,4 +1,4 @@ -package com.codenames.models.game; +package com.codenames.domain.game; import lombok.AllArgsConstructor; diff --git a/codenames-domain/src/main/java/com/codenames/models/room/Room.java b/codenames-domain/src/main/java/com/codenames/domain/room/Room.java similarity index 91% rename from codenames-domain/src/main/java/com/codenames/models/room/Room.java rename to codenames-domain/src/main/java/com/codenames/domain/room/Room.java index a64a07b..7fdd58d 100644 --- a/codenames-domain/src/main/java/com/codenames/models/room/Room.java +++ b/codenames-domain/src/main/java/com/codenames/domain/room/Room.java @@ -1,7 +1,7 @@ -package com.codenames.models.room; +package com.codenames.domain.room; -import com.codenames.models.game.Player; -import com.codenames.models.game.User; +import com.codenames.domain.game.Player; +import com.codenames.domain.game.User; import com.codenames.enums.Color; import com.codenames.enums.GameTurn; import com.codenames.enums.GameStatus; diff --git a/codenames-domain/src/main/java/com/codenames/models/room/Settings.java b/codenames-domain/src/main/java/com/codenames/domain/room/Settings.java similarity index 92% rename from codenames-domain/src/main/java/com/codenames/models/room/Settings.java rename to codenames-domain/src/main/java/com/codenames/domain/room/Settings.java index ddad725..40953d8 100644 --- a/codenames-domain/src/main/java/com/codenames/models/room/Settings.java +++ b/codenames-domain/src/main/java/com/codenames/domain/room/Settings.java @@ -1,4 +1,4 @@ -package com.codenames.models.room; +package com.codenames.domain.room; import com.codenames.enums.Language; import com.codenames.enums.WordsSettings; diff --git a/codenames-domain/src/main/java/com/codenames/models/room/Team.java b/codenames-domain/src/main/java/com/codenames/domain/room/Team.java similarity index 88% rename from codenames-domain/src/main/java/com/codenames/models/room/Team.java rename to codenames-domain/src/main/java/com/codenames/domain/room/Team.java index 63eaf19..da57c23 100644 --- a/codenames-domain/src/main/java/com/codenames/models/room/Team.java +++ b/codenames-domain/src/main/java/com/codenames/domain/room/Team.java @@ -1,7 +1,7 @@ -package com.codenames.models.room; +package com.codenames.domain.room; import com.codenames.enums.Color; -import com.codenames.models.game.Player; +import com.codenames.domain.game.Player; import lombok.Getter; import lombok.RequiredArgsConstructor; import lombok.Setter; diff --git a/codenames-domain/src/main/java/com/codenames/models/room/Timer.java b/codenames-domain/src/main/java/com/codenames/domain/room/Timer.java similarity index 69% rename from codenames-domain/src/main/java/com/codenames/models/room/Timer.java rename to codenames-domain/src/main/java/com/codenames/domain/room/Timer.java index 0fcd1e6..abf6816 100644 --- a/codenames-domain/src/main/java/com/codenames/models/room/Timer.java +++ b/codenames-domain/src/main/java/com/codenames/domain/room/Timer.java @@ -1,4 +1,4 @@ -package com.codenames.models.room; +package com.codenames.domain.room; import lombok.Getter; diff --git a/codenames-domain/src/main/java/com/codenames/models/room/Word.java b/codenames-domain/src/main/java/com/codenames/domain/room/Word.java similarity index 91% rename from codenames-domain/src/main/java/com/codenames/models/room/Word.java rename to codenames-domain/src/main/java/com/codenames/domain/room/Word.java index 9f1d1be..1ab8a15 100644 --- a/codenames-domain/src/main/java/com/codenames/models/room/Word.java +++ b/codenames-domain/src/main/java/com/codenames/domain/room/Word.java @@ -1,4 +1,4 @@ -package com.codenames.models.room; +package com.codenames.domain.room; import com.codenames.enums.Color; import lombok.Getter; diff --git a/codenames-domain/src/main/java/com/codenames/entity/UserEntity.java b/codenames-domain/src/main/java/com/codenames/entity/UserEntity.java index f6031df..bffc976 100644 --- a/codenames-domain/src/main/java/com/codenames/entity/UserEntity.java +++ b/codenames-domain/src/main/java/com/codenames/entity/UserEntity.java @@ -23,7 +23,7 @@ public class UserEntity { @Column(name = "nickname") private String nickname; - @Column(name = "login") + @Column(name = "login", unique = true) private String login; @Column(name = "password") diff --git a/codenames-domain/src/main/java/com/codenames/exception/UserRoomSessionNotFoundExeption.java b/codenames-domain/src/main/java/com/codenames/exception/UserRoomSessionNotFoundExeption.java new file mode 100644 index 0000000..ad4f1d6 --- /dev/null +++ b/codenames-domain/src/main/java/com/codenames/exception/UserRoomSessionNotFoundExeption.java @@ -0,0 +1,8 @@ +package com.codenames.exception; + +public class UserRoomSessionNotFoundExeption extends RuntimeException{ + + public UserRoomSessionNotFoundExeption(String message){ + super(message); + } +} diff --git a/codenames-domain/src/main/java/com/codenames/mapper/PlayerMapper.java b/codenames-domain/src/main/java/com/codenames/mapper/PlayerMapper.java index fa5dfbb..68f35d0 100644 --- a/codenames-domain/src/main/java/com/codenames/mapper/PlayerMapper.java +++ b/codenames-domain/src/main/java/com/codenames/mapper/PlayerMapper.java @@ -1,7 +1,7 @@ package com.codenames.mapper; import com.codenames.dto.UserDto; -import com.codenames.models.game.Player; +import com.codenames.domain.game.Player; import org.mapstruct.IterableMapping; import org.mapstruct.Mapper; import org.mapstruct.Mapping; diff --git a/codenames-domain/src/main/java/com/codenames/mapper/RoomMapper.java b/codenames-domain/src/main/java/com/codenames/mapper/RoomMapper.java index 34966bf..363a06e 100644 --- a/codenames-domain/src/main/java/com/codenames/mapper/RoomMapper.java +++ b/codenames-domain/src/main/java/com/codenames/mapper/RoomMapper.java @@ -4,7 +4,7 @@ import org.mapstruct.Mapping; import com.codenames.dto.RoomDto; -import com.codenames.models.room.Room; +import com.codenames.domain.room.Room; @Mapper( diff --git a/codenames-domain/src/main/java/com/codenames/mapper/TeamMapper.java b/codenames-domain/src/main/java/com/codenames/mapper/TeamMapper.java index 02178b5..65fa80d 100644 --- a/codenames-domain/src/main/java/com/codenames/mapper/TeamMapper.java +++ b/codenames-domain/src/main/java/com/codenames/mapper/TeamMapper.java @@ -1,7 +1,7 @@ package com.codenames.mapper; import com.codenames.dto.TeamDto; -import com.codenames.models.room.Team; +import com.codenames.domain.room.Team; import org.mapstruct.Mapper; import org.mapstruct.Mapping; diff --git a/codenames-domain/src/main/java/com/codenames/mapper/UserMapper.java b/codenames-domain/src/main/java/com/codenames/mapper/UserMapper.java index e00a211..6c3a5db 100644 --- a/codenames-domain/src/main/java/com/codenames/mapper/UserMapper.java +++ b/codenames-domain/src/main/java/com/codenames/mapper/UserMapper.java @@ -4,7 +4,7 @@ import com.codenames.dto.UserAuthDto; import com.codenames.dto.UserDto; import com.codenames.entity.UserEntity; -import com.codenames.models.game.User; +import com.codenames.domain.game.User; import org.mapstruct.Mapper; import org.mapstruct.Mapping; diff --git a/codenames-domain/src/main/java/com/codenames/mapper/UserRoomSessionMapper.java b/codenames-domain/src/main/java/com/codenames/mapper/UserRoomSessionMapper.java new file mode 100644 index 0000000..f015ae0 --- /dev/null +++ b/codenames-domain/src/main/java/com/codenames/mapper/UserRoomSessionMapper.java @@ -0,0 +1,14 @@ +package com.codenames.mapper; + +import com.codenames.domain.game.UserRoomSession; +import com.codenames.redis_entity.HashedUserRoomSession; +import org.mapstruct.Mapper; + +@Mapper(componentModel = "spring") +public interface UserRoomSessionMapper { + + UserRoomSession hashedUserRoomSessionToUserRoomSession(HashedUserRoomSession hashedUserRoomSession); + + HashedUserRoomSession userRoomSessionToHashedUserRoomSession(UserRoomSession userRoomSession); + +} diff --git a/codenames-domain/src/main/java/com/codenames/mapper/WordsMapper.java b/codenames-domain/src/main/java/com/codenames/mapper/WordsMapper.java index 0e2a1be..96d1c1d 100644 --- a/codenames-domain/src/main/java/com/codenames/mapper/WordsMapper.java +++ b/codenames-domain/src/main/java/com/codenames/mapper/WordsMapper.java @@ -2,7 +2,7 @@ import com.codenames.dto.WordDto; import com.codenames.enums.Color; -import com.codenames.models.room.Word; +import com.codenames.domain.room.Word; import org.mapstruct.IterableMapping; import org.mapstruct.Mapper; import org.mapstruct.Mapping; diff --git a/codenames-domain/src/main/java/com/codenames/models/game/AuthorizedUsers.java b/codenames-domain/src/main/java/com/codenames/models/game/AuthorizedUsers.java deleted file mode 100644 index a583f2a..0000000 --- a/codenames-domain/src/main/java/com/codenames/models/game/AuthorizedUsers.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.codenames.models.game; - -import com.codenames.services.PlayerService; -import lombok.Data; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Component; - -import java.util.HashMap; -import java.util.Map; - -@Component -@Data -@RequiredArgsConstructor -public class AuthorizedUsers { - - // TODO: 18.04.2023 This class will be reworked - - private final Map authorizedUsers = new HashMap<>(); - - public UserRoomSession getUserRoomSession(String sessionID){ - return authorizedUsers.get(sessionID); - } - - public boolean checkUserAuthorized(User user){ - return this.authorizedUsers.containsValue(user); - } - - public void addUserRoomSession(String sessionID, int roomID, User user){ - authorizedUsers.put(sessionID, new UserRoomSession(roomID, new Player(sessionID, null, user))); - } - - public void removeUserRoomSession(Player player){ - authorizedUsers.remove(player.getWsSessionId()); - player.setWsSessionId(null); - } - - public void setNewRoomID(String sessionID, int newRoomID){ - authorizedUsers.get(sessionID).setRoomID(newRoomID); - } -} diff --git a/codenames-domain/src/main/java/com/codenames/models/game/User.java b/codenames-domain/src/main/java/com/codenames/models/game/User.java deleted file mode 100644 index 889d60a..0000000 --- a/codenames-domain/src/main/java/com/codenames/models/game/User.java +++ /dev/null @@ -1,4 +0,0 @@ -package com.codenames.models.game; - -public record User(int id, String ip, String nickname) { -} diff --git a/codenames-domain/src/main/java/com/codenames/provider/UserAuthProvider.java b/codenames-domain/src/main/java/com/codenames/provider/UserAuthProvider.java index b72193c..b231304 100644 --- a/codenames-domain/src/main/java/com/codenames/provider/UserAuthProvider.java +++ b/codenames-domain/src/main/java/com/codenames/provider/UserAuthProvider.java @@ -19,7 +19,7 @@ @Component public class UserAuthProvider { - private static final int TOKEN_LIFETIME = 60 * 60 * 60 * 24 * 30; + private static final long TOKEN_LIFETIME = 1000 * 60 * 60 * 24 * 14; private SecurityProperties securityProperties; diff --git a/codenames-domain/src/main/java/com/codenames/redis_entity/HashedPlayer.java b/codenames-domain/src/main/java/com/codenames/redis_entity/HashedPlayer.java index 5bb06f3..8d4d3d7 100644 --- a/codenames-domain/src/main/java/com/codenames/redis_entity/HashedPlayer.java +++ b/codenames-domain/src/main/java/com/codenames/redis_entity/HashedPlayer.java @@ -6,14 +6,12 @@ import lombok.NoArgsConstructor; import lombok.Setter; import org.springframework.data.annotation.Id; -import org.springframework.data.redis.core.RedisHash; import java.io.Serializable; @Getter @Setter @AllArgsConstructor @NoArgsConstructor -@RedisHash("authorize_player") public class HashedPlayer implements Serializable { @Id private int id; diff --git a/codenames-domain/src/main/java/com/codenames/redis_entity/HashedUserRoomSession.java b/codenames-domain/src/main/java/com/codenames/redis_entity/HashedUserRoomSession.java new file mode 100644 index 0000000..8d37584 --- /dev/null +++ b/codenames-domain/src/main/java/com/codenames/redis_entity/HashedUserRoomSession.java @@ -0,0 +1,20 @@ +package com.codenames.redis_entity; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.springframework.data.redis.core.RedisHash; + +import java.io.Serializable; + +@Getter @Setter +@AllArgsConstructor +@NoArgsConstructor +@RedisHash("user_room_sessions") +public class HashedUserRoomSession implements Serializable { + + private int roomID; + + private HashedPlayer player; +} diff --git a/codenames-domain/src/main/java/com/codenames/services/AuthorizedUsersService.java b/codenames-domain/src/main/java/com/codenames/services/AuthorizedUsersService.java new file mode 100644 index 0000000..fbe0ffb --- /dev/null +++ b/codenames-domain/src/main/java/com/codenames/services/AuthorizedUsersService.java @@ -0,0 +1,57 @@ +package com.codenames.services; + +import com.codenames.dao.RedisUserRoomSessionDao; +import com.codenames.exception.UserRoomSessionNotFoundExeption; +import com.codenames.mapper.UserRoomSessionMapper; +import com.codenames.domain.game.Player; +import com.codenames.domain.game.User; +import com.codenames.domain.game.UserRoomSession; +import com.codenames.redis_entity.HashedUserRoomSession; +import lombok.Data; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + + +@Component +@Data +@RequiredArgsConstructor +public class AuthorizedUsersService { + + private final RedisUserRoomSessionDao redisPlayerDao; + + private final UserRoomSessionMapper userRoomSessionMapper; + + public UserRoomSession getUserRoomSession(String sessionID){ + return userRoomSessionMapper.hashedUserRoomSessionToUserRoomSession(redisPlayerDao.findByWsSessionID(sessionID) + .orElseThrow(() -> new UserRoomSessionNotFoundExeption(sessionID + " not found"))); + } + + public boolean checkUserAuthorized(String sessionID){ + return redisPlayerDao.findByWsSessionID(sessionID).isPresent(); + } + + public boolean checkUserAuthorized(Player player){ + return redisPlayerDao.findByWsSessionID(player.getWsSessionId()).isPresent(); + } + + public void addUserRoomSession(String sessionID, int roomID, User user){ + UserRoomSession session = new UserRoomSession(roomID, new Player(sessionID, null, user)); + + redisPlayerDao.save(userRoomSessionMapper.userRoomSessionToHashedUserRoomSession(session)); + } + + public void removeUserRoomSession(Player player){ + redisPlayerDao.removeByWsSessionID(player.getWsSessionId()); + + player.setWsSessionId(null); + } + + public void setNewRoomID(String sessionID, int newRoomID){ + HashedUserRoomSession session = redisPlayerDao.findByWsSessionID(sessionID) + .orElseThrow(() -> new UserRoomSessionNotFoundExeption(sessionID + " not found")); + + session.setRoomID(newRoomID); + + redisPlayerDao.save(session); + } +} diff --git a/codenames-domain/src/main/java/com/codenames/services/GameService.java b/codenames-domain/src/main/java/com/codenames/services/GameService.java index a778f88..f94ffaf 100644 --- a/codenames-domain/src/main/java/com/codenames/services/GameService.java +++ b/codenames-domain/src/main/java/com/codenames/services/GameService.java @@ -1,10 +1,10 @@ package com.codenames.services; import com.codenames.exceptions.RoomLimitIsOver; -import com.codenames.models.game.CodeNamesGame; -import com.codenames.models.game.User; -import com.codenames.models.room.Room; -import com.codenames.models.game.Player; +import com.codenames.domain.game.CodeNamesGame; +import com.codenames.domain.game.User; +import com.codenames.domain.room.Room; +import com.codenames.domain.game.Player; import com.codenames.properties.CodeNamesProperties; import com.codenames.properties.WSResponsePathProperties; import com.jjerome.dto.Request; diff --git a/codenames-domain/src/main/java/com/codenames/services/PlayerService.java b/codenames-domain/src/main/java/com/codenames/services/PlayerService.java index 9343543..e83a960 100644 --- a/codenames-domain/src/main/java/com/codenames/services/PlayerService.java +++ b/codenames-domain/src/main/java/com/codenames/services/PlayerService.java @@ -1,9 +1,8 @@ package com.codenames.services; -import com.codenames.models.game.AuthorizedUsers; -import com.codenames.models.game.Player; -import com.codenames.models.game.User; -import com.codenames.models.game.UserRoomSession; +import com.codenames.domain.game.Player; +import com.codenames.domain.game.User; +import com.codenames.domain.game.UserRoomSession; import com.jjerome.dto.Request; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -12,7 +11,7 @@ @RequiredArgsConstructor public class PlayerService { - private final AuthorizedUsers authorizedUsers; + private final AuthorizedUsersService authorizedUsers; private User getUser(Request request){ return this.authorizedUsers.getUserRoomSession(request.getSessionID()).getPlayer().getUser(); diff --git a/codenames-domain/src/main/java/com/codenames/services/RoomService.java b/codenames-domain/src/main/java/com/codenames/services/RoomService.java index f33534e..ddacb73 100644 --- a/codenames-domain/src/main/java/com/codenames/services/RoomService.java +++ b/codenames-domain/src/main/java/com/codenames/services/RoomService.java @@ -5,9 +5,9 @@ import com.codenames.enums.GameTurn; import com.codenames.enums.PlayerRole; import com.codenames.mapper.RoomMapper; -import com.codenames.models.game.Player; -import com.codenames.models.room.Room; -import com.codenames.models.room.Team; +import com.codenames.domain.game.Player; +import com.codenames.domain.room.Room; +import com.codenames.domain.room.Team; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; diff --git a/codenames-domain/src/main/java/com/codenames/services/TeamService.java b/codenames-domain/src/main/java/com/codenames/services/TeamService.java index fb5cf0d..be5f4ff 100644 --- a/codenames-domain/src/main/java/com/codenames/services/TeamService.java +++ b/codenames-domain/src/main/java/com/codenames/services/TeamService.java @@ -1,7 +1,7 @@ package com.codenames.services; -import com.codenames.models.game.Player; -import com.codenames.models.room.Team; +import com.codenames.domain.game.Player; +import com.codenames.domain.room.Team; import org.springframework.stereotype.Service; import java.util.List; diff --git a/codenames-domain/src/main/java/com/codenames/services/UserService.java b/codenames-domain/src/main/java/com/codenames/services/UserService.java index 8789cd4..3f329ef 100644 --- a/codenames-domain/src/main/java/com/codenames/services/UserService.java +++ b/codenames-domain/src/main/java/com/codenames/services/UserService.java @@ -10,6 +10,7 @@ import com.codenames.repository.UserRepository; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; +import org.springframework.security.core.Authentication; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Controller; @@ -64,4 +65,15 @@ public UserEntity register(SignUpDto signUpDto){ return userRepository.save(user); } + + public Optional getUserEntityFromAuthentication(Authentication authentication){ + Object principal = authentication.getPrincipal(); + + if (!(principal instanceof UserEntity)){ + return Optional.empty(); + } + + return Optional.of((UserEntity) principal); + + } } diff --git a/codenames-domain/src/main/java/com/codenames/services/WordsService.java b/codenames-domain/src/main/java/com/codenames/services/WordsService.java index eb8edff..dc5eac5 100644 --- a/codenames-domain/src/main/java/com/codenames/services/WordsService.java +++ b/codenames-domain/src/main/java/com/codenames/services/WordsService.java @@ -3,11 +3,9 @@ import com.codenames.enums.Color; import com.codenames.enums.Language; -import com.codenames.models.game.Player; -import com.codenames.models.room.Settings; -import com.codenames.models.room.Word; +import com.codenames.domain.room.Settings; +import com.codenames.domain.room.Word; import com.codenames.properties.CodeNamesProperties; -import com.codenames.properties.WordsProperties; import lombok.RequiredArgsConstructor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/codenames-front-end/src/App.tsx b/codenames-front-end/src/App.tsx index 25993b6..c752bda 100644 --- a/codenames-front-end/src/App.tsx +++ b/codenames-front-end/src/App.tsx @@ -5,6 +5,7 @@ import useFetching from "./hooks/useFetching"; import {authRequest, getAuthToken} from "./helper"; import {RestConfig} from "./config"; import {AxiosResponse} from "axios"; +import {Flip, ToastContainer} from "react-toastify"; function App() { const isAuthorized = useRef(!!getAuthToken()) @@ -59,6 +60,19 @@ function App() { element={} /> + ); } diff --git a/codenames-front-end/src/hooks/useCodeNamesWsRoomConnect.ts b/codenames-front-end/src/hooks/useCodeNamesWsRoomConnect.ts index 7f460c9..97d99fd 100644 --- a/codenames-front-end/src/hooks/useCodeNamesWsRoomConnect.ts +++ b/codenames-front-end/src/hooks/useCodeNamesWsRoomConnect.ts @@ -113,8 +113,6 @@ export const useCodeNamesWsRoomConnect = ( if (typeof message.data === "string") { const messageData: WebSocketResponse = JSON.parse(message.data) - console.log(messageData) - if (messageData.responsePath === WebSocketConfig.paths.response.newRoomInfo) { onNewRoomInfo(messageData.responseBody) } diff --git a/codenames-front-end/src/pages/Authentication/SignIn/SingIn.tsx b/codenames-front-end/src/pages/Authentication/SignIn/SingIn.tsx index 29c9d04..07f9904 100644 --- a/codenames-front-end/src/pages/Authentication/SignIn/SingIn.tsx +++ b/codenames-front-end/src/pages/Authentication/SignIn/SingIn.tsx @@ -41,8 +41,8 @@ const SingIn = () => { } else { navigate('/room', { replace: true }); } - } catch (e){ - notify.error("Check the data you entered") + } catch (e: any){ + notify.error(e.response.data.message) } }) @@ -71,23 +71,7 @@ const SingIn = () => { Sign In - - - - ); }; diff --git a/codenames-front-end/src/pages/Authentication/SignUp/SingUp.tsx b/codenames-front-end/src/pages/Authentication/SignUp/SingUp.tsx index 45a630f..da5e238 100644 --- a/codenames-front-end/src/pages/Authentication/SignUp/SingUp.tsx +++ b/codenames-front-end/src/pages/Authentication/SignUp/SingUp.tsx @@ -54,7 +54,6 @@ const SingUp = () => { } else { tryRegister() } - tryRegister() } @@ -97,20 +96,6 @@ const SingUp = () => { - - ); }; diff --git a/codenames-front-end/src/pages/Connect/Connect.tsx b/codenames-front-end/src/pages/Connect/Connect.tsx index 9b2eed3..b51c857 100644 --- a/codenames-front-end/src/pages/Connect/Connect.tsx +++ b/codenames-front-end/src/pages/Connect/Connect.tsx @@ -1,7 +1,6 @@ import React, {useState} from 'react'; import {useNavigate} from "react-router-dom"; -import {Flip, ToastContainer} from "react-toastify"; import {notify} from "../../models"; import useFetching from "../../hooks/useFetching"; @@ -20,6 +19,7 @@ import { StyledTitle } from "../Connect"; import {StyledBlueYellowBG} from "../../components"; +import {HttpStatusCode} from "axios"; export const Connect = () => { const minRoomID = 100000; @@ -32,12 +32,20 @@ export const Connect = () => { const [createRoom, connectToRoom] = useCodeNamesRestRequests(); const [fetchConnectToRoom] = useFetching(async () => { - const response = await connectToRoom(Number(roomID)); + try { + const response = await connectToRoom(Number(roomID)); - if (response.data === -1) { - notify.error(`Room with number "${roomID}" was not found`) - } else { - navigate(`/room/${roomID}`) + if (response.data === -1) { + notify.error(`Room with number "${roomID}" was not found`) + } else { + navigate(`/room/${roomID}`) + } + } catch (e: any){ + notify.error(e.response.data.message) + + if (e.response.status === HttpStatusCode.Unauthorized){ + navigate("/sign_in") + } } }); @@ -112,20 +120,6 @@ export const Connect = () => { } - - ) } \ No newline at end of file diff --git a/codenames-front-end/src/pages/GameRoom/GameRoom.tsx b/codenames-front-end/src/pages/GameRoom/GameRoom.tsx index df4689b..104a2bf 100644 --- a/codenames-front-end/src/pages/GameRoom/GameRoom.tsx +++ b/codenames-front-end/src/pages/GameRoom/GameRoom.tsx @@ -5,7 +5,6 @@ import {useParams} from "react-router-dom"; import {IGameRoom, Status} from "../../models"; import {useCodeNamesWsRoomConnect} from "../../hooks"; -import {Flip, ToastContainer} from "react-toastify"; import {StyledCodeNamesGameFrame} from "./GameRoomStyles"; import { StyledCNRunGameFrame, StyledCNStopGameMenu, @@ -59,21 +58,6 @@ const GameRoom = () => { onClickToGameStop={() => requests?.stopGame()} hidden={room?.status !== Status.RUN} /> - - - ); diff --git a/codenames-web-services/src/main/java/com/codenames/controllers/AdminWebSocketController.java b/codenames-web-services/src/main/java/com/codenames/controllers/AdminWebSocketController.java index 08ce355..7d719ac 100644 --- a/codenames-web-services/src/main/java/com/codenames/controllers/AdminWebSocketController.java +++ b/codenames-web-services/src/main/java/com/codenames/controllers/AdminWebSocketController.java @@ -2,10 +2,10 @@ import com.codenames.enums.GameStatus; import com.codenames.filters.method.GameStoppedFilter; -import com.codenames.filters.method.UserAuthorizedFilter; -import com.codenames.models.game.CodeNamesGame; -import com.codenames.models.room.Room; -import com.codenames.models.room.Settings; +import com.codenames.domain.game.CodeNamesGame; +import com.codenames.domain.room.Room; +import com.codenames.domain.room.Settings; +import com.codenames.filters.method.UserIsRoomAdminFilter; import com.codenames.services.GameService; import com.codenames.services.PlayerService; import com.codenames.services.RoomService; @@ -32,10 +32,8 @@ public class AdminWebSocketController { @SocketMapping(reqPath = "/room/admin/start") @SocketMappingFilters(filters = { - UserAuthorizedFilter.class, GameStoppedFilter.class, - // TODO: 05.05.2023 Temporarily to simplify tests -// UserIsRoomAdminFilter.class + UserIsRoomAdminFilter.class }) public void startGameInRoom(Request request){ Room room = codeNamesGame.getGameRoom(playerService.getPlayerRoomID(request)); @@ -47,9 +45,7 @@ public void startGameInRoom(Request request){ @SocketMapping(reqPath = "/room/admin/stop") @SocketMappingFilters(filters = { - UserAuthorizedFilter.class - // TODO: 05.05.2023 Temporarily to simplify tests -// UserIsRoomAdminFilter.class + UserIsRoomAdminFilter.class }) public void stopGameInRoom(Request request){ Room room = codeNamesGame.getGameRoom(playerService.getPlayerRoomID(request)); @@ -61,9 +57,7 @@ public void stopGameInRoom(Request request){ @SocketMapping(reqPath = "/room/admin/restart") @SocketMappingFilters(filters = { - UserAuthorizedFilter.class - // TODO: 05.05.2023 Temporarily to simplify tests -// UserIsRoomAdminFilter.class + UserIsRoomAdminFilter.class }) public void restartGameInRoom(Request request){ Room room = codeNamesGame.getGameRoom(playerService.getPlayerRoomID(request)); diff --git a/codenames-web-services/src/main/java/com/codenames/controllers/GameRoomsWebSocketController.java b/codenames-web-services/src/main/java/com/codenames/controllers/GameRoomsWebSocketController.java index 4661a7d..ab66267 100644 --- a/codenames-web-services/src/main/java/com/codenames/controllers/GameRoomsWebSocketController.java +++ b/codenames-web-services/src/main/java/com/codenames/controllers/GameRoomsWebSocketController.java @@ -1,7 +1,6 @@ package com.codenames.controllers; -import com.codenames.dao.RedisPlayerDao; import com.codenames.enums.PlayerRole; import com.codenames.filters.method.AvailableRoomFilter; import com.codenames.filters.method.GameRunningFilter; @@ -9,11 +8,10 @@ import com.codenames.filters.method.SelectWordAvailableFilter; import com.codenames.filters.method.SendMessageFilter; import com.codenames.filters.method.SkipGameTurnFilter; -import com.codenames.filters.method.UserAuthorizedFilter; -import com.codenames.models.game.AuthorizedUsers; -import com.codenames.models.game.CodeNamesGame; -import com.codenames.models.game.Player; -import com.codenames.models.room.Room; +import com.codenames.services.AuthorizedUsersService; +import com.codenames.domain.game.CodeNamesGame; +import com.codenames.domain.game.Player; +import com.codenames.domain.room.Room; import com.codenames.services.PlayerService; import com.codenames.services.RoomService; import com.codenames.services.GameService; @@ -43,9 +41,7 @@ public class GameRoomsWebSocketController { private final CodeNamesGame codeNamesGame; - private final AuthorizedUsers authorizedUsers; - - private final RedisPlayerDao redisPlayerDao; + private final AuthorizedUsersService authorizedUsers; @SocketConnectMapping @@ -53,6 +49,7 @@ public void userConnect(WebSocketSession session) { LOGGER.info(authorizedUsers.getUserRoomSession(session.getId()).getPlayer().getUser().nickname() + " - connected"); } + @SocketDisconnectMapping public void disconnect(WebSocketSession session, CloseStatus status){ authorizedUsers.removeUserRoomSession(playerService.getPlayerBySessionID(session.getId())); @@ -62,7 +59,6 @@ public void disconnect(WebSocketSession session, CloseStatus status){ @SocketMapping(reqPath = "/room/connect") @SocketMappingFilters(filters = { - UserAuthorizedFilter.class, AvailableRoomFilter.class }) public void connectToRoom(Request request){ @@ -78,7 +74,6 @@ public void connectToRoom(Request request){ @SocketMapping(reqPath = "/room/select/role") @SocketMappingFilters(filters = { - UserAuthorizedFilter.class, GameStoppedFilter.class }) public void selectRoomRole(Request request){ @@ -93,7 +88,6 @@ public void selectRoomRole(Request request){ @SocketMapping(reqPath = "/room/select/word") @SocketMappingFilters(filters = { - UserAuthorizedFilter.class, GameRunningFilter.class, SelectWordAvailableFilter.class }) @@ -108,7 +102,6 @@ public void selectWord(Request request){ @SocketMapping(reqPath = "/room/endTurn") @SocketMappingFilters(filters = { - UserAuthorizedFilter.class, GameRunningFilter.class, SkipGameTurnFilter.class }) @@ -119,9 +112,9 @@ public void endTurn(Request request){ gameService.sendRoomInfoToAllRoomPlayer(room); } + @SocketMapping(reqPath = "/room/sendMassage") @SocketMappingFilters(filters = { - UserAuthorizedFilter.class, GameRunningFilter.class, SendMessageFilter.class }) diff --git a/codenames-web-services/src/main/java/com/codenames/controllers/RoomsRestController.java b/codenames-web-services/src/main/java/com/codenames/controllers/RoomsRestController.java index ce36bb7..a77e812 100644 --- a/codenames-web-services/src/main/java/com/codenames/controllers/RoomsRestController.java +++ b/codenames-web-services/src/main/java/com/codenames/controllers/RoomsRestController.java @@ -1,14 +1,18 @@ package com.codenames.controllers; -import com.codenames.models.game.CodeNamesGame; -import com.codenames.models.game.Player; -import com.codenames.models.game.User; +import com.codenames.domain.game.CodeNamesGame; +import com.codenames.domain.game.Player; +import com.codenames.domain.game.User; +import com.codenames.exception.UserNotFoundException; +import com.codenames.mapper.UserMapper; import com.codenames.services.GameService; +import com.codenames.services.UserService; import lombok.RequiredArgsConstructor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.HttpStatus; +import org.springframework.security.core.Authentication; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; @@ -26,17 +30,23 @@ public class RoomsRestController { private final CodeNamesGame codeNamesGame; + private final UserMapper userMapper; + + private final UserService userService; + @GetMapping(path = "/connect/{id}") - public int connectToRoomById(@PathVariable("id") int roomID){ + public int connectToRoomById(@PathVariable("id") int roomID) { return gameService.checkAvailabilityRoom(codeNamesGame, roomID) ? roomID : -1; } @GetMapping(path = "/create") @ResponseStatus(code = HttpStatus.CREATED) - public Integer createNewRoom(){ - // TODO: 03.05.2023 This is a temporary code, it will be recycled after adding authorization - int newRoomID = gameService.createNewRoom(codeNamesGame, - new Player(null ,null, new User(23243, "234", "JJerome"))); + public Integer createNewRoom(Authentication authentication) { + User user = userMapper.userEntityToUser(userService.getUserEntityFromAuthentication(authentication) + .orElseThrow(() -> + new UserNotFoundException("Error receiving an authorized user", HttpStatus.UNAUTHORIZED))); + + int newRoomID = gameService.createNewRoom(codeNamesGame, new Player(null, null, user)); LOGGER.info("Created new room, id - " + newRoomID); diff --git a/codenames-web-services/src/main/java/com/codenames/filters/connect/BannedUserFilter.java b/codenames-web-services/src/main/java/com/codenames/filters/connect/BannedUserFilter.java index f07b9b1..07958c6 100644 --- a/codenames-web-services/src/main/java/com/codenames/filters/connect/BannedUserFilter.java +++ b/codenames-web-services/src/main/java/com/codenames/filters/connect/BannedUserFilter.java @@ -1,11 +1,10 @@ package com.codenames.filters.connect; -import com.codenames.models.game.AuthorizedUsers; -import com.codenames.models.game.CodeNamesGame; +import com.codenames.services.AuthorizedUsersService; +import com.codenames.domain.game.CodeNamesGame; import com.codenames.services.GameService; import com.jjerome.filters.SocketConnectionFilter; import lombok.RequiredArgsConstructor; -import org.springframework.http.HttpHeaders; import org.springframework.stereotype.Component; import org.springframework.web.socket.WebSocketSession; @@ -17,7 +16,7 @@ public class BannedUserFilter implements SocketConnectionFilter { private final GameService gameService; - private final AuthorizedUsers authorizedUsers; + private final AuthorizedUsersService authorizedUsers; @Override diff --git a/codenames-web-services/src/main/java/com/codenames/filters/connect/UserIsAuthorizedFilter.java b/codenames-web-services/src/main/java/com/codenames/filters/connect/UserIsAuthorizedFilter.java index 5b56026..a2f351a 100644 --- a/codenames-web-services/src/main/java/com/codenames/filters/connect/UserIsAuthorizedFilter.java +++ b/codenames-web-services/src/main/java/com/codenames/filters/connect/UserIsAuthorizedFilter.java @@ -1,14 +1,12 @@ package com.codenames.filters.connect; -import com.codenames.dao.RedisPlayerDao; import com.codenames.entity.UserEntity; import com.codenames.exception.UserNotFoundException; import com.codenames.mapper.UserMapper; -import com.codenames.models.game.AuthorizedUsers; +import com.codenames.services.AuthorizedUsersService; import com.codenames.provider.UserAuthProvider; import com.jjerome.filters.SocketConnectionFilter; import lombok.RequiredArgsConstructor; -import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Component; import org.springframework.util.MultiValueMap; @@ -21,13 +19,12 @@ public class UserIsAuthorizedFilter implements SocketConnectionFilter { private final UserAuthProvider userAuthProvider; - private final AuthorizedUsers authorizedUsers; + private final AuthorizedUsersService authorizedUsers; private final UserMapper userMapper; @Override public boolean doFilter(WebSocketSession session) { - MultiValueMap params = UriComponentsBuilder.fromUri(session.getUri()).build().getQueryParams(); UserEntity userEntity = userAuthProvider.verifyToken(params.getFirst("auth_token")) diff --git a/codenames-web-services/src/main/java/com/codenames/filters/message/UserAuthorizedFilter.java b/codenames-web-services/src/main/java/com/codenames/filters/message/UserAuthorizedFilter.java new file mode 100644 index 0000000..02ce5af --- /dev/null +++ b/codenames-web-services/src/main/java/com/codenames/filters/message/UserAuthorizedFilter.java @@ -0,0 +1,20 @@ +package com.codenames.filters.message; + +import com.codenames.services.AuthorizedUsersService; +import com.jjerome.filters.SocketMessageFilter; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; +import org.springframework.web.socket.TextMessage; +import org.springframework.web.socket.WebSocketSession; + + +@Component +@RequiredArgsConstructor +public class UserAuthorizedFilter implements SocketMessageFilter { + + private final AuthorizedUsersService authorizedUsers; + + public boolean doFilter(WebSocketSession session, TextMessage message) { + return authorizedUsers.getUserRoomSession(session.getId()) != null; + } +} diff --git a/codenames-web-services/src/main/java/com/codenames/filters/method/AllowedToChangeRoleFilter.java b/codenames-web-services/src/main/java/com/codenames/filters/method/AllowedToChangeRoleFilter.java index 8b8a78e..c87f105 100644 --- a/codenames-web-services/src/main/java/com/codenames/filters/method/AllowedToChangeRoleFilter.java +++ b/codenames-web-services/src/main/java/com/codenames/filters/method/AllowedToChangeRoleFilter.java @@ -1,9 +1,9 @@ package com.codenames.filters.method; import com.codenames.enums.PlayerRole; -import com.codenames.models.game.AuthorizedUsers; -import com.codenames.models.game.CodeNamesGame; -import com.codenames.models.room.Room; +import com.codenames.services.AuthorizedUsersService; +import com.codenames.domain.game.CodeNamesGame; +import com.codenames.domain.room.Room; import com.codenames.services.RoomService; import com.jjerome.dto.Request; import com.jjerome.filters.SocketMethodFilter; @@ -20,7 +20,7 @@ public class AllowedToChangeRoleFilter implements SocketMethodFilter { private final RoomService roomService; - private final AuthorizedUsers authorizedUsers; + private final AuthorizedUsersService authorizedUsers; @Override diff --git a/codenames-web-services/src/main/java/com/codenames/filters/method/AvailableRoomFilter.java b/codenames-web-services/src/main/java/com/codenames/filters/method/AvailableRoomFilter.java index af59308..12957cb 100644 --- a/codenames-web-services/src/main/java/com/codenames/filters/method/AvailableRoomFilter.java +++ b/codenames-web-services/src/main/java/com/codenames/filters/method/AvailableRoomFilter.java @@ -1,7 +1,7 @@ package com.codenames.filters.method; -import com.codenames.models.game.CodeNamesGame; +import com.codenames.domain.game.CodeNamesGame; import com.codenames.services.GameService; import com.jjerome.annotations.FilteringOrder; import com.jjerome.dto.Request; diff --git a/codenames-web-services/src/main/java/com/codenames/filters/method/GameRunningFilter.java b/codenames-web-services/src/main/java/com/codenames/filters/method/GameRunningFilter.java index be55501..e16ea55 100644 --- a/codenames-web-services/src/main/java/com/codenames/filters/method/GameRunningFilter.java +++ b/codenames-web-services/src/main/java/com/codenames/filters/method/GameRunningFilter.java @@ -1,9 +1,9 @@ package com.codenames.filters.method; import com.codenames.enums.GameStatus; -import com.codenames.models.game.AuthorizedUsers; -import com.codenames.models.game.CodeNamesGame; -import com.codenames.models.room.Room; +import com.codenames.services.AuthorizedUsersService; +import com.codenames.domain.game.CodeNamesGame; +import com.codenames.domain.room.Room; import com.codenames.services.RoomService; import com.jjerome.annotations.FilteringOrder; import com.jjerome.dto.Request; @@ -22,7 +22,7 @@ public class GameRunningFilter implements SocketMethodFilter { private final RoomService roomService; - private final AuthorizedUsers authorizedUsers; + private final AuthorizedUsersService authorizedUsers; @Override diff --git a/codenames-web-services/src/main/java/com/codenames/filters/method/GameStoppedFilter.java b/codenames-web-services/src/main/java/com/codenames/filters/method/GameStoppedFilter.java index 82bcb43..9e14c8e 100644 --- a/codenames-web-services/src/main/java/com/codenames/filters/method/GameStoppedFilter.java +++ b/codenames-web-services/src/main/java/com/codenames/filters/method/GameStoppedFilter.java @@ -1,9 +1,9 @@ package com.codenames.filters.method; import com.codenames.enums.GameStatus; -import com.codenames.models.game.AuthorizedUsers; -import com.codenames.models.game.CodeNamesGame; -import com.codenames.models.room.Room; +import com.codenames.services.AuthorizedUsersService; +import com.codenames.domain.game.CodeNamesGame; +import com.codenames.domain.room.Room; import com.codenames.services.RoomService; import com.jjerome.annotations.FilteringOrder; import com.jjerome.dto.Request; @@ -22,7 +22,7 @@ public class GameStoppedFilter implements SocketMethodFilter { private final RoomService roomService; - private final AuthorizedUsers authorizedUsers; + private final AuthorizedUsersService authorizedUsers; @Override diff --git a/codenames-web-services/src/main/java/com/codenames/filters/method/SelectWordAvailableFilter.java b/codenames-web-services/src/main/java/com/codenames/filters/method/SelectWordAvailableFilter.java index 51be839..728a6c3 100644 --- a/codenames-web-services/src/main/java/com/codenames/filters/method/SelectWordAvailableFilter.java +++ b/codenames-web-services/src/main/java/com/codenames/filters/method/SelectWordAvailableFilter.java @@ -1,10 +1,10 @@ package com.codenames.filters.method; import com.codenames.enums.GameTurn; -import com.codenames.models.game.AuthorizedUsers; -import com.codenames.models.game.CodeNamesGame; -import com.codenames.models.game.Player; -import com.codenames.models.room.Room; +import com.codenames.services.AuthorizedUsersService; +import com.codenames.domain.game.CodeNamesGame; +import com.codenames.domain.game.Player; +import com.codenames.domain.room.Room; import com.codenames.services.TeamService; import com.jjerome.annotations.FilteringOrder; import com.jjerome.dto.Request; @@ -24,7 +24,7 @@ public class SelectWordAvailableFilter implements SocketMethodFilter { private final CodeNamesGame codeNamesGame; - private final AuthorizedUsers authorizedUsers; + private final AuthorizedUsersService authorizedUsers; private final TeamService teamService; diff --git a/codenames-web-services/src/main/java/com/codenames/filters/method/SendMessageFilter.java b/codenames-web-services/src/main/java/com/codenames/filters/method/SendMessageFilter.java index ec45ebc..7b6f686 100644 --- a/codenames-web-services/src/main/java/com/codenames/filters/method/SendMessageFilter.java +++ b/codenames-web-services/src/main/java/com/codenames/filters/method/SendMessageFilter.java @@ -1,10 +1,10 @@ package com.codenames.filters.method; import com.codenames.enums.PlayerRole; -import com.codenames.models.game.AuthorizedUsers; -import com.codenames.models.game.CodeNamesGame; -import com.codenames.models.game.Player; -import com.codenames.models.room.Room; +import com.codenames.services.AuthorizedUsersService; +import com.codenames.domain.game.CodeNamesGame; +import com.codenames.domain.game.Player; +import com.codenames.domain.room.Room; import com.codenames.services.TeamService; import com.jjerome.annotations.FilteringOrder; import com.jjerome.dto.Request; @@ -21,7 +21,7 @@ public class SendMessageFilter implements SocketMethodFilter { private final CodeNamesGame codeNamesGame; - private final AuthorizedUsers authorizedUsers; + private final AuthorizedUsersService authorizedUsers; private final TeamService teamService; diff --git a/codenames-web-services/src/main/java/com/codenames/filters/method/SkipGameTurnFilter.java b/codenames-web-services/src/main/java/com/codenames/filters/method/SkipGameTurnFilter.java index d0cd055..644f840 100644 --- a/codenames-web-services/src/main/java/com/codenames/filters/method/SkipGameTurnFilter.java +++ b/codenames-web-services/src/main/java/com/codenames/filters/method/SkipGameTurnFilter.java @@ -1,10 +1,10 @@ package com.codenames.filters.method; import com.codenames.enums.GameTurn; -import com.codenames.models.game.AuthorizedUsers; -import com.codenames.models.game.CodeNamesGame; -import com.codenames.models.game.Player; -import com.codenames.models.room.Room; +import com.codenames.services.AuthorizedUsersService; +import com.codenames.domain.game.CodeNamesGame; +import com.codenames.domain.game.Player; +import com.codenames.domain.room.Room; import com.codenames.services.TeamService; import com.jjerome.annotations.FilteringOrder; import com.jjerome.dto.Request; @@ -23,7 +23,7 @@ public class SkipGameTurnFilter implements SocketMethodFilter { private final CodeNamesGame codeNamesGame; - private final AuthorizedUsers authorizedUsers; + private final AuthorizedUsersService authorizedUsers; private final TeamService teamService; diff --git a/codenames-web-services/src/main/java/com/codenames/filters/method/UserAuthorizedFilter.java b/codenames-web-services/src/main/java/com/codenames/filters/method/UserAuthorizedFilter.java deleted file mode 100644 index 45a1097..0000000 --- a/codenames-web-services/src/main/java/com/codenames/filters/method/UserAuthorizedFilter.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.codenames.filters.method; - -import com.codenames.models.game.AuthorizedUsers; -import com.jjerome.annotations.FilteringOrder; -import com.jjerome.dto.Request; -import com.jjerome.filters.SocketMethodFilter; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Component; -import org.springframework.web.socket.TextMessage; -import org.springframework.web.socket.WebSocketSession; - -@Component -@FilteringOrder(order = 1) -@RequiredArgsConstructor -public class UserAuthorizedFilter implements SocketMethodFilter { - - private final AuthorizedUsers authorizedUsers; - - @Override - public boolean doFilter(WebSocketSession session, TextMessage message, Request request) { - return authorizedUsers.getUserRoomSession(session.getId()) != null; - } -} diff --git a/codenames-web-services/src/main/java/com/codenames/filters/method/UserIsRoomAdminFilter.java b/codenames-web-services/src/main/java/com/codenames/filters/method/UserIsRoomAdminFilter.java index 5b86a5a..ce4c924 100644 --- a/codenames-web-services/src/main/java/com/codenames/filters/method/UserIsRoomAdminFilter.java +++ b/codenames-web-services/src/main/java/com/codenames/filters/method/UserIsRoomAdminFilter.java @@ -1,8 +1,8 @@ package com.codenames.filters.method; -import com.codenames.models.game.CodeNamesGame; -import com.codenames.models.game.Player; -import com.codenames.models.room.Room; +import com.codenames.domain.game.CodeNamesGame; +import com.codenames.domain.game.Player; +import com.codenames.domain.room.Room; import com.codenames.services.PlayerService; import com.jjerome.annotations.FilteringOrder; import com.jjerome.dto.Request; From 8189036ee0094564e0fcb16998826775b47594a0 Mon Sep 17 00:00:00 2001 From: JJerome-NM Date: Fri, 19 May 2023 10:41:58 +0300 Subject: [PATCH 3/8] CodeNames #7: Fixed mistakes after pull request --- .../properties/CodeNamesProperties.java | 9 +++++ .../codenames/properties/GameProperties.java | 6 +++- .../codenames/properties/RedisProperties.java | 4 ++- .../properties/RestControllerProperties.java | 4 ++- .../properties/SecurityProperties.java | 5 ++- .../properties/WSResponsePathProperties.java | 4 ++- .../codenames/properties/WordsProperties.java | 8 +++-- .../src/main/resources/application.yml | 36 ++++++++++--------- .../main/java/com/codenames/dto/ErrorDto.java | 9 +++-- .../exception/UserAuthEntryPoint.java | 7 ---- .../codenames/provider/UserAuthProvider.java | 28 +++++++-------- .../RedisUserRoomSessionRepository.java} | 8 ++--- .../services/AuthorizedUsersService.java | 4 +-- .../com/codenames/services/WordsService.java | 5 +-- .../src/pages/GameRoom/GameRoom.tsx | 5 +-- 15 files changed, 81 insertions(+), 61 deletions(-) rename codenames-domain/src/main/java/com/codenames/{dao/RedisUserRoomSessionDao.java => repository/RedisUserRoomSessionRepository.java} (80%) diff --git a/codenames-common/src/main/java/com/codenames/properties/CodeNamesProperties.java b/codenames-common/src/main/java/com/codenames/properties/CodeNamesProperties.java index 2995d6b..b349ce5 100644 --- a/codenames-common/src/main/java/com/codenames/properties/CodeNamesProperties.java +++ b/codenames-common/src/main/java/com/codenames/properties/CodeNamesProperties.java @@ -3,6 +3,7 @@ import lombok.Getter; import lombok.Setter; import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.NestedConfigurationProperty; import org.springframework.stereotype.Component; @Component @@ -10,13 +11,21 @@ @Getter @Setter public class CodeNamesProperties { + @NestedConfigurationProperty private WordsProperties words; + @NestedConfigurationProperty private GameProperties gameRooms; + @NestedConfigurationProperty private WSResponsePathProperties wsResponsePaths; + @NestedConfigurationProperty private RestControllerProperties restController; + @NestedConfigurationProperty private RedisProperties redis; + + @NestedConfigurationProperty + private SecurityProperties security; } diff --git a/codenames-common/src/main/java/com/codenames/properties/GameProperties.java b/codenames-common/src/main/java/com/codenames/properties/GameProperties.java index fbbb0ba..a575ce8 100644 --- a/codenames-common/src/main/java/com/codenames/properties/GameProperties.java +++ b/codenames-common/src/main/java/com/codenames/properties/GameProperties.java @@ -2,14 +2,18 @@ import lombok.Getter; import lombok.Setter; +import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; @Component -@ConfigurationProperties(prefix = "codenames-game-rooms") +@ConfigurationProperties(prefix = "codenames.game-rooms") @Getter @Setter public class GameProperties { + + @Value("${codenames.game-rooms.min-room-id:0}") private int minRoomId; + @Value("${codenames.game-rooms.max-room-id:1000000}") private int maxRoomId; } diff --git a/codenames-common/src/main/java/com/codenames/properties/RedisProperties.java b/codenames-common/src/main/java/com/codenames/properties/RedisProperties.java index 51211be..455c1ec 100644 --- a/codenames-common/src/main/java/com/codenames/properties/RedisProperties.java +++ b/codenames-common/src/main/java/com/codenames/properties/RedisProperties.java @@ -3,13 +3,15 @@ import lombok.Getter; import lombok.Setter; +import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; @Component -@ConfigurationProperties(prefix = "codenames-redis") +@ConfigurationProperties(prefix = "codenames.redis") @Getter @Setter public class RedisProperties { + @Value("${codenames.redis.user-room-sessions-hash-key:HashKeyNotFound}") private String userRoomSessionsHashKey; } diff --git a/codenames-common/src/main/java/com/codenames/properties/RestControllerProperties.java b/codenames-common/src/main/java/com/codenames/properties/RestControllerProperties.java index 4dd3b47..1bcc050 100644 --- a/codenames-common/src/main/java/com/codenames/properties/RestControllerProperties.java +++ b/codenames-common/src/main/java/com/codenames/properties/RestControllerProperties.java @@ -2,13 +2,15 @@ import lombok.Getter; import lombok.Setter; +import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; @Component -@ConfigurationProperties(prefix = "codenames-server") +@ConfigurationProperties(prefix = "codenames.rest-controller") @Getter @Setter public class RestControllerProperties { + @Value("${codenames.rest-controller.cross-origin:}") String[] crossOrigin; } diff --git a/codenames-common/src/main/java/com/codenames/properties/SecurityProperties.java b/codenames-common/src/main/java/com/codenames/properties/SecurityProperties.java index 16ecdc0..e6ef512 100644 --- a/codenames-common/src/main/java/com/codenames/properties/SecurityProperties.java +++ b/codenames-common/src/main/java/com/codenames/properties/SecurityProperties.java @@ -3,12 +3,15 @@ import lombok.Getter; import lombok.Setter; +import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; @Component +@ConfigurationProperties(prefix = "codenames.security") @Getter @Setter -@ConfigurationProperties(prefix = "security") public class SecurityProperties { + + @Value("${codenames.security.jwt:eyJhbGciOiJIUzI1NiIsInR5}") String jwt; } diff --git a/codenames-common/src/main/java/com/codenames/properties/WSResponsePathProperties.java b/codenames-common/src/main/java/com/codenames/properties/WSResponsePathProperties.java index 9fe4e77..ddf043a 100644 --- a/codenames-common/src/main/java/com/codenames/properties/WSResponsePathProperties.java +++ b/codenames-common/src/main/java/com/codenames/properties/WSResponsePathProperties.java @@ -3,14 +3,16 @@ import lombok.Getter; import lombok.Setter; +import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; @Component -@ConfigurationProperties(prefix = "codenames-response-paths") +@ConfigurationProperties(prefix = "codenames.ws-response-paths") @Getter @Setter public class WSResponsePathProperties { + @Value("${codenames.ws-response-paths.new-room-info-path:/room/new/info}") private String newRoomInfoPath; } diff --git a/codenames-common/src/main/java/com/codenames/properties/WordsProperties.java b/codenames-common/src/main/java/com/codenames/properties/WordsProperties.java index d577330..92ae2b8 100644 --- a/codenames-common/src/main/java/com/codenames/properties/WordsProperties.java +++ b/codenames-common/src/main/java/com/codenames/properties/WordsProperties.java @@ -2,16 +2,18 @@ import lombok.Getter; import lombok.Setter; +import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; @Component -@ConfigurationProperties(prefix = "codenames-words") +@ConfigurationProperties(prefix = "codenames.words") @Getter @Setter public class WordsProperties { + @Value("${codenames.words.resources-path:}") private String resourcesPath; - private String fileName; - + @Value("${codenames.words.file-prefix:}") + private String filePrefix; } diff --git a/codenames-common/src/main/resources/application.yml b/codenames-common/src/main/resources/application.yml index 18abfc3..e9134b8 100644 --- a/codenames-common/src/main/resources/application.yml +++ b/codenames-common/src/main/resources/application.yml @@ -1,9 +1,25 @@ +spring: + datasource: + url: jdbc:mysql://localhost:3306/codenames + username: root + password: 1234 + driver-class-name: com.mysql.cj.jdbc.Driver + + jpa: + hibernate: + ddl-auto: update + generate-ddl: true + show-sql: true + + + + codenames: words: resources-path: codenames-domain/src/main/resources/ - file-name: game-words- + file-prefix: game-words- game-rooms: min-room-id: 100000 @@ -19,19 +35,5 @@ codenames: redis: user-room-sessions-hash-key: user_room_sessions - -security: - jwt: I don't understand this stupid thing. - -spring: - datasource: - url: jdbc:mysql://localhost:3306/codenames - username: root - password: 1234 - driver-class-name: com.mysql.cj.jdbc.Driver - - jpa: - hibernate: - ddl-auto: update - generate-ddl: true - show-sql: true + security: + jwt: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 diff --git a/codenames-domain/src/main/java/com/codenames/dto/ErrorDto.java b/codenames-domain/src/main/java/com/codenames/dto/ErrorDto.java index 1b84bef..456dcda 100644 --- a/codenames-domain/src/main/java/com/codenames/dto/ErrorDto.java +++ b/codenames-domain/src/main/java/com/codenames/dto/ErrorDto.java @@ -1,10 +1,15 @@ package com.codenames.dto; -import lombok.*; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; @AllArgsConstructor @NoArgsConstructor -@Getter @Setter +@Getter +@Setter public class ErrorDto { private String message; diff --git a/codenames-domain/src/main/java/com/codenames/exception/UserAuthEntryPoint.java b/codenames-domain/src/main/java/com/codenames/exception/UserAuthEntryPoint.java index 4bc2215..77537d1 100644 --- a/codenames-domain/src/main/java/com/codenames/exception/UserAuthEntryPoint.java +++ b/codenames-domain/src/main/java/com/codenames/exception/UserAuthEntryPoint.java @@ -22,15 +22,8 @@ public class UserAuthEntryPoint implements AuthenticationEntryPoint { public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException { - response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); response.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE); - response.setHeader("Access-Control-Allow-Origin", "*"); -// response.setHeader("Access-Control-Allow-Methods", "GET, POST, DELETE, PUT, OPTIONS"); - response.setHeader("Access-Control-Allow-Headers", "*"); -// response.setHeader("Access-Control-Allow-Credentials", String.valueOf(true)); -// response.setHeader("Access-Control-Max-Age", String.valueOf(3600)); - OBJECT_MAPPER.writeValue(response.getOutputStream(), new ErrorDto("Unauthorized path")); } } diff --git a/codenames-domain/src/main/java/com/codenames/provider/UserAuthProvider.java b/codenames-domain/src/main/java/com/codenames/provider/UserAuthProvider.java index b231304..f5fe72b 100644 --- a/codenames-domain/src/main/java/com/codenames/provider/UserAuthProvider.java +++ b/codenames-domain/src/main/java/com/codenames/provider/UserAuthProvider.java @@ -5,7 +5,7 @@ import com.auth0.jwt.algorithms.Algorithm; import com.auth0.jwt.interfaces.DecodedJWT; import com.codenames.entity.UserEntity; -import com.codenames.properties.SecurityProperties; +import com.codenames.properties.CodeNamesProperties; import com.codenames.services.UserService; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; @@ -19,39 +19,37 @@ @Component public class UserAuthProvider { - private static final long TOKEN_LIFETIME = 1000 * 60 * 60 * 24 * 14; - - private SecurityProperties securityProperties; + private static final long TOKEN_LIFETIME = 1000 * 60 * 60 * 24L * 14; private String secretKey; + private final Algorithm algorithm; + private final UserService userService; UserAuthProvider(UserService userService, - SecurityProperties securityProperties) { - this.secretKey = securityProperties.getJwt(); - - if (this.secretKey == null){ - this.secretKey = "secret-key"; - } + CodeNamesProperties codeNamesProperties) { + this.secretKey = codeNamesProperties.getSecurity().getJwt(); this.secretKey = Base64.getEncoder().encodeToString(this.secretKey.getBytes()); + this.algorithm = Algorithm.HMAC256(this.secretKey); + this.userService = userService; } public String createToken(String login) { Date now = new Date(); - Date validity = new Date(now.getTime() + TOKEN_LIFETIME); + Date expiration = new Date(now.getTime() + TOKEN_LIFETIME); return JWT.create() .withIssuer(login) .withIssuedAt(now) - .withExpiresAt(validity) - .sign(Algorithm.HMAC256(secretKey)); + .withExpiresAt(expiration) + .sign(this.algorithm); } public Authentication validateToken(String token) { - JWTVerifier verifier = JWT.require(Algorithm.HMAC256(secretKey)).build(); + JWTVerifier verifier = JWT.require(this.algorithm).build(); DecodedJWT decodedJWT = verifier.verify(token); @@ -61,7 +59,7 @@ public Authentication validateToken(String token) { } public Optional verifyToken(String token){ - JWTVerifier verifier = JWT.require(Algorithm.HMAC256(secretKey)).build(); + JWTVerifier verifier = JWT.require(this.algorithm).build(); DecodedJWT decodedJWT = verifier.verify(token); diff --git a/codenames-domain/src/main/java/com/codenames/dao/RedisUserRoomSessionDao.java b/codenames-domain/src/main/java/com/codenames/repository/RedisUserRoomSessionRepository.java similarity index 80% rename from codenames-domain/src/main/java/com/codenames/dao/RedisUserRoomSessionDao.java rename to codenames-domain/src/main/java/com/codenames/repository/RedisUserRoomSessionRepository.java index a8ffff4..b71ce88 100644 --- a/codenames-domain/src/main/java/com/codenames/dao/RedisUserRoomSessionDao.java +++ b/codenames-domain/src/main/java/com/codenames/repository/RedisUserRoomSessionRepository.java @@ -1,4 +1,4 @@ -package com.codenames.dao; +package com.codenames.repository; import com.codenames.properties.CodeNamesProperties; import com.codenames.redis_entity.HashedUserRoomSession; @@ -8,14 +8,14 @@ import java.util.Optional; @Repository -public class RedisUserRoomSessionDao { +public class RedisUserRoomSessionRepository { private final String hashKey; private final RedisTemplate template; - RedisUserRoomSessionDao(CodeNamesProperties codeNamesProperties, - RedisTemplate template){ + RedisUserRoomSessionRepository(CodeNamesProperties codeNamesProperties, + RedisTemplate template){ this.template = template; this.hashKey = codeNamesProperties.getRedis().getUserRoomSessionsHashKey(); } diff --git a/codenames-domain/src/main/java/com/codenames/services/AuthorizedUsersService.java b/codenames-domain/src/main/java/com/codenames/services/AuthorizedUsersService.java index fbe0ffb..bed6d3c 100644 --- a/codenames-domain/src/main/java/com/codenames/services/AuthorizedUsersService.java +++ b/codenames-domain/src/main/java/com/codenames/services/AuthorizedUsersService.java @@ -1,6 +1,6 @@ package com.codenames.services; -import com.codenames.dao.RedisUserRoomSessionDao; +import com.codenames.repository.RedisUserRoomSessionRepository; import com.codenames.exception.UserRoomSessionNotFoundExeption; import com.codenames.mapper.UserRoomSessionMapper; import com.codenames.domain.game.Player; @@ -17,7 +17,7 @@ @RequiredArgsConstructor public class AuthorizedUsersService { - private final RedisUserRoomSessionDao redisPlayerDao; + private final RedisUserRoomSessionRepository redisPlayerDao; private final UserRoomSessionMapper userRoomSessionMapper; diff --git a/codenames-domain/src/main/java/com/codenames/services/WordsService.java b/codenames-domain/src/main/java/com/codenames/services/WordsService.java index dc5eac5..90e5cb0 100644 --- a/codenames-domain/src/main/java/com/codenames/services/WordsService.java +++ b/codenames-domain/src/main/java/com/codenames/services/WordsService.java @@ -33,8 +33,9 @@ public class WordsService { private List readWordsFile(Language language){ List words = new ArrayList<>(); - try (BufferedReader reader = new BufferedReader(new FileReader(codeNamesProperties.getWords().getResourcesPath() - + codeNamesProperties.getWords().getFileName() + language.getLanguageCode() + ".txt"))) { + try (BufferedReader reader = new BufferedReader( + new FileReader(codeNamesProperties.getWords().getResourcesPath() + + codeNamesProperties.getWords().getFilePrefix() + language.getLanguageCode() + ".txt"))) { String line; while((line = reader.readLine()) != null){ diff --git a/codenames-front-end/src/pages/GameRoom/GameRoom.tsx b/codenames-front-end/src/pages/GameRoom/GameRoom.tsx index 104a2bf..d633bd3 100644 --- a/codenames-front-end/src/pages/GameRoom/GameRoom.tsx +++ b/codenames-front-end/src/pages/GameRoom/GameRoom.tsx @@ -39,7 +39,7 @@ const GameRoom = () => { return ( - + - org.springframework.boot spring-boot-starter-data-jpa @@ -70,12 +69,6 @@ 8.0.33 - - - - - - org.springframework.boot spring-boot-starter-data-redis @@ -88,18 +81,24 @@ 4.4.0 - org.springframework.boot spring-boot-configuration-processor true + + org.springframework.boot + spring-boot-starter-validation + 3.1.0 + + org.projectlombok lombok true + org.springframework.boot spring-boot-starter-test From af6a90e20b79bed1890bcd70c8ff15b32d8e63aa Mon Sep 17 00:00:00 2001 From: JJerome-NM Date: Mon, 22 May 2023 14:08:46 +0300 Subject: [PATCH 6/8] CodeNames #7: Litle fix whoami method --- .../com/codenames/enums/UserAuthRole.java | 14 +++++++++++ codenames-front-end/src/App.tsx | 12 ++++++--- codenames-front-end/src/config/RestConfig.ts | 2 +- .../src/models/CodeNames/UserAuthRole.ts | 7 ++++++ .../controllers/UsersController.java | 25 ++++++++++++++++--- 5 files changed, 52 insertions(+), 8 deletions(-) create mode 100644 codenames-common/src/main/java/com/codenames/enums/UserAuthRole.java create mode 100644 codenames-front-end/src/models/CodeNames/UserAuthRole.ts diff --git a/codenames-common/src/main/java/com/codenames/enums/UserAuthRole.java b/codenames-common/src/main/java/com/codenames/enums/UserAuthRole.java new file mode 100644 index 0000000..d411ec7 --- /dev/null +++ b/codenames-common/src/main/java/com/codenames/enums/UserAuthRole.java @@ -0,0 +1,14 @@ +package com.codenames.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@AllArgsConstructor +@Getter +public enum UserAuthRole { + GUEST("ROLE_GUEST"), + USER("ROLE_USER"), + ADMIN("ROLE_ADMIN"); + + private final String role; +} diff --git a/codenames-front-end/src/App.tsx b/codenames-front-end/src/App.tsx index 35c2d31..bc92706 100644 --- a/codenames-front-end/src/App.tsx +++ b/codenames-front-end/src/App.tsx @@ -2,23 +2,27 @@ import React, {useEffect, useRef} from 'react'; import {privateRouters, publicRouters} from "./router"; import {BrowserRouter, Navigate, Route, Routes} from "react-router-dom"; import useFetching from "./hooks/useFetching"; -import {authRequest, getAuthToken} from "./helper"; +import {authRequest, getAuthToken, removeAuthToken} from "./helper"; import {RestConfig} from "./config"; import {AxiosResponse} from "axios"; import {Flip, ToastContainer} from "react-toastify"; +import {UserAuthRole} from "./models/CodeNames/UserAuthRole"; +import {notify} from "./models"; function App() { const isAuthorized = useRef(!!getAuthToken()) const [checkUserIsAuth, isLoading, error] = useFetching(async () => { try { - const response: AxiosResponse = await authRequest("GET", RestConfig.paths.request.userIsAuth, {}) + const response: AxiosResponse = await authRequest("GET", RestConfig.paths.request.whoami, {}) - if (response.data) { + if (response.data === UserAuthRole.USER || response.data === UserAuthRole.ADMIN) { isAuthorized.current = true + } else if (response.data === UserAuthRole.GUEST) { + notify.info("You are logged in as a GUEST, which may limit your possibilities") } } catch (e) { - console.log(e) + removeAuthToken(); } }) diff --git a/codenames-front-end/src/config/RestConfig.ts b/codenames-front-end/src/config/RestConfig.ts index 072f846..0440a41 100644 --- a/codenames-front-end/src/config/RestConfig.ts +++ b/codenames-front-end/src/config/RestConfig.ts @@ -14,7 +14,7 @@ export const RestConfig = { request: { createRoom: `http://${restIP}:${restPort}/room/create`, connectToRoom: `http://${restIP}:${restPort}/room/connect`, - userIsAuth: `http://${restIP}:${restPort}/check_user_is_auth`, + whoami: `http://${restIP}:${restPort}/whoami`, signIn: `http://${restIP}:${restPort}/login`, signUp: `http://${restIP}:${restPort}/register` } diff --git a/codenames-front-end/src/models/CodeNames/UserAuthRole.ts b/codenames-front-end/src/models/CodeNames/UserAuthRole.ts new file mode 100644 index 0000000..4fdeba1 --- /dev/null +++ b/codenames-front-end/src/models/CodeNames/UserAuthRole.ts @@ -0,0 +1,7 @@ + + +export enum UserAuthRole { + GUEST = "GUEST", + USER = "USER", + ADMIN = "ADMIN" +} \ No newline at end of file diff --git a/codenames-web-services/src/main/java/com/codenames/controllers/UsersController.java b/codenames-web-services/src/main/java/com/codenames/controllers/UsersController.java index 4c4b048..cd4241a 100644 --- a/codenames-web-services/src/main/java/com/codenames/controllers/UsersController.java +++ b/codenames-web-services/src/main/java/com/codenames/controllers/UsersController.java @@ -3,17 +3,23 @@ import com.codenames.dto.CredentialDto; import com.codenames.dto.SignUpDto; import com.codenames.dto.UserAuthDto; +import com.codenames.enums.UserAuthRole; import com.codenames.mapper.UserMapper; import com.codenames.provider.UserAuthProvider; import com.codenames.services.UserService; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; +import java.util.Collection; + @RestController @RequiredArgsConstructor @@ -43,8 +49,21 @@ public ResponseEntity registerUser(@Valid @RequestBody SignUpDto si return ResponseEntity.ok(user); } - @GetMapping("/check_user_is_auth") - public boolean checkUserIsAuth(){ - return true; + @GetMapping("/whoami") + public ResponseEntity whoami(Authentication authentication){ + + // TODO: 22.05.2023 Perhaps this method will need to be corrected, but this is after adding a full-fledged user role assignment system + + if (authentication.getAuthorities() != null){ + Collection authorities = authentication.getAuthorities(); + + for (UserAuthRole role : UserAuthRole.values()){ + if (authorities.contains(new SimpleGrantedAuthority(role.getRole()))) { + return ResponseEntity.ok(role); + } + } + } + + return ResponseEntity.ok(UserAuthRole.GUEST); } } From 49c259fad26bbfaaf206a944f120fd0dfdfc06c4 Mon Sep 17 00:00:00 2001 From: JJerome-NM Date: Mon, 22 May 2023 23:41:48 +0300 Subject: [PATCH 7/8] Codenames #17: Start adding roles. Non-working stage --- .../codenames/enums/DefaultUserAuthRole.java | 14 ++++++++ .../src/main/resources/application.yml | 3 ++ .../db/migration/V1__InitDatabase.sql | 17 ++++++++++ .../codenames/config/WebSecurityConfig.java | 4 +++ .../com/codenames/dto/UserAuthRoleDto.java | 14 ++++++++ .../codenames/entity/UserAuthRoleEntity.java | 33 +++++++++++++++++++ .../java/com/codenames/entity/UserEntity.java | 7 ++++ .../codenames/mapper/UserAuthRoleMapper.java | 11 +++++++ .../repository/UserAuthRoleRepository.java | 15 +++++++++ .../com/codenames/services/UserService.java | 33 +++++++++++++++++++ codenames-front-end/src/App.tsx | 17 ++++++++++ .../src/models/CodeNames/UserAuthRole.ts | 6 ++++ .../controllers/UsersController.java | 29 +++++++--------- pom.xml | 10 ++++++ 14 files changed, 195 insertions(+), 18 deletions(-) create mode 100644 codenames-common/src/main/java/com/codenames/enums/DefaultUserAuthRole.java create mode 100644 codenames-common/src/main/resources/db/migration/V1__InitDatabase.sql create mode 100644 codenames-domain/src/main/java/com/codenames/dto/UserAuthRoleDto.java create mode 100644 codenames-domain/src/main/java/com/codenames/entity/UserAuthRoleEntity.java create mode 100644 codenames-domain/src/main/java/com/codenames/mapper/UserAuthRoleMapper.java create mode 100644 codenames-domain/src/main/java/com/codenames/repository/UserAuthRoleRepository.java diff --git a/codenames-common/src/main/java/com/codenames/enums/DefaultUserAuthRole.java b/codenames-common/src/main/java/com/codenames/enums/DefaultUserAuthRole.java new file mode 100644 index 0000000..b837537 --- /dev/null +++ b/codenames-common/src/main/java/com/codenames/enums/DefaultUserAuthRole.java @@ -0,0 +1,14 @@ +package com.codenames.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@AllArgsConstructor +@Getter +public enum DefaultUserAuthRole { + GUEST("ROLE_GUEST"), + USER("ROLE_USER"), + ADMIN("ROLE_ADMIN"); + + private final String role; +} diff --git a/codenames-common/src/main/resources/application.yml b/codenames-common/src/main/resources/application.yml index fe830ae..83a0042 100644 --- a/codenames-common/src/main/resources/application.yml +++ b/codenames-common/src/main/resources/application.yml @@ -1,6 +1,9 @@ spring: + flyway: + out-of-order: true + datasource: url: "${MYSQL_URL:jdbc:mysql://localhost:3306/codenames}" username: "${MYSQL_USERNAME:root}" diff --git a/codenames-common/src/main/resources/db/migration/V1__InitDatabase.sql b/codenames-common/src/main/resources/db/migration/V1__InitDatabase.sql new file mode 100644 index 0000000..e7f3000 --- /dev/null +++ b/codenames-common/src/main/resources/db/migration/V1__InitDatabase.sql @@ -0,0 +1,17 @@ + + +CREATE TABLE IF NOT EXISTS users( + id INT NOT NULL AUTO_INCREMENT, + login VARCHAR(100) NOT NULL UNIQUE, + nickname VARCHAR(50) NOT NULL, + password VARCHAR(255) NOT NULL, + PRIMARY KEY (id) +); + +CREATE TABLE IF NOT EXISTS users_roles( + id INT NOT NULL AUTO_INCREMENT, + user_id INT NOT NULL, + role VARCHAR(50) NOT NULL DEFAULT "GUEST", + PRIMARY KEY (id), + FOREIGN KEY (user_id) REFERENCES users (id) +); \ No newline at end of file diff --git a/codenames-domain/src/main/java/com/codenames/config/WebSecurityConfig.java b/codenames-domain/src/main/java/com/codenames/config/WebSecurityConfig.java index 1ce6a31..6642665 100644 --- a/codenames-domain/src/main/java/com/codenames/config/WebSecurityConfig.java +++ b/codenames-domain/src/main/java/com/codenames/config/WebSecurityConfig.java @@ -43,6 +43,10 @@ public SecurityFilterChain filterChain (HttpSecurity http) throws Exception { .and() .authorizeHttpRequests(auth -> auth .requestMatchers(HttpMethod.POST , "/login", "/register").permitAll() +<<<<<<< HEAD + .requestMatchers("/whoami").permitAll() +======= +>>>>>>> c272801cf53c51aafc9e629308295f7a0a6d9bd7 .requestMatchers("/socket/**").permitAll() .anyRequest().authenticated() ); diff --git a/codenames-domain/src/main/java/com/codenames/dto/UserAuthRoleDto.java b/codenames-domain/src/main/java/com/codenames/dto/UserAuthRoleDto.java new file mode 100644 index 0000000..7e32baf --- /dev/null +++ b/codenames-domain/src/main/java/com/codenames/dto/UserAuthRoleDto.java @@ -0,0 +1,14 @@ +package com.codenames.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@AllArgsConstructor +@NoArgsConstructor +@Getter @Setter +public class UserAuthRoleDto { + + private String role; +} diff --git a/codenames-domain/src/main/java/com/codenames/entity/UserAuthRoleEntity.java b/codenames-domain/src/main/java/com/codenames/entity/UserAuthRoleEntity.java new file mode 100644 index 0000000..4a03bee --- /dev/null +++ b/codenames-domain/src/main/java/com/codenames/entity/UserAuthRoleEntity.java @@ -0,0 +1,33 @@ +package com.codenames.entity; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.Table; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@AllArgsConstructor +@NoArgsConstructor +@Getter +@Entity +@Table(name = "users_roles") +public class UserAuthRoleEntity { + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + @Column(name = "id") + private Long id; + + @ManyToOne + @JoinColumn(name = "user_id") + private UserEntity user; + + @Column(name = "role") + private String role; +} diff --git a/codenames-domain/src/main/java/com/codenames/entity/UserEntity.java b/codenames-domain/src/main/java/com/codenames/entity/UserEntity.java index bffc976..03e45c1 100644 --- a/codenames-domain/src/main/java/com/codenames/entity/UserEntity.java +++ b/codenames-domain/src/main/java/com/codenames/entity/UserEntity.java @@ -1,16 +1,20 @@ package com.codenames.entity; +import jakarta.persistence.CascadeType; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; +import jakarta.persistence.OneToMany; import jakarta.persistence.Table; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; +import java.util.List; + @Entity @Table(name = "users") @Getter @Setter @@ -28,4 +32,7 @@ public class UserEntity { @Column(name = "password") private String password; + + @OneToMany(mappedBy = "user", cascade = CascadeType.ALL) + private List roles; } diff --git a/codenames-domain/src/main/java/com/codenames/mapper/UserAuthRoleMapper.java b/codenames-domain/src/main/java/com/codenames/mapper/UserAuthRoleMapper.java new file mode 100644 index 0000000..7a853e8 --- /dev/null +++ b/codenames-domain/src/main/java/com/codenames/mapper/UserAuthRoleMapper.java @@ -0,0 +1,11 @@ +package com.codenames.mapper; + +import com.codenames.dto.UserAuthRoleDto; +import com.codenames.entity.UserAuthRoleEntity; +import org.mapstruct.Mapper; + +@Mapper(componentModel = "spring") +public interface UserAuthRoleMapper { + + UserAuthRoleDto userAuthRoleEntityToUserAuthRoleDto(UserAuthRoleEntity authRoleEntity); +} diff --git a/codenames-domain/src/main/java/com/codenames/repository/UserAuthRoleRepository.java b/codenames-domain/src/main/java/com/codenames/repository/UserAuthRoleRepository.java new file mode 100644 index 0000000..d5b554a --- /dev/null +++ b/codenames-domain/src/main/java/com/codenames/repository/UserAuthRoleRepository.java @@ -0,0 +1,15 @@ +package com.codenames.repository; + +import com.codenames.entity.UserAuthRoleEntity; +import com.codenames.entity.UserEntity; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.List; +import java.util.Optional; + +@Repository +public interface UserAuthRoleRepository extends JpaRepository { + + Optional> findUserAuthRoleEntitiesByUser(UserEntity userEntity); +} diff --git a/codenames-domain/src/main/java/com/codenames/services/UserService.java b/codenames-domain/src/main/java/com/codenames/services/UserService.java index f243619..4320e1c 100644 --- a/codenames-domain/src/main/java/com/codenames/services/UserService.java +++ b/codenames-domain/src/main/java/com/codenames/services/UserService.java @@ -3,17 +3,24 @@ import com.codenames.dto.CredentialDto; import com.codenames.dto.SignUpDto; import com.codenames.entity.UserEntity; +import com.codenames.entity.UserAuthRoleEntity; import com.codenames.exception.UserAlreadyExistsException; import com.codenames.exception.UserNotFoundException; import com.codenames.mapper.UserMapper; +import com.codenames.repository.UserAuthRoleRepository; import com.codenames.repository.UserRepository; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Controller; import java.nio.CharBuffer; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; import java.util.Optional; @Controller @@ -26,6 +33,8 @@ public class UserService { private final UserMapper userMapper; + private final UserAuthRoleRepository userAuthRoleRepository; + public T findByNickname(Class rClass, String login){ return userRepository.findByLogin(rClass, login) .orElseThrow(() -> new UserNotFoundException("User not found", HttpStatus.NOT_FOUND)); @@ -69,4 +78,28 @@ public Optional getUserEntityFromAuthentication(Authentication authe return Optional.of((UserEntity) principal); } + + public Optional getUserAuthRole(Authentication authentication){ + + // TODO: 22.05.2023 Perhaps this method will need to be corrected, but this is after adding a full-fledged user role assignment system + + if (authentication.getAuthorities() != null){ + if (!(authentication.getPrincipal() instanceof UserEntity user)){ + return Optional.empty(); + } + + Collection authorities = authentication.getAuthorities(); + + List userAuthRoles = userAuthRoleRepository.findUserAuthRoleEntitiesByUser(user) + .orElse(new ArrayList<>()); + + for (UserAuthRoleEntity role : userAuthRoles){ + if (authorities.contains(new SimpleGrantedAuthority(role.getRole()))) { + return Optional.of(role); + } + } + } + + return Optional.empty(); + } } diff --git a/codenames-front-end/src/App.tsx b/codenames-front-end/src/App.tsx index bc92706..6c98353 100644 --- a/codenames-front-end/src/App.tsx +++ b/codenames-front-end/src/App.tsx @@ -8,10 +8,26 @@ import {AxiosResponse} from "axios"; import {Flip, ToastContainer} from "react-toastify"; import {UserAuthRole} from "./models/CodeNames/UserAuthRole"; import {notify} from "./models"; +<<<<<<< HEAD + +type WhoamiResponse = { + role: UserAuthRole; +} +======= +>>>>>>> c272801cf53c51aafc9e629308295f7a0a6d9bd7 function App() { const isAuthorized = useRef(!!getAuthToken()) +<<<<<<< HEAD + const [checkUserIsAuth] = useFetching(async () => { + try { + const response: AxiosResponse = await authRequest("GET", RestConfig.paths.request.whoami, {}) + + if (response.data.role === UserAuthRole.USER || response.data.role === UserAuthRole.ADMIN) { + isAuthorized.current = true + } else if (response.data.role === UserAuthRole.GUEST) { +======= const [checkUserIsAuth, isLoading, error] = useFetching(async () => { try { const response: AxiosResponse = await authRequest("GET", RestConfig.paths.request.whoami, {}) @@ -19,6 +35,7 @@ function App() { if (response.data === UserAuthRole.USER || response.data === UserAuthRole.ADMIN) { isAuthorized.current = true } else if (response.data === UserAuthRole.GUEST) { +>>>>>>> c272801cf53c51aafc9e629308295f7a0a6d9bd7 notify.info("You are logged in as a GUEST, which may limit your possibilities") } } catch (e) { diff --git a/codenames-front-end/src/models/CodeNames/UserAuthRole.ts b/codenames-front-end/src/models/CodeNames/UserAuthRole.ts index 4fdeba1..3901b2c 100644 --- a/codenames-front-end/src/models/CodeNames/UserAuthRole.ts +++ b/codenames-front-end/src/models/CodeNames/UserAuthRole.ts @@ -1,7 +1,13 @@ export enum UserAuthRole { +<<<<<<< HEAD + GUEST = "ROLE_GUEST", + USER = "ROLE_USER", + ADMIN = "ROLE_ADMIN" +======= GUEST = "GUEST", USER = "USER", ADMIN = "ADMIN" +>>>>>>> c272801cf53c51aafc9e629308295f7a0a6d9bd7 } \ No newline at end of file diff --git a/codenames-web-services/src/main/java/com/codenames/controllers/UsersController.java b/codenames-web-services/src/main/java/com/codenames/controllers/UsersController.java index cd4241a..49e26c4 100644 --- a/codenames-web-services/src/main/java/com/codenames/controllers/UsersController.java +++ b/codenames-web-services/src/main/java/com/codenames/controllers/UsersController.java @@ -3,7 +3,10 @@ import com.codenames.dto.CredentialDto; import com.codenames.dto.SignUpDto; import com.codenames.dto.UserAuthDto; -import com.codenames.enums.UserAuthRole; +import com.codenames.dto.UserAuthRoleDto; +import com.codenames.entity.UserAuthRoleEntity; +import com.codenames.enums.DefaultUserAuthRole; +import com.codenames.mapper.UserAuthRoleMapper; import com.codenames.mapper.UserMapper; import com.codenames.provider.UserAuthProvider; import com.codenames.services.UserService; @@ -11,16 +14,11 @@ import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.security.core.Authentication; -import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; -import java.util.Collection; - - @RestController @RequiredArgsConstructor public class UsersController { @@ -31,6 +29,8 @@ public class UsersController { private final UserMapper userMapper; + private final UserAuthRoleMapper userAuthRoleMapper; + @PostMapping("/login") public ResponseEntity loginUser(@Valid @RequestBody CredentialDto credentialDto){ UserAuthDto user = userMapper.userEntityToUserAuthDto(userService.login(credentialDto)); @@ -50,20 +50,13 @@ public ResponseEntity registerUser(@Valid @RequestBody SignUpDto si } @GetMapping("/whoami") - public ResponseEntity whoami(Authentication authentication){ + public ResponseEntity whoami(Authentication authentication){ - // TODO: 22.05.2023 Perhaps this method will need to be corrected, but this is after adding a full-fledged user role assignment system - - if (authentication.getAuthorities() != null){ - Collection authorities = authentication.getAuthorities(); + // TODO: 22.05.2023 Perhaps this method will need to be corrected, but this is after adding a full-fledged user role assignment system - for (UserAuthRole role : UserAuthRole.values()){ - if (authorities.contains(new SimpleGrantedAuthority(role.getRole()))) { - return ResponseEntity.ok(role); - } - } - } + UserAuthRoleEntity userAuthRole = userService.getUserAuthRole(authentication) + .orElse(new UserAuthRoleEntity(-1L, null, DefaultUserAuthRole.GUEST.getRole())); - return ResponseEntity.ok(UserAuthRole.GUEST); + return ResponseEntity.ok(userAuthRoleMapper.userAuthRoleEntityToUserAuthRoleDto(userAuthRole)); } } diff --git a/pom.xml b/pom.xml index 5042ad3..9a1a2c3 100644 --- a/pom.xml +++ b/pom.xml @@ -69,6 +69,16 @@ 8.0.33 + + org.flywaydb + flyway-core + + + + org.flywaydb + flyway-mysql + + org.springframework.boot spring-boot-starter-data-redis From a71478c4fc7cc55627e47fcf0f2b77c4bfa3865f Mon Sep 17 00:00:00 2001 From: JJerome-NM Date: Mon, 22 May 2023 23:59:39 +0300 Subject: [PATCH 8/8] CodeNames #17: Fixed merging trables --- .../com/codenames/config/WebSecurityConfig.java | 3 --- .../com/codenames/services/UserService.java | 1 - codenames-front-end/src/App.tsx | 17 +++-------------- .../src/models/CodeNames/UserAuthRole.ts | 6 ------ .../codenames/controllers/UsersController.java | 1 + 5 files changed, 4 insertions(+), 24 deletions(-) diff --git a/codenames-domain/src/main/java/com/codenames/config/WebSecurityConfig.java b/codenames-domain/src/main/java/com/codenames/config/WebSecurityConfig.java index 6642665..2acd441 100644 --- a/codenames-domain/src/main/java/com/codenames/config/WebSecurityConfig.java +++ b/codenames-domain/src/main/java/com/codenames/config/WebSecurityConfig.java @@ -43,10 +43,7 @@ public SecurityFilterChain filterChain (HttpSecurity http) throws Exception { .and() .authorizeHttpRequests(auth -> auth .requestMatchers(HttpMethod.POST , "/login", "/register").permitAll() -<<<<<<< HEAD .requestMatchers("/whoami").permitAll() -======= ->>>>>>> c272801cf53c51aafc9e629308295f7a0a6d9bd7 .requestMatchers("/socket/**").permitAll() .anyRequest().authenticated() ); diff --git a/codenames-domain/src/main/java/com/codenames/services/UserService.java b/codenames-domain/src/main/java/com/codenames/services/UserService.java index 4320e1c..9bb5703 100644 --- a/codenames-domain/src/main/java/com/codenames/services/UserService.java +++ b/codenames-domain/src/main/java/com/codenames/services/UserService.java @@ -78,7 +78,6 @@ public Optional getUserEntityFromAuthentication(Authentication authe return Optional.of((UserEntity) principal); } - public Optional getUserAuthRole(Authentication authentication){ // TODO: 22.05.2023 Perhaps this method will need to be corrected, but this is after adding a full-fledged user role assignment system diff --git a/codenames-front-end/src/App.tsx b/codenames-front-end/src/App.tsx index 6c98353..6167961 100644 --- a/codenames-front-end/src/App.tsx +++ b/codenames-front-end/src/App.tsx @@ -8,18 +8,16 @@ import {AxiosResponse} from "axios"; import {Flip, ToastContainer} from "react-toastify"; import {UserAuthRole} from "./models/CodeNames/UserAuthRole"; import {notify} from "./models"; -<<<<<<< HEAD + type WhoamiResponse = { role: UserAuthRole; } -======= ->>>>>>> c272801cf53c51aafc9e629308295f7a0a6d9bd7 + function App() { const isAuthorized = useRef(!!getAuthToken()) -<<<<<<< HEAD const [checkUserIsAuth] = useFetching(async () => { try { const response: AxiosResponse = await authRequest("GET", RestConfig.paths.request.whoami, {}) @@ -27,15 +25,6 @@ function App() { if (response.data.role === UserAuthRole.USER || response.data.role === UserAuthRole.ADMIN) { isAuthorized.current = true } else if (response.data.role === UserAuthRole.GUEST) { -======= - const [checkUserIsAuth, isLoading, error] = useFetching(async () => { - try { - const response: AxiosResponse = await authRequest("GET", RestConfig.paths.request.whoami, {}) - - if (response.data === UserAuthRole.USER || response.data === UserAuthRole.ADMIN) { - isAuthorized.current = true - } else if (response.data === UserAuthRole.GUEST) { ->>>>>>> c272801cf53c51aafc9e629308295f7a0a6d9bd7 notify.info("You are logged in as a GUEST, which may limit your possibilities") } } catch (e) { @@ -86,4 +75,4 @@ function App() { ); } -export default App; +export default App; \ No newline at end of file diff --git a/codenames-front-end/src/models/CodeNames/UserAuthRole.ts b/codenames-front-end/src/models/CodeNames/UserAuthRole.ts index 3901b2c..34085c9 100644 --- a/codenames-front-end/src/models/CodeNames/UserAuthRole.ts +++ b/codenames-front-end/src/models/CodeNames/UserAuthRole.ts @@ -1,13 +1,7 @@ export enum UserAuthRole { -<<<<<<< HEAD GUEST = "ROLE_GUEST", USER = "ROLE_USER", ADMIN = "ROLE_ADMIN" -======= - GUEST = "GUEST", - USER = "USER", - ADMIN = "ADMIN" ->>>>>>> c272801cf53c51aafc9e629308295f7a0a6d9bd7 } \ No newline at end of file diff --git a/codenames-web-services/src/main/java/com/codenames/controllers/UsersController.java b/codenames-web-services/src/main/java/com/codenames/controllers/UsersController.java index 49e26c4..f1f2567 100644 --- a/codenames-web-services/src/main/java/com/codenames/controllers/UsersController.java +++ b/codenames-web-services/src/main/java/com/codenames/controllers/UsersController.java @@ -19,6 +19,7 @@ import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; + @RestController @RequiredArgsConstructor public class UsersController {