core: pre-hydrate config into templates to directly load correct assets

closes #3228

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
This commit is contained in:
Jens Langhammer 2022-07-29 00:02:48 +02:00
parent d4af47f576
commit cada292e00
8 changed files with 66 additions and 18 deletions

View File

@ -68,10 +68,9 @@ class ConfigView(APIView):
caps.append(Capabilities.CAN_IMPERSONATE) caps.append(Capabilities.CAN_IMPERSONATE)
return caps return caps
@extend_schema(responses={200: ConfigSerializer(many=False)}) def get_config(self) -> ConfigSerializer:
def get(self, request: Request) -> Response: """Get Config"""
"""Retrieve public configuration options""" return ConfigSerializer(
config = ConfigSerializer(
{ {
"error_reporting": { "error_reporting": {
"enabled": CONFIG.y("error_reporting.enabled"), "enabled": CONFIG.y("error_reporting.enabled"),
@ -86,4 +85,8 @@ class ConfigView(APIView):
"cache_timeout_reputation": int(CONFIG.y("redis.cache_timeout_reputation")), "cache_timeout_reputation": int(CONFIG.y("redis.cache_timeout_reputation")),
} }
) )
return Response(config.data)
@extend_schema(responses={200: ConfigSerializer(many=False)})
def get(self, request: Request) -> Response:
"""Retrieve public configuration options"""
return Response(self.get_config().data)

View File

@ -7,6 +7,12 @@
<script src="{% static 'dist/admin/AdminInterface.js' %}" type="module"></script> <script src="{% static 'dist/admin/AdminInterface.js' %}" type="module"></script>
<meta name="theme-color" content="#18191a" media="(prefers-color-scheme: dark)"> <meta name="theme-color" content="#18191a" media="(prefers-color-scheme: dark)">
<meta name="theme-color" content="#ffffff" media="(prefers-color-scheme: light)"> <meta name="theme-color" content="#ffffff" media="(prefers-color-scheme: light)">
<script>
window.authentik = {};
window.authentik.locale = "{{ tenant.default_locale }}";
window.authentik.config = JSON.parse('{{ config_json|safe }}');
window.authentik.tenant = JSON.parse('{{ tenant_json|safe }}');
</script>
{% endblock %} {% endblock %}
{% block body %} {% block body %}

View File

@ -10,9 +10,10 @@
<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.locale = "{{ tenant.default_locale }}";
}; window.authentik.config = JSON.parse( '{{ config_json|safe }}');
window.authentik.tenant = JSON.parse('{{ tenant_json|safe }}');
window.authentik.flow = { window.authentik.flow = {
"layout": "{{ flow.layout }}", "layout": "{{ flow.layout }}",
}; };

View File

@ -7,6 +7,12 @@
<script src="{% static 'dist/user/UserInterface.js' %}" type="module"></script> <script src="{% static 'dist/user/UserInterface.js' %}" type="module"></script>
<meta name="theme-color" content="#151515" media="(prefers-color-scheme: light)"> <meta name="theme-color" content="#151515" media="(prefers-color-scheme: light)">
<meta name="theme-color" content="#151515" media="(prefers-color-scheme: dark)"> <meta name="theme-color" content="#151515" media="(prefers-color-scheme: dark)">
<script>
window.authentik = {};
window.authentik.locale = "{{ tenant.default_locale }}";
window.authentik.config = JSON.parse('{{ config_json|safe }}');
window.authentik.tenant = JSON.parse('{{ tenant_json|safe }}');
</script>
{% endblock %} {% endblock %}
{% block body %} {% block body %}

View File

@ -4,11 +4,10 @@ from django.contrib.auth.decorators import login_required
from django.urls import path from django.urls import path
from django.views.decorators.csrf import ensure_csrf_cookie from django.views.decorators.csrf import ensure_csrf_cookie
from django.views.generic import RedirectView from django.views.generic import RedirectView
from django.views.generic.base import TemplateView
from authentik.core.views import apps, impersonate from authentik.core.views import apps, impersonate
from authentik.core.views.debug import AccessDeniedView from authentik.core.views.debug import AccessDeniedView
from authentik.core.views.interface import FlowInterfaceView from authentik.core.views.interface import FlowInterfaceView, InterfaceView
from authentik.core.views.session import EndSessionView from authentik.core.views.session import EndSessionView
urlpatterns = [ urlpatterns = [
@ -39,12 +38,12 @@ urlpatterns = [
# Interfaces # Interfaces
path( path(
"if/admin/", "if/admin/",
ensure_csrf_cookie(TemplateView.as_view(template_name="if/admin.html")), ensure_csrf_cookie(InterfaceView.as_view(template_name="if/admin.html")),
name="if-admin", name="if-admin",
), ),
path( path(
"if/user/", "if/user/",
ensure_csrf_cookie(TemplateView.as_view(template_name="if/user.html")), ensure_csrf_cookie(InterfaceView.as_view(template_name="if/user.html")),
name="if-user", name="if-user",
), ),
path( path(
@ -58,10 +57,10 @@ urlpatterns = [
name="if-session-end", name="if-session-end",
), ),
# Fallback for WS # Fallback for WS
path("ws/outpost/<uuid:pk>/", TemplateView.as_view(template_name="if/admin.html")), path("ws/outpost/<uuid:pk>/", InterfaceView.as_view(template_name="if/admin.html")),
path( path(
"ws/client/", "ws/client/",
TemplateView.as_view(template_name="if/admin.html"), InterfaceView.as_view(template_name="if/admin.html"),
), ),
] ]

View File

@ -1,13 +1,26 @@
"""Interface views""" """Interface views"""
from typing import Any from typing import Any
from json import dumps
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from django.views.generic.base import TemplateView from django.views.generic.base import TemplateView
from rest_framework.request import Request
from authentik.api.v3.config import ConfigView
from authentik.flows.models import Flow from authentik.flows.models import Flow
from authentik.tenants.api import CurrentTenantSerializer
class FlowInterfaceView(TemplateView): class InterfaceView(TemplateView):
"""Base interface view"""
def get_context_data(self, **kwargs: Any) -> dict[str, Any]:
kwargs["config_json"] = dumps(ConfigView(request=Request(self.request)).get_config().data)
kwargs["tenant_json"] = dumps(CurrentTenantSerializer(self.request.tenant).data)
return super().get_context_data(**kwargs)
class FlowInterfaceView(InterfaceView):
"""Flow interface""" """Flow interface"""
template_name = "if/flow.html" template_name = "if/flow.html"

View File

@ -1,4 +1,5 @@
import { VERSION } from "@goauthentik/web/constants"; import { globalAK } from "@goauthentik/web/api/Global";
import { EVENT_REFRESH, 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 { activateLocale } from "@goauthentik/web/interfaces/locale";
@ -6,9 +7,11 @@ import { getCookie } from "@goauthentik/web/utils";
import { import {
Config, Config,
ConfigFromJSON,
Configuration, Configuration,
CoreApi, CoreApi,
CurrentTenant, CurrentTenant,
CurrentTenantFromJSON,
FetchParams, FetchParams,
Middleware, Middleware,
RequestContext, RequestContext,
@ -27,7 +30,9 @@ export class LoggingMiddleware implements Middleware {
} }
} }
let globalConfigPromise: Promise<Config>; let globalConfigPromise: Promise<Config> | undefined = Promise.resolve(
ConfigFromJSON(globalAK()?.config),
);
export function config(): Promise<Config> { export function config(): Promise<Config> {
if (!globalConfigPromise) { if (!globalConfigPromise) {
globalConfigPromise = new RootApi(DEFAULT_CONFIG).rootConfigRetrieve(); globalConfigPromise = new RootApi(DEFAULT_CONFIG).rootConfigRetrieve();
@ -60,7 +65,9 @@ export function tenantSetLocale(tenant: CurrentTenant) {
activateLocale(tenant.defaultLocale); activateLocale(tenant.defaultLocale);
} }
let globalTenantPromise: Promise<CurrentTenant>; let globalTenantPromise: Promise<CurrentTenant> | undefined = Promise.resolve(
CurrentTenantFromJSON(globalAK()?.tenant),
);
export function tenant(): Promise<CurrentTenant> { export function tenant(): Promise<CurrentTenant> {
if (!globalTenantPromise) { if (!globalTenantPromise) {
globalTenantPromise = new CoreApi(DEFAULT_CONFIG) globalTenantPromise = new CoreApi(DEFAULT_CONFIG)
@ -108,4 +115,13 @@ export function AndNext(url: string): string {
return `?next=${encodeURIComponent(url)}`; return `?next=${encodeURIComponent(url)}`;
} }
window.addEventListener(EVENT_REFRESH, () => {
// Upon global refresh, disregard whatever was pre-hydrated and
// actually load info from API
globalConfigPromise = undefined;
globalTenantPromise = undefined;
config();
tenant();
});
console.debug(`authentik(early): version ${VERSION}, apiBase ${DEFAULT_CONFIG.basePath}`); console.debug(`authentik(early): version ${VERSION}, apiBase ${DEFAULT_CONFIG.basePath}`);

View File

@ -1,8 +1,12 @@
import { Config, CurrentTenant } from "@goauthentik/api";
export interface GlobalAuthentik { export interface GlobalAuthentik {
locale?: string; locale?: string;
flow?: { flow?: {
layout: string; layout: string;
}; };
config: Config;
tenant: CurrentTenant;
} }
export interface AuthentikWindow { export interface AuthentikWindow {