tenants: add default_locale read only field, pre-hydrate in flows and read in autodetect as first choice
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
This commit is contained in:
		
							
								
								
									
										2
									
								
								.vscode/settings.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.vscode/settings.json
									
									
									
									
										vendored
									
									
								
							| @ -24,7 +24,7 @@ | |||||||
|         "*.akflow": "json" |         "*.akflow": "json" | ||||||
|     }, |     }, | ||||||
|     "typescript.preferences.importModuleSpecifier": "non-relative", |     "typescript.preferences.importModuleSpecifier": "non-relative", | ||||||
|     "typescript.preferences.importModuleSpecifierEnding": "js", |     "typescript.preferences.importModuleSpecifierEnding": "index", | ||||||
|     "typescript.tsdk": "./web/node_modules/typescript/lib", |     "typescript.tsdk": "./web/node_modules/typescript/lib", | ||||||
|     "typescript.enablePromptUseWorkspaceTsdk": true |     "typescript.enablePromptUseWorkspaceTsdk": true | ||||||
| } | } | ||||||
|  | |||||||
| @ -10,7 +10,9 @@ | |||||||
| <script>ShadyDOM = { force: !navigator.webdriver };</script> | <script>ShadyDOM = { force: !navigator.webdriver };</script> | ||||||
| {% endif %} | {% endif %} | ||||||
| <script> | <script> | ||||||
| window.authentik = {}; | window.authentik = { | ||||||
|  |     "locale": "{{ tenant.default_locale }}", | ||||||
|  | }; | ||||||
| window.authentik.flow = { | window.authentik.flow = { | ||||||
|     "layout": "{{ flow.layout }}", |     "layout": "{{ flow.layout }}", | ||||||
| }; | }; | ||||||
|  | |||||||
| @ -4,7 +4,7 @@ from typing import Any | |||||||
| from drf_spectacular.utils import extend_schema | from drf_spectacular.utils import extend_schema | ||||||
| from rest_framework.decorators import action | from rest_framework.decorators import action | ||||||
| from rest_framework.exceptions import ValidationError | from rest_framework.exceptions import ValidationError | ||||||
| from rest_framework.fields import CharField, ListField | from rest_framework.fields import CharField, ListField, ReadOnlyField | ||||||
| from rest_framework.permissions import AllowAny | from rest_framework.permissions import AllowAny | ||||||
| from rest_framework.request import Request | from rest_framework.request import Request | ||||||
| from rest_framework.response import Response | from rest_framework.response import Response | ||||||
| @ -76,6 +76,8 @@ class CurrentTenantSerializer(PassiveSerializer): | |||||||
|     flow_unenrollment = CharField(source="flow_unenrollment.slug", required=False) |     flow_unenrollment = CharField(source="flow_unenrollment.slug", required=False) | ||||||
|     flow_user_settings = CharField(source="flow_user_settings.slug", required=False) |     flow_user_settings = CharField(source="flow_user_settings.slug", required=False) | ||||||
|  |  | ||||||
|  |     default_locale = ReadOnlyField() | ||||||
|  |  | ||||||
|  |  | ||||||
| class TenantViewSet(UsedByMixin, ModelViewSet): | class TenantViewSet(UsedByMixin, ModelViewSet): | ||||||
|     """Tenant Viewset""" |     """Tenant Viewset""" | ||||||
|  | |||||||
| @ -65,6 +65,11 @@ class Tenant(models.Model): | |||||||
|  |  | ||||||
|     attributes = models.JSONField(default=dict, blank=True) |     attributes = models.JSONField(default=dict, blank=True) | ||||||
|  |  | ||||||
|  |     @property | ||||||
|  |     def default_locale(self) -> str: | ||||||
|  |         """Get default locale""" | ||||||
|  |         return self.attributes.get("settings", {}).get("locale", "") | ||||||
|  |  | ||||||
|     def __str__(self) -> str: |     def __str__(self) -> str: | ||||||
|         if self.default: |         if self.default: | ||||||
|             return "Default tenant" |             return "Default tenant" | ||||||
|  | |||||||
| @ -20658,10 +20658,14 @@ components: | |||||||
|           type: string |           type: string | ||||||
|         flow_user_settings: |         flow_user_settings: | ||||||
|           type: string |           type: string | ||||||
|  |         default_locale: | ||||||
|  |           type: string | ||||||
|  |           readOnly: true | ||||||
|       required: |       required: | ||||||
|       - branding_favicon |       - branding_favicon | ||||||
|       - branding_logo |       - branding_logo | ||||||
|       - branding_title |       - branding_title | ||||||
|  |       - default_locale | ||||||
|       - matched_domain |       - matched_domain | ||||||
|       - ui_footer_links |       - ui_footer_links | ||||||
|     DeniedActionEnum: |     DeniedActionEnum: | ||||||
|  | |||||||
| @ -1,6 +1,7 @@ | |||||||
| import { VERSION } from "@goauthentik/web/constants"; | import { VERSION } from "@goauthentik/web/constants"; | ||||||
| import { MessageMiddleware } from "@goauthentik/web/elements/messages/Middleware"; | import { MessageMiddleware } from "@goauthentik/web/elements/messages/Middleware"; | ||||||
| import { APIMiddleware } from "@goauthentik/web/elements/notifications/APIDrawer"; | import { APIMiddleware } from "@goauthentik/web/elements/notifications/APIDrawer"; | ||||||
|  | import { activateLocale } from "@goauthentik/web/interfaces/locale"; | ||||||
| import { getCookie } from "@goauthentik/web/utils"; | import { getCookie } from "@goauthentik/web/utils"; | ||||||
|  |  | ||||||
| import { | import { | ||||||
| @ -34,28 +35,39 @@ export function config(): Promise<Config> { | |||||||
|     return globalConfigPromise; |     return globalConfigPromise; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | export function tenantSetFavicon(tenant: CurrentTenant) { | ||||||
|  |     /** | ||||||
|  |      *  <link rel="icon" href="/static/dist/assets/icons/icon.png"> | ||||||
|  |      *  <link rel="shortcut icon" href="/static/dist/assets/icons/icon.png"> | ||||||
|  |      */ | ||||||
|  |     const rels = ["icon", "shortcut icon"]; | ||||||
|  |     rels.forEach((rel) => { | ||||||
|  |         let relIcon = document.head.querySelector<HTMLLinkElement>(`link[rel='${rel}']`); | ||||||
|  |         if (!relIcon) { | ||||||
|  |             relIcon = document.createElement("link"); | ||||||
|  |             relIcon.rel = rel; | ||||||
|  |             document.getElementsByTagName("head")[0].appendChild(relIcon); | ||||||
|  |         } | ||||||
|  |         relIcon.href = tenant.brandingFavicon; | ||||||
|  |     }); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export function tenantSetLocale(tenant: CurrentTenant) { | ||||||
|  |     if (tenant.defaultLocale === "") { | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  |     console.debug("authentik/locale: setting locale from tenant default"); | ||||||
|  |     activateLocale(tenant.defaultLocale); | ||||||
|  | } | ||||||
|  |  | ||||||
| let globalTenantPromise: Promise<CurrentTenant>; | let globalTenantPromise: Promise<CurrentTenant>; | ||||||
| export function tenant(): Promise<CurrentTenant> { | export function tenant(): Promise<CurrentTenant> { | ||||||
|     if (!globalTenantPromise) { |     if (!globalTenantPromise) { | ||||||
|         globalTenantPromise = new CoreApi(DEFAULT_CONFIG) |         globalTenantPromise = new CoreApi(DEFAULT_CONFIG) | ||||||
|             .coreTenantsCurrentRetrieve() |             .coreTenantsCurrentRetrieve() | ||||||
|             .then((tenant) => { |             .then((tenant) => { | ||||||
|                 /** |                 tenantSetFavicon(tenant); | ||||||
|                  *  <link rel="icon" href="/static/dist/assets/icons/icon.png"> |                 tenantSetLocale(tenant); | ||||||
|                  *  <link rel="shortcut icon" href="/static/dist/assets/icons/icon.png"> |  | ||||||
|                  */ |  | ||||||
|                 const rels = ["icon", "shortcut icon"]; |  | ||||||
|                 rels.forEach((rel) => { |  | ||||||
|                     let relIcon = document.head.querySelector<HTMLLinkElement>( |  | ||||||
|                         `link[rel='${rel}']`, |  | ||||||
|                     ); |  | ||||||
|                     if (!relIcon) { |  | ||||||
|                         relIcon = document.createElement("link"); |  | ||||||
|                         relIcon.rel = rel; |  | ||||||
|                         document.getElementsByTagName("head")[0].appendChild(relIcon); |  | ||||||
|                     } |  | ||||||
|                     relIcon.href = tenant.brandingFavicon; |  | ||||||
|                 }); |  | ||||||
|                 return tenant; |                 return tenant; | ||||||
|             }); |             }); | ||||||
|     } |     } | ||||||
|  | |||||||
							
								
								
									
										14
									
								
								web/src/api/Global.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								web/src/api/Global.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,14 @@ | |||||||
|  | export interface GlobalAuthentik { | ||||||
|  |     locale?: string; | ||||||
|  |     flow?: { | ||||||
|  |         layout: string; | ||||||
|  |     }; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export interface AuthentikWindow { | ||||||
|  |     authentik: GlobalAuthentik; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export function globalAK(): GlobalAuthentik { | ||||||
|  |     return (window as unknown as AuthentikWindow).authentik; | ||||||
|  | } | ||||||
| @ -1,4 +1,5 @@ | |||||||
| import { DEFAULT_CONFIG, tenant } from "@goauthentik/web/api/Config"; | import { DEFAULT_CONFIG, tenant } from "@goauthentik/web/api/Config"; | ||||||
|  | import { globalAK } from "@goauthentik/web/api/Global"; | ||||||
| import { configureSentry } from "@goauthentik/web/api/Sentry"; | import { configureSentry } from "@goauthentik/web/api/Sentry"; | ||||||
| import { WebsocketClient } from "@goauthentik/web/common/ws"; | import { WebsocketClient } from "@goauthentik/web/common/ws"; | ||||||
| import { EVENT_FLOW_ADVANCE, TITLE_DEFAULT } from "@goauthentik/web/constants"; | import { EVENT_FLOW_ADVANCE, TITLE_DEFAULT } from "@goauthentik/web/constants"; | ||||||
| @ -46,14 +47,6 @@ import { | |||||||
|  |  | ||||||
| import { StageHost } from "./stages/base"; | import { StageHost } from "./stages/base"; | ||||||
|  |  | ||||||
| export interface FlowWindow extends Window { |  | ||||||
|     authentik: { |  | ||||||
|         flow: { |  | ||||||
|             layout: LayoutEnum; |  | ||||||
|         }; |  | ||||||
|     }; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| @customElement("ak-flow-executor") | @customElement("ak-flow-executor") | ||||||
| export class FlowExecutor extends LitElement implements StageHost { | export class FlowExecutor extends LitElement implements StageHost { | ||||||
|     flowSlug?: string; |     flowSlug?: string; | ||||||
| @ -435,7 +428,7 @@ export class FlowExecutor extends LitElement implements StageHost { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     getLayout(): string { |     getLayout(): string { | ||||||
|         const prefilledFlow = (window as unknown as FlowWindow).authentik.flow.layout; |         const prefilledFlow = globalAK().flow?.layout || LayoutEnum.Stacked; | ||||||
|         if (this.challenge) { |         if (this.challenge) { | ||||||
|             return this.challenge?.flowInfo?.layout || prefilledFlow; |             return this.challenge?.flowInfo?.layout || prefilledFlow; | ||||||
|         } |         } | ||||||
|  | |||||||
| @ -1,3 +1,5 @@ | |||||||
|  | import { globalAK } from "@goauthentik/web/api/Global"; | ||||||
|  |  | ||||||
| import { Messages, i18n } from "@lingui/core"; | import { Messages, i18n } from "@lingui/core"; | ||||||
| import { detect, fromNavigator, fromUrl } from "@lingui/detect-locale"; | import { detect, fromNavigator, fromUrl } from "@lingui/detect-locale"; | ||||||
| import { t } from "@lingui/macro"; | import { t } from "@lingui/macro"; | ||||||
| @ -121,7 +123,14 @@ const DEFAULT_FALLBACK = () => "en"; | |||||||
|  |  | ||||||
| export function autoDetectLanguage() { | export function autoDetectLanguage() { | ||||||
|     const detected = |     const detected = | ||||||
|         detect(fromUrl("locale"), fromNavigator(), DEFAULT_FALLBACK) || DEFAULT_FALLBACK(); |         detect( | ||||||
|  |             () => { | ||||||
|  |                 return globalAK().locale; | ||||||
|  |             }, | ||||||
|  |             fromUrl("locale"), | ||||||
|  |             fromNavigator(), | ||||||
|  |             DEFAULT_FALLBACK, | ||||||
|  |         ) || DEFAULT_FALLBACK(); | ||||||
|     const locales = [detected]; |     const locales = [detected]; | ||||||
|     // For now we only care about the first locale part |     // For now we only care about the first locale part | ||||||
|     if (detected.includes("_")) { |     if (detected.includes("_")) { | ||||||
|  | |||||||
		Reference in New Issue
	
	Block a user
	 Jens Langhammer
					Jens Langhammer