From 95349340cc7c02ba554c0d7b0913ac6669606d44 Mon Sep 17 00:00:00 2001 From: nenzen Date: Sat, 10 Jan 2026 16:40:43 +0100 Subject: [PATCH 01/12] Upgrade Spring Boot from 3.4.4 to 4.0.1 --- bff/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bff/pom.xml b/bff/pom.xml index d5ac45a..9786d17 100644 --- a/bff/pom.xml +++ b/bff/pom.xml @@ -7,7 +7,7 @@ org.springframework.boot spring-boot-starter-parent - 3.4.4 + 4.0.1 -- 2.39.5 From a0a392760d92755909deaaf21933c6335200bc5f Mon Sep 17 00:00:00 2001 From: nenzen Date: Sat, 10 Jan 2026 16:41:11 +0100 Subject: [PATCH 02/12] Move BackendApiConfiguration to config package --- .../bff/{ => config}/BackendApiConfiguration.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename bff/src/main/java/se/su/dsv/studentportalen/bff/{ => config}/BackendApiConfiguration.java (80%) diff --git a/bff/src/main/java/se/su/dsv/studentportalen/bff/BackendApiConfiguration.java b/bff/src/main/java/se/su/dsv/studentportalen/bff/config/BackendApiConfiguration.java similarity index 80% rename from bff/src/main/java/se/su/dsv/studentportalen/bff/BackendApiConfiguration.java rename to bff/src/main/java/se/su/dsv/studentportalen/bff/config/BackendApiConfiguration.java index 47eba93..a11ff1f 100644 --- a/bff/src/main/java/se/su/dsv/studentportalen/bff/BackendApiConfiguration.java +++ b/bff/src/main/java/se/su/dsv/studentportalen/bff/config/BackendApiConfiguration.java @@ -1,4 +1,4 @@ -package se.su.dsv.studentportalen.bff; +package se.su.dsv.studentportalen.bff.config; import org.springframework.boot.context.properties.ConfigurationProperties; -- 2.39.5 From fb1ee3b440dfbe3e2f8c426659855734698afd23 Mon Sep 17 00:00:00 2001 From: nenzen Date: Sat, 10 Jan 2026 16:41:24 +0100 Subject: [PATCH 03/12] Move FrontendConfiguration to config package --- .../studentportalen/bff/{ => config}/FrontendConfiguration.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename bff/src/main/java/se/su/dsv/studentportalen/bff/{ => config}/FrontendConfiguration.java (88%) diff --git a/bff/src/main/java/se/su/dsv/studentportalen/bff/FrontendConfiguration.java b/bff/src/main/java/se/su/dsv/studentportalen/bff/config/FrontendConfiguration.java similarity index 88% rename from bff/src/main/java/se/su/dsv/studentportalen/bff/FrontendConfiguration.java rename to bff/src/main/java/se/su/dsv/studentportalen/bff/config/FrontendConfiguration.java index b6aea15..ad670db 100644 --- a/bff/src/main/java/se/su/dsv/studentportalen/bff/FrontendConfiguration.java +++ b/bff/src/main/java/se/su/dsv/studentportalen/bff/config/FrontendConfiguration.java @@ -1,4 +1,4 @@ -package se.su.dsv.studentportalen.bff; +package se.su.dsv.studentportalen.bff.config; import org.springframework.boot.context.properties.ConfigurationProperties; -- 2.39.5 From a0313dd74510029b8b28f0a67ccbf82a09f57394 Mon Sep 17 00:00:00 2001 From: nenzen Date: Sat, 10 Jan 2026 16:41:55 +0100 Subject: [PATCH 04/12] Extract SecurityConfiguration with CSRF SPA and updated CORS --- .../studentportalen/bff/Studentportalen.java | 51 ------------------- .../bff/config/SecurityConfiguration.java | 51 +++++++++++++++++++ 2 files changed, 51 insertions(+), 51 deletions(-) create mode 100644 bff/src/main/java/se/su/dsv/studentportalen/bff/config/SecurityConfiguration.java diff --git a/bff/src/main/java/se/su/dsv/studentportalen/bff/Studentportalen.java b/bff/src/main/java/se/su/dsv/studentportalen/bff/Studentportalen.java index 6f92487..19d9281 100644 --- a/bff/src/main/java/se/su/dsv/studentportalen/bff/Studentportalen.java +++ b/bff/src/main/java/se/su/dsv/studentportalen/bff/Studentportalen.java @@ -5,64 +5,13 @@ import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.context.properties.ConfigurationPropertiesScan; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; -import org.springframework.context.annotation.Bean; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.web.SecurityFilterChain; -import org.springframework.security.web.util.matcher.RequestMatcher; -import org.springframework.security.web.util.matcher.RequestMatchers; -import org.springframework.web.cors.CorsConfiguration; -import se.su.dsv.studentportalen.bff.login.BFFAuthenticationEntryPoint; - -import java.util.List; - -import static org.springframework.security.web.util.matcher.AntPathRequestMatcher.antMatcher; @SpringBootApplication @EnableConfigurationProperties @ConfigurationPropertiesScan public class Studentportalen extends SpringBootServletInitializer { - private static final RequestMatcher DOCUMENTATION_MATCHER = RequestMatchers.anyOf( - antMatcher("/swagger"), - antMatcher("/swagger-ui/**"), - antMatcher("/v3/api-docs/**")); - public static void main(String[] args) { SpringApplication.run(Studentportalen.class, args); } - - @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(DOCUMENTATION_MATCHER).permitAll() - .anyRequest().authenticated()); - http.cors(cors -> cors - .configurationSource(_ -> frontendOnlyCors(frontendConfiguration))); - return http.build(); - } - - private static CorsConfiguration frontendOnlyCors(FrontendConfiguration frontendConfiguration) { - var corsConfiguration = new CorsConfiguration(); - corsConfiguration.setAllowedOrigins(List.of(frontendConfiguration.url())); - corsConfiguration.setAllowedMethods(List.of("GET", "POST")); - - // Allow the frontend to see the X-Authorization-Url header - corsConfiguration.setExposedHeaders(List.of("X-Authorization-Url")); - - // To allow the session cookie to be included - corsConfiguration.setAllowCredentials(true); - - // 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 - corsConfiguration.setAllowedHeaders(List.of("Content-Type")); - return corsConfiguration; - } } diff --git a/bff/src/main/java/se/su/dsv/studentportalen/bff/config/SecurityConfiguration.java b/bff/src/main/java/se/su/dsv/studentportalen/bff/config/SecurityConfiguration.java new file mode 100644 index 0000000..d188365 --- /dev/null +++ b/bff/src/main/java/se/su/dsv/studentportalen/bff/config/SecurityConfiguration.java @@ -0,0 +1,51 @@ +package se.su.dsv.studentportalen.bff.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +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))); + http.csrf(csrf -> csrf.spa()); + return http.build(); + } + + private static CorsConfiguration frontendOnlyCors(FrontendConfiguration frontendConfiguration) { + var corsConfiguration = new CorsConfiguration(); + corsConfiguration.setAllowedOrigins(List.of(frontendConfiguration.url())); + 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")); + + // To allow the session cookie to be included + corsConfiguration.setAllowCredentials(true); + + // 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")); + return corsConfiguration; + } +} -- 2.39.5 From a6b675d9a55ee59b5ff61d29cbc53684f3f3f967 Mon Sep 17 00:00:00 2001 From: nenzen Date: Sat, 10 Jan 2026 16:42:11 +0100 Subject: [PATCH 05/12] Add CSRF and Accept-Language middleware to frontend --- frontend/src/hooks/backend.ts | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/frontend/src/hooks/backend.ts b/frontend/src/hooks/backend.ts index e5f4bcc..a1838d2 100644 --- a/frontend/src/hooks/backend.ts +++ b/frontend/src/hooks/backend.ts @@ -11,6 +11,32 @@ const includeCredentials: Middleware = { }, }; +function getCookie(name: string): string | undefined { + const match = document.cookie.match(new RegExp(`(^| )${name}=([^;]+)`)); + return match ? match[2] : undefined; +} + +const includeCsrfToken: Middleware = { + onRequest({ request }) { + const method = request.method.toUpperCase(); + if (method === "POST" || method === "PUT" || method === "DELETE") { + const csrfToken = getCookie("XSRF-TOKEN"); + if (csrfToken) { + request.headers.set("X-XSRF-TOKEN", csrfToken); + } + } + return request; + }, +}; + +const includeAcceptLanguage: Middleware = { + onRequest({ request }) { + const language = navigator.language.split("-")[0] || "en"; + request.headers.set("Accept-Language", language); + return request; + }, +}; + const initiateAuthorizationOnUnauthorized: Middleware = { onResponse({ response }) { if (response.status === 401) { @@ -24,6 +50,8 @@ const initiateAuthorizationOnUnauthorized: Middleware = { }; client.use(includeCredentials); +client.use(includeCsrfToken); +client.use(includeAcceptLanguage); client.use(initiateAuthorizationOnUnauthorized); export function useBackend() { -- 2.39.5 From 2c7c23ab9ce417ec99a38875ddf7c8feaffbdca8 Mon Sep 17 00:00:00 2001 From: nenzen Date: Sat, 10 Jan 2026 16:42:22 +0100 Subject: [PATCH 06/12] Move TestController to controller package --- .../bff/{ => controller}/TestController.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) rename bff/src/main/java/se/su/dsv/studentportalen/bff/{ => controller}/TestController.java (88%) diff --git a/bff/src/main/java/se/su/dsv/studentportalen/bff/TestController.java b/bff/src/main/java/se/su/dsv/studentportalen/bff/controller/TestController.java similarity index 88% rename from bff/src/main/java/se/su/dsv/studentportalen/bff/TestController.java rename to bff/src/main/java/se/su/dsv/studentportalen/bff/controller/TestController.java index 950a214..b977a13 100644 --- a/bff/src/main/java/se/su/dsv/studentportalen/bff/TestController.java +++ b/bff/src/main/java/se/su/dsv/studentportalen/bff/controller/TestController.java @@ -1,9 +1,10 @@ -package se.su.dsv.studentportalen.bff; +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; @@ -15,6 +16,9 @@ import java.util.concurrent.StructuredTaskScope.Subtask; @Profile("development") public class TestController { + private static final int NAME_DELAY_SECONDS = 2; + private static final int EMAIL_DELAY_SECONDS = 3; + private final RestClient restClient; private final BackendApiConfiguration backendApiConfiguration; @@ -67,13 +71,13 @@ public class TestController { @RequestMapping("/name") public String name() throws InterruptedException { - Thread.sleep(Duration.ofSeconds(2)); + Thread.sleep(Duration.ofSeconds(NAME_DELAY_SECONDS)); return "Greg"; } @RequestMapping("/email") public String email() throws InterruptedException { - Thread.sleep(Duration.ofSeconds(3)); + Thread.sleep(Duration.ofSeconds(EMAIL_DELAY_SECONDS)); return "greg@localhost"; } -- 2.39.5 From d1735c12cb55cd804e030ea105f4d4de89265513 Mon Sep 17 00:00:00 2001 From: nenzen Date: Sat, 10 Jan 2026 16:42:33 +0100 Subject: [PATCH 07/12] Move ProfileController and extract ProfileService --- .../ProfileController.java | 18 +++++++++++++++--- .../bff/service/ProfileService.java | 17 +++++++++++++++++ 2 files changed, 32 insertions(+), 3 deletions(-) rename bff/src/main/java/se/su/dsv/studentportalen/bff/{frontend/profile => controller}/ProfileController.java (57%) create mode 100644 bff/src/main/java/se/su/dsv/studentportalen/bff/service/ProfileService.java diff --git a/bff/src/main/java/se/su/dsv/studentportalen/bff/frontend/profile/ProfileController.java b/bff/src/main/java/se/su/dsv/studentportalen/bff/controller/ProfileController.java similarity index 57% rename from bff/src/main/java/se/su/dsv/studentportalen/bff/frontend/profile/ProfileController.java rename to bff/src/main/java/se/su/dsv/studentportalen/bff/controller/ProfileController.java index 4483c71..eb97bd2 100644 --- a/bff/src/main/java/se/su/dsv/studentportalen/bff/frontend/profile/ProfileController.java +++ b/bff/src/main/java/se/su/dsv/studentportalen/bff/controller/ProfileController.java @@ -1,4 +1,4 @@ -package se.su.dsv.studentportalen.bff.frontend.profile; +package se.su.dsv.studentportalen.bff.controller; import org.springframework.http.MediaType; import org.springframework.security.core.annotation.AuthenticationPrincipal; @@ -6,14 +6,26 @@ import org.springframework.security.oauth2.core.user.OAuth2User; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import se.su.dsv.studentportalen.bff.dto.response.ProfileResponse; +import se.su.dsv.studentportalen.bff.service.ProfileService; +/** + * REST controller for user profile operations. + */ @RestController @RequestMapping(produces = MediaType.APPLICATION_JSON_VALUE) public class ProfileController { + + private final ProfileService service; + + public ProfileController(ProfileService service) { + this.service = service; + } + @GetMapping("/profile") - public Profile getProfile( + public ProfileResponse getProfile( @AuthenticationPrincipal(errorOnInvalidType = true) OAuth2User currentUser) { - return new Profile(currentUser.getAttribute("name"), Profile.Language.ENGLISH); + return service.getProfile(currentUser); } } diff --git a/bff/src/main/java/se/su/dsv/studentportalen/bff/service/ProfileService.java b/bff/src/main/java/se/su/dsv/studentportalen/bff/service/ProfileService.java new file mode 100644 index 0000000..a21963c --- /dev/null +++ b/bff/src/main/java/se/su/dsv/studentportalen/bff/service/ProfileService.java @@ -0,0 +1,17 @@ +package se.su.dsv.studentportalen.bff.service; + +import org.springframework.security.oauth2.core.user.OAuth2User; +import org.springframework.stereotype.Service; +import se.su.dsv.studentportalen.bff.dto.response.ProfileResponse; + +/** + * Service for user profile operations. + */ +@Service +public class ProfileService { + + public ProfileResponse getProfile(OAuth2User currentUser) { + String name = currentUser.getAttribute("name"); + return new ProfileResponse(name, ProfileResponse.Language.ENGLISH); + } +} -- 2.39.5 From efc0c701037a88426993d82bfc445aed19b7a917 Mon Sep 17 00:00:00 2001 From: nenzen Date: Sat, 10 Jan 2026 16:42:43 +0100 Subject: [PATCH 08/12] Move Profile to ProfileResponse DTO --- .../response/ProfileResponse.java} | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) rename bff/src/main/java/se/su/dsv/studentportalen/bff/{frontend/profile/Profile.java => dto/response/ProfileResponse.java} (53%) diff --git a/bff/src/main/java/se/su/dsv/studentportalen/bff/frontend/profile/Profile.java b/bff/src/main/java/se/su/dsv/studentportalen/bff/dto/response/ProfileResponse.java similarity index 53% rename from bff/src/main/java/se/su/dsv/studentportalen/bff/frontend/profile/Profile.java rename to bff/src/main/java/se/su/dsv/studentportalen/bff/dto/response/ProfileResponse.java index f0be027..a5c655c 100644 --- a/bff/src/main/java/se/su/dsv/studentportalen/bff/frontend/profile/Profile.java +++ b/bff/src/main/java/se/su/dsv/studentportalen/bff/dto/response/ProfileResponse.java @@ -1,19 +1,25 @@ -package se.su.dsv.studentportalen.bff.frontend.profile; +package se.su.dsv.studentportalen.bff.dto.response; import com.fasterxml.jackson.annotation.JsonProperty; import java.util.Objects; -public record Profile( - @JsonProperty(value = "name", required = true) String name, - @JsonProperty(value = "language", required = true) Language language) +/** + * User profile information. + */ +public record ProfileResponse( + @JsonProperty(value = "name", required = true) + String name, + + @JsonProperty(value = "language", required = true) + Language language) { public enum Language { @JsonProperty("sv") SWEDISH, @JsonProperty("en") ENGLISH } - public Profile { + public ProfileResponse { Objects.requireNonNull(name, "name must be specified"); Objects.requireNonNull(language, "language must be specified"); } -- 2.39.5 From 67d9a518e6c84c4bc6fe751bdb959e04f16feab4 Mon Sep 17 00:00:00 2001 From: nenzen Date: Sat, 10 Jan 2026 16:42:54 +0100 Subject: [PATCH 09/12] Remove frontend package-info --- .../su/dsv/studentportalen/bff/frontend/package-info.java | 7 ------- 1 file changed, 7 deletions(-) delete mode 100644 bff/src/main/java/se/su/dsv/studentportalen/bff/frontend/package-info.java diff --git a/bff/src/main/java/se/su/dsv/studentportalen/bff/frontend/package-info.java b/bff/src/main/java/se/su/dsv/studentportalen/bff/frontend/package-info.java deleted file mode 100644 index 476dc55..0000000 --- a/bff/src/main/java/se/su/dsv/studentportalen/bff/frontend/package-info.java +++ /dev/null @@ -1,7 +0,0 @@ -/// Everything related to the API used by the frontend -@NonNullApi -@NonNullFields -package se.su.dsv.studentportalen.bff.frontend; - -import org.springframework.lang.NonNullApi; -import org.springframework.lang.NonNullFields; -- 2.39.5 From 58fe9b07d0bd94fe098a8185463d776bab549ce3 Mon Sep 17 00:00:00 2001 From: nenzen Date: Sat, 10 Jan 2026 16:43:06 +0100 Subject: [PATCH 10/12] Code formatting in login package --- .../bff/login/BFFAuthenticationEntryPoint.java | 6 +++++- .../se/su/dsv/studentportalen/bff/login/package-info.java | 3 ++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/bff/src/main/java/se/su/dsv/studentportalen/bff/login/BFFAuthenticationEntryPoint.java b/bff/src/main/java/se/su/dsv/studentportalen/bff/login/BFFAuthenticationEntryPoint.java index 32290fe..9992289 100644 --- a/bff/src/main/java/se/su/dsv/studentportalen/bff/login/BFFAuthenticationEntryPoint.java +++ b/bff/src/main/java/se/su/dsv/studentportalen/bff/login/BFFAuthenticationEntryPoint.java @@ -8,7 +8,11 @@ import org.springframework.web.servlet.support.ServletUriComponentsBuilder; public class BFFAuthenticationEntryPoint implements AuthenticationEntryPoint { @Override - public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) { + public void commence( + HttpServletRequest request, + HttpServletResponse response, + AuthenticationException authException) + { String loginUri = ServletUriComponentsBuilder.fromRequest(request) .replacePath("/oauth2/authorization/studentportalen") .build() diff --git a/bff/src/main/java/se/su/dsv/studentportalen/bff/login/package-info.java b/bff/src/main/java/se/su/dsv/studentportalen/bff/login/package-info.java index ad2e5cc..986d63e 100644 --- a/bff/src/main/java/se/su/dsv/studentportalen/bff/login/package-info.java +++ b/bff/src/main/java/se/su/dsv/studentportalen/bff/login/package-info.java @@ -1,5 +1,6 @@ /// This package contains the classes and logic for handling the login process -/// described in section 6.1 of [OAuth 2.0 for Browser-Based Applications](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-browser-based-apps#name-backend-for-frontend-bff). +/// described in section 6.1 of OAuth 2.0 for Browser-Based Applications. +/// See: https://datatracker.ietf.org/doc/html/draft-ietf-oauth-browser-based-apps /// /// The frontend running in the browser will attempt to make a request to the /// backend-for-frontend (BFF). If the client is not already authenticated (by -- 2.39.5 From 37489895b92f44e22269d0867166beb8eb6ba003 Mon Sep 17 00:00:00 2001 From: nenzen Date: Sat, 10 Jan 2026 16:51:09 +0100 Subject: [PATCH 11/12] Add null-safety annotations to new packages --- .../su/dsv/studentportalen/bff/controller/package-info.java | 6 ++++++ .../dsv/studentportalen/bff/dto/response/package-info.java | 6 ++++++ .../se/su/dsv/studentportalen/bff/service/package-info.java | 6 ++++++ 3 files changed, 18 insertions(+) create mode 100644 bff/src/main/java/se/su/dsv/studentportalen/bff/controller/package-info.java create mode 100644 bff/src/main/java/se/su/dsv/studentportalen/bff/dto/response/package-info.java create mode 100644 bff/src/main/java/se/su/dsv/studentportalen/bff/service/package-info.java diff --git a/bff/src/main/java/se/su/dsv/studentportalen/bff/controller/package-info.java b/bff/src/main/java/se/su/dsv/studentportalen/bff/controller/package-info.java new file mode 100644 index 0000000..4315538 --- /dev/null +++ b/bff/src/main/java/se/su/dsv/studentportalen/bff/controller/package-info.java @@ -0,0 +1,6 @@ +@NonNullApi +@NonNullFields +package se.su.dsv.studentportalen.bff.controller; + +import org.springframework.lang.NonNullApi; +import org.springframework.lang.NonNullFields; diff --git a/bff/src/main/java/se/su/dsv/studentportalen/bff/dto/response/package-info.java b/bff/src/main/java/se/su/dsv/studentportalen/bff/dto/response/package-info.java new file mode 100644 index 0000000..238166f --- /dev/null +++ b/bff/src/main/java/se/su/dsv/studentportalen/bff/dto/response/package-info.java @@ -0,0 +1,6 @@ +@NonNullApi +@NonNullFields +package se.su.dsv.studentportalen.bff.dto.response; + +import org.springframework.lang.NonNullApi; +import org.springframework.lang.NonNullFields; diff --git a/bff/src/main/java/se/su/dsv/studentportalen/bff/service/package-info.java b/bff/src/main/java/se/su/dsv/studentportalen/bff/service/package-info.java new file mode 100644 index 0000000..8740265 --- /dev/null +++ b/bff/src/main/java/se/su/dsv/studentportalen/bff/service/package-info.java @@ -0,0 +1,6 @@ +@NonNullApi +@NonNullFields +package se.su.dsv.studentportalen.bff.service; + +import org.springframework.lang.NonNullApi; +import org.springframework.lang.NonNullFields; -- 2.39.5 From 63eaebb7dccf52e3e1d9d086e5d4c60267370241 Mon Sep 17 00:00:00 2001 From: nenzen Date: Sun, 11 Jan 2026 14:27:36 +0100 Subject: [PATCH 12/12] Use profile language preference for Accept-Language header --- frontend/src/App.tsx | 6 ++++++ frontend/src/hooks/backend.ts | 9 ++++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 77e508c..195cbb8 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,5 +1,7 @@ +import { useEffect } from "react"; import "./App.css"; import suLogoLandscape from "./assets/SU_logo_optimized.svg"; +import { setLanguagePreference } from "./hooks/backend.ts"; import { ProfileContext } from "./hooks/profile.ts"; import Studentportalen from "./Studentportalen.tsx"; import { useViewTransitioningFetch } from "./hooks/fetch"; @@ -7,6 +9,10 @@ import { useViewTransitioningFetch } from "./hooks/fetch"; function App() { const { data: profile, error } = useViewTransitioningFetch("/profile"); + useEffect(() => { + setLanguagePreference(profile?.language); + }, [profile?.language]); + if (!profile) { return splashScreen("Loading..."); } diff --git a/frontend/src/hooks/backend.ts b/frontend/src/hooks/backend.ts index a1838d2..22242e0 100644 --- a/frontend/src/hooks/backend.ts +++ b/frontend/src/hooks/backend.ts @@ -1,6 +1,12 @@ import createClient, { Middleware } from "openapi-fetch"; import type { paths } from "../lib/api"; +let languagePreference: string | undefined; + +export function setLanguagePreference(language: string | undefined) { + languagePreference = language; +} + const client = createClient({ baseUrl: import.meta.env.VITE_BACKEND_URL, }); @@ -31,7 +37,8 @@ const includeCsrfToken: Middleware = { const includeAcceptLanguage: Middleware = { onRequest({ request }) { - const language = navigator.language.split("-")[0] || "en"; + const language = + languagePreference ?? navigator.language.split("-")[0] ?? "en"; request.headers.set("Accept-Language", language); return request; }, -- 2.39.5