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:
Jens Langhammer 2022-07-05 23:04:25 +02:00
parent 3344af72c2
commit 0a73e7ac9f
9 changed files with 70 additions and 29 deletions

View File

@ -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
} }

View File

@ -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 }}",
}; };

View File

@ -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"""

View File

@ -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"

View File

@ -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:

View File

@ -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,21 +35,14 @@ export function config(): Promise<Config> {
return globalConfigPromise; return globalConfigPromise;
} }
let globalTenantPromise: Promise<CurrentTenant>; export function tenantSetFavicon(tenant: CurrentTenant) {
export function tenant(): Promise<CurrentTenant> {
if (!globalTenantPromise) {
globalTenantPromise = new CoreApi(DEFAULT_CONFIG)
.coreTenantsCurrentRetrieve()
.then((tenant) => {
/** /**
* <link rel="icon" href="/static/dist/assets/icons/icon.png"> * <link rel="icon" href="/static/dist/assets/icons/icon.png">
* <link rel="shortcut icon" href="/static/dist/assets/icons/icon.png"> * <link rel="shortcut icon" href="/static/dist/assets/icons/icon.png">
*/ */
const rels = ["icon", "shortcut icon"]; const rels = ["icon", "shortcut icon"];
rels.forEach((rel) => { rels.forEach((rel) => {
let relIcon = document.head.querySelector<HTMLLinkElement>( let relIcon = document.head.querySelector<HTMLLinkElement>(`link[rel='${rel}']`);
`link[rel='${rel}']`,
);
if (!relIcon) { if (!relIcon) {
relIcon = document.createElement("link"); relIcon = document.createElement("link");
relIcon.rel = rel; relIcon.rel = rel;
@ -56,6 +50,24 @@ export function tenant(): Promise<CurrentTenant> {
} }
relIcon.href = tenant.brandingFavicon; 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>;
export function tenant(): Promise<CurrentTenant> {
if (!globalTenantPromise) {
globalTenantPromise = new CoreApi(DEFAULT_CONFIG)
.coreTenantsCurrentRetrieve()
.then((tenant) => {
tenantSetFavicon(tenant);
tenantSetLocale(tenant);
return tenant; return tenant;
}); });
} }

14
web/src/api/Global.ts Normal file
View 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;
}

View File

@ -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;
} }

View File

@ -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("_")) {