diff --git a/.idea/runConfigurations/Flashcards_DEVMODE.xml b/.idea/runConfigurations/Flashcards_DEVMODE.xml new file mode 100644 index 0000000..cdf692a --- /dev/null +++ b/.idea/runConfigurations/Flashcards_DEVMODE.xml @@ -0,0 +1,16 @@ + + + + + + + + + + \ No newline at end of file diff --git a/.idea/runConfigurations/Vue_Server_DEVMODE.xml b/.idea/runConfigurations/Vue_Server_DEVMODE.xml new file mode 100644 index 0000000..8780a4e --- /dev/null +++ b/.idea/runConfigurations/Vue_Server_DEVMODE.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/frontend/src/feature/cards/components/CardItemScroller.vue b/frontend/src/feature/cards/components/CardItemScroller.vue index 5330a59..50d9758 100644 --- a/frontend/src/feature/cards/components/CardItemScroller.vue +++ b/frontend/src/feature/cards/components/CardItemScroller.vue @@ -61,41 +61,4 @@ const openCard = (id: string) => { console.log("Opening card with id: " + id); router.push(`/card/${props.categoryId}/${id}`); } -// ------------------- TEST DATA ------------------- -// ---uncomment if on npm run mode - and comment out the fetchCardsPage() call -// const load = async ({done} : {done: Function}) => { -// console.log(`Loading cards ... Now we have ${items.value.length} items.`); -// items.value.push({ -// id: "123", -// title: "A math question", -// type: CardType.SIMPLEQA, -// }); -// items.value.push({ -// id: "456", -// title: "A Java question", -// type: CardType.SINGLE_CHOICE, -// }); -// items.value.push({ -// id: "456", -// title: "Another Java question", -// type: CardType.SINGLE_CHOICE, -// }); -// items.value.push({ -// id: "456", -// title: "Another Math question", -// type: CardType.SINGLE_CHOICE, -// }); -// items.value.push({ -// id: "456", -// title: "Another Python question", -// type: CardType.SINGLE_CHOICE, -// }); -// items.value.push({ -// id: "456", -// title: "A Python question", -// type: CardType.SINGLE_CHOICE, -// }); -// done('ok'); -// } -// load({done: console.log}); diff --git a/server/src/main/java/org/hyperskill/community/flashcards/common/AuthenticationResolver.java b/server/src/main/java/org/hyperskill/community/flashcards/common/AuthenticationResolver.java index 0e6d891..4644b39 100644 --- a/server/src/main/java/org/hyperskill/community/flashcards/common/AuthenticationResolver.java +++ b/server/src/main/java/org/hyperskill/community/flashcards/common/AuthenticationResolver.java @@ -1,14 +1,22 @@ package org.hyperskill.community.flashcards.common; +import lombok.RequiredArgsConstructor; +import org.springframework.core.env.Environment; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.oauth2.core.oidc.user.OidcUser; import org.springframework.security.oauth2.jwt.Jwt; import org.springframework.stereotype.Component; @Component +@RequiredArgsConstructor public class AuthenticationResolver { + private final Environment env; + public String resolveUsername() { + if (Boolean.TRUE.equals(env.getProperty("DEV_MODE", Boolean.class, false))) { + return env.getProperty("DEV_USER", "test1@test.com"); + } var authentication = SecurityContextHolder.getContext().getAuthentication().getPrincipal(); return switch (authentication) { case OidcUser oidcUser -> oidcUser.getSubject(); diff --git a/server/src/main/java/org/hyperskill/community/flashcards/config/WebSecurityConfiguration.java b/server/src/main/java/org/hyperskill/community/flashcards/config/WebSecurityConfiguration.java index d53d4f0..8438556 100644 --- a/server/src/main/java/org/hyperskill/community/flashcards/config/WebSecurityConfiguration.java +++ b/server/src/main/java/org/hyperskill/community/flashcards/config/WebSecurityConfiguration.java @@ -1,6 +1,8 @@ package org.hyperskill.community.flashcards.config; +import lombok.extern.slf4j.Slf4j; import org.hyperskill.community.flashcards.registration.User; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.mongodb.core.MongoTemplate; @@ -13,42 +15,76 @@ import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.SecurityFilterChain; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.CorsConfigurationSource; +import org.springframework.web.cors.UrlBasedCorsConfigurationSource; +import java.util.List; import java.util.Optional; import static org.springframework.security.config.Customizer.withDefaults; /** - * new Spring security 6.0 style provision of SecurityFilterChain bean with the security configuration, + * Spring security 6.x style provision of SecurityFilterChain bean with the security configuration, * as well as PasswordProvider and AuthenticationManager that makes use of our UserDetails persistence. */ @Configuration @EnableWebSecurity +@Slf4j public class WebSecurityConfiguration { @Bean + @ConditionalOnProperty(name = "DEV_MODE", havingValue = "false", matchIfMissing = true) public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { return http .csrf(CsrfConfigurer::disable) .oauth2ResourceServer(auth -> auth.jwt(withDefaults())) + .oauth2Login(withDefaults()) + .oauth2Client(withDefaults()) .authorizeHttpRequests(auth -> auth .requestMatchers("/register.html", "/js/register.js", "/css/register.css").permitAll() .requestMatchers(HttpMethod.POST, "/api/register").permitAll() - .anyRequest().authenticated()) - .oauth2Login(withDefaults()) - .oauth2Client(withDefaults()) - .build(); + .anyRequest().authenticated() + ).build(); } @Bean public UserDetailsService userDetailsService(MongoTemplate mongoTemplate) { return username -> - Optional.ofNullable(mongoTemplate.findById(username, User.class)) - .orElseThrow(() -> new UsernameNotFoundException("User not found.")); + Optional.ofNullable(mongoTemplate.findById(username, User.class)) + .orElseThrow(() -> new UsernameNotFoundException("User not found.")); } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } + + @Bean + @ConditionalOnProperty(name = "DEV_MODE", havingValue = "true") + public SecurityFilterChain filterChainDevMode(HttpSecurity http) throws Exception { + log.warn("Running in DEV_MODE, permitting all requests."); + return http + .csrf(CsrfConfigurer::disable) + .cors(withDefaults()) + .authorizeHttpRequests( + auth -> auth.anyRequest().permitAll() + ).build(); + } + + @Bean + @ConditionalOnProperty(name = "DEV_MODE", havingValue = "true") + public CorsConfigurationSource corsConfigurationSource() { + log.info("Configuring CORS for DEV MODE"); + var configuration = new CorsConfiguration(); + configuration.setAllowedOrigins(List.of("http://localhost:3000")); + configuration.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE")); + configuration.setAllowedHeaders(List.of("Authorization", + "Access-Control-Allow-Origin", "Content-Type")); + configuration.setExposedHeaders(List.of("Authorization", + "Access-Control-Allow-Origin", "Content-Type")); + UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + source.registerCorsConfiguration("/api/**", configuration); + return source; + } } diff --git a/server/src/test/java/org/hyperskill/community/flashcards/common/AuthenticationResolverTest.java b/server/src/test/java/org/hyperskill/community/flashcards/common/AuthenticationResolverTest.java index c4a6cab..24dc2fe 100644 --- a/server/src/test/java/org/hyperskill/community/flashcards/common/AuthenticationResolverTest.java +++ b/server/src/test/java/org/hyperskill/community/flashcards/common/AuthenticationResolverTest.java @@ -5,8 +5,10 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; +import org.mockito.Mock; import org.mockito.MockedStatic; import org.mockito.junit.jupiter.MockitoSettings; +import org.springframework.core.env.Environment; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; @@ -29,9 +31,12 @@ class AuthenticationResolverTest { AuthenticationResolver resolver; + @Mock + Environment env; + @BeforeEach void setup() { - resolver = new AuthenticationResolver(); + resolver = new AuthenticationResolver(env); } static Stream whenJwtOrOidcAuthentication_resolverWorks() {