diff --git a/backend/global_settings/urls.py b/backend/global_settings/urls.py index 71a7515a69..9cf8da8f2b 100644 --- a/backend/global_settings/urls.py +++ b/backend/global_settings/urls.py @@ -3,7 +3,12 @@ from iam.sso.views import SSOSettingsViewSet -from .views import GlobalSettingsViewSet, get_sso_info, update_general_settings +from .views import ( + GlobalSettingsViewSet, + get_sso_info, + update_general_settings, + get_general_settings, +) from .routers import DefaultSettingsRouter @@ -21,6 +26,7 @@ # This route should ideally be placed under the routes of the routers, but the DefaultRouter usage overwrite the route and makes it inaccessible. # Could we use DefaultSettingsRouter to register the "global" route to fix that ? path(r"general/update/", update_general_settings, name="update_general_settings"), + path(r"general/info/", get_general_settings, name="get_general_settings"), path(r"", include(router.urls)), path(r"", include(settings_router.urls)), path(r"sso/info/", get_sso_info, name="get_sso_info"), diff --git a/backend/global_settings/views.py b/backend/global_settings/views.py index e6645a9379..adedb51c43 100644 --- a/backend/global_settings/views.py +++ b/backend/global_settings/views.py @@ -37,6 +37,25 @@ def update(self, request, *args, **kwargs): UPDATABLE_GENERAL_SETTINGS = frozenset( ["lang"] ) # This represents the list of "general" GlobalSettings an admin has the right to change. +PUBLIC_GENERAL_SETTINGS = [ + "lang" +] # List of general settings accessible by anyone (non-sensitive general settings). + + +@api_view(["GET"]) +@permission_classes([permissions.AllowAny]) +def get_general_settings(request): + """ + API endpoint to get the general settings. + """ + general_settings = GlobalSettings.objects.filter(name="general").first() + if general_settings is None: + return {} + + public_settings = { + key: general_settings.value.get(key) for key in PUBLIC_GENERAL_SETTINGS + } + return Response(public_settings) @api_view(["PATCH"]) diff --git a/frontend/src/hooks.server.ts b/frontend/src/hooks.server.ts index a63710bcf9..169adb6754 100644 --- a/frontend/src/hooks.server.ts +++ b/frontend/src/hooks.server.ts @@ -7,6 +7,8 @@ import { languageTag, setLanguageTag } from '$paraglide/runtime'; import { loadFeatureFlags } from '$lib/feature-flags'; +let generalSettings = {}; + async function ensureCsrfToken(event: RequestEvent): Promise { let csrfToken = event.cookies.get('csrftoken') || ''; if (!csrfToken) { @@ -28,23 +30,43 @@ async function ensureCsrfToken(event: RequestEvent): Promise { async function validateUserSession(event: RequestEvent): Promise { const token = event.cookies.get('token'); - if (!token) return null; + const requestList = [fetch(`${BASE_API_URL}/settings/general/info/`)]; + if (token) { + requestList.push( + fetch(`${BASE_API_URL}/iam/current-user/`, { + credentials: 'include', + headers: { + 'content-type': 'application/json', + Authorization: `Token ${token}` + } + }) + ); + } - const res = await fetch(`${BASE_API_URL}/iam/current-user/`, { - credentials: 'include', - headers: { - 'content-type': 'application/json', - Authorization: `Token ${token}` - } - }); + const responseList = await Promise.all(requestList); + const settingsRes = responseList[0]; + const newGeneralSettings = await settingsRes.json(); + generalSettings = newGeneralSettings; + + if (!event.cookies.get('ciso_lang')) { + event.cookies.set('ciso_lang', generalSettings.lang || 'en', { + httpOnly: false, + sameSite: 'lax', + path: '/', + secure: true + }); + } + + if (!token) return null; - if (!res.ok) { + const userRes = responseList[1]; + if (!userRes.ok) { event.cookies.delete('token', { path: '/' }); redirect(302, `/login?next=${event.url.pathname}`); } - return res.json(); + return userRes.json(); } export const handle: Handle = async ({ event, resolve }) => { @@ -56,7 +78,7 @@ export const handle: Handle = async ({ event, resolve }) => { const errorId = new URL(event.request.url).searchParams.get('error'); if (errorId) { - setLanguageTag(event.cookies.get('ciso_lang') || 'en'); + setLanguageTag(event.cookies.get('ciso_lang') || generalSettings.lang || 'en'); setFlash({ type: 'error', message: safeTranslate(errorId) }, event); redirect(302, '/login'); } diff --git a/frontend/src/lib/components/Forms/ModelForm/GeneralSettingForm.svelte b/frontend/src/lib/components/Forms/ModelForm/GeneralSettingForm.svelte index d90bcfd724..48f37f81f9 100644 --- a/frontend/src/lib/components/Forms/ModelForm/GeneralSettingForm.svelte +++ b/frontend/src/lib/components/Forms/ModelForm/GeneralSettingForm.svelte @@ -2,9 +2,10 @@ import Select from '../Select.svelte'; import type { SuperValidated } from 'sveltekit-superforms'; import type { ModelInfo, CacheLock } from '$lib/utils/types'; - import { availableLanguageTags, languageTag, setLanguageTag } from '$paraglide/runtime'; + import { availableLanguageTags } from '$paraglide/runtime'; import { LOCALE_DISPLAY_MAP } from '$lib/utils/constants'; import * as m from '$paraglide/messages.js'; + export let form: SuperValidated; export let model: ModelInfo; export let cacheLocks: Record = {}; diff --git a/frontend/src/routes/(app)/(internal)/settings/+page.server.ts b/frontend/src/routes/(app)/(internal)/settings/+page.server.ts index c19378b452..1318d40cc8 100644 --- a/frontend/src/routes/(app)/(internal)/settings/+page.server.ts +++ b/frontend/src/routes/(app)/(internal)/settings/+page.server.ts @@ -14,34 +14,42 @@ export const load: PageServerLoad = async ({ fetch }) => { const selectOptions: Record = {}; - const ssoMmodel = getModelInfo('sso-settings'); + const ssoModel = getModelInfo('sso-settings'); const generalSettingModel = getModelInfo('general-settings'); - if (ssoMmodel.selectFields) { - for (const selectField of ssoMmodel.selectFields) { - const url = `${BASE_API_URL}/settings/sso/${selectField.field}/`; - const response = await fetch(url); - if (response.ok) { - selectOptions[selectField.field] = await response.json().then((data) => - Object.entries(data).map(([key, value]) => ({ - label: value, - value: key - })) - ); - } else { - console.error(`Failed to fetch data for ${selectField.field}: ${response.statusText}`); - } - } + const requestList = [fetch(`${BASE_API_URL}/settings/general/info/`)]; + + const selectFields = ssoModel.selectFields ?? []; + for (const selectField of selectFields) { + const field = selectField.field; + const url = `${BASE_API_URL}/settings/sso/${field}/`; + requestList.push(fetch(url).then((response) => [response, field])); } - ssoMmodel.selectOptions = selectOptions; + const responseList = await Promise.all(requestList); + const generalSettingResponse = responseList[0]; + for (let i = 1; i < responseList.length; i++) { + const [response, field] = responseList[i]; + if (response.ok) { + selectOptions[field] = await response.json().then((data) => + Object.entries(data).map(([key, value]) => ({ + label: value, + value: key + })) + ); + } else { + console.error(`Failed to fetch data for ${selectField.field}: ${response.statusText}`); + } + } + ssoModel.selectOptions = selectOptions; const ssoForm = await superValidate(settings, zod(SSOSettingsSchema), { errors: false }); - const generalSettingForm = await superValidate(settings, zod(GeneralSettingsSchema), { + const generalSettings = await generalSettingResponse.json(); + const generalSettingForm = await superValidate(generalSettings, zod(GeneralSettingsSchema), { errors: false }); - return { settings, ssoForm, ssoMmodel, generalSettingForm, generalSettingModel }; + return { settings, ssoForm, ssoModel, generalSettingForm, generalSettingModel }; }; export const actions: Actions = { diff --git a/frontend/src/routes/(app)/(internal)/settings/+page.svelte b/frontend/src/routes/(app)/(internal)/settings/+page.svelte index 867c59f864..9129b76a20 100644 --- a/frontend/src/routes/(app)/(internal)/settings/+page.svelte +++ b/frontend/src/routes/(app)/(internal)/settings/+page.svelte @@ -25,7 +25,7 @@ {#if tabSet === 0}
{m.ssoSettingsDescription()} - +
{:else if tabSet === 1}
diff --git a/frontend/src/routes/(app)/+layout.server.ts b/frontend/src/routes/(app)/+layout.server.ts index ca35e11945..371c99ac69 100644 --- a/frontend/src/routes/(app)/+layout.server.ts +++ b/frontend/src/routes/(app)/+layout.server.ts @@ -21,6 +21,5 @@ export const load = loadFlash(async ({ locals, url, cookies, request }) => { }); } } - setLanguageTag(cookies.get('ciso_lang') || sourceLanguageTag); return { user: locals.user }; }) satisfies LayoutServerLoad; diff --git a/frontend/src/routes/ParaglideJsProvider.svelte b/frontend/src/routes/ParaglideJsProvider.svelte index 777a8ae3a9..1758119abd 100644 --- a/frontend/src/routes/ParaglideJsProvider.svelte +++ b/frontend/src/routes/ParaglideJsProvider.svelte @@ -11,9 +11,8 @@ onMount(() => { // const valueFromSession = sessionStorage.getItem('lang') || sourceLanguageTag; - const valueFromCookies = getCookie('ciso_lang') || sourceLanguageTag; + const valueFromCookies = getCookie('ciso_lang') || languageTag(); // @ts-ignore - setCookie('ciso_lang', valueFromCookies); setLanguageTag(valueFromCookies); });