Add Checkstyle and Prettier to BFF #65

Merged
stne3960 merged 15 commits from chore/checkstyle into main 2026-01-13 13:17:19 +01:00
9 changed files with 164 additions and 149 deletions
Showing only changes of commit 0e3c33a1d0 - Show all commits

View File

@ -3,5 +3,4 @@ package se.su.dsv.studentportalen.bff.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties("se.su.dsv.backend-api")
public record BackendApiConfiguration(String daisyUrl) {
}
public record BackendApiConfiguration(String daisyUrl) {}

View File

@ -6,7 +6,9 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
public record FrontendConfiguration(String url) {
public FrontendConfiguration {
if (url == null || url.isBlank()) {
throw new IllegalArgumentException("se.su.dsv.frontend.url must not be null or blank");
throw new IllegalArgumentException(
"se.su.dsv.frontend.url must not be null or blank"
);
}
}
}

View File

@ -1,5 +1,6 @@
package se.su.dsv.studentportalen.bff.config;
import java.util.List;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
@ -7,34 +8,42 @@ import org.springframework.security.web.SecurityFilterChain;
import org.springframework.web.cors.CorsConfiguration;
import se.su.dsv.studentportalen.bff.login.BFFAuthenticationEntryPoint;
import java.util.List;
@Configuration
public class SecurityConfiguration {
@Bean
public SecurityFilterChain securityFilterChain(
HttpSecurity http,
FrontendConfiguration frontendConfiguration)
throws Exception
{
http.exceptionHandling(exception -> exception
.authenticationEntryPoint(new BFFAuthenticationEntryPoint()));
http.oauth2Login(login -> login
.defaultSuccessUrl(frontendConfiguration.url(), true));
http.authorizeHttpRequests(authorize -> authorize
.requestMatchers("/swagger", "/swagger-ui/**", "/v3/api-docs/**").permitAll()
.anyRequest().authenticated());
http.cors(cors -> cors
.configurationSource(_ -> frontendOnlyCors(frontendConfiguration)));
FrontendConfiguration frontendConfiguration
) throws Exception {
http.exceptionHandling(exception ->
exception.authenticationEntryPoint(new BFFAuthenticationEntryPoint())
);
http.oauth2Login(login ->
login.defaultSuccessUrl(frontendConfiguration.url(), true)
);
http.authorizeHttpRequests(authorize ->
authorize
.requestMatchers("/swagger", "/swagger-ui/**", "/v3/api-docs/**")
.permitAll()
.anyRequest()
.authenticated()
);
http.cors(cors ->
cors.configurationSource(_ -> frontendOnlyCors(frontendConfiguration))
);
http.csrf(csrf -> csrf.spa());
return http.build();
}
private static CorsConfiguration frontendOnlyCors(FrontendConfiguration frontendConfiguration) {
private static CorsConfiguration frontendOnlyCors(
FrontendConfiguration frontendConfiguration
) {
var corsConfiguration = new CorsConfiguration();
corsConfiguration.setAllowedOrigins(List.of(frontendConfiguration.url()));
corsConfiguration.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE"));
corsConfiguration.setAllowedMethods(
List.of("GET", "POST", "PUT", "DELETE")
);
// Allow the frontend to see the X-Authorization-Url header
corsConfiguration.setExposedHeaders(List.of("X-Authorization-Url"));
@ -45,7 +54,9 @@ public class SecurityConfiguration {
// Content-Type is allowed by default but with a restriction on the value
// The restriction does not allow "application/json" so we add it as an allowed header
// X-XSRF-TOKEN is needed for CSRF protection
corsConfiguration.setAllowedHeaders(List.of("Content-Type", "X-XSRF-TOKEN"));
corsConfiguration.setAllowedHeaders(
List.of("Content-Type", "X-XSRF-TOKEN")
);
return corsConfiguration;
}
}

View File

@ -24,8 +24,8 @@ public class ProfileController {
@GetMapping("/profile")
public ProfileResponse getProfile(
@AuthenticationPrincipal(errorOnInvalidType = true) OAuth2User currentUser)
{
@AuthenticationPrincipal(errorOnInvalidType = true) OAuth2User currentUser
) {
return service.getProfile(currentUser);
}
}

View File

@ -1,15 +1,14 @@
package se.su.dsv.studentportalen.bff.controller;
import org.springframework.context.annotation.Profile;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestClient;
import se.su.dsv.studentportalen.bff.config.BackendApiConfiguration;
import java.time.Duration;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.StructuredTaskScope;
import java.util.concurrent.StructuredTaskScope.Subtask;
import org.springframework.context.annotation.Profile;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestClient;
import se.su.dsv.studentportalen.bff.config.BackendApiConfiguration;
@RestController
@RequestMapping("/test")
@ -34,7 +33,8 @@ public class TestController {
// Pick the scope based on the desired behaviour
try (var scope = StructuredTaskScope.open()) {
Subtask<String> nameTask = scope.fork(() -> {
String name = restClient.get()
String name = restClient
.get()
.uri("/test/name")
.retrieve()
.body(String.class);
@ -42,7 +42,8 @@ public class TestController {
});
Subtask<String> emailTask = scope.fork(() -> {
String email = restClient.get()
String email = restClient
.get()
.uri("/test/email")
.retrieve()
.body(String.class);
@ -50,10 +51,11 @@ public class TestController {
});
Subtask<DaisyProfile> daisyProfile = scope.fork(() -> {
DaisyProfile profile = restClient.get()
.uri(backendApiConfiguration.daisyUrl(), builder -> builder
.path("/profile")
.build())
DaisyProfile profile = restClient
.get()
.uri(backendApiConfiguration.daisyUrl(), builder ->
builder.path("/profile").build()
)
.retrieve()
.body(DaisyProfile.class);
return profile;
@ -65,7 +67,8 @@ public class TestController {
return "Hello, I am %s and my email is %s. My Daisy profile is: %s".formatted(
nameTask.get(),
emailTask.get(),
daisyProfile.get());
daisyProfile.get()
);
}
}

View File

@ -1,22 +1,21 @@
package se.su.dsv.studentportalen.bff.dto.response;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.Objects;
/**
* User profile information.
*/
public record ProfileResponse(
@JsonProperty(value = "name", required = true)
String name,
@JsonProperty(value = "name", required = true) String name,
@JsonProperty(value = "language", required = true)
Language language)
{
@JsonProperty(value = "language", required = true) Language language
) {
public enum Language {
@JsonProperty("sv") SWEDISH,
@JsonProperty("en") ENGLISH
@JsonProperty("sv")
SWEDISH,
@JsonProperty("en")
ENGLISH,
}
public ProfileResponse {

View File

@ -7,12 +7,13 @@ import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
public class BFFAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(
HttpServletRequest request,
HttpServletResponse response,
AuthenticationException authException)
{
AuthenticationException authException
) {
String loginUri = ServletUriComponentsBuilder.fromRequest(request)
.replacePath("/oauth2/authorization/studentportalen")
.build()