Compare commits

...

1 Commits

Author SHA1 Message Date
86c1d60093 web: Flesh out static config exports. 2025-04-10 17:16:18 +02:00
29 changed files with 299 additions and 248 deletions

View File

@ -1,6 +1,6 @@
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { VERSION } from "@goauthentik/common/constants"; import { VERSION } from "@goauthentik/common/constants";
import { globalAK } from "@goauthentik/common/global"; import { BrandConfig, ServerConfig } from "@goauthentik/common/global";
import "@goauthentik/elements/EmptyState"; import "@goauthentik/elements/EmptyState";
import { WithBrandConfig } from "@goauthentik/elements/Interface/brandProvider"; import { WithBrandConfig } from "@goauthentik/elements/Interface/brandProvider";
import { WithLicenseSummary } from "@goauthentik/elements/Interface/licenseSummaryProvider"; import { WithLicenseSummary } from "@goauthentik/elements/Interface/licenseSummaryProvider";
@ -33,7 +33,7 @@ export class AboutModal extends WithLicenseSummary(WithBrandConfig(ModalButton))
const status = await new AdminApi(DEFAULT_CONFIG).adminSystemRetrieve(); const status = await new AdminApi(DEFAULT_CONFIG).adminSystemRetrieve();
const version = await new AdminApi(DEFAULT_CONFIG).adminVersionRetrieve(); const version = await new AdminApi(DEFAULT_CONFIG).adminVersionRetrieve();
let build: string | TemplateResult = msg("Release"); let build: string | TemplateResult = msg("Release");
if (globalAK().config.capabilities.includes(CapabilitiesEnum.CanDebug)) { if (ServerConfig.capabilities.includes(CapabilitiesEnum.CanDebug)) {
build = msg("Development"); build = msg("Development");
} else if (version.buildHash !== "") { } else if (version.buildHash !== "") {
build = html`<a build = html`<a
@ -58,7 +58,7 @@ export class AboutModal extends WithLicenseSummary(WithBrandConfig(ModalButton))
} }
renderModal() { renderModal() {
let product = globalAK().brand.brandingTitle || DefaultBrand.brandingTitle; let product = BrandConfig.brandingTitle || DefaultBrand.brandingTitle;
if (this.licenseSummary.status != LicenseSummaryStatusEnum.Unlicensed) { if (this.licenseSummary.status != LicenseSummaryStatusEnum.Unlicensed) {
product += ` ${msg("Enterprise")}`; product += ` ${msg("Enterprise")}`;
} }

View File

@ -5,7 +5,8 @@ import {
GroupMatchingModeToLabel, GroupMatchingModeToLabel,
UserMatchingModeToLabel, UserMatchingModeToLabel,
} from "@goauthentik/admin/sources/oauth/utils"; } from "@goauthentik/admin/sources/oauth/utils";
import { DEFAULT_CONFIG, config } from "@goauthentik/common/api/config"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { ServerConfig } from "@goauthentik/common/global";
import { first } from "@goauthentik/common/utils"; import { first } from "@goauthentik/common/utils";
import "@goauthentik/components/ak-switch-input"; import "@goauthentik/components/ak-switch-input";
import "@goauthentik/components/ak-text-input"; import "@goauthentik/components/ak-text-input";
@ -61,8 +62,7 @@ export class KerberosSourceForm extends WithCapabilitiesConfig(BaseSourceForm<Ke
kerberosSourceRequest: data as unknown as KerberosSourceRequest, kerberosSourceRequest: data as unknown as KerberosSourceRequest,
}); });
} }
const c = await config(); if (ServerConfig.capabilities.includes(CapabilitiesEnum.CanSaveMedia)) {
if (c.capabilities.includes(CapabilitiesEnum.CanSaveMedia)) {
const icon = this.getFormFiles()["icon"]; const icon = this.getFormFiles()["icon"];
if (icon || this.clearIcon) { if (icon || this.clearIcon) {
await new SourcesApi(DEFAULT_CONFIG).sourcesAllSetIconCreate({ await new SourcesApi(DEFAULT_CONFIG).sourcesAllSetIconCreate({

View File

@ -5,7 +5,8 @@ import {
GroupMatchingModeToLabel, GroupMatchingModeToLabel,
UserMatchingModeToLabel, UserMatchingModeToLabel,
} from "@goauthentik/admin/sources/oauth/utils"; } from "@goauthentik/admin/sources/oauth/utils";
import { DEFAULT_CONFIG, config } from "@goauthentik/common/api/config"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { ServerConfig } from "@goauthentik/common/global";
import { first } from "@goauthentik/common/utils"; import { first } from "@goauthentik/common/utils";
import "@goauthentik/elements/CodeMirror"; import "@goauthentik/elements/CodeMirror";
import { CodeMirrorMode } from "@goauthentik/elements/CodeMirror"; import { CodeMirrorMode } from "@goauthentik/elements/CodeMirror";
@ -71,8 +72,7 @@ export class OAuthSourceForm extends WithCapabilitiesConfig(BaseSourceForm<OAuth
oAuthSourceRequest: data as unknown as OAuthSourceRequest, oAuthSourceRequest: data as unknown as OAuthSourceRequest,
}); });
} }
const c = await config(); if (ServerConfig.capabilities.includes(CapabilitiesEnum.CanSaveMedia)) {
if (c.capabilities.includes(CapabilitiesEnum.CanSaveMedia)) {
const icon = this.getFormFiles()["icon"]; const icon = this.getFormFiles()["icon"];
if (icon || this.clearIcon) { if (icon || this.clearIcon) {
await new SourcesApi(DEFAULT_CONFIG).sourcesAllSetIconCreate({ await new SourcesApi(DEFAULT_CONFIG).sourcesAllSetIconCreate({

View File

@ -6,7 +6,8 @@ import {
GroupMatchingModeToLabel, GroupMatchingModeToLabel,
UserMatchingModeToLabel, UserMatchingModeToLabel,
} from "@goauthentik/admin/sources/oauth/utils"; } from "@goauthentik/admin/sources/oauth/utils";
import { DEFAULT_CONFIG, config } from "@goauthentik/common/api/config"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { ServerConfig } from "@goauthentik/common/global";
import { first } from "@goauthentik/common/utils"; import { first } from "@goauthentik/common/utils";
import { import {
CapabilitiesEnum, CapabilitiesEnum,
@ -62,8 +63,7 @@ export class SAMLSourceForm extends WithCapabilitiesConfig(BaseSourceForm<SAMLSo
sAMLSourceRequest: data, sAMLSourceRequest: data,
}); });
} }
const c = await config(); if (ServerConfig.capabilities.includes(CapabilitiesEnum.CanSaveMedia)) {
if (c.capabilities.includes(CapabilitiesEnum.CanSaveMedia)) {
const icon = this.getFormFiles()["icon"]; const icon = this.getFormFiles()["icon"];
if (icon || this.clearIcon) { if (icon || this.clearIcon) {
await new SourcesApi(DEFAULT_CONFIG).sourcesAllSetIconCreate({ await new SourcesApi(DEFAULT_CONFIG).sourcesAllSetIconCreate({

View File

@ -1,5 +1,5 @@
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { globalAK } from "@goauthentik/common/global"; import { APIConfig } from "@goauthentik/common/global";
import "@goauthentik/components/ak-text-input"; import "@goauthentik/components/ak-text-input";
import { Form } from "@goauthentik/elements/forms/Form"; import { Form } from "@goauthentik/elements/forms/Form";
@ -21,7 +21,7 @@ export class UserImpersonateForm extends Form<ImpersonationRequest> {
impersonationRequest: data, impersonationRequest: data,
}) })
.then(() => { .then(() => {
window.location.href = globalAK().api.base; window.location.href = APIConfig.base;
}); });
} }

View File

@ -3,79 +3,40 @@ import {
EventMiddleware, EventMiddleware,
LoggingMiddleware, LoggingMiddleware,
} from "@goauthentik/common/api/middleware"; } from "@goauthentik/common/api/middleware";
import { EVENT_LOCALE_REQUEST, VERSION } from "@goauthentik/common/constants"; import { VERSION } from "@goauthentik/common/constants";
import { globalAK } from "@goauthentik/common/global"; import { APIConfig, BrandConfig } from "@goauthentik/common/global";
import { Config, Configuration, CoreApi, CurrentBrand, RootApi } from "@goauthentik/api"; import { Configuration as ApiConfiguration } from "@goauthentik/api";
let globalConfigPromise: Promise<Config> | undefined = Promise.resolve(globalAK().config); /**
export function config(): Promise<Config> { * Extract the content of a meta tag by name.
if (!globalConfigPromise) { *
globalConfigPromise = new RootApi(DEFAULT_CONFIG).rootConfigRetrieve(); * @todo Can we memoize this?
} */
return globalConfigPromise; function extractMetaContent(name: string): string {
} const metaEl = document.querySelector<HTMLMetaElement>(`meta[name=${name}]`);
export function brandSetFavicon(brand: CurrentBrand) {
/**
* <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 = brand.brandingFavicon;
});
}
export function brandSetLocale(brand: CurrentBrand) {
if (brand.defaultLocale === "") {
return;
}
console.debug("authentik/locale: setting locale from brand default");
window.dispatchEvent(
new CustomEvent(EVENT_LOCALE_REQUEST, {
composed: true,
bubbles: true,
detail: { locale: brand.defaultLocale },
}),
);
}
let globalBrandPromise: Promise<CurrentBrand> | undefined = Promise.resolve(globalAK().brand);
export function brand(): Promise<CurrentBrand> {
if (!globalBrandPromise) {
globalBrandPromise = new CoreApi(DEFAULT_CONFIG)
.coreBrandsCurrentRetrieve()
.then((brand) => {
brandSetFavicon(brand);
brandSetLocale(brand);
return brand;
});
}
return globalBrandPromise;
}
export function getMetaContent(key: string): string {
const metaEl = document.querySelector<HTMLMetaElement>(`meta[name=${key}]`);
if (!metaEl) return ""; if (!metaEl) return "";
return metaEl.content; return metaEl.content;
} }
export const DEFAULT_CONFIG = new Configuration({ /**
basePath: `${globalAK().api.base}api/v3`, * Default API Configuration.
*
* @todo This is a frequent source of duplication when working with the API.
* We should consider moving this to a more central location.
*/
export const DEFAULT_CONFIG = new ApiConfiguration({
basePath: `${APIConfig.base}api/v3`,
headers: { headers: {
"sentry-trace": getMetaContent("sentry-trace"), "sentry-trace": extractMetaContent("sentry-trace"),
}, },
middleware: [ middleware: [
// ---
new CSRFMiddleware(), new CSRFMiddleware(),
new EventMiddleware(), new EventMiddleware(),
new LoggingMiddleware(globalAK().brand), new LoggingMiddleware(BrandConfig),
], ],
}); });

View File

@ -1,59 +1,142 @@
import { Config, ConfigFromJSON, CurrentBrand, CurrentBrandFromJSON } from "@goauthentik/api"; import { Config, ConfigFromJSON, CurrentBrand, CurrentBrandFromJSON } from "@goauthentik/api";
export interface GlobalAuthentik { export interface APIConfig {
_converted?: boolean; /**
* Absolute base path to the API.
*/
base: string;
/**
* Relative base path to the API.
*/
relBase: string;
}
export interface FlowConfig {
/**
* The current flow ID.
*/
layout: string;
}
export interface SerializedClientState {
/**
* The BCP47 language tag.
*/
locale?: string; locale?: string;
flow?: { /**
layout: string; * Current flow state.
}; */
config: Config; flow?: FlowConfig;
brand: CurrentBrand; /**
* Server configuration.
*/
config: unknown;
/**
* Branding information.
*/
brand: unknown;
/**
* The major and minor components of the current version.
*/
versionFamily: string; versionFamily: string;
/**
* A subdomain compatible SemVer.
*/
versionSubdomain: string; versionSubdomain: string;
/**
* The current build hash.
*/
build: string; build: string;
api: {
base: string; /**
relBase: string; * The API configuration.
*/
api: APIConfig;
}
type ClientConfigRealm<T> = T & {
readonly authentik: Readonly<SerializedClientState>;
};
function isClientConfigRealm<T>(namespace: object): namespace is ClientConfigRealm<T> {
return typeof namespace === "object" && "authentik" in namespace;
}
/**
* The current locale as defined by the server.
*
* @format BCP47
*/
export let ServerLocale = "";
export let FlowConfig: FlowConfig | undefined;
/**
* The current build hash.
*/
export let BuildHash = "";
/**
* The major and minor components of the SemVer.
*/
export let VersionFamily = "";
/**
* A subdomain compatible SemVer.
*/
export let VersionSubdomain = "";
/**
* The parsed API configuration extracted from the global scope.
*/
export let APIConfig: Readonly<APIConfig>;
/**
* The parsed server configuration extracted from the global scope.
*/
export let ServerConfig: Readonly<Config>;
/**
* The parsed brand configuration extracted from the global scope.
*/
export let BrandConfig: Readonly<CurrentBrand>;
if (!isClientConfigRealm(self)) {
const apiOrigin = new URL(process.env.AK_API_BASE_PATH || window.location.origin);
APIConfig = {
base: apiOrigin.toString(),
relBase: apiOrigin.pathname,
}; };
BrandConfig = CurrentBrandFromJSON({
ui_footer_links: [],
});
} else {
ServerLocale = self.authentik.locale || "";
FlowConfig = self.authentik.flow;
BuildHash = self.authentik.build;
VersionFamily = self.authentik.versionFamily;
VersionSubdomain = self.authentik.versionSubdomain;
ServerConfig = ConfigFromJSON(self.authentik.config);
BrandConfig = CurrentBrandFromJSON(self.authentik.brand);
APIConfig = self.authentik.api;
} }
export interface AuthentikWindow { /**
authentik: GlobalAuthentik; * Generate a link to the documentation.
} */
export function docLink(documentationPath: string): string {
const origin =
// Default case or beta build which should always point to latest
BuildHash || !VersionSubdomain
? "https://goauthentik.io"
: `https://${VersionSubdomain}.goauthentik.io`;
export function globalAK(): GlobalAuthentik { const docsURL = new URL(documentationPath, origin);
const ak = (window as unknown as AuthentikWindow).authentik;
if (ak && !ak._converted) {
ak._converted = true;
ak.brand = CurrentBrandFromJSON(ak.brand);
ak.config = ConfigFromJSON(ak.config);
}
const apiBase = new URL(process.env.AK_API_BASE_PATH || window.location.origin);
if (!ak) {
return {
config: ConfigFromJSON({
capabilities: [],
}),
brand: CurrentBrandFromJSON({
ui_footer_links: [],
}),
versionFamily: "",
versionSubdomain: "",
build: "",
api: {
base: apiBase.toString(),
relBase: apiBase.pathname,
},
};
}
return ak;
}
export function docLink(path: string): string { return docsURL.toString();
const ak = globalAK();
// Default case or beta build which should always point to latest
if (!ak || ak.build !== "") {
return `https://goauthentik.io${path}`;
}
return `https://${ak.versionSubdomain}.goauthentik.io${path}`;
} }

View File

@ -1,98 +1,106 @@
import { config } from "@goauthentik/common/api/config";
import { VERSION } from "@goauthentik/common/constants"; import { VERSION } from "@goauthentik/common/constants";
import { SentryIgnoredError } from "@goauthentik/common/errors";
import { ServerConfig } from "@goauthentik/common/global";
import { me } from "@goauthentik/common/users"; import { me } from "@goauthentik/common/users";
import { import { browserTracingIntegration, init, setTag, setUser } from "@sentry/browser";
ErrorEvent,
EventHint,
browserTracingIntegration,
init,
setTag,
setUser,
} from "@sentry/browser";
import { CapabilitiesEnum, Config, ResponseError } from "@goauthentik/api"; import { CapabilitiesEnum, ResponseError } from "@goauthentik/api";
/**
* A generic error that can be thrown without triggering Sentry's reporting.
*/
export class SentryIgnoredError extends Error {}
export const TAG_SENTRY_COMPONENT = "authentik.component"; export const TAG_SENTRY_COMPONENT = "authentik.component";
export const TAG_SENTRY_CAPABILITIES = "authentik.capabilities"; export const TAG_SENTRY_CAPABILITIES = "authentik.capabilities";
export async function configureSentry(canDoPpi = false): Promise<Config> { /**
const cfg = await config(); * Configure Sentry with the given configuration.
*
* @param canSendPII Whether the user can send personally identifiable information.
*/
export async function configureSentry(canSendPII?: boolean): Promise<void> {
if (!ServerConfig.errorReporting.enabled) return;
const { capabilities, errorReporting } = ServerConfig;
if (cfg.errorReporting.enabled) { init({
init({ dsn: errorReporting.sentryDsn,
dsn: cfg.errorReporting.sentryDsn, ignoreErrors: [
ignoreErrors: [ /network/gi,
/network/gi, /fetch/gi,
/fetch/gi, /module/gi,
/module/gi, // Error on edge on ios,
// Error on edge on ios, // https://stackoverflow.com/questions/69261499/what-is-instantsearchsdkjsbridgeclearhighlight
// https://stackoverflow.com/questions/69261499/what-is-instantsearchsdkjsbridgeclearhighlight /instantSearchSDKJSBridgeClearHighlight/gi,
/instantSearchSDKJSBridgeClearHighlight/gi, // Seems to be an issue in Safari and Firefox
// Seems to be an issue in Safari and Firefox /MutationObserver.observe/gi,
/MutationObserver.observe/gi, /NS_ERROR_FAILURE/gi,
/NS_ERROR_FAILURE/gi, ],
], release: `authentik@${VERSION}`,
release: `authentik@${VERSION}`, integrations: [
integrations: [ browserTracingIntegration({
browserTracingIntegration({ shouldCreateSpanForRequest: (url: string) => {
shouldCreateSpanForRequest: (url: string) => { return url.startsWith(window.location.host);
return url.startsWith(window.location.host); },
}, }),
}), ],
], tracesSampleRate: errorReporting.tracesSampleRate,
tracesSampleRate: cfg.errorReporting.tracesSampleRate, environment: errorReporting.environment,
environment: cfg.errorReporting.environment, beforeSend: (event, hint) => {
beforeSend: ( if (!hint) return event;
event: ErrorEvent,
hint: EventHint,
): ErrorEvent | PromiseLike<ErrorEvent | null> | null => {
if (!hint) {
return event;
}
if (hint.originalException instanceof SentryIgnoredError) {
return null;
}
if (
hint.originalException instanceof ResponseError ||
hint.originalException instanceof DOMException
) {
return null;
}
return event;
},
});
setTag(TAG_SENTRY_CAPABILITIES, cfg.capabilities.join(","));
if (window.location.pathname.includes("if/")) {
setTag(TAG_SENTRY_COMPONENT, `web/${currentInterface()}`);
}
if (cfg.capabilities.includes(CapabilitiesEnum.CanDebug)) {
const Spotlight = await import("@spotlightjs/spotlight");
Spotlight.init({ injectImmediately: true }); const { originalException } = hint;
}
if (cfg.errorReporting.sendPii && canDoPpi) { if (originalException instanceof SentryIgnoredError) {
me().then((user) => { return null;
setUser({ email: user.user.email }); }
console.debug("authentik/config: Sentry with PII enabled.");
if (originalException instanceof ResponseError) return null;
if (originalException instanceof DOMException) return null;
return event;
},
});
setTag(TAG_SENTRY_CAPABILITIES, capabilities.join(","));
if (window.location.pathname.includes("if/")) {
setTag(TAG_SENTRY_COMPONENT, `web/${currentInterface()}`);
}
if (
// Retain this predicate order to allow ESBuild to tree-shake the import in production.
process.env.NODE_ENV === "development" &&
capabilities.includes(CapabilitiesEnum.CanDebug)
) {
await import("@spotlightjs/spotlight")
.then((Spotlight) => {
return Spotlight.init({
injectImmediately: true,
});
})
.catch((error) => {
console.error("Failed to init Spotlight", error);
}); });
} else { }
console.debug("authentik/config: Sentry enabled.");
if (errorReporting.sendPii && canSendPII) {
const session = await me().catch(() => null);
if (session) {
setUser({ email: session.user.email });
console.debug("authentik/config: Sentry PII enabled.");
} }
} }
return cfg;
console.debug("authentik/config: Sentry enabled.");
} }
// Get the interface name from URL /**
* Get the current interface from the URL.
*/
export function currentInterface(): string { export function currentInterface(): string {
const pathMatches = window.location.pathname.match(/.+if\/(\w+)\//); const pathMatches = window.location.pathname.match(/.+if\/(\w+)\//);
let currentInterface = "unknown"; let currentInterface = "unknown";
if (pathMatches && pathMatches.length >= 2) { if (pathMatches && pathMatches.length >= 2) {
currentInterface = pathMatches[1]; currentInterface = pathMatches[1];
} }
return currentInterface.toLowerCase(); return currentInterface.toLowerCase();
} }

View File

@ -1,5 +1,5 @@
import { EVENT_MESSAGE, EVENT_WS_MESSAGE } from "@goauthentik/common/constants"; import { EVENT_MESSAGE, EVENT_WS_MESSAGE } from "@goauthentik/common/constants";
import { globalAK } from "@goauthentik/common/global"; import { APIConfig } from "@goauthentik/common/global";
import { MessageLevel } from "@goauthentik/common/messages"; import { MessageLevel } from "@goauthentik/common/messages";
import { msg } from "@lit/localize"; import { msg } from "@lit/localize";
@ -22,7 +22,8 @@ export class WebsocketClient {
connect(): void { connect(): void {
if (navigator.webdriver) return; if (navigator.webdriver) return;
const apiURL = new URL(globalAK().api.base);
const apiURL = new URL(APIConfig.base);
const wsUrl = `${window.location.protocol.replace("http", "ws")}//${apiURL.host}${apiURL.pathname}ws/client/`; const wsUrl = `${window.location.protocol.replace("http", "ws")}//${apiURL.host}${apiURL.pathname}ws/client/`;
this.messageSocket = new WebSocket(wsUrl); this.messageSocket = new WebSocket(wsUrl);
this.messageSocket.addEventListener("open", () => { this.messageSocket.addEventListener("open", () => {

View File

@ -3,7 +3,7 @@ import {
EVENT_API_DRAWER_TOGGLE, EVENT_API_DRAWER_TOGGLE,
EVENT_NOTIFICATION_DRAWER_TOGGLE, EVENT_NOTIFICATION_DRAWER_TOGGLE,
} from "@goauthentik/common/constants"; } from "@goauthentik/common/constants";
import { globalAK } from "@goauthentik/common/global"; import { APIConfig } from "@goauthentik/common/global";
import { UIConfig, UserDisplay, uiConfig } from "@goauthentik/common/ui/config"; import { UIConfig, UserDisplay, uiConfig } from "@goauthentik/common/ui/config";
import { me } from "@goauthentik/common/users"; import { me } from "@goauthentik/common/users";
import { AKElement } from "@goauthentik/elements/Base"; import { AKElement } from "@goauthentik/elements/Base";
@ -146,7 +146,7 @@ export class NavigationButtons extends AKElement {
<a <a
class="pf-c-button pf-m-plain" class="pf-c-button pf-m-plain"
type="button" type="button"
href="${globalAK().api.base}if/user/#/settings" href="${APIConfig.base}if/user/#/settings"
> >
<pf-tooltip position="top" content=${msg("Settings")}> <pf-tooltip position="top" content=${msg("Settings")}>
<i class="fas fa-cog" aria-hidden="true"></i> <i class="fas fa-cog" aria-hidden="true"></i>
@ -194,7 +194,7 @@ export class NavigationButtons extends AKElement {
${this.renderSettings()} ${this.renderSettings()}
<div class="pf-c-page__header-tools-item"> <div class="pf-c-page__header-tools-item">
<a <a
href="${globalAK().api.base}flows/-/default/invalidation/" href="${APIConfig.base}flows/-/default/invalidation/"
class="pf-c-button pf-m-plain" class="pf-c-button pf-m-plain"
> >
<pf-tooltip position="top" content=${msg("Sign out")}> <pf-tooltip position="top" content=${msg("Sign out")}>

View File

@ -1,5 +1,5 @@
import { EVENT_THEME_CHANGE } from "@goauthentik/common/constants"; import { EVENT_THEME_CHANGE } from "@goauthentik/common/constants";
import { globalAK } from "@goauthentik/common/global"; import { BrandConfig } from "@goauthentik/common/global";
import { UIConfig } from "@goauthentik/common/ui/config"; import { UIConfig } from "@goauthentik/common/ui/config";
import { adaptCSS } from "@goauthentik/common/utils"; import { adaptCSS } from "@goauthentik/common/utils";
import { ensureCSSStyleSheet } from "@goauthentik/elements/utils/ensureCSSStyleSheet"; import { ensureCSSStyleSheet } from "@goauthentik/elements/utils/ensureCSSStyleSheet";
@ -78,7 +78,7 @@ export class AKElement extends LitElement {
async _initTheme(root: DocumentOrShadowRoot): Promise<void> { async _initTheme(root: DocumentOrShadowRoot): Promise<void> {
// Early activate theme based on media query to prevent light flash // Early activate theme based on media query to prevent light flash
// when dark is preferred // when dark is preferred
this._applyTheme(root, globalAK().brand.uiTheme); this._applyTheme(root, BrandConfig.uiTheme);
this._applyTheme(root, await this.getTheme()); this._applyTheme(root, await this.getTheme());
} }

View File

@ -1,6 +1,6 @@
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { EVENT_REFRESH } from "@goauthentik/common/constants"; import { EVENT_REFRESH } from "@goauthentik/common/constants";
import { globalAK } from "@goauthentik/common/global"; import { ServerConfig } from "@goauthentik/common/global";
import { authentikConfigContext } from "@goauthentik/elements/AuthentikContexts"; import { authentikConfigContext } from "@goauthentik/elements/AuthentikContexts";
import type { ReactiveElementHost } from "@goauthentik/elements/types.js"; import type { ReactiveElementHost } from "@goauthentik/elements/types.js";
@ -24,8 +24,8 @@ export class ConfigContextController implements ReactiveController {
initialValue: undefined, initialValue: undefined,
}); });
// Pre-hydrate from template-embedded config // Pre-hydrate from template-embedded config
this.context.setValue(globalAK().config); this.context.setValue(ServerConfig);
this.host.config = globalAK().config; this.host.config = ServerConfig;
this.fetch = this.fetch.bind(this); this.fetch = this.fetch.bind(this);
this.fetch(); this.fetch();
} }

View File

@ -3,7 +3,7 @@ import {
EVENT_WS_MESSAGE, EVENT_WS_MESSAGE,
TITLE_DEFAULT, TITLE_DEFAULT,
} from "@goauthentik/common/constants"; } from "@goauthentik/common/constants";
import { globalAK } from "@goauthentik/common/global"; import { APIConfig } from "@goauthentik/common/global";
import { currentInterface } from "@goauthentik/common/sentry"; import { currentInterface } from "@goauthentik/common/sentry";
import { UIConfig, UserDisplay, uiConfig } from "@goauthentik/common/ui/config"; import { UIConfig, UserDisplay, uiConfig } from "@goauthentik/common/ui/config";
import { me } from "@goauthentik/common/users"; import { me } from "@goauthentik/common/users";
@ -186,7 +186,7 @@ export class PageHeader extends WithBrandConfig(AKElement) {
<ak-nav-buttons .uiConfig=${this.uiConfig} .me=${this.me}> <ak-nav-buttons .uiConfig=${this.uiConfig} .me=${this.me}>
<a <a
class="pf-c-button pf-m-secondary pf-m-small pf-u-display-none pf-u-display-block-on-md" class="pf-c-button pf-m-secondary pf-m-small pf-u-display-none pf-u-display-block-on-md"
href="${globalAK().api.base}if/user/" href="${APIConfig.base}if/user/"
slot="extra" slot="extra"
> >
${msg("User interface")} ${msg("User interface")}

View File

@ -1,4 +1,4 @@
import { globalAK } from "@goauthentik/common/global"; import { ServerLocale } from "@goauthentik/common/global";
import { LOCALES as RAW_LOCALES, enLocale } from "./definitions"; import { LOCALES as RAW_LOCALES, enLocale } from "./definitions";
import { AkLocale } from "./types"; import { AkLocale } from "./types";
@ -51,7 +51,7 @@ export function autoDetectLanguage(userReq = TOMBSTONE, brandReq = TOMBSTONE): s
userReq, userReq,
window.navigator?.language ?? TOMBSTONE, window.navigator?.language ?? TOMBSTONE,
brandReq, brandReq,
globalAK()?.locale ?? TOMBSTONE, ServerLocale ?? TOMBSTONE,
DEFAULT_LOCALE, DEFAULT_LOCALE,
].filter(isLocaleCandidate); ].filter(isLocaleCandidate);

View File

@ -1,4 +1,4 @@
import { globalAK } from "@goauthentik/common/global"; import { APIConfig } from "@goauthentik/common/global";
import { AKElement } from "@goauthentik/elements/Base"; import { AKElement } from "@goauthentik/elements/Base";
import { WithLicenseSummary } from "@goauthentik/elements/Interface/licenseSummaryProvider"; import { WithLicenseSummary } from "@goauthentik/elements/Interface/licenseSummaryProvider";
@ -76,7 +76,7 @@ export class EnterpriseStatusBanner extends WithLicenseSummary(AKElement) {
: "pf-m-gold"}" : "pf-m-gold"}"
> >
${message} ${message}
<a href="${globalAK().api.base}if/admin/#/enterprise/licenses" <a href="${APIConfig.base}if/admin/#/enterprise/licenses"
>${msg("Click here for more info.")}</a >${msg("Click here for more info.")}</a
> >
</div>`; </div>`;

View File

@ -1,6 +1,6 @@
import { RequestInfo } from "@goauthentik/common/api/middleware"; import { RequestInfo } from "@goauthentik/common/api/middleware";
import { EVENT_API_DRAWER_TOGGLE, EVENT_REQUEST_POST } from "@goauthentik/common/constants"; import { EVENT_API_DRAWER_TOGGLE, EVENT_REQUEST_POST } from "@goauthentik/common/constants";
import { globalAK } from "@goauthentik/common/global"; import { APIConfig } from "@goauthentik/common/global";
import { formatElapsedTime } from "@goauthentik/common/temporal"; import { formatElapsedTime } from "@goauthentik/common/temporal";
import { AKElement } from "@goauthentik/elements/Base"; import { AKElement } from "@goauthentik/elements/Base";
@ -92,7 +92,7 @@ export class APIDrawer extends AKElement {
<h1 class="pf-c-notification-drawer__header-title"> <h1 class="pf-c-notification-drawer__header-title">
${msg("API Requests")} ${msg("API Requests")}
</h1> </h1>
<a href="${globalAK().api.base}api/v3/" target="_blank" <a href="${APIConfig.base}api/v3/" target="_blank"
>${msg("Open API Browser")}</a >${msg("Open API Browser")}</a
> >
</div> </div>

View File

@ -1,6 +1,6 @@
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { EVENT_NOTIFICATION_DRAWER_TOGGLE, EVENT_REFRESH } from "@goauthentik/common/constants"; import { EVENT_NOTIFICATION_DRAWER_TOGGLE, EVENT_REFRESH } from "@goauthentik/common/constants";
import { globalAK } from "@goauthentik/common/global"; import { APIConfig } from "@goauthentik/common/global";
import { actionToLabel } from "@goauthentik/common/labels"; import { actionToLabel } from "@goauthentik/common/labels";
import { MessageLevel } from "@goauthentik/common/messages"; import { MessageLevel } from "@goauthentik/common/messages";
import { formatElapsedTime } from "@goauthentik/common/temporal"; import { formatElapsedTime } from "@goauthentik/common/temporal";
@ -99,7 +99,7 @@ export class NotificationDrawer extends AKElement {
html` html`
<a <a
class="pf-c-dropdown__toggle pf-m-plain" class="pf-c-dropdown__toggle pf-m-plain"
href="${globalAK().api.base}if/admin/#/events/log/${item.event?.pk}" href="${APIConfig.base}if/admin/#/events/log/${item.event?.pk}"
> >
<pf-tooltip position="top" content=${msg("Show details")}> <pf-tooltip position="top" content=${msg("Show details")}>
<i class="fas fa-share-square"></i> <i class="fas fa-share-square"></i>

View File

@ -1,5 +1,5 @@
import type { AdminInterface } from "@goauthentik/admin/AdminInterface/AdminInterface"; import type { AdminInterface } from "@goauthentik/admin/AdminInterface/AdminInterface";
import { globalAK } from "@goauthentik/common/global"; import { BrandConfig } from "@goauthentik/common/global";
import { AKElement, rootInterface } from "@goauthentik/elements/Base"; import { AKElement, rootInterface } from "@goauthentik/elements/Base";
import { WithLicenseSummary } from "@goauthentik/elements/Interface/licenseSummaryProvider"; import { WithLicenseSummary } from "@goauthentik/elements/Interface/licenseSummaryProvider";
import { WithVersion } from "@goauthentik/elements/Interface/versionProvider"; import { WithVersion } from "@goauthentik/elements/Interface/versionProvider";
@ -45,7 +45,7 @@ export class SidebarVersion extends WithLicenseSummary(WithVersion(AKElement)) {
if (!this.version || !this.licenseSummary) { if (!this.version || !this.licenseSummary) {
return nothing; return nothing;
} }
let product = globalAK().brand.brandingTitle || DefaultBrand.brandingTitle; let product = BrandConfig.brandingTitle || DefaultBrand.brandingTitle;
if (this.licenseSummary.status != LicenseSummaryStatusEnum.Unlicensed) { if (this.licenseSummary.status != LicenseSummaryStatusEnum.Unlicensed) {
product += ` ${msg("Enterprise")}`; product += ` ${msg("Enterprise")}`;
} }

View File

@ -4,7 +4,7 @@ import {
EVENT_FLOW_INSPECTOR_TOGGLE, EVENT_FLOW_INSPECTOR_TOGGLE,
TITLE_DEFAULT, TITLE_DEFAULT,
} from "@goauthentik/common/constants"; } from "@goauthentik/common/constants";
import { globalAK } from "@goauthentik/common/global"; import { BrandConfig, FlowConfig } from "@goauthentik/common/global";
import { configureSentry } from "@goauthentik/common/sentry"; import { configureSentry } from "@goauthentik/common/sentry";
import { first } from "@goauthentik/common/utils"; import { first } from "@goauthentik/common/utils";
import { WebsocketClient } from "@goauthentik/common/ws"; import { WebsocketClient } from "@goauthentik/common/ws";
@ -201,7 +201,7 @@ export class FlowExecutor extends Interface implements StageHost {
} }
async getTheme(): Promise<UiThemeEnum> { async getTheme(): Promise<UiThemeEnum> {
return globalAK()?.brand.uiTheme || UiThemeEnum.Automatic; return BrandConfig.uiTheme || UiThemeEnum.Automatic;
} }
async submit( async submit(
@ -487,7 +487,7 @@ export class FlowExecutor extends Interface implements StageHost {
} }
getLayout(): string { getLayout(): string {
const prefilledFlow = globalAK()?.flow?.layout || FlowLayoutEnum.Stacked; const prefilledFlow = FlowConfig?.layout || FlowLayoutEnum.Stacked;
if (this.challenge) { if (this.challenge) {
return this.challenge?.flowInfo?.layout || prefilledFlow; return this.challenge?.flowInfo?.layout || prefilledFlow;
} }
@ -528,7 +528,7 @@ export class FlowExecutor extends Interface implements StageHost {
src="${themeImage( src="${themeImage(
first( first(
this.brand?.brandingLogo, this.brand?.brandingLogo,
globalAK()?.brand.brandingLogo, BrandConfig.brandingLogo,
DefaultBrand.brandingLogo, DefaultBrand.brandingLogo,
), ),
)}" )}"

View File

@ -1,4 +1,4 @@
import { globalAK } from "@goauthentik/common/global"; import { APIConfig } from "@goauthentik/common/global";
import "@goauthentik/flow/FormStatic"; import "@goauthentik/flow/FormStatic";
import { BaseStage } from "@goauthentik/flow/stages/base"; import { BaseStage } from "@goauthentik/flow/stages/base";
@ -48,7 +48,7 @@ export class SessionEnd extends BaseStage<SessionEndChallenge, unknown> {
str`You've logged out of ${this.challenge.applicationName}. You can go back to the overview to launch another application, or log out of your authentik account.`, str`You've logged out of ${this.challenge.applicationName}. You can go back to the overview to launch another application, or log out of your authentik account.`,
)} )}
</p> </p>
<a href="${globalAK().api.base}" class="pf-c-button pf-m-primary"> <a href="${APIConfig.base}" class="pf-c-button pf-m-primary">
${msg("Go back to overview")} ${msg("Go back to overview")}
</a> </a>
${this.challenge.invalidationFlowUrl ${this.challenge.invalidationFlowUrl

View File

@ -1,6 +1,6 @@
import { CSRFHeaderName } from "@goauthentik/common/api/middleware"; import { CSRFHeaderName } from "@goauthentik/common/api/middleware";
import { EVENT_THEME_CHANGE } from "@goauthentik/common/constants"; import { EVENT_THEME_CHANGE } from "@goauthentik/common/constants";
import { globalAK } from "@goauthentik/common/global"; import { BrandConfig } from "@goauthentik/common/global";
import { first, getCookie } from "@goauthentik/common/utils"; import { first, getCookie } from "@goauthentik/common/utils";
import { Interface } from "@goauthentik/elements/Interface"; import { Interface } from "@goauthentik/elements/Interface";
import "@goauthentik/elements/ak-locale-context"; import "@goauthentik/elements/ak-locale-context";
@ -61,7 +61,7 @@ export class APIBrowser extends Interface {
} }
async getTheme(): Promise<UiThemeEnum> { async getTheme(): Promise<UiThemeEnum> {
return globalAK()?.brand.uiTheme || UiThemeEnum.Automatic; return BrandConfig.uiTheme || UiThemeEnum.Automatic;
} }
render(): TemplateResult { render(): TemplateResult {

View File

@ -1,4 +1,4 @@
import { globalAK } from "@goauthentik/common/global"; import { BrandConfig } from "@goauthentik/common/global";
import { Interface } from "@goauthentik/elements/Interface"; import { Interface } from "@goauthentik/elements/Interface";
import { msg } from "@lit/localize"; import { msg } from "@lit/localize";
@ -39,7 +39,7 @@ export class Loading extends Interface {
} }
async getTheme(): Promise<UiThemeEnum> { async getTheme(): Promise<UiThemeEnum> {
return globalAK()?.brand.uiTheme || UiThemeEnum.Automatic; return BrandConfig.uiTheme || UiThemeEnum.Automatic;
} }
render(): TemplateResult { render(): TemplateResult {

View File

@ -1,5 +1,5 @@
import { PFSize } from "@goauthentik/common/enums.js"; import { PFSize } from "@goauthentik/common/enums.js";
import { globalAK } from "@goauthentik/common/global"; import { APIConfig } from "@goauthentik/common/global";
import { truncateWords } from "@goauthentik/common/utils"; import { truncateWords } from "@goauthentik/common/utils";
import "@goauthentik/elements/AppIcon"; import "@goauthentik/elements/AppIcon";
import { AKElement, rootInterface } from "@goauthentik/elements/Base"; import { AKElement, rootInterface } from "@goauthentik/elements/Base";
@ -82,8 +82,7 @@ export class LibraryApplication extends AKElement {
? html` ? html`
<a <a
class="pf-c-button pf-m-control pf-m-small pf-m-block" class="pf-c-button pf-m-control pf-m-small pf-m-block"
href="${globalAK().api href="${APIConfig.base}if/admin/#/core/applications/${application?.slug}"
.base}if/admin/#/core/applications/${application?.slug}"
> >
<i class="fas fa-edit"></i>&nbsp;${msg("Edit")} <i class="fas fa-edit"></i>&nbsp;${msg("Edit")}
</a> </a>

View File

@ -1,4 +1,4 @@
import { docLink, globalAK } from "@goauthentik/common/global"; import { APIConfig, docLink } from "@goauthentik/common/global";
import { AKElement } from "@goauthentik/elements/Base"; import { AKElement } from "@goauthentik/elements/Base";
import { paramURL } from "@goauthentik/elements/router/RouterOutlet"; import { paramURL } from "@goauthentik/elements/router/RouterOutlet";
@ -49,7 +49,7 @@ export class LibraryPageApplicationEmptyList extends AKElement {
<a <a
aria-disabled="false" aria-disabled="false"
class="cta pf-c-button pf-m-secondary" class="cta pf-c-button pf-m-secondary"
href="${globalAK().api.base}if/admin/${href}" href="${APIConfig.base}if/admin/${href}"
>${msg("Create a new application")}</a >${msg("Create a new application")}</a
> >
</div> </div>

View File

@ -4,7 +4,7 @@ import {
EVENT_NOTIFICATION_DRAWER_TOGGLE, EVENT_NOTIFICATION_DRAWER_TOGGLE,
EVENT_WS_MESSAGE, EVENT_WS_MESSAGE,
} from "@goauthentik/common/constants"; } from "@goauthentik/common/constants";
import { globalAK } from "@goauthentik/common/global"; import { APIConfig } from "@goauthentik/common/global";
import { configureSentry } from "@goauthentik/common/sentry"; import { configureSentry } from "@goauthentik/common/sentry";
import { UIConfig } from "@goauthentik/common/ui/config"; import { UIConfig } from "@goauthentik/common/ui/config";
import { me } from "@goauthentik/common/users"; import { me } from "@goauthentik/common/users";
@ -166,14 +166,14 @@ class UserInterfacePresentation extends AKElement {
return html`<a return html`<a
class="pf-c-button pf-m-secondary pf-m-small pf-u-display-none pf-u-display-block-on-md" class="pf-c-button pf-m-secondary pf-m-small pf-u-display-none pf-u-display-block-on-md"
href="${globalAK().api.base}if/admin/" href="${APIConfig.base}if/admin/"
slot="extra" slot="extra"
> >
${msg("Admin interface")} ${msg("Admin interface")}
</a> </a>
<a <a
class="pf-c-button pf-m-secondary pf-m-small pf-u-display-none-on-md pf-u-display-block" class="pf-c-button pf-m-secondary pf-m-small pf-u-display-none-on-md pf-u-display-block"
href="${globalAK().api.base}if/admin/" href="${APIConfig.base}if/admin/"
slot="extra" slot="extra"
> >
${msg("Admin")} ${msg("Admin")}

View File

@ -1,5 +1,5 @@
import { AndNext } from "@goauthentik/common/api/config"; import { AndNext } from "@goauthentik/common/api/config";
import { globalAK } from "@goauthentik/common/global"; import { APIConfig } from "@goauthentik/common/global";
import { AKElement } from "@goauthentik/elements/Base"; import { AKElement } from "@goauthentik/elements/Base";
import { msg } from "@lit/localize"; import { msg } from "@lit/localize";
@ -32,7 +32,7 @@ export class UserSettingsPassword extends AKElement {
<div class="pf-c-card__body"> <div class="pf-c-card__body">
<a <a
href="${ifDefined(this.configureUrl)}${AndNext( href="${ifDefined(this.configureUrl)}${AndNext(
`${globalAK().api.relBase}if/user/#/settings;${JSON.stringify({ page: "page-details" })}`, `${APIConfig.relBase}if/user/#/settings;${JSON.stringify({ page: "page-details" })}`,
)}" )}"
class="pf-c-button pf-m-primary" class="pf-c-button pf-m-primary"
> >

View File

@ -5,7 +5,7 @@ import {
parseAPIResponseError, parseAPIResponseError,
pluckErrorDetail, pluckErrorDetail,
} from "@goauthentik/common/errors/network"; } from "@goauthentik/common/errors/network";
import { globalAK } from "@goauthentik/common/global"; import { APIConfig } from "@goauthentik/common/global";
import { MessageLevel } from "@goauthentik/common/messages"; import { MessageLevel } from "@goauthentik/common/messages";
import { refreshMe } from "@goauthentik/common/users"; import { refreshMe } from "@goauthentik/common/users";
import { AKElement } from "@goauthentik/elements/Base"; import { AKElement } from "@goauthentik/elements/Base";
@ -181,7 +181,7 @@ export class UserSettingsFlowExecutor
); );
return html` return html`
<a <a
href="${globalAK().api.base}if/flow/${this.flowSlug}/" href="${APIConfig.base}if/flow/${this.flowSlug}/"
class="pf-c-button pf-m-primary" class="pf-c-button pf-m-primary"
> >
${msg("Open settings")} ${msg("Open settings")}

View File

@ -1,4 +1,4 @@
import { globalAK } from "@goauthentik/common/global"; import { APIConfig } from "@goauthentik/common/global";
import "@goauthentik/elements/forms/HorizontalFormElement"; import "@goauthentik/elements/forms/HorizontalFormElement";
import { PromptStage } from "@goauthentik/flow/stages/prompt/PromptStage"; import { PromptStage } from "@goauthentik/flow/stages/prompt/PromptStage";
@ -51,8 +51,7 @@ export class UserSettingsPromptStage extends PromptStage {
${this.host.brand?.flowUnenrollment ${this.host.brand?.flowUnenrollment
? html` <a ? html` <a
class="pf-c-button pf-m-danger" class="pf-c-button pf-m-danger"
href="${globalAK().api.base}if/flow/${this.host.brand href="${APIConfig.base}if/flow/${this.host.brand.flowUnenrollment}/"
.flowUnenrollment}/"
> >
${msg("Delete account")} ${msg("Delete account")}
</a>` </a>`

View File

@ -1,7 +1,7 @@
import { AndNext, DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { AndNext, DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { globalAK } from "@goauthentik/common/global"; import { SentryIgnoredError } from "@goauthentik/common/errors";
import { APIConfig } from "@goauthentik/common/global";
import { deviceTypeName } from "@goauthentik/common/labels"; import { deviceTypeName } from "@goauthentik/common/labels";
import { SentryIgnoredError } from "@goauthentik/common/sentry";
import { formatElapsedTime } from "@goauthentik/common/temporal"; import { formatElapsedTime } from "@goauthentik/common/temporal";
import "@goauthentik/elements/buttons/Dropdown"; import "@goauthentik/elements/buttons/Dropdown";
import "@goauthentik/elements/buttons/ModalButton"; import "@goauthentik/elements/buttons/ModalButton";
@ -74,7 +74,7 @@ export class MFADevicesPage extends Table<Device> {
return html`<li> return html`<li>
<a <a
href="${ifDefined(stage.configureUrl)}${AndNext( href="${ifDefined(stage.configureUrl)}${AndNext(
`${globalAK().api.relBase}if/user/#/settings;${JSON.stringify({ `${APIConfig.relBase}if/user/#/settings;${JSON.stringify({
page: "page-mfa", page: "page-mfa",
})}`, })}`,
)}" )}"