Compare commits
2 Commits
monorepo-d
...
server-con
Author | SHA1 | Date | |
---|---|---|---|
29a3117a94 | |||
66e40720c5 |
@ -1,5 +1,14 @@
|
||||
{% load i18n %}
|
||||
{% get_current_language as LANGUAGE_CODE %}
|
||||
|
||||
{{ config_json|json_script:":ak-config:" }}
|
||||
|
||||
{{ brand_json|json_script:":ak-brand:" }}
|
||||
|
||||
<meta name="ak-version-family" content="{{ version_family }}">
|
||||
<meta name="ak-version-subdomain" content="{{ version_subdomain }}">
|
||||
<meta name="ak-build" content="{{ build }}">
|
||||
<meta name="ak-base-url" content="{{ base_url }}">
|
||||
<meta name="ak-base-url-rel" content="{{ base_url_rel }}">
|
||||
|
||||
<script>
|
||||
window.authentik = {
|
||||
|
@ -1,6 +1,5 @@
|
||||
"""Interface views"""
|
||||
|
||||
from json import dumps
|
||||
from typing import Any
|
||||
|
||||
from django.http import HttpRequest
|
||||
@ -46,14 +45,19 @@ 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["brand_json"] = dumps(CurrentBrandSerializer(self.request.brand).data)
|
||||
"""Add common context data to all interface views"""
|
||||
|
||||
kwargs["config_json"] = ConfigView(request=Request(self.request)).get_config().data
|
||||
kwargs["brand_json"] = CurrentBrandSerializer(self.request.brand).data
|
||||
|
||||
kwargs["version_family"] = f"{LOCAL_VERSION.major}.{LOCAL_VERSION.minor}"
|
||||
kwargs["version_subdomain"] = f"version-{LOCAL_VERSION.major}-{LOCAL_VERSION.minor}"
|
||||
|
||||
kwargs["build"] = get_build_hash()
|
||||
kwargs["url_kwargs"] = self.kwargs
|
||||
kwargs["base_url"] = self.request.build_absolute_uri(CONFIG.get("web.path", "/"))
|
||||
kwargs["base_url_rel"] = CONFIG.get("web.path", "/")
|
||||
|
||||
return super().get_context_data(**kwargs)
|
||||
|
||||
|
||||
|
4
packages/docusaurus-config/package-lock.json
generated
4
packages/docusaurus-config/package-lock.json
generated
@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@goauthentik/docusaurus-config",
|
||||
"version": "1.0.6",
|
||||
"version": "1.0.5",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@goauthentik/docusaurus-config",
|
||||
"version": "1.0.6",
|
||||
"version": "1.0.5",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"deepmerge-ts": "^7.1.5",
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@goauthentik/docusaurus-config",
|
||||
"version": "1.0.6",
|
||||
"version": "1.0.5",
|
||||
"description": "authentik's Docusaurus config",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||
import { VERSION } from "@goauthentik/common/constants";
|
||||
import { globalAK } from "@goauthentik/common/global";
|
||||
import { ServerContext } from "@goauthentik/common/server-context";
|
||||
import { DefaultBrand } from "@goauthentik/common/ui/config";
|
||||
import "@goauthentik/elements/EmptyState";
|
||||
import { WithBrandConfig } from "@goauthentik/elements/Interface/brandProvider";
|
||||
@ -33,7 +33,7 @@ export class AboutModal extends WithLicenseSummary(WithBrandConfig(ModalButton))
|
||||
const status = await new AdminApi(DEFAULT_CONFIG).adminSystemRetrieve();
|
||||
const version = await new AdminApi(DEFAULT_CONFIG).adminVersionRetrieve();
|
||||
let build: string | TemplateResult = msg("Release");
|
||||
if (globalAK().config.capabilities.includes(CapabilitiesEnum.CanDebug)) {
|
||||
if (ServerContext.config.capabilities.includes(CapabilitiesEnum.CanDebug)) {
|
||||
build = msg("Development");
|
||||
} else if (version.buildHash !== "") {
|
||||
build = html`<a
|
||||
@ -58,10 +58,12 @@ export class AboutModal extends WithLicenseSummary(WithBrandConfig(ModalButton))
|
||||
}
|
||||
|
||||
renderModal() {
|
||||
let product = globalAK().brand.brandingTitle || DefaultBrand.brandingTitle;
|
||||
let product = ServerContext.brand.brandingTitle || DefaultBrand.brandingTitle;
|
||||
|
||||
if (this.licenseSummary.status != LicenseSummaryStatusEnum.Unlicensed) {
|
||||
product += ` ${msg("Enterprise")}`;
|
||||
}
|
||||
|
||||
return html`<div
|
||||
class="pf-c-backdrop"
|
||||
@click=${(e: PointerEvent) => {
|
||||
|
@ -6,7 +6,8 @@ import {
|
||||
EVENT_NOTIFICATION_DRAWER_TOGGLE,
|
||||
EVENT_SIDEBAR_TOGGLE,
|
||||
} from "@goauthentik/common/constants";
|
||||
import { configureSentry } from "@goauthentik/common/sentry";
|
||||
import { setSentryPII, tryInitializeSentry } from "@goauthentik/common/sentry";
|
||||
import { ServerContext } from "@goauthentik/common/server-context";
|
||||
import { me } from "@goauthentik/common/users";
|
||||
import { WebsocketClient } from "@goauthentik/common/ws";
|
||||
import { AuthenticatedInterface } from "@goauthentik/elements/Interface";
|
||||
@ -167,15 +168,21 @@ export class AdminInterface extends WithLicenseSummary(AuthenticatedInterface) {
|
||||
}
|
||||
|
||||
async firstUpdated(): Promise<void> {
|
||||
configureSentry(true);
|
||||
tryInitializeSentry(ServerContext.config);
|
||||
this.user = await me();
|
||||
|
||||
setSentryPII(this.user.user);
|
||||
|
||||
const canAccessAdmin =
|
||||
this.user.user.isSuperuser ||
|
||||
// TODO: somehow add `access_admin_interface` to the API schema
|
||||
this.user.user.systemPermissions.includes("access_admin_interface");
|
||||
|
||||
if (!canAccessAdmin && this.user.user.pk > 0) {
|
||||
console.debug(
|
||||
"authentik/admin: User does not have access to admin interface. Redirecting...",
|
||||
);
|
||||
|
||||
window.location.assign("/if/user/");
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||
import { docLink } from "@goauthentik/common/global";
|
||||
import { docLink } from "@goauthentik/common/server-context";
|
||||
import "@goauthentik/components/ak-toggle-group";
|
||||
import "@goauthentik/elements/CodeMirror";
|
||||
import { CodeMirrorMode } from "@goauthentik/elements/CodeMirror";
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { docLink } from "@goauthentik/common/global";
|
||||
import { docLink } from "@goauthentik/common/server-context";
|
||||
import { ModalButton } from "@goauthentik/elements/buttons/ModalButton";
|
||||
import "@goauthentik/elements/buttons/TokenCopyButton";
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||
import { docLink } from "@goauthentik/common/global";
|
||||
import { docLink } from "@goauthentik/common/server-context";
|
||||
import { groupBy } from "@goauthentik/common/utils";
|
||||
import "@goauthentik/elements/CodeMirror";
|
||||
import { CodeMirrorMode } from "@goauthentik/elements/CodeMirror";
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { BasePolicyForm } from "@goauthentik/admin/policies/BasePolicyForm";
|
||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||
import { docLink } from "@goauthentik/common/global";
|
||||
import { docLink } from "@goauthentik/common/server-context";
|
||||
import "@goauthentik/elements/CodeMirror";
|
||||
import { CodeMirrorMode } from "@goauthentik/elements/CodeMirror";
|
||||
import "@goauthentik/elements/forms/FormGroup";
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { docLink } from "@goauthentik/common/global";
|
||||
import { docLink } from "@goauthentik/common/server-context";
|
||||
import { CodeMirrorMode } from "@goauthentik/elements/CodeMirror";
|
||||
import { ModelForm } from "@goauthentik/elements/forms/ModelForm";
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { BasePropertyMappingForm } from "@goauthentik/admin/property-mappings/BasePropertyMappingForm";
|
||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||
import { docLink } from "@goauthentik/common/global";
|
||||
import { docLink } from "@goauthentik/common/server-context";
|
||||
import "@goauthentik/elements/CodeMirror";
|
||||
import { CodeMirrorMode } from "@goauthentik/elements/CodeMirror";
|
||||
import "@goauthentik/elements/forms/FormGroup";
|
||||
|
@ -5,7 +5,8 @@ import {
|
||||
GroupMatchingModeToLabel,
|
||||
UserMatchingModeToLabel,
|
||||
} from "@goauthentik/admin/sources/oauth/utils";
|
||||
import { DEFAULT_CONFIG, config } from "@goauthentik/common/api/config";
|
||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||
import { ServerContext } from "@goauthentik/common/server-context.js";
|
||||
import "@goauthentik/components/ak-switch-input";
|
||||
import "@goauthentik/components/ak-text-input";
|
||||
import "@goauthentik/components/ak-textarea-input";
|
||||
@ -60,8 +61,9 @@ export class KerberosSourceForm extends WithCapabilitiesConfig(BaseSourceForm<Ke
|
||||
kerberosSourceRequest: data as unknown as KerberosSourceRequest,
|
||||
});
|
||||
}
|
||||
const c = await config();
|
||||
if (c.capabilities.includes(CapabilitiesEnum.CanSaveMedia)) {
|
||||
const { capabilities } = ServerContext.config;
|
||||
|
||||
if (capabilities.includes(CapabilitiesEnum.CanSaveMedia)) {
|
||||
const icon = this.getFormFiles()["icon"];
|
||||
if (icon || this.clearIcon) {
|
||||
await new SourcesApi(DEFAULT_CONFIG).sourcesAllSetIconCreate({
|
||||
|
@ -5,7 +5,8 @@ import {
|
||||
GroupMatchingModeToLabel,
|
||||
UserMatchingModeToLabel,
|
||||
} from "@goauthentik/admin/sources/oauth/utils";
|
||||
import { DEFAULT_CONFIG, config } from "@goauthentik/common/api/config";
|
||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||
import { ServerContext } from "@goauthentik/common/server-context.js";
|
||||
import "@goauthentik/components/ak-radio-input";
|
||||
import "@goauthentik/elements/CodeMirror";
|
||||
import { CodeMirrorMode } from "@goauthentik/elements/CodeMirror";
|
||||
@ -85,8 +86,9 @@ export class OAuthSourceForm extends WithCapabilitiesConfig(BaseSourceForm<OAuth
|
||||
oAuthSourceRequest: data as unknown as OAuthSourceRequest,
|
||||
});
|
||||
}
|
||||
const c = await config();
|
||||
if (c.capabilities.includes(CapabilitiesEnum.CanSaveMedia)) {
|
||||
const { capabilities } = ServerContext.config;
|
||||
|
||||
if (capabilities.includes(CapabilitiesEnum.CanSaveMedia)) {
|
||||
const icon = this.getFormFiles()["icon"];
|
||||
if (icon || this.clearIcon) {
|
||||
await new SourcesApi(DEFAULT_CONFIG).sourcesAllSetIconCreate({
|
||||
|
@ -6,7 +6,8 @@ import {
|
||||
GroupMatchingModeToLabel,
|
||||
UserMatchingModeToLabel,
|
||||
} from "@goauthentik/admin/sources/oauth/utils";
|
||||
import { DEFAULT_CONFIG, config } from "@goauthentik/common/api/config";
|
||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||
import { ServerContext } from "@goauthentik/common/server-context.js";
|
||||
import {
|
||||
CapabilitiesEnum,
|
||||
WithCapabilitiesConfig,
|
||||
@ -61,8 +62,9 @@ export class SAMLSourceForm extends WithCapabilitiesConfig(BaseSourceForm<SAMLSo
|
||||
sAMLSourceRequest: data,
|
||||
});
|
||||
}
|
||||
const c = await config();
|
||||
if (c.capabilities.includes(CapabilitiesEnum.CanSaveMedia)) {
|
||||
const { capabilities } = ServerContext.config;
|
||||
|
||||
if (capabilities.includes(CapabilitiesEnum.CanSaveMedia)) {
|
||||
const icon = this.getFormFiles()["icon"];
|
||||
if (icon || this.clearIcon) {
|
||||
await new SourcesApi(DEFAULT_CONFIG).sourcesAllSetIconCreate({
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||
import { globalAK } from "@goauthentik/common/global";
|
||||
import { ServerContext } from "@goauthentik/common/server-context";
|
||||
import "@goauthentik/components/ak-text-input";
|
||||
import { Form } from "@goauthentik/elements/forms/Form";
|
||||
|
||||
@ -21,7 +21,7 @@ export class UserImpersonateForm extends Form<ImpersonationRequest> {
|
||||
impersonationRequest: data,
|
||||
})
|
||||
.then(() => {
|
||||
window.location.href = globalAK().api.base;
|
||||
window.location.href = ServerContext.baseURL;
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -5,22 +5,14 @@ import {
|
||||
LoggingMiddleware,
|
||||
} from "@goauthentik/common/api/middleware";
|
||||
import { EVENT_LOCALE_REQUEST, VERSION } from "@goauthentik/common/constants";
|
||||
import { globalAK } from "@goauthentik/common/global";
|
||||
import { ServerContext } from "@goauthentik/common/server-context";
|
||||
|
||||
import { Config, Configuration, CoreApi, CurrentBrand, RootApi } from "@goauthentik/api";
|
||||
import { Configuration, CurrentBrand } from "@goauthentik/api";
|
||||
|
||||
// HACK: Workaround for ESBuild not being able to hoist import statement across entrypoints.
|
||||
// This can be removed after ESBuild uses a single build context for all entrypoints.
|
||||
export { CSRFHeaderName };
|
||||
|
||||
let globalConfigPromise: Promise<Config> | undefined = Promise.resolve(globalAK().config);
|
||||
export function config(): Promise<Config> {
|
||||
if (!globalConfigPromise) {
|
||||
globalConfigPromise = new RootApi(DEFAULT_CONFIG).rootConfigRetrieve();
|
||||
}
|
||||
return globalConfigPromise;
|
||||
}
|
||||
|
||||
export function brandSetFavicon(brand: CurrentBrand) {
|
||||
/**
|
||||
* <link rel="icon" href="/static/dist/assets/icons/icon.png">
|
||||
@ -52,35 +44,22 @@ export function brandSetLocale(brand: CurrentBrand) {
|
||||
);
|
||||
}
|
||||
|
||||
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 "";
|
||||
|
||||
return metaEl.content;
|
||||
}
|
||||
|
||||
export const DEFAULT_CONFIG = new Configuration({
|
||||
basePath: `${globalAK().api.base}api/v3`,
|
||||
basePath: `${ServerContext.baseURL}api/v3`,
|
||||
headers: {
|
||||
"sentry-trace": getMetaContent("sentry-trace"),
|
||||
"sentry-trace": ServerContext.sentryTrace,
|
||||
},
|
||||
middleware: [
|
||||
new CSRFMiddleware(),
|
||||
new EventMiddleware(),
|
||||
new LoggingMiddleware(globalAK().brand),
|
||||
new LoggingMiddleware(ServerContext.brand),
|
||||
],
|
||||
});
|
||||
|
||||
|
@ -1,59 +0,0 @@
|
||||
import { Config, ConfigFromJSON, CurrentBrand, CurrentBrandFromJSON } from "@goauthentik/api";
|
||||
|
||||
export interface GlobalAuthentik {
|
||||
_converted?: boolean;
|
||||
locale?: string;
|
||||
flow?: {
|
||||
layout: string;
|
||||
};
|
||||
config: Config;
|
||||
brand: CurrentBrand;
|
||||
versionFamily: string;
|
||||
versionSubdomain: string;
|
||||
build: string;
|
||||
api: {
|
||||
base: string;
|
||||
relBase: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface AuthentikWindow {
|
||||
authentik: GlobalAuthentik;
|
||||
}
|
||||
|
||||
export function globalAK(): GlobalAuthentik {
|
||||
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 {
|
||||
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}`;
|
||||
}
|
@ -1,89 +1,131 @@
|
||||
import { config } from "@goauthentik/common/api/config";
|
||||
import { VERSION } from "@goauthentik/common/constants";
|
||||
import { me } from "@goauthentik/common/users";
|
||||
import { readInterfaceRouteParam } from "@goauthentik/elements/router/utils";
|
||||
import {
|
||||
ErrorEvent,
|
||||
EventHint,
|
||||
browserTracingIntegration,
|
||||
init,
|
||||
setTag,
|
||||
setUser,
|
||||
} from "@sentry/browser";
|
||||
import { RouteInterfaceName, readInterfaceRouteParam } from "@goauthentik/elements/router/utils";
|
||||
import { BrowserOptions, browserTracingIntegration, init, setTag, setUser } from "@sentry/browser";
|
||||
|
||||
import { CapabilitiesEnum, Config, ResponseError } from "@goauthentik/api";
|
||||
import { CapabilitiesEnum, Config, ResponseError, UserSelf } from "@goauthentik/api";
|
||||
|
||||
/**
|
||||
* A generic error that can be thrown without triggering Sentry's reporting.
|
||||
*
|
||||
* @category Sentry
|
||||
*/
|
||||
export class SentryIgnoredError extends Error {}
|
||||
|
||||
export const TAG_SENTRY_COMPONENT = "authentik.component";
|
||||
export const TAG_SENTRY_CAPABILITIES = "authentik.capabilities";
|
||||
|
||||
export async function configureSentry(canDoPpi = false): Promise<Config> {
|
||||
const cfg = await config();
|
||||
|
||||
if (cfg.errorReporting.enabled) {
|
||||
init({
|
||||
dsn: cfg.errorReporting.sentryDsn,
|
||||
ignoreErrors: [
|
||||
/network/gi,
|
||||
/fetch/gi,
|
||||
/module/gi,
|
||||
// Error on edge on ios,
|
||||
// https://stackoverflow.com/questions/69261499/what-is-instantsearchsdkjsbridgeclearhighlight
|
||||
/instantSearchSDKJSBridgeClearHighlight/gi,
|
||||
// Seems to be an issue in Safari and Firefox
|
||||
/MutationObserver.observe/gi,
|
||||
/NS_ERROR_FAILURE/gi,
|
||||
],
|
||||
release: `authentik@${VERSION}`,
|
||||
integrations: [
|
||||
browserTracingIntegration({
|
||||
shouldCreateSpanForRequest: (url: string) => {
|
||||
return url.startsWith(window.location.host);
|
||||
},
|
||||
}),
|
||||
],
|
||||
tracesSampleRate: cfg.errorReporting.tracesSampleRate,
|
||||
environment: cfg.errorReporting.environment,
|
||||
beforeSend: (
|
||||
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/${readInterfaceRouteParam()}`);
|
||||
}
|
||||
if (cfg.capabilities.includes(CapabilitiesEnum.CanDebug)) {
|
||||
const Spotlight = await import("@spotlightjs/spotlight");
|
||||
|
||||
Spotlight.init({ injectImmediately: true });
|
||||
}
|
||||
if (cfg.errorReporting.sendPii && canDoPpi) {
|
||||
me().then((user) => {
|
||||
setUser({ email: user.user.email });
|
||||
console.debug("authentik/config: Sentry with PII enabled.");
|
||||
});
|
||||
} else {
|
||||
console.debug("authentik/config: Sentry enabled.");
|
||||
}
|
||||
}
|
||||
return cfg;
|
||||
/**
|
||||
* Attempt initializing Spotlight.
|
||||
*
|
||||
* @see {@link https://spotlightjs.com/ Spotlight}
|
||||
* @category Sentry
|
||||
*/
|
||||
export async function tryInitializingSpotlight() {
|
||||
return import("@spotlightjs/spotlight").then((Spotlight) =>
|
||||
Spotlight.init({ injectImmediately: true }),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Default Sentry options for the browser.
|
||||
*
|
||||
* @category Sentry
|
||||
*/
|
||||
const DEFAULT_SENTRY_BROWSER_OPTIONS = {
|
||||
ignoreErrors: [
|
||||
/network/gi,
|
||||
/fetch/gi,
|
||||
/module/gi,
|
||||
// Error on edge on ios,
|
||||
// https://stackoverflow.com/questions/69261499/what-is-instantsearchsdkjsbridgeclearhighlight
|
||||
/instantSearchSDKJSBridgeClearHighlight/gi,
|
||||
// Seems to be an issue in Safari and Firefox
|
||||
/MutationObserver.observe/gi,
|
||||
/NS_ERROR_FAILURE/gi,
|
||||
],
|
||||
release: `authentik@${VERSION}`,
|
||||
integrations: [
|
||||
browserTracingIntegration({
|
||||
shouldCreateSpanForRequest: (url: string) => {
|
||||
return url.startsWith(window.location.host);
|
||||
},
|
||||
}),
|
||||
],
|
||||
beforeSend: (event, hint) => {
|
||||
if (!hint) {
|
||||
return event;
|
||||
}
|
||||
if (hint.originalException instanceof SentryIgnoredError) {
|
||||
return null;
|
||||
}
|
||||
if (
|
||||
hint.originalException instanceof ResponseError ||
|
||||
hint.originalException instanceof DOMException
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
return event;
|
||||
},
|
||||
} as const satisfies BrowserOptions;
|
||||
|
||||
/**
|
||||
* Include the given user in Sentry events.
|
||||
*
|
||||
* @category Sentry
|
||||
*/
|
||||
export function setSentryPII(user: UserSelf): void {
|
||||
console.debug("authentik/sentry: PII enabled.");
|
||||
|
||||
setUser({ email: user.email });
|
||||
}
|
||||
|
||||
/**
|
||||
* Include the given capabilities in Sentry events.
|
||||
*
|
||||
* @category Sentry
|
||||
*/
|
||||
export function setSentryCapabilities(capabilities: CapabilitiesEnum[]): void {
|
||||
setTag("authentik.capabilities", capabilities.join(","));
|
||||
}
|
||||
|
||||
/**
|
||||
* Include the given route interface in Sentry events.
|
||||
*
|
||||
* @category Sentry
|
||||
*/
|
||||
export function setSentryInterface(interfaceName: RouteInterfaceName) {
|
||||
setTag("authentik.component", `web/${interfaceName}}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to initialize Sentry with the given configuration.
|
||||
*
|
||||
* @see {@linkcode setSentryPII}
|
||||
* @see {@linkcode setSentryCapabilities}
|
||||
* @see {@linkcode setSentryInterface}
|
||||
* @category Sentry
|
||||
*/
|
||||
export function tryInitializeSentry({ errorReporting, capabilities }: Config): void {
|
||||
if (!errorReporting.enabled) return;
|
||||
|
||||
init({
|
||||
...DEFAULT_SENTRY_BROWSER_OPTIONS,
|
||||
dsn: errorReporting.sentryDsn,
|
||||
tracesSampleRate: errorReporting.tracesSampleRate,
|
||||
environment: errorReporting.environment,
|
||||
enabled: process.env.NODE_ENV !== "development",
|
||||
});
|
||||
|
||||
setSentryCapabilities(capabilities);
|
||||
setSentryInterface(readInterfaceRouteParam());
|
||||
|
||||
if (
|
||||
process.env.NODE_ENV === "development" &&
|
||||
capabilities.includes(CapabilitiesEnum.CanDebug)
|
||||
) {
|
||||
tryInitializingSpotlight()
|
||||
.then(() => {
|
||||
console.debug("authentik/sentry: Sentry with Spotlight enabled.");
|
||||
})
|
||||
.catch((err) => {
|
||||
console.warn("authentik/sentry: Failed to load Spotlight", err);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
116
web/src/common/server-context.ts
Normal file
116
web/src/common/server-context.ts
Normal file
@ -0,0 +1,116 @@
|
||||
/**
|
||||
* @file Server context singleton.
|
||||
*/
|
||||
import { Config, ConfigFromJSON, CurrentBrand, CurrentBrandFromJSON } from "@goauthentik/api";
|
||||
|
||||
function readMetaElement(key: string, fallback: string = ""): string {
|
||||
const metaElement = document.querySelector<HTMLMetaElement>(`meta[name="${key}"]`);
|
||||
const value = metaElement?.getAttribute("content") || fallback;
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
interface ServerContextValue {
|
||||
/**
|
||||
* Server-injected authentik configuration.
|
||||
*/
|
||||
config: Readonly<Config>;
|
||||
|
||||
/**
|
||||
* Brand information used to customize the UI.
|
||||
*/
|
||||
brand: Readonly<CurrentBrand>;
|
||||
|
||||
/**
|
||||
* A semantic versioning string representing the current version of authentik.
|
||||
*/
|
||||
versionFamily: string;
|
||||
|
||||
/**
|
||||
* A subdomain-compatible version string representing the current version of authentik.
|
||||
*/
|
||||
versionSubdomain: string;
|
||||
|
||||
/**
|
||||
* A build hash string representing the current build of authentik.
|
||||
*/
|
||||
build: string;
|
||||
|
||||
/**
|
||||
* The base URL of the authentik instance.
|
||||
*/
|
||||
baseURL: string;
|
||||
|
||||
/**
|
||||
* The relative base URL of the authentik instance.
|
||||
*/
|
||||
baseURLRelative: string;
|
||||
|
||||
/**
|
||||
* The layout of the flow, if any.
|
||||
*/
|
||||
flowLayout: string;
|
||||
|
||||
/**
|
||||
* The Sentry trace ID for the current request.
|
||||
*/
|
||||
sentryTrace: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the server context from the DOM.
|
||||
*/
|
||||
export function refreshServerContext(): Readonly<ServerContextValue> {
|
||||
const configElement = document.getElementById(":ak-config:");
|
||||
|
||||
const config = configElement?.textContent
|
||||
? ConfigFromJSON(JSON.parse(configElement.textContent))
|
||||
: ConfigFromJSON({
|
||||
capabilities: [],
|
||||
});
|
||||
|
||||
const brandElement = document.getElementById(":ak-brand:");
|
||||
|
||||
const brand = brandElement?.textContent
|
||||
? CurrentBrandFromJSON(JSON.parse(brandElement.textContent))
|
||||
: CurrentBrandFromJSON({
|
||||
ui_footer_links: [],
|
||||
});
|
||||
|
||||
const apiBaseURL = new URL(process.env.AK_API_BASE_PATH || window.location.origin);
|
||||
|
||||
const value: ServerContextValue = {
|
||||
sentryTrace: readMetaElement("sentry-trace"),
|
||||
baseURL: readMetaElement("ak-base-url") || apiBaseURL.toString(),
|
||||
baseURLRelative: readMetaElement("ak-base-url-rel"),
|
||||
|
||||
versionFamily: readMetaElement("ak-version-family"),
|
||||
versionSubdomain: readMetaElement("ak-version-subdomain"),
|
||||
|
||||
build: readMetaElement("ak-build"),
|
||||
|
||||
flowLayout: readMetaElement("ak-flow-layout"),
|
||||
config,
|
||||
brand,
|
||||
};
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Server injected values used to configure application.
|
||||
*
|
||||
* @singleton
|
||||
*/
|
||||
export const ServerContext = refreshServerContext();
|
||||
|
||||
export function docLink(path: string): string {
|
||||
const { build, versionSubdomain } = ServerContext;
|
||||
|
||||
// Default case or beta build which should always point to latest
|
||||
if (build) {
|
||||
return new URL(path, "https://goauthentik.io").toString();
|
||||
}
|
||||
|
||||
return new URL(path, `https://${versionSubdomain}.goauthentik.io`).toString();
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
import { EVENT_MESSAGE, EVENT_WS_MESSAGE } from "@goauthentik/common/constants";
|
||||
import { globalAK } from "@goauthentik/common/global";
|
||||
import { MessageLevel } from "@goauthentik/common/messages";
|
||||
import { ServerContext } from "@goauthentik/common/server-context";
|
||||
|
||||
import { msg } from "@lit/localize";
|
||||
|
||||
@ -22,11 +22,14 @@ export class WebsocketClient {
|
||||
|
||||
connect(): void {
|
||||
if (navigator.webdriver) return;
|
||||
const apiURL = new URL(globalAK().api.base);
|
||||
const wsUrl = `${window.location.protocol.replace("http", "ws")}//${apiURL.host}${apiURL.pathname}ws/client/`;
|
||||
this.messageSocket = new WebSocket(wsUrl);
|
||||
|
||||
const apiURL = new URL(ServerContext.baseURL);
|
||||
|
||||
const wsURL = `${window.location.protocol.replace("http", "ws")}//${apiURL.host}${apiURL.pathname}ws/client/`;
|
||||
|
||||
this.messageSocket = new WebSocket(wsURL);
|
||||
this.messageSocket.addEventListener("open", () => {
|
||||
console.debug(`authentik/ws: connected to ${wsUrl}`);
|
||||
console.debug(`authentik/ws: connected to ${wsURL}`);
|
||||
this.retryDelay = 200;
|
||||
});
|
||||
this.messageSocket.addEventListener("close", (e) => {
|
||||
|
@ -3,7 +3,7 @@ import {
|
||||
EVENT_API_DRAWER_TOGGLE,
|
||||
EVENT_NOTIFICATION_DRAWER_TOGGLE,
|
||||
} from "@goauthentik/common/constants";
|
||||
import { globalAK } from "@goauthentik/common/global";
|
||||
import { ServerContext } from "@goauthentik/common/server-context";
|
||||
import { UIConfig, UserDisplay, uiConfig } from "@goauthentik/common/ui/config";
|
||||
import { me } from "@goauthentik/common/users";
|
||||
import { AKElement } from "@goauthentik/elements/Base";
|
||||
@ -146,7 +146,7 @@ export class NavigationButtons extends AKElement {
|
||||
<a
|
||||
class="pf-c-button pf-m-plain"
|
||||
type="button"
|
||||
href="${globalAK().api.base}if/user/#/settings"
|
||||
href="${ServerContext.baseURL}if/user/#/settings"
|
||||
>
|
||||
<pf-tooltip position="top" content=${msg("Settings")}>
|
||||
<i class="fas fa-cog" aria-hidden="true"></i>
|
||||
@ -200,7 +200,7 @@ export class NavigationButtons extends AKElement {
|
||||
${this.renderSettings()}
|
||||
<div class="pf-c-page__header-tools-item">
|
||||
<a
|
||||
href="${globalAK().api.base}flows/-/default/invalidation/"
|
||||
href="${ServerContext.baseURL}flows/-/default/invalidation/"
|
||||
class="pf-c-button pf-m-plain"
|
||||
>
|
||||
<pf-tooltip position="top" content=${msg("Sign out")}>
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { globalAK } from "@goauthentik/common/global";
|
||||
import { ServerContext } from "@goauthentik/common/server-context";
|
||||
import {
|
||||
StyleSheetInit,
|
||||
StyleSheetParent,
|
||||
@ -82,7 +82,7 @@ export class AKElement extends LitElement implements ThemedElement {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
const { brand } = globalAK();
|
||||
const { brand } = ServerContext;
|
||||
|
||||
this.#preferredColorScheme = formatColorScheme(brand.uiTheme);
|
||||
this.activeTheme = resolveUITheme(brand?.uiTheme);
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||
import { EVENT_REFRESH } from "@goauthentik/common/constants";
|
||||
import { globalAK } from "@goauthentik/common/global";
|
||||
import { ServerContext } from "@goauthentik/common/server-context";
|
||||
import { ThemedElement } from "@goauthentik/common/theme";
|
||||
import { authentikConfigContext } from "@goauthentik/elements/AuthentikContexts";
|
||||
import type { ReactiveElementHost } from "@goauthentik/elements/types.js";
|
||||
@ -23,8 +23,8 @@ export class ConfigContextController implements ReactiveController {
|
||||
initialValue: undefined,
|
||||
});
|
||||
// Pre-hydrate from template-embedded config
|
||||
this.context.setValue(globalAK().config);
|
||||
this.host.config = globalAK().config;
|
||||
this.context.setValue(ServerContext.config);
|
||||
this.host.config = ServerContext.config;
|
||||
this.fetch = this.fetch.bind(this);
|
||||
this.fetch();
|
||||
}
|
||||
|
@ -3,9 +3,10 @@ import {
|
||||
EVENT_WS_MESSAGE,
|
||||
TITLE_DEFAULT,
|
||||
} from "@goauthentik/common/constants";
|
||||
import { globalAK } from "@goauthentik/common/global";
|
||||
import { UIConfig, UserDisplay, getConfigForUser } from "@goauthentik/common/ui/config";
|
||||
import { ServerContext } from "@goauthentik/common/server-context";
|
||||
import { getConfigForUser } from "@goauthentik/common/ui/config";
|
||||
import { DefaultBrand } from "@goauthentik/common/ui/config";
|
||||
import { UIConfig, UserDisplay } from "@goauthentik/common/ui/config";
|
||||
import { me } from "@goauthentik/common/users";
|
||||
import "@goauthentik/components/ak-nav-buttons";
|
||||
import { AKElement } from "@goauthentik/elements/Base";
|
||||
@ -404,7 +405,7 @@ export class AKPageNavbar extends WithBrandConfig(AKElement) implements PageNavb
|
||||
<ak-nav-buttons .uiConfig=${this.uiConfig} .me=${this.session}>
|
||||
<a
|
||||
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="${ServerContext.baseURL}if/user/"
|
||||
slot="extra"
|
||||
>
|
||||
${msg("User interface")}
|
||||
|
@ -1,5 +1,3 @@
|
||||
import { globalAK } from "@goauthentik/common/global";
|
||||
|
||||
import { LOCALES as RAW_LOCALES, enLocale } from "./definitions";
|
||||
import { AkLocale } from "./types";
|
||||
|
||||
@ -51,7 +49,7 @@ export function autoDetectLanguage(userReq = TOMBSTONE, brandReq = TOMBSTONE): s
|
||||
userReq,
|
||||
window.navigator?.language ?? TOMBSTONE,
|
||||
brandReq,
|
||||
globalAK()?.locale ?? TOMBSTONE,
|
||||
document.documentElement.getAttribute("lang") ?? TOMBSTONE,
|
||||
DEFAULT_LOCALE,
|
||||
].filter(isLocaleCandidate);
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { globalAK } from "@goauthentik/common/global";
|
||||
import { ServerContext } from "@goauthentik/common/server-context";
|
||||
import { AKElement } from "@goauthentik/elements/Base";
|
||||
import { WithLicenseSummary } from "@goauthentik/elements/Interface/licenseSummaryProvider";
|
||||
|
||||
@ -76,7 +76,7 @@ export class EnterpriseStatusBanner extends WithLicenseSummary(AKElement) {
|
||||
: "pf-m-gold"}"
|
||||
>
|
||||
${message}
|
||||
<a href="${globalAK().api.base}if/admin/#/enterprise/licenses"
|
||||
<a href="${ServerContext.baseURL}if/admin/#/enterprise/licenses"
|
||||
>${msg("Click here for more info.")}</a
|
||||
>
|
||||
</div>`;
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { RequestInfo } from "@goauthentik/common/api/middleware";
|
||||
import { EVENT_API_DRAWER_TOGGLE, EVENT_REQUEST_POST } from "@goauthentik/common/constants";
|
||||
import { globalAK } from "@goauthentik/common/global";
|
||||
import { ServerContext } from "@goauthentik/common/server-context";
|
||||
import { formatElapsedTime } from "@goauthentik/common/temporal";
|
||||
import { AKElement } from "@goauthentik/elements/Base";
|
||||
|
||||
@ -92,7 +92,7 @@ export class APIDrawer extends AKElement {
|
||||
<h1 class="pf-c-notification-drawer__header-title">
|
||||
${msg("API Requests")}
|
||||
</h1>
|
||||
<a href="${globalAK().api.base}api/v3/" target="_blank"
|
||||
<a href="${ServerContext.baseURL}api/v3/" target="_blank"
|
||||
>${msg("Open API Browser")}</a
|
||||
>
|
||||
</div>
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||
import { EVENT_NOTIFICATION_DRAWER_TOGGLE, EVENT_REFRESH } from "@goauthentik/common/constants";
|
||||
import { globalAK } from "@goauthentik/common/global";
|
||||
import { actionToLabel } from "@goauthentik/common/labels";
|
||||
import { MessageLevel } from "@goauthentik/common/messages";
|
||||
import { ServerContext } from "@goauthentik/common/server-context";
|
||||
import { formatElapsedTime } from "@goauthentik/common/temporal";
|
||||
import { me } from "@goauthentik/common/users";
|
||||
import { AKElement } from "@goauthentik/elements/Base";
|
||||
@ -99,7 +99,7 @@ export class NotificationDrawer extends AKElement {
|
||||
html`
|
||||
<a
|
||||
class="pf-c-dropdown__toggle pf-m-plain"
|
||||
href="${globalAK().api.base}if/admin/#/events/log/${item.event?.pk}"
|
||||
href="${ServerContext.baseURL}if/admin/#/events/log/${item.event?.pk}"
|
||||
>
|
||||
<pf-tooltip position="top" content=${msg("Show details")}>
|
||||
<i class="fas fa-share-square"></i>
|
||||
|
@ -1,5 +1,5 @@
|
||||
import type { AdminInterface } from "@goauthentik/admin/AdminInterface/index.entrypoint.js";
|
||||
import { globalAK } from "@goauthentik/common/global";
|
||||
import { ServerContext } from "@goauthentik/common/server-context";
|
||||
import { DefaultBrand } from "@goauthentik/common/ui/config";
|
||||
import { AKElement, rootInterface } from "@goauthentik/elements/Base";
|
||||
import { WithLicenseSummary } from "@goauthentik/elements/Interface/licenseSummaryProvider";
|
||||
@ -45,7 +45,7 @@ export class SidebarVersion extends WithLicenseSummary(WithVersion(AKElement)) {
|
||||
if (!this.version || !this.licenseSummary) {
|
||||
return nothing;
|
||||
}
|
||||
let product = globalAK().brand.brandingTitle || DefaultBrand.brandingTitle;
|
||||
let product = ServerContext.brand.brandingTitle || DefaultBrand.brandingTitle;
|
||||
if (this.licenseSummary.status != LicenseSummaryStatusEnum.Unlicensed) {
|
||||
product += ` ${msg("Enterprise")}`;
|
||||
}
|
||||
|
@ -4,8 +4,8 @@ import {
|
||||
EVENT_FLOW_INSPECTOR_TOGGLE,
|
||||
TITLE_DEFAULT,
|
||||
} from "@goauthentik/common/constants";
|
||||
import { globalAK } from "@goauthentik/common/global";
|
||||
import { configureSentry } from "@goauthentik/common/sentry";
|
||||
import { tryInitializeSentry } from "@goauthentik/common/sentry";
|
||||
import { ServerContext } from "@goauthentik/common/server-context";
|
||||
import { DefaultBrand } from "@goauthentik/common/ui/config";
|
||||
import { WebsocketClient } from "@goauthentik/common/ws";
|
||||
import { Interface } from "@goauthentik/elements/Interface";
|
||||
@ -237,10 +237,12 @@ export class FlowExecutor extends Interface implements StageHost {
|
||||
}
|
||||
|
||||
async firstUpdated(): Promise<void> {
|
||||
configureSentry();
|
||||
tryInitializeSentry(ServerContext.config);
|
||||
|
||||
if (this.config?.capabilities.includes(CapabilitiesEnum.CanDebug)) {
|
||||
this.inspectorAvailable = true;
|
||||
}
|
||||
|
||||
this.loading = true;
|
||||
try {
|
||||
const challenge = await new FlowsApi(DEFAULT_CONFIG).flowsExecutorGet({
|
||||
@ -481,7 +483,8 @@ export class FlowExecutor extends Interface implements StageHost {
|
||||
}
|
||||
|
||||
getLayout(): string {
|
||||
const prefilledFlow = globalAK()?.flow?.layout || FlowLayoutEnum.Stacked;
|
||||
const prefilledFlow = ServerContext.flowLayout || FlowLayoutEnum.Stacked;
|
||||
|
||||
if (this.challenge) {
|
||||
return this.challenge?.flowInfo?.layout || prefilledFlow;
|
||||
}
|
||||
@ -521,7 +524,7 @@ export class FlowExecutor extends Interface implements StageHost {
|
||||
<img
|
||||
src="${themeImage(
|
||||
this.brand?.brandingLogo ??
|
||||
globalAK()?.brand.brandingLogo ??
|
||||
ServerContext.brand.brandingLogo ??
|
||||
DefaultBrand.brandingLogo,
|
||||
)}"
|
||||
alt="${msg("authentik Logo")}"
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { globalAK } from "@goauthentik/common/global";
|
||||
import { ServerContext } from "@goauthentik/common/server-context";
|
||||
import "@goauthentik/flow/FormStatic";
|
||||
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.`,
|
||||
)}
|
||||
</p>
|
||||
<a href="${globalAK().api.base}" class="pf-c-button pf-m-primary">
|
||||
<a href="${ServerContext.baseURL}" class="pf-c-button pf-m-primary">
|
||||
${msg("Go back to overview")}
|
||||
</a>
|
||||
${this.challenge.invalidationFlowUrl
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { PFSize } from "@goauthentik/common/enums.js";
|
||||
import { globalAK } from "@goauthentik/common/global";
|
||||
import { ServerContext } from "@goauthentik/common/server-context";
|
||||
import { truncateWords } from "@goauthentik/common/utils";
|
||||
import "@goauthentik/elements/AppIcon";
|
||||
import { AKElement, rootInterface } from "@goauthentik/elements/Base";
|
||||
@ -82,8 +82,7 @@ export class LibraryApplication extends AKElement {
|
||||
? html`
|
||||
<a
|
||||
class="pf-c-button pf-m-control pf-m-small pf-m-block"
|
||||
href="${globalAK().api
|
||||
.base}if/admin/#/core/applications/${application?.slug}"
|
||||
href="${ServerContext.baseURL}if/admin/#/core/applications/${application?.slug}"
|
||||
>
|
||||
<i class="fas fa-edit"></i> ${msg("Edit")}
|
||||
</a>
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { docLink, globalAK } from "@goauthentik/common/global";
|
||||
import { ServerContext, docLink } from "@goauthentik/common/server-context";
|
||||
import { AKElement } from "@goauthentik/elements/Base";
|
||||
import { paramURL } from "@goauthentik/elements/router/RouterOutlet";
|
||||
|
||||
@ -49,7 +49,7 @@ export class LibraryPageApplicationEmptyList extends AKElement {
|
||||
<a
|
||||
aria-disabled="false"
|
||||
class="cta pf-c-button pf-m-secondary"
|
||||
href="${globalAK().api.base}if/admin/${href}"
|
||||
href="${ServerContext.baseURL}if/admin/${href}"
|
||||
>${msg("Create a new application")}</a
|
||||
>
|
||||
</div>
|
||||
|
@ -4,8 +4,8 @@ import {
|
||||
EVENT_NOTIFICATION_DRAWER_TOGGLE,
|
||||
EVENT_WS_MESSAGE,
|
||||
} from "@goauthentik/common/constants";
|
||||
import { globalAK } from "@goauthentik/common/global";
|
||||
import { configureSentry } from "@goauthentik/common/sentry";
|
||||
import { setSentryPII, tryInitializeSentry } from "@goauthentik/common/sentry";
|
||||
import { ServerContext } from "@goauthentik/common/server-context";
|
||||
import { UIConfig, getConfigForUser } from "@goauthentik/common/ui/config";
|
||||
import { DefaultBrand } from "@goauthentik/common/ui/config";
|
||||
import { me } from "@goauthentik/common/users";
|
||||
@ -170,14 +170,14 @@ class UserInterfacePresentation extends AKElement {
|
||||
|
||||
return html`<a
|
||||
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="${ServerContext.baseURL}if/admin/"
|
||||
slot="extra"
|
||||
>
|
||||
${msg("Admin interface")}
|
||||
</a>
|
||||
<a
|
||||
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="${ServerContext.baseURL}if/admin/"
|
||||
slot="extra"
|
||||
>
|
||||
${msg("Admin")}
|
||||
@ -284,7 +284,9 @@ export class UserInterface extends AuthenticatedInterface {
|
||||
super();
|
||||
this.ws = new WebsocketClient();
|
||||
this.fetchConfigurationDetails();
|
||||
configureSentry(true);
|
||||
|
||||
tryInitializeSentry(ServerContext.config);
|
||||
|
||||
this.toggleNotificationDrawer = this.toggleNotificationDrawer.bind(this);
|
||||
this.toggleApiDrawer = this.toggleApiDrawer.bind(this);
|
||||
this.fetchConfigurationDetails = this.fetchConfigurationDetails.bind(this);
|
||||
@ -325,6 +327,8 @@ export class UserInterface extends AuthenticatedInterface {
|
||||
this.me = session;
|
||||
this.uiConfig = getConfigForUser(session.user);
|
||||
|
||||
setSentryPII(session.user);
|
||||
|
||||
new EventsApi(DEFAULT_CONFIG)
|
||||
.eventsNotificationsList({
|
||||
seen: false,
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { AndNext } from "@goauthentik/common/api/config";
|
||||
import { globalAK } from "@goauthentik/common/global";
|
||||
import { ServerContext } from "@goauthentik/common/server-context";
|
||||
import { AKElement } from "@goauthentik/elements/Base";
|
||||
|
||||
import { msg } from "@lit/localize";
|
||||
@ -32,7 +32,7 @@ export class UserSettingsPassword extends AKElement {
|
||||
<div class="pf-c-card__body">
|
||||
<a
|
||||
href="${ifDefined(this.configureUrl)}${AndNext(
|
||||
`${globalAK().api.relBase}if/user/#/settings;${JSON.stringify({ page: "page-details" })}`,
|
||||
`${ServerContext.baseURL}if/user/#/settings;${JSON.stringify({ page: "page-details" })}`,
|
||||
)}"
|
||||
class="pf-c-button pf-m-primary"
|
||||
>
|
||||
|
@ -5,8 +5,8 @@ import {
|
||||
parseAPIResponseError,
|
||||
pluckErrorDetail,
|
||||
} from "@goauthentik/common/errors/network";
|
||||
import { globalAK } from "@goauthentik/common/global";
|
||||
import { MessageLevel } from "@goauthentik/common/messages";
|
||||
import { ServerContext } from "@goauthentik/common/server-context";
|
||||
import { refreshMe } from "@goauthentik/common/users";
|
||||
import { AKElement } from "@goauthentik/elements/Base";
|
||||
import { WithBrandConfig } from "@goauthentik/elements/Interface/brandProvider";
|
||||
@ -181,7 +181,7 @@ export class UserSettingsFlowExecutor
|
||||
);
|
||||
return html`
|
||||
<a
|
||||
href="${globalAK().api.base}if/flow/${this.flowSlug}/"
|
||||
href="${ServerContext.baseURL}if/flow/${this.flowSlug}/"
|
||||
class="pf-c-button pf-m-primary"
|
||||
>
|
||||
${msg("Open settings")}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { globalAK } from "@goauthentik/common/global";
|
||||
import { ServerContext } from "@goauthentik/common/server-context";
|
||||
import "@goauthentik/elements/forms/HorizontalFormElement";
|
||||
import { PromptStage } from "@goauthentik/flow/stages/prompt/PromptStage";
|
||||
|
||||
@ -51,7 +51,7 @@ export class UserSettingsPromptStage extends PromptStage {
|
||||
${this.host.brand?.flowUnenrollment
|
||||
? html` <a
|
||||
class="pf-c-button pf-m-danger"
|
||||
href="${globalAK().api.base}if/flow/${this.host.brand
|
||||
href="${ServerContext.baseURL}if/flow/${this.host.brand
|
||||
.flowUnenrollment}/"
|
||||
>
|
||||
${msg("Delete account")}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { AndNext, DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||
import { globalAK } from "@goauthentik/common/global";
|
||||
import { deviceTypeName } from "@goauthentik/common/labels";
|
||||
import { SentryIgnoredError } from "@goauthentik/common/sentry";
|
||||
import { ServerContext } from "@goauthentik/common/server-context";
|
||||
import { formatElapsedTime } from "@goauthentik/common/temporal";
|
||||
import "@goauthentik/elements/buttons/Dropdown";
|
||||
import "@goauthentik/elements/buttons/ModalButton";
|
||||
@ -74,7 +74,7 @@ export class MFADevicesPage extends Table<Device> {
|
||||
return html`<li>
|
||||
<a
|
||||
href="${ifDefined(stage.configureUrl)}${AndNext(
|
||||
`${globalAK().api.relBase}if/user/#/settings;${JSON.stringify({
|
||||
`${ServerContext.baseURL}if/user/#/settings;${JSON.stringify({
|
||||
page: "page-mfa",
|
||||
})}`,
|
||||
)}"
|
||||
|
1
website/.gitignore
vendored
1
website/.gitignore
vendored
@ -25,5 +25,4 @@ yarn-error.log*
|
||||
|
||||
static/docker-compose.yml
|
||||
static/schema.yml
|
||||
static/releases.gen.json
|
||||
docs/developer-docs/api/reference/**
|
||||
|
7
website/.prettierignore
Normal file
7
website/.prettierignore
Normal file
@ -0,0 +1,7 @@
|
||||
# Ignore artifacts:
|
||||
build
|
||||
coverage
|
||||
.docusaurus
|
||||
node_modules
|
||||
help
|
||||
static
|
1
website/.prettierrc.json
Normal file
1
website/.prettierrc.json
Normal file
@ -0,0 +1 @@
|
||||
{}
|
@ -29,8 +29,8 @@ You can also use custom email templates, to use your own design or layout.
|
||||
Starting with authentik 2024.2, it is possible to create `.txt` files with the same name as the `.html` template. If a matching `.txt` file exists, the email sent will be a multipart email with both the text and HTML template.
|
||||
:::
|
||||
|
||||
import TabItem from "@theme/TabItem";
|
||||
import Tabs from "@theme/Tabs";
|
||||
import TabItem from "@theme/TabItem";
|
||||
|
||||
<Tabs
|
||||
defaultValue="docker-compose"
|
||||
|
@ -2,9 +2,8 @@
|
||||
title: Caddy
|
||||
---
|
||||
|
||||
import TabItem from "@theme/TabItem";
|
||||
import Tabs from "@theme/Tabs";
|
||||
|
||||
import TabItem from "@theme/TabItem";
|
||||
import Placeholders from "./__placeholders.md";
|
||||
import CaddyStandalone from "./_caddy_standalone.md";
|
||||
|
||||
|
@ -2,9 +2,8 @@
|
||||
title: Envoy
|
||||
---
|
||||
|
||||
import TabItem from "@theme/TabItem";
|
||||
import Tabs from "@theme/Tabs";
|
||||
|
||||
import TabItem from "@theme/TabItem";
|
||||
import Placeholders from "./__placeholders.md";
|
||||
import EnvoyIstio from "./_envoy_istio.md";
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
import TabItem from "@theme/TabItem";
|
||||
import Tabs from "@theme/Tabs";
|
||||
import TabItem from "@theme/TabItem";
|
||||
|
||||
# nginx
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
import TabItem from "@theme/TabItem";
|
||||
import Tabs from "@theme/Tabs";
|
||||
import TabItem from "@theme/TabItem";
|
||||
|
||||
# Traefik
|
||||
|
||||
|
@ -1,82 +0,0 @@
|
||||
import styles from "./styles.module.css";
|
||||
|
||||
const RADIUSProtocols = [
|
||||
"PAP",
|
||||
"CHAP",
|
||||
"Digest",
|
||||
"MS-CHAP",
|
||||
"PEAP",
|
||||
"MS-CHAPv2",
|
||||
"Cisco LEAP",
|
||||
"EAP-GTC",
|
||||
"EAP-MD5",
|
||||
"EAP-PWD",
|
||||
] as const satisfies string[];
|
||||
|
||||
type RADIUSProtocol = (typeof RADIUSProtocols)[number];
|
||||
|
||||
const HashKinds = [
|
||||
"Cleartext",
|
||||
"NT",
|
||||
"MD5",
|
||||
"Salted MD5",
|
||||
"SHA1",
|
||||
"Salted SHA1",
|
||||
"Unix Crypt",
|
||||
] as const satisfies string[];
|
||||
|
||||
type HashKind = (typeof HashKinds)[number];
|
||||
|
||||
const supportMatrix: Record<HashKind, RADIUSProtocol[]> = {
|
||||
"Cleartext": [
|
||||
"PAP",
|
||||
"CHAP",
|
||||
"Digest",
|
||||
"MS-CHAP",
|
||||
"PEAP",
|
||||
"MS-CHAPv2",
|
||||
"Cisco LEAP",
|
||||
"EAP-GTC",
|
||||
"EAP-MD5",
|
||||
"EAP-PWD",
|
||||
],
|
||||
"NT": ["PAP", "MS-CHAP", "PEAP", "MS-CHAPv2", "Cisco LEAP", "EAP-GTC"],
|
||||
"MD5": ["PAP", "EAP-GTC"],
|
||||
"Salted MD5": ["PAP", "EAP-GTC"],
|
||||
"SHA1": ["PAP", "EAP-GTC"],
|
||||
"Salted SHA1": ["PAP", "EAP-GTC", "EAP-PWD"],
|
||||
"Unix Crypt": ["PAP", "EAP-GTC", "EAP-PWD"],
|
||||
};
|
||||
|
||||
export const HashSupport: React.FC = () => {
|
||||
return (
|
||||
<table className={styles.table}>
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
{HashKinds.map((hashKind, i) => (
|
||||
<th key={i}>{hashKind}</th>
|
||||
))}
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
{RADIUSProtocols.map((radiusProtocol, i) => (
|
||||
<tr key={i}>
|
||||
<td>{radiusProtocol}</td>
|
||||
{HashKinds.map((hashKind) => {
|
||||
const protocols = supportMatrix[hashKind];
|
||||
const supported = protocols.includes(radiusProtocol);
|
||||
|
||||
return (
|
||||
<td data-supported={supported} key={hashKind}>
|
||||
{supported ? "✓" : "✗"}
|
||||
</td>
|
||||
);
|
||||
})}
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
);
|
||||
};
|
@ -2,7 +2,7 @@
|
||||
title: RADIUS Provider
|
||||
---
|
||||
|
||||
import { HashSupport } from "./HashSupport";
|
||||
import { Check, X, AlertTriangle } from "react-feather";
|
||||
|
||||
You can configure a Radius provider for applications that don't support any other protocols or that require Radius.
|
||||
|
||||
@ -56,4 +56,15 @@ After creation, make sure to select the RADIUS property mapping in the RADIUS pr
|
||||
|
||||
The RADIUS provider only supports the [PAP](https://en.wikipedia.org/wiki/Password_Authentication_Protocol) (Password Authentication Protocol) protocol:
|
||||
|
||||
<HashSupport />
|
||||
| | Clear-text | NT hash | MD5 hash | Salted MD5 hash | SHA1 hash | Salted SHA1 hash | Unix Crypt |
|
||||
| ------------ | --------------- | --------------- | --------------- | --------------- | --------------- | ---------------- | --------------- |
|
||||
| PAP | <Check></Check> | <Check></Check> | <Check></Check> | <Check></Check> | <Check></Check> | <Check></Check> | <Check></Check> |
|
||||
| CHAP | <Check></Check> | <X></X> | <X></X> | <X></X> | <X></X> | <X></X> | <X></X> |
|
||||
| Digest | <Check></Check> | <X></X> | <X></X> | <X></X> | <X></X> | <X></X> | <X></X> |
|
||||
| MS-CHAP | <Check></Check> | <Check></Check> | <X></X> | <X></X> | <X></X> | <X></X> | <X></X> |
|
||||
| PEAP | <Check></Check> | <Check></Check> | <X></X> | <X></X> | <X></X> | <X></X> | <X></X> |
|
||||
| EAP-MSCHAPv2 | <Check></Check> | <Check></Check> | <X></X> | <X></X> | <X></X> | <X></X> | <X></X> |
|
||||
| Cisco LEAP | <Check></Check> | <Check></Check> | <X></X> | <X></X> | <X></X> | <X></X> | <X></X> |
|
||||
| EAP-GTC | <Check></Check> | <Check></Check> | <Check></Check> | <Check></Check> | <Check></Check> | <Check></Check> | <Check></Check> |
|
||||
| EAP-MD5 | <Check></Check> | <X></X> | <X></X> | <X></X> | <X></X> | <X></X> | <X></X> |
|
||||
| EAP-PWD | <Check></Check> | <X></X> | <X></X> | <X></X> | <X></X> | <Check></Check> | <Check></Check> |
|
||||
|
@ -1,20 +0,0 @@
|
||||
.table td {
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
|
||||
&:first-child {
|
||||
text-align: right;
|
||||
width: 13ch;
|
||||
}
|
||||
|
||||
&:not(:first-child) {
|
||||
width: 10ch;
|
||||
}
|
||||
|
||||
&[data-supported="true"] {
|
||||
color: var(--ifm-color-success-dark);
|
||||
}
|
||||
&[data-supported="false"] {
|
||||
color: var(--ifm-color-danger-dark);
|
||||
}
|
||||
}
|
@ -4,7 +4,6 @@ title: Example
|
||||
|
||||
This is one of the default packaged blueprints to create the default authentication flow.
|
||||
|
||||
<!-- prettier-ignore-start -->
|
||||
```yaml
|
||||
version: 1
|
||||
metadata:
|
||||
@ -65,4 +64,3 @@ entries:
|
||||
target: !KeyOf flow
|
||||
model: authentik_flows.flowstagebinding
|
||||
```
|
||||
<!-- prettier-ignore-end -->
|
||||
|
@ -48,8 +48,6 @@ Returns the value of the given environment variable. Can be used as a scalar wit
|
||||
|
||||
Examples:
|
||||
|
||||
{/* prettier-ignore-start */}
|
||||
|
||||
```yaml
|
||||
configure_flow: !Find [authentik_flows.flow, [slug, default-password-change]]
|
||||
```
|
||||
@ -62,8 +60,6 @@ configure_flow:
|
||||
]
|
||||
```
|
||||
|
||||
{/* prettier-ignore-end */}
|
||||
|
||||
Looks up any model and resolves to the the matches' primary key.
|
||||
First argument is the model to be queried, remaining arguments are expected to be pairs of key=value pairs to query for.
|
||||
|
||||
@ -71,15 +67,10 @@ First argument is the model to be queried, remaining arguments are expected to b
|
||||
|
||||
Example:
|
||||
|
||||
{/* prettier-ignore-start */}
|
||||
|
||||
|
||||
```yaml
|
||||
configure_flow: !Context foo
|
||||
```
|
||||
|
||||
{/* prettier-ignore-end */}
|
||||
|
||||
Find values from the context. Can optionally be called with a default like `!Context [foo, default-value]`.
|
||||
|
||||
#### `!Format`
|
||||
@ -218,8 +209,6 @@ For example, given a sequence like this - `["a", "b", "c"]`, this tag will resol
|
||||
|
||||
Minimal examples:
|
||||
|
||||
{/* prettier-ignore-start */}
|
||||
|
||||
```yaml
|
||||
configuration_stages: !Enumerate [
|
||||
!Context map_of_totp_stage_names_and_types,
|
||||
@ -235,8 +224,6 @@ configuration_stages: !Enumerate [
|
||||
]
|
||||
```
|
||||
|
||||
{/* prettier-ignore-end */}
|
||||
|
||||
The above example will resolve to something like this:
|
||||
|
||||
```yaml
|
||||
@ -278,8 +265,6 @@ Full example:
|
||||
Note that an `!Enumeration` tag's iterable can never be an `!Item` or `!Value` tag with a depth of `0`. Minimum depth allowed is `1`. This is because a depth of `0` refers to the `!Enumeration` tag the `!Item` or `!Value` tag is in, and an `!Enumeration` tag cannot iterate over itself.
|
||||
:::
|
||||
|
||||
{/* prettier-ignore-start */}
|
||||
|
||||
```yaml
|
||||
example: !Enumerate [
|
||||
!Context sequence, # ["foo", "bar"]
|
||||
@ -303,8 +288,6 @@ example: !Enumerate [
|
||||
]
|
||||
```
|
||||
|
||||
{/* prettier-ignore-end */}
|
||||
|
||||
The above example will resolve to something like this:
|
||||
|
||||
```yaml
|
||||
|
@ -2,8 +2,8 @@
|
||||
|
||||
To further modify the look of authentik, a custom CSS file can be created. Creating such a file is outside the scope of this document.
|
||||
|
||||
import TabItem from "@theme/TabItem";
|
||||
import Tabs from "@theme/Tabs";
|
||||
import TabItem from "@theme/TabItem";
|
||||
|
||||
<Tabs
|
||||
defaultValue="docker-compose"
|
||||
|
@ -255,8 +255,8 @@ This section covers the usage of React components within our documentation. File
|
||||
Use **Tabs** to display different configurations (e.g., setting up authentication with OIDC vs. SAML) to help users navigate between options. Default to the easier or more common option. Insert the following lines wherever you want the code block to appear:
|
||||
|
||||
```jsx
|
||||
import TabItem from "@theme/TabItem";
|
||||
import Tabs from "@theme/Tabs";
|
||||
import TabItem from "@theme/TabItem";
|
||||
|
||||
<Tabs
|
||||
defaultValue="oidc"
|
||||
|
@ -148,6 +148,7 @@ We'll be publishing a security Issue (CVE-2022-xxxxx) and accompanying fix on _d
|
||||
|
||||
<details>
|
||||
<summary>Mailing list template</summary>
|
||||
<p>
|
||||
|
||||
Subject: `Release of authentik Security releases 2022.10.3 and 2022.11.3`
|
||||
|
||||
@ -157,10 +158,12 @@ The security advisory for CVE-2022-xxxxx has been published: https://github.com/
|
||||
Releases 2022.10.3 and 2022.11.3 with fixes included are available here: https://github.com/goauthentik/authentik/releases
|
||||
```
|
||||
|
||||
</p>
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Discord template</summary>
|
||||
<p>
|
||||
|
||||
```markdown
|
||||
[...existing announcement...]
|
||||
@ -172,4 +175,5 @@ Advisory for for CVE-2022-xxxxx has been published here https://github.com/goaut
|
||||
The fixed versions 2022.10.3 and 2022.11.3 are available here: https://github.com/goauthentik/authentik/releases
|
||||
```
|
||||
|
||||
</p>
|
||||
</details>
|
||||
|
@ -62,7 +62,7 @@ Depending on platform, some native dependencies might be required. On macOS, run
|
||||
4. From the cloned repository root, install the front-end dependencies using NPM.
|
||||
|
||||
```shell
|
||||
# From the root of the repository
|
||||
cd web
|
||||
npm ci
|
||||
```
|
||||
|
||||
|
@ -9,9 +9,9 @@ tags:
|
||||
- docker
|
||||
---
|
||||
|
||||
import ExecutionEnvironment from "@docusaurus/ExecutionEnvironment";
|
||||
import TabItem from "@theme/TabItem";
|
||||
import Tabs from "@theme/Tabs";
|
||||
import TabItem from "@theme/TabItem";
|
||||
import ExecutionEnvironment from "@docusaurus/ExecutionEnvironment";
|
||||
|
||||
## Requirements
|
||||
|
||||
|
@ -24,34 +24,39 @@ The authentik product provides the following consoles:
|
||||
|
||||
In authentik, you can use Light or Dark mode for the Admin interface, User interface, and the Flow interface.
|
||||
|
||||
import useBaseUrl from "@docusaurus/useBaseUrl";
|
||||
import ReactBeforeSliderComponent from "react-before-after-slider-component";
|
||||
|
||||
import "react-before-after-slider-component/dist/build.css";
|
||||
import ReactBeforeSliderComponent from "react-before-after-slider-component";
|
||||
import useBaseUrl from "@docusaurus/useBaseUrl";
|
||||
|
||||
<ReactBeforeSliderComponent
|
||||
firstImage={{
|
||||
id: 1,
|
||||
imageUrl: useBaseUrl("img/screen_flow_dark.jpg"),
|
||||
}}
|
||||
secondImage={{
|
||||
id: 2,
|
||||
imageUrl: useBaseUrl("img/screen_flow_light.jpg"),
|
||||
}}
|
||||
/>
|
||||
|
||||
<ReactBeforeSliderComponent
|
||||
firstImage={{
|
||||
id: 1,
|
||||
imageUrl: useBaseUrl("img/screen_apps_dark.jpg"),
|
||||
}}
|
||||
secondImage={{
|
||||
id: 2,
|
||||
imageUrl: useBaseUrl("img/screen_apps_light.jpg"),
|
||||
}}
|
||||
/>
|
||||
|
||||
<ReactBeforeSliderComponent
|
||||
firstImage={{
|
||||
id: 1,
|
||||
imageUrl: useBaseUrl("img/screen_admin_dark.jpg"),
|
||||
}}
|
||||
secondImage={{
|
||||
id: 2,
|
||||
imageUrl: useBaseUrl("img/screen_admin_light.jpg"),
|
||||
}}
|
||||
/>
|
||||
|
@ -17,8 +17,8 @@ To disable these outbound connections, adjust the settings as follows:
|
||||
|
||||
To view a list of all configuration options, refer to the [Configuration](./configuration/configuration.mdx) documentation.
|
||||
|
||||
import TabItem from "@theme/TabItem";
|
||||
import Tabs from "@theme/Tabs";
|
||||
import TabItem from "@theme/TabItem";
|
||||
|
||||
<Tabs
|
||||
defaultValue="docker-compose"
|
||||
|
@ -8,8 +8,8 @@ You can test upcoming authentik versions, including major new features that are
|
||||
Downgrading from the Beta is not supported. It is recommended to take a backup before upgrading, or test Beta versions on a separate install. Upgrading from Beta versions to the next release is usually possible, however also not supported.
|
||||
:::
|
||||
|
||||
import TabItem from "@theme/TabItem";
|
||||
import Tabs from "@theme/Tabs";
|
||||
import TabItem from "@theme/TabItem";
|
||||
|
||||
<Tabs
|
||||
defaultValue="docker-compose"
|
||||
|
@ -17,8 +17,8 @@ All of these variables can be set to values, but you can also use a URI-like for
|
||||
|
||||
## Set your environment variables
|
||||
|
||||
import TabItem from "@theme/TabItem";
|
||||
import Tabs from "@theme/Tabs";
|
||||
import TabItem from "@theme/TabItem";
|
||||
|
||||
<Tabs groupId="platform">
|
||||
<TabItem value="docker-compose" label="Docker Compose" default>
|
||||
|
@ -27,8 +27,8 @@ This installation method is for test setups and small-scale production setups.
|
||||
To download the latest `docker-compose.yml` open your terminal and navigate to the directory of your choice.
|
||||
Run the following command:
|
||||
|
||||
import TabItem from "@theme/TabItem";
|
||||
import Tabs from "@theme/Tabs";
|
||||
import TabItem from "@theme/TabItem";
|
||||
|
||||
{/* prettier-ignore */}
|
||||
<Tabs groupId="OS">
|
||||
|
@ -20,8 +20,8 @@ authentik does not support downgrading. Make sure to back up your database in ca
|
||||
|
||||
## Upgrade authentik
|
||||
|
||||
import TabItem from "@theme/TabItem";
|
||||
import Tabs from "@theme/Tabs";
|
||||
import TabItem from "@theme/TabItem";
|
||||
|
||||
<Tabs groupId="platform">
|
||||
<TabItem value="docker-compose" label="Docker Compose" default>
|
||||
|
@ -14,8 +14,8 @@ By default, the GeoIP database is loaded from `/geoip/GeoLite2-City.mmdb`. If mo
|
||||
|
||||
If you want to disable GeoIP, you can set the path to a non-existent path and authentik will skip the GeoIP.
|
||||
|
||||
import TabItem from "@theme/TabItem";
|
||||
import Tabs from "@theme/Tabs";
|
||||
import TabItem from "@theme/TabItem";
|
||||
|
||||
<Tabs
|
||||
defaultValue="docker-compose"
|
||||
|
@ -2,8 +2,8 @@
|
||||
title: General troubleshooting steps
|
||||
---
|
||||
|
||||
import TabItem from "@theme/TabItem";
|
||||
import Tabs from "@theme/Tabs";
|
||||
import TabItem from "@theme/TabItem";
|
||||
|
||||
## Set the log level to TRACE
|
||||
|
||||
|
@ -10,8 +10,8 @@ The server and worker containers support multiple log levels: `debug`, `info`, `
|
||||
|
||||
To modify the log level, follow the steps for your platform
|
||||
|
||||
import TabItem from "@theme/TabItem";
|
||||
import Tabs from "@theme/Tabs";
|
||||
import TabItem from "@theme/TabItem";
|
||||
|
||||
<Tabs
|
||||
groupId="platform"
|
||||
|
@ -32,7 +32,6 @@ See the [overview](../../property-mappings/index.md) for information on how prop
|
||||
|
||||
Each top level SCIM attribute is available as a variable in the expression. For example given an SCIM request with the payload of
|
||||
|
||||
<!-- prettier-ignore-start -->
|
||||
```json
|
||||
{
|
||||
"schemas": [
|
||||
@ -58,7 +57,6 @@ Each top level SCIM attribute is available as a variable in the expression. For
|
||||
}
|
||||
}
|
||||
```
|
||||
<!-- prettier-ignore-end -->
|
||||
|
||||
The following variables are available in the expression:
|
||||
|
||||
|
@ -1,14 +1,13 @@
|
||||
import type * as Preset from "@docusaurus/preset-classic";
|
||||
import type { Config } from "@docusaurus/types";
|
||||
import type * as OpenApiPlugin from "docusaurus-plugin-openapi-docs";
|
||||
import * as path from "node:path";
|
||||
import type * as Preset from "@docusaurus/preset-classic";
|
||||
import { themes as prismThemes } from "prism-react-renderer";
|
||||
import type * as OpenApiPlugin from "docusaurus-plugin-openapi-docs";
|
||||
import remarkGithub, { BuildUrlValues } from "remark-github";
|
||||
import { defaultBuildUrl } from "remark-github";
|
||||
import remarkDirective from "remark-directive";
|
||||
import remarkGithub, { BuildUrlValues, defaultBuildUrl } from "remark-github";
|
||||
|
||||
import remarkPreviewDirective from "./remark/preview-directive.mjs";
|
||||
import remarkSupportDirective from "./remark/support-directive.mjs";
|
||||
import remarkVersionDirective from "./remark/version-directive.mjs";
|
||||
import remarkVersionDirective from "./remark/version-directive.js";
|
||||
import remarkPreviewDirective from "./remark/preview-directive.js";
|
||||
import remarkSupportDirective from "./remark/support-directive.js";
|
||||
|
||||
const createConfig = (): Config => {
|
||||
return {
|
||||
@ -60,16 +59,16 @@ const createConfig = (): Config => {
|
||||
target: "_self",
|
||||
},
|
||||
{
|
||||
"href": "https://github.com/goauthentik/authentik",
|
||||
href: "https://github.com/goauthentik/authentik",
|
||||
"data-icon": "github",
|
||||
"aria-label": "GitHub",
|
||||
"position": "right",
|
||||
position: "right",
|
||||
},
|
||||
{
|
||||
"href": "https://goauthentik.io/discord",
|
||||
href: "https://goauthentik.io/discord",
|
||||
"data-icon": "discord",
|
||||
"aria-label": "Discord",
|
||||
"position": "right",
|
||||
position: "right",
|
||||
},
|
||||
],
|
||||
},
|
||||
@ -112,7 +111,8 @@ const createConfig = (): Config => {
|
||||
id: "docs",
|
||||
sidebarPath: "./sidebars.js",
|
||||
showLastUpdateTime: false,
|
||||
editUrl: "https://github.com/goauthentik/authentik/edit/main/website/",
|
||||
editUrl:
|
||||
"https://github.com/goauthentik/authentik/edit/main/website/",
|
||||
docItemComponent: "@theme/ApiItem",
|
||||
|
||||
beforeDefaultRemarkPlugins: [
|
||||
@ -128,7 +128,8 @@ const createConfig = (): Config => {
|
||||
repository: "goauthentik/authentik",
|
||||
// Only replace issues and PR links
|
||||
buildUrl: (values: BuildUrlValues) => {
|
||||
return values.type === "issue" || values.type === "mention"
|
||||
return values.type === "issue" ||
|
||||
values.type === "mention"
|
||||
? defaultBuildUrl(values)
|
||||
: false;
|
||||
},
|
||||
@ -137,19 +138,14 @@ const createConfig = (): Config => {
|
||||
],
|
||||
},
|
||||
theme: {
|
||||
customCss: require.resolve("@goauthentik/docusaurus-config/css/index.css"),
|
||||
customCss: require.resolve(
|
||||
"@goauthentik/docusaurus-config/css/index.css",
|
||||
),
|
||||
},
|
||||
} satisfies Preset.Options,
|
||||
],
|
||||
],
|
||||
plugins: [
|
||||
[
|
||||
"./releases/plugin.mjs",
|
||||
{
|
||||
docsDirectory: path.join(__dirname, "docs"),
|
||||
staticDirectory: "static",
|
||||
},
|
||||
],
|
||||
[
|
||||
"@docusaurus/plugin-content-docs",
|
||||
{
|
||||
@ -157,7 +153,8 @@ const createConfig = (): Config => {
|
||||
path: "integrations",
|
||||
routeBasePath: "integrations",
|
||||
sidebarPath: "./sidebarsIntegrations.js",
|
||||
editUrl: "https://github.com/goauthentik/authentik/edit/main/website/",
|
||||
editUrl:
|
||||
"https://github.com/goauthentik/authentik/edit/main/website/",
|
||||
},
|
||||
],
|
||||
[
|
||||
|
@ -4,8 +4,8 @@ sidebar_label: Actual Budget
|
||||
support_level: community
|
||||
---
|
||||
|
||||
import TabItem from "@theme/TabItem";
|
||||
import Tabs from "@theme/Tabs";
|
||||
import TabItem from "@theme/TabItem";
|
||||
|
||||
## What is Actual Budget
|
||||
|
||||
|
@ -4,8 +4,8 @@ sidebar_label: Apache Guacamole™
|
||||
support_level: authentik
|
||||
---
|
||||
|
||||
import TabItem from "@theme/TabItem";
|
||||
import Tabs from "@theme/Tabs";
|
||||
import TabItem from "@theme/TabItem";
|
||||
|
||||
## What is Apache Guacamole™
|
||||
|
||||
|
@ -21,8 +21,8 @@ The following placeholders are used in this guide:
|
||||
This documentation lists only the settings that you need to change from their default values. Be aware that any changes other than those explicitly mentioned in this guide could cause issues accessing your application.
|
||||
:::
|
||||
|
||||
import TabItem from "@theme/TabItem";
|
||||
import Tabs from "@theme/Tabs";
|
||||
import TabItem from "@theme/TabItem";
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="iam" label="Classic IAM" default>
|
||||
|
@ -3,8 +3,8 @@ title: Integrate with BookStack
|
||||
sidebar_label: BookStack
|
||||
---
|
||||
|
||||
import TabItem from "@theme/TabItem";
|
||||
import Tabs from "@theme/Tabs";
|
||||
import TabItem from "@theme/TabItem";
|
||||
|
||||
# Integrate with BookStack
|
||||
|
||||
|
@ -29,8 +29,8 @@ This documentation lists only the settings that you need to change from their de
|
||||
|
||||
There are two ways to configure single sign-on for GitLab. You can configure it via SAML authentication or via OpenID Connect.
|
||||
|
||||
import TabItem from "@theme/TabItem";
|
||||
import Tabs from "@theme/Tabs";
|
||||
import TabItem from "@theme/TabItem";
|
||||
|
||||
<Tabs
|
||||
defaultValue="saml"
|
||||
|
@ -101,8 +101,8 @@ resource "authentik_group" "grafana_viewers" {
|
||||
|
||||
## Grafana configuration
|
||||
|
||||
import TabItem from "@theme/TabItem";
|
||||
import Tabs from "@theme/Tabs";
|
||||
import TabItem from "@theme/TabItem";
|
||||
|
||||
<Tabs
|
||||
defaultValue="docker"
|
||||
|
@ -3,8 +3,8 @@ title: Integrate with Applications
|
||||
sidebar_label: Applications
|
||||
---
|
||||
|
||||
import SupportBadge from "@site/src/components/SupportBadge";
|
||||
import DocCardList from "@theme/DocCardList";
|
||||
import SupportBadge from "@site/src/components/SupportBadge";
|
||||
|
||||
# Applications
|
||||
|
||||
|
@ -49,8 +49,6 @@ Paste the following block in your `local.yaml` file, after replacing the placeho
|
||||
|
||||
To get the value for `x509cert`, go to _System_ > _Certificates_, and download the public Signing Certificate. To avoid further problems, concat it into "string format" using e.g.: https://www.samltool.com/format_x509cert.php
|
||||
|
||||
<!-- prettier-ignore-start -->
|
||||
|
||||
```yaml
|
||||
# Optionally add this for docker debug-logging
|
||||
# monolog:
|
||||
@ -129,6 +127,4 @@ kimai:
|
||||
url: "https://kimai.company"
|
||||
```
|
||||
|
||||
<!-- prettier-ignore-end -->
|
||||
|
||||
Afterwards, either [rebuild the cache](https://www.kimai.org/documentation/cache.html) or restart the docker container.
|
||||
|
@ -30,8 +30,8 @@ This documentation lists only the settings that you need to change from their de
|
||||
|
||||
It is possible to configure Nextcloud to use either OpenID Connect or SAML for authentication. Below are the steps to configure both methods.
|
||||
|
||||
import TabItem from "@theme/TabItem";
|
||||
import Tabs from "@theme/Tabs";
|
||||
import TabItem from "@theme/TabItem";
|
||||
|
||||
<Tabs
|
||||
defaultValue="oidc"
|
||||
|
@ -41,8 +41,8 @@ To support the integration of Paperless-ngx with authentik, you need to create a
|
||||
|
||||
## Paperless Configuration
|
||||
|
||||
import TabItem from "@theme/TabItem";
|
||||
import Tabs from "@theme/Tabs";
|
||||
import TabItem from "@theme/TabItem";
|
||||
|
||||
<Tabs
|
||||
defaultValue="docker"
|
||||
|
@ -42,8 +42,8 @@ To support the integration of Wekan with authentik, you need to create an applic
|
||||
|
||||
## Wekan configuration
|
||||
|
||||
import TabItem from "@theme/TabItem";
|
||||
import Tabs from "@theme/Tabs";
|
||||
import TabItem from "@theme/TabItem";
|
||||
|
||||
<Tabs
|
||||
defaultValue="docker"
|
||||
|
8648
website/package-lock.json
generated
8648
website/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -1,27 +1,24 @@
|
||||
{
|
||||
"name": "@goauthentik/docs",
|
||||
"name": "@goauthentik/website-docs",
|
||||
"version": "0.0.0",
|
||||
"license": "MIT",
|
||||
"private": true,
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"build:docker": "cp ../docker-compose.yml ./static/docker-compose.yml",
|
||||
"build:schema": "cp -f ../schema.yml ./static/schema.yml",
|
||||
"build:api": "docusaurus gen-api-docs all",
|
||||
"build:docusaurus": "cross-env NODE_OPTIONS='--max_old_space_size=65536' docusaurus build",
|
||||
"build-bundled": "run-s build:schema build:api build:docusaurus",
|
||||
"build": "run-s build:docker build:schema build:api build:docusaurus",
|
||||
"build": "cp ../docker-compose.yml static/docker-compose.yml && cp ../schema.yml static/schema.yml && docusaurus gen-api-docs all && cross-env NODE_OPTIONS='--max_old_space_size=65536' docusaurus build",
|
||||
"build-bundled": "cp ../schema.yml static/schema.yml && docusaurus gen-api-docs all && cross-env NODE_OPTIONS='--max_old_space_size=65536' docusaurus build",
|
||||
"deploy": "docusaurus deploy",
|
||||
"docusaurus": "docusaurus",
|
||||
"lint:lockfile": "echo 'Skipping lockfile linting'",
|
||||
"lint:lockfile": "wireit",
|
||||
"prettier": "prettier --write .",
|
||||
"prettier-check": "prettier --check .",
|
||||
"start": "docusaurus start",
|
||||
"serve": "docusaurus serve",
|
||||
"swizzle": "docusaurus swizzle",
|
||||
"test": "node --test",
|
||||
"watch": "run-s build:schema build:api start"
|
||||
"watch": "cp -f ../schema.yml ./static/schema.yml && docusaurus gen-api-docs all && docusaurus start"
|
||||
},
|
||||
"dependencies": {
|
||||
"@goauthentik/docusaurus-config": "^1.0.4",
|
||||
"semver": "^7.7.1",
|
||||
"@docusaurus/core": "^3.7.0",
|
||||
"@docusaurus/faster": "^3.7.0",
|
||||
"@docusaurus/plugin-client-redirects": "^3.7.0",
|
||||
@ -29,52 +26,20 @@
|
||||
"@docusaurus/preset-classic": "^3.7.0",
|
||||
"@docusaurus/theme-common": "^3.7.0",
|
||||
"@docusaurus/theme-mermaid": "^3.7.0",
|
||||
"@goauthentik/docusaurus-config": "^1.0.6",
|
||||
"@goauthentik/tsconfig": "^1.0.4",
|
||||
"@mdx-js/react": "^3.1.0",
|
||||
"clsx": "^2.1.1",
|
||||
"docusaurus-plugin-openapi-docs": "^4.3.7",
|
||||
"docusaurus-theme-openapi-docs": "^4.3.7",
|
||||
"disqus-react": "^1.1.6",
|
||||
"docusaurus-plugin-openapi-docs": "4.3.4",
|
||||
"docusaurus-theme-openapi-docs": "4.3.4",
|
||||
"postcss": "^8.5.3",
|
||||
"prism-react-renderer": "^2.4.1",
|
||||
"react": "^18.3.1",
|
||||
"react-before-after-slider-component": "^1.1.8",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-feather": "^2.0.10",
|
||||
"react-toggle": "^4.1.3",
|
||||
"remark-directive": "^4.0.0",
|
||||
"remark-github": "^12.0.0",
|
||||
"semver": "^7.7.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@docusaurus/module-type-aliases": "^3.7.0",
|
||||
"@docusaurus/tsconfig": "^3.7.0",
|
||||
"@docusaurus/types": "^3.7.0",
|
||||
"@goauthentik/prettier-config": "^1.0.4",
|
||||
"@types/lodash": "^4.17.16",
|
||||
"@types/postman-collection": "^3.5.10",
|
||||
"@types/react": "^18.3.13",
|
||||
"@types/semver": "^7.7.0",
|
||||
"cross-env": "^7.0.3",
|
||||
"fast-glob": "^3.3.3",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"prettier": "^3.5.3",
|
||||
"typescript": "~5.8.2"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@rspack/binding-darwin-arm64": "1.3.8",
|
||||
"@rspack/binding-linux-arm64-gnu": "1.3.8",
|
||||
"@rspack/binding-linux-x64-gnu": "1.3.8",
|
||||
"@swc/core-darwin-arm64": "1.11.24",
|
||||
"@swc/core-linux-arm64-gnu": "1.11.24",
|
||||
"@swc/core-linux-x64-gnu": "1.11.24",
|
||||
"@swc/html-darwin-arm64": "1.11.24",
|
||||
"@swc/html-linux-arm64-gnu": "1.11.24",
|
||||
"@swc/html-linux-x64-gnu": "1.11.24",
|
||||
"lightningcss-darwin-arm64": "1.29.3",
|
||||
"lightningcss-linux-arm64-gnu": "1.29.3",
|
||||
"lightningcss-linux-x64-gnu": "1.29.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=22.14.0"
|
||||
"remark-github": "^12.0.0"
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
@ -88,16 +53,39 @@
|
||||
"last 1 safari version"
|
||||
]
|
||||
},
|
||||
"prettier": "@goauthentik/prettier-config",
|
||||
"overrides": {
|
||||
"postman-collection": {
|
||||
"@faker-js/faker": "^6.3.1"
|
||||
},
|
||||
"webpack-dev-server": {
|
||||
"rimraf": "6.0.1"
|
||||
},
|
||||
"fork-ts-checker-webpack-plugin": {
|
||||
"glob": "^11.0.1"
|
||||
"devDependencies": {
|
||||
"@types/semver": "^7.7.0",
|
||||
"@docusaurus/module-type-aliases": "^3.3.2",
|
||||
"@docusaurus/tsconfig": "^3.7.0",
|
||||
"@docusaurus/types": "^3.3.2",
|
||||
"@types/react": "^18.3.13",
|
||||
"cross-env": "^7.0.3",
|
||||
"prettier": "3.5.3",
|
||||
"typescript": "~5.8.3",
|
||||
"wireit": "^0.14.12"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@rspack/binding-darwin-arm64": "1.3.8",
|
||||
"@rspack/binding-linux-arm64-gnu": "1.3.8",
|
||||
"@rspack/binding-linux-x64-gnu": "1.3.8",
|
||||
"lightningcss-darwin-arm64": "1.29.3",
|
||||
"lightningcss-linux-arm64-gnu": "1.29.3",
|
||||
"lightningcss-linux-x64-gnu": "1.29.3",
|
||||
"@swc/core-darwin-arm64": "1.11.24",
|
||||
"@swc/core-linux-arm64-gnu": "1.11.24",
|
||||
"@swc/core-linux-x64-gnu": "1.11.24",
|
||||
"@swc/html-darwin-arm64": "1.11.24",
|
||||
"@swc/html-linux-arm64-gnu": "1.11.24",
|
||||
"@swc/html-linux-x64-gnu": "1.11.24"
|
||||
},
|
||||
"wireit": {
|
||||
"lint:lockfile": {
|
||||
"__comment": "The lockfile-lint package does not have an option to ensure resolved hashes are set everywhere",
|
||||
"shell": true,
|
||||
"command": "[ -z \"$(jq -r '.packages | to_entries[] | select((.key | contains(\"node_modules\")) and (.value | has(\"resolved\") | not)) | .key' < package-lock.json)\" ]"
|
||||
}
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
}
|
||||
}
|
||||
|
@ -1,67 +0,0 @@
|
||||
/**
|
||||
* @file Sidebar utilities.
|
||||
*
|
||||
* @todo This needs a revision to better align with Docusaurus's components.
|
||||
*/
|
||||
|
||||
const html = String.raw;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string[]} releases
|
||||
* @returns
|
||||
*/
|
||||
export function generateVersionDropdown(releases) {
|
||||
return html`<div
|
||||
class="navbar__item dropdown dropdown--hoverable dropdown--right psuedo-dropdown"
|
||||
>
|
||||
<style>
|
||||
.psuedo-dropdown .navbar__link.menu__link {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
justify-content: space-between;
|
||||
font-weight: var(--ifm-font-weight-semibold);
|
||||
}
|
||||
.psuedo-dropdown .navbar__link.menu__link::after {
|
||||
color: var(--ifm-color-emphasis-400);
|
||||
filter: var(--ifm-menu-link-sublist-icon-filter);
|
||||
margin-inline-end: calc(var(--ifm-navbar-padding-horizontal) * 0.5);
|
||||
}
|
||||
|
||||
.psuedo-dropdown .dropdown__menu {
|
||||
background: var(--ifm-dropdown-background-color);
|
||||
box-shadow: var(--ifm-global-shadow-lw);
|
||||
border: 1px solid var(--ifm-color-emphasis-200);
|
||||
}
|
||||
</style>
|
||||
|
||||
<div
|
||||
aria-haspopup="true"
|
||||
aria-expanded="false"
|
||||
role="button"
|
||||
class="navbar__link menu__link"
|
||||
>
|
||||
Version: Pre-Release
|
||||
</div>
|
||||
|
||||
<ul class="dropdown__menu menu__list-item--collapsed">
|
||||
${releases
|
||||
.map((semver, idx) => {
|
||||
const subdomain = `version-${semver.replace(".", "-")}`;
|
||||
const label = `Version: ${semver}${idx === 0 ? " (Current)" : ""}`;
|
||||
|
||||
return html`<li>
|
||||
<a
|
||||
href="https://${subdomain}.goauthentik.io/docs"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="dropdown__link menu__link"
|
||||
>${label}</a
|
||||
>
|
||||
</li>`;
|
||||
})
|
||||
.join("")}
|
||||
</ul>
|
||||
</div>
|
||||
<hr />`;
|
||||
}
|
@ -1,46 +0,0 @@
|
||||
/**
|
||||
* @file Docusaurus releases plugin.
|
||||
*
|
||||
* @import { LoadContext, Plugin } from "@docusaurus/types"
|
||||
*/
|
||||
import * as fs from "node:fs/promises";
|
||||
import * as path from "node:path";
|
||||
|
||||
import { collectReleaseFiles } from "./utils.mjs";
|
||||
|
||||
const RELEASES_FILENAME = "releases.gen.json";
|
||||
|
||||
/**
|
||||
* @typedef {object} ReleasesPluginOptions
|
||||
* @property {string} docsDirectory
|
||||
* @property {string} staticDirectory
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param {LoadContext} _context
|
||||
* @param {ReleasesPluginOptions} options
|
||||
* @returns {Promise<Plugin>}
|
||||
*/
|
||||
async function releasesPlugin(_context, { docsDirectory, staticDirectory }) {
|
||||
if (!staticDirectory) {
|
||||
throw new Error("releases-plugin: staticDirectory is required");
|
||||
}
|
||||
|
||||
return {
|
||||
name: "releases-plugin",
|
||||
async loadContent() {
|
||||
console.log("🚀 releases-plugin loaded");
|
||||
|
||||
const releases = collectReleaseFiles(docsDirectory).map((release) => release.name);
|
||||
|
||||
const outputPath = path.join(staticDirectory, RELEASES_FILENAME);
|
||||
|
||||
await fs.mkdir(path.dirname(outputPath), { recursive: true });
|
||||
await fs.writeFile(outputPath, JSON.stringify(releases, null, 2), "utf-8");
|
||||
|
||||
console.log(`✅ ${RELEASES_FILENAME} generated`);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export default releasesPlugin;
|
@ -1,57 +0,0 @@
|
||||
/**
|
||||
* @file Docusaurus release utils.
|
||||
*/
|
||||
import FastGlob from "fast-glob";
|
||||
import * as path from "node:path";
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} releasesParentDirectory
|
||||
* @returns {FastGlob.Entry[]}
|
||||
*/
|
||||
export function collectReleaseFiles(releasesParentDirectory) {
|
||||
const releaseFiles = FastGlob.sync("releases/**/v*.{md,mdx}", {
|
||||
cwd: releasesParentDirectory,
|
||||
onlyFiles: true,
|
||||
objectMode: true,
|
||||
})
|
||||
.map((fileEntry) => {
|
||||
return {
|
||||
...fileEntry,
|
||||
path: fileEntry.path.replace(/\.mdx?$/, ""),
|
||||
name: fileEntry.name.replace(/^v/, "").replace(/\.mdx?$/, ""),
|
||||
};
|
||||
})
|
||||
.sort((a, b) => b.name.localeCompare(a.name));
|
||||
|
||||
return releaseFiles;
|
||||
}
|
||||
|
||||
export const SUPPORTED_RELEASE_COUNT = 3;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {FastGlob.Entry[]} releaseFiles
|
||||
*/
|
||||
export function createReleaseSidebarEntries(releaseFiles) {
|
||||
/**
|
||||
* @type {any[]}
|
||||
*/
|
||||
let sidebarEntries = releaseFiles.map((fileEntry) => {
|
||||
return path.join(fileEntry.path);
|
||||
});
|
||||
|
||||
if (releaseFiles.length > SUPPORTED_RELEASE_COUNT) {
|
||||
// Then we add the rest of the releases as a category.
|
||||
sidebarEntries = [
|
||||
...sidebarEntries.slice(0, SUPPORTED_RELEASE_COUNT),
|
||||
{
|
||||
type: "category",
|
||||
label: "Previous versions",
|
||||
items: sidebarEntries.slice(SUPPORTED_RELEASE_COUNT),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
return sidebarEntries;
|
||||
}
|
@ -1,19 +1,15 @@
|
||||
/**
|
||||
* @file Remark plugin to transform `ak-preview` directives into preview badges.
|
||||
*
|
||||
* @import { Root } from "mdast";
|
||||
*/
|
||||
import "mdast-util-to-hast";
|
||||
import "mdast-util-directive";
|
||||
|
||||
import { h } from "hastscript";
|
||||
import { SKIP, visit } from "unist-util-visit";
|
||||
import { Root } from "mdast";
|
||||
import { visit, SKIP } from "unist-util-visit";
|
||||
|
||||
/**
|
||||
* MDAST plugin to transform `ak-preview` directives into preview badges.
|
||||
*/
|
||||
function remarkPreviewDirective() {
|
||||
/**
|
||||
* @param {Root} tree The MDAST tree to transform.
|
||||
*/
|
||||
return function (tree) {
|
||||
return function (tree: Root) {
|
||||
visit(tree, "textDirective", function (node) {
|
||||
if (node.name !== "ak-preview") return SKIP;
|
||||
|
||||
@ -21,10 +17,10 @@ function remarkPreviewDirective() {
|
||||
|
||||
const hast = h("span", {
|
||||
...node.attributes,
|
||||
"className": "badge badge--preview",
|
||||
"title": `This feature is in preview and may change in the future.`,
|
||||
className: "badge badge--preview",
|
||||
title: `This feature is in preview and may change in the future.`,
|
||||
"aria-description": "Preview badge",
|
||||
"role": "img",
|
||||
role: "img",
|
||||
});
|
||||
|
||||
data.hName = hast.tagName;
|
@ -1,35 +1,30 @@
|
||||
/**
|
||||
* @file Remark plugin to transform `ak-support` directives into support level badges.
|
||||
*
|
||||
* @import { Root } from "mdast";
|
||||
*/
|
||||
import "mdast-util-to-hast";
|
||||
import "mdast-util-directive";
|
||||
|
||||
import { h } from "hastscript";
|
||||
import { SKIP, visit } from "unist-util-visit";
|
||||
import { Root } from "mdast";
|
||||
import { visit, SKIP } from "unist-util-visit";
|
||||
import { coerce } from "semver";
|
||||
|
||||
/**
|
||||
* Support levels for authentik.
|
||||
* @typedef {"authentik" | "community" | "vendor" | "deprecated"} SupportLevel
|
||||
*/
|
||||
export type SupportLevel = "authentik" | "community" | "vendor" | "deprecated";
|
||||
|
||||
/**
|
||||
* Mapping of support levels to badge classes.
|
||||
*
|
||||
* @satisfies {Record<SupportLevel, string>}
|
||||
*/
|
||||
export const SupportLevelToLabel = /** @type {const} */ ({
|
||||
export const SupportLevelToLabel = {
|
||||
authentik: "authentik",
|
||||
community: "Community",
|
||||
vendor: "Vendor",
|
||||
deprecated: "Deprecated",
|
||||
});
|
||||
} as const satisfies Record<SupportLevel, string>;
|
||||
|
||||
/**
|
||||
* Type-predicate to determine if a string is a known support level.
|
||||
*
|
||||
* @param {string} input The string to check.
|
||||
* @return {input is SupportLevel} True if the string is a known support level.
|
||||
*/
|
||||
export function isSupportLevel(input) {
|
||||
export function isSupportLevel(input: string): input is SupportLevel {
|
||||
return Object.hasOwn(SupportLevelToLabel, input);
|
||||
}
|
||||
|
||||
@ -37,10 +32,7 @@ export function isSupportLevel(input) {
|
||||
* MDAST plugin to transform `ak-support` directives into preview badges.
|
||||
*/
|
||||
function remarkSupportDirective() {
|
||||
/**
|
||||
* @param {Root} tree The MDAST tree to transform.
|
||||
*/
|
||||
return function (tree) {
|
||||
return function (tree: Root) {
|
||||
visit(tree, "textDirective", function (node) {
|
||||
if (node.name !== "ak-support") return SKIP;
|
||||
|
||||
@ -60,10 +52,10 @@ function remarkSupportDirective() {
|
||||
|
||||
const hast = h("span", {
|
||||
...node.attributes,
|
||||
"className": `badge badge--support-${level}`,
|
||||
"title": `This feature is supported at the ${label} level.`,
|
||||
className: `badge badge--support-${level}`,
|
||||
title: `This feature is supported at the ${label} level.`,
|
||||
"aria-description": "Support level badge",
|
||||
"role": "img",
|
||||
role: "img",
|
||||
});
|
||||
|
||||
data.hName = hast.tagName;
|
@ -1,11 +1,10 @@
|
||||
/**
|
||||
* @file Remark plugin to transform `ak-version` directives into version badges.
|
||||
*
|
||||
* @import { Root } from "mdast";
|
||||
*/
|
||||
import "mdast-util-to-hast";
|
||||
import "mdast-util-directive";
|
||||
|
||||
import { h } from "hastscript";
|
||||
import { Root } from "mdast";
|
||||
import { visit, SKIP } from "unist-util-visit";
|
||||
import { coerce } from "semver";
|
||||
import { SKIP, visit } from "unist-util-visit";
|
||||
|
||||
/**
|
||||
* MDAST plugin to transform `ak-version` directives into version badges.
|
||||
@ -23,10 +22,7 @@ import { SKIP, visit } from "unist-util-visit";
|
||||
* ```
|
||||
*/
|
||||
function remarkVersionDirective() {
|
||||
/**
|
||||
* @param {Root} tree The MDAST tree to transform.
|
||||
*/
|
||||
return function (tree) {
|
||||
return function (tree: Root) {
|
||||
visit(tree, "textDirective", function (node) {
|
||||
if (node.name !== "ak-version") return SKIP;
|
||||
|
||||
@ -44,17 +40,19 @@ function remarkVersionDirective() {
|
||||
const yearCutoff = new Date().getFullYear() - 2;
|
||||
|
||||
if (parsed.major <= yearCutoff) {
|
||||
throw new Error(`Semver version <= ${yearCutoff} is not supported: ${semver}`);
|
||||
throw new Error(
|
||||
`Semver version <= ${yearCutoff} is not supported: ${semver}`,
|
||||
);
|
||||
}
|
||||
|
||||
const data = node.data || (node.data = {});
|
||||
|
||||
const hast = h("span", {
|
||||
...node.attributes,
|
||||
"className": "badge badge--version",
|
||||
"title": `Available in authentik ${parsed.format()} and later`,
|
||||
className: "badge badge--version",
|
||||
title: `Available in authentik ${parsed.format()} and later`,
|
||||
"aria-description": "Version badge",
|
||||
"role": "img",
|
||||
role: "img",
|
||||
});
|
||||
|
||||
data.hName = hast.tagName;
|
1533
website/sidebars.js
1533
website/sidebars.js
File diff suppressed because it is too large
Load Diff
@ -1,8 +1,4 @@
|
||||
/**
|
||||
* @file Sidebar configuration for the authentik integrations.
|
||||
*/
|
||||
|
||||
const sidebarsIntegrations = {
|
||||
module.exports = {
|
||||
integrations: [
|
||||
{
|
||||
type: "doc",
|
||||
@ -210,5 +206,3 @@ const sidebarsIntegrations = {
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export default sidebarsIntegrations;
|
||||
|
@ -1,5 +1,8 @@
|
||||
import { SupportLevelToLabel, isSupportLevel } from "@site/remark/support-directive.mjs";
|
||||
import React from "react";
|
||||
import {
|
||||
isSupportLevel,
|
||||
SupportLevelToLabel,
|
||||
} from "@site/remark/support-directive";
|
||||
|
||||
export interface SupportBadgeProps {
|
||||
level: string;
|
||||
|
@ -18,7 +18,9 @@ export const VersionBadge: React.FC<AuthentikVersionProps> = ({ semver }) => {
|
||||
const yearCutoff = new Date().getFullYear() - 2;
|
||||
|
||||
if (parsed.major <= yearCutoff) {
|
||||
throw new Error(`Semver version <= ${yearCutoff} is not supported: ${semver}`);
|
||||
throw new Error(
|
||||
`Semver version <= ${yearCutoff} is not supported: ${semver}`,
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -14,7 +14,8 @@ import { useDoc } from "@docusaurus/plugin-content-docs/client";
|
||||
*/
|
||||
export function useSyntheticTitle(): string | null {
|
||||
const { metadata, frontMatter, contentTitle } = useDoc();
|
||||
const shouldRender = !frontMatter.hide_title && typeof contentTitle === "undefined";
|
||||
const shouldRender =
|
||||
!frontMatter.hide_title && typeof contentTitle === "undefined";
|
||||
if (!shouldRender) {
|
||||
return null;
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { Redirect } from "@docusaurus/router";
|
||||
import React from "react";
|
||||
import { Redirect } from "@docusaurus/router";
|
||||
|
||||
function Home() {
|
||||
return <Redirect to="/docs" />;
|
||||
|
@ -1,4 +1,3 @@
|
||||
/// <reference types="@docusaurus/plugin-content-docs" />
|
||||
/**
|
||||
* @file Swizzled DocItemContent component.
|
||||
*
|
||||
@ -8,17 +7,21 @@
|
||||
* the content of a documentation page. However, it also adds support for
|
||||
* support badges, and Authentik version badges.
|
||||
*/
|
||||
import { DocFrontMatter } from "@docusaurus/plugin-content-docs";
|
||||
import { DocContextValue, useDoc } from "@docusaurus/plugin-content-docs/client";
|
||||
|
||||
import React from "react";
|
||||
import clsx from "clsx";
|
||||
import { ThemeClassNames } from "@docusaurus/theme-common";
|
||||
import { SupportBadge } from "@site/src/components/SupportBadge";
|
||||
import { VersionBadge } from "@site/src/components/VersionBadge";
|
||||
import { useSyntheticTitle } from "@site/src/hooks/title";
|
||||
import type { Props } from "@theme/DocItem/Content";
|
||||
import {
|
||||
DocContextValue,
|
||||
useDoc,
|
||||
} from "@docusaurus/plugin-content-docs/client";
|
||||
import Heading from "@theme/Heading";
|
||||
import MDXContent from "@theme/MDXContent";
|
||||
import clsx from "clsx";
|
||||
import React from "react";
|
||||
import type { Props } from "@theme/DocItem/Content";
|
||||
import { DocFrontMatter } from "@docusaurus/plugin-content-docs";
|
||||
import { useSyntheticTitle } from "@site/src/hooks/title";
|
||||
import { SupportBadge } from "@site/src/components/SupportBadge";
|
||||
import { VersionBadge } from "@site/src/components/VersionBadge";
|
||||
|
||||
interface SwizzledDocFrontMatter extends DocFrontMatter {
|
||||
support_level?: string;
|
||||
@ -34,8 +37,12 @@ interface SwizzledDocContextValue extends DocContextValue {
|
||||
const DocItemContent: React.FC<Props> = ({ children }) => {
|
||||
const syntheticTitle = useSyntheticTitle();
|
||||
const { frontMatter } = useDoc() as SwizzledDocContextValue;
|
||||
const { support_level, authentik_version, authentik_enterprise, authentik_preview } =
|
||||
frontMatter;
|
||||
const {
|
||||
support_level,
|
||||
authentik_version,
|
||||
authentik_enterprise,
|
||||
authentik_preview,
|
||||
} = frontMatter;
|
||||
|
||||
const badges: JSX.Element[] = [];
|
||||
|
||||
@ -64,7 +71,9 @@ const DocItemContent: React.FC<Props> = ({ children }) => {
|
||||
{badges.length ? (
|
||||
<p className="badge-group">
|
||||
{badges.map((badge, index) => (
|
||||
<React.Fragment key={index}>{badge}</React.Fragment>
|
||||
<React.Fragment key={index}>
|
||||
{badge}
|
||||
</React.Fragment>
|
||||
))}
|
||||
</p>
|
||||
) : null}
|
||||
|
@ -1,12 +1,13 @@
|
||||
import Translate from "@docusaurus/Translate";
|
||||
import Admonition from "@theme/Admonition";
|
||||
import type { Props } from "@theme/EditMetaRow";
|
||||
import EditThisPage from "@theme/EditThisPage";
|
||||
import LastUpdated from "@theme/LastUpdated";
|
||||
import React, { type ReactNode } from "react";
|
||||
import clsx from "clsx";
|
||||
import React from "react";
|
||||
import EditThisPage from "@theme/EditThisPage";
|
||||
import type { Props } from "@theme/EditMetaRow";
|
||||
|
||||
import LastUpdated from "@theme/LastUpdated";
|
||||
import Admonition from "@theme/Admonition";
|
||||
import styles from "./styles.module.css";
|
||||
import Translate from "@docusaurus/Translate";
|
||||
import IconNote from "@theme/Admonition/Icon/Note";
|
||||
|
||||
const EditMetaRow: React.FC<Props> = ({
|
||||
className,
|
||||
@ -39,20 +40,22 @@ const EditMetaRow: React.FC<Props> = ({
|
||||
id="theme.common.contributor.footerDescription1"
|
||||
description="The description for the contribution footer"
|
||||
>
|
||||
We welcome your knowledge and expertise. If you see areas of the
|
||||
documentation that you can improve (fix a typo, correct a technical detail,
|
||||
add additional context, etc.) we would really appreciate your contribution.
|
||||
We welcome your knowledge and expertise. If you see
|
||||
areas of the documentation that you can improve (fix a
|
||||
typo, correct a technical detail, add additional
|
||||
context, etc.) we would really appreciate your
|
||||
contribution.
|
||||
</Translate>
|
||||
</p>
|
||||
|
||||
<div className="row">
|
||||
<div className="col col--12">
|
||||
<ul>
|
||||
{editUrl ? (
|
||||
{editUrl && (
|
||||
<li>
|
||||
<EditThisPage editUrl={editUrl} />
|
||||
</li>
|
||||
) : null}
|
||||
)}
|
||||
|
||||
<li>
|
||||
<a
|
||||
@ -105,9 +108,12 @@ const EditMetaRow: React.FC<Props> = ({
|
||||
|
||||
<div className="row">
|
||||
<div className={clsx("col", styles.lastUpdated)}>
|
||||
{lastUpdatedAt || lastUpdatedBy ? (
|
||||
<LastUpdated lastUpdatedAt={lastUpdatedAt} lastUpdatedBy={lastUpdatedBy} />
|
||||
) : null}
|
||||
{(lastUpdatedAt || lastUpdatedBy) && (
|
||||
<LastUpdated
|
||||
lastUpdatedAt={lastUpdatedAt}
|
||||
lastUpdatedBy={lastUpdatedBy}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
|
@ -52,7 +52,8 @@ html[data-theme="dark"] {
|
||||
display: flex;
|
||||
gap: var(--ifm-spacing-horizontal);
|
||||
justify-content: center;
|
||||
margin: calc(var(--ifm-spacing-horizontal) * 2) 0 var(--ifm-spacing-horizontal);
|
||||
margin: calc(var(--ifm-spacing-horizontal) * 2) 0
|
||||
var(--ifm-spacing-horizontal);
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
|
@ -1,8 +1,8 @@
|
||||
import Link from "@docusaurus/Link";
|
||||
import React, { type ReactNode } from "react";
|
||||
import Translate from "@docusaurus/Translate";
|
||||
import { ThemeClassNames } from "@docusaurus/theme-common";
|
||||
import Link from "@docusaurus/Link";
|
||||
import type { Props } from "@theme/EditThisPage";
|
||||
import React, { type ReactNode } from "react";
|
||||
|
||||
export default function EditThisPage({ editUrl }: Props): ReactNode {
|
||||
return (
|
||||
|
27
website/src/utils.js
Normal file
27
website/src/utils.js
Normal file
@ -0,0 +1,27 @@
|
||||
export function generateVersionDropdown(allReleases) {
|
||||
const releases = allReleases.filter(
|
||||
(release) => typeof release === "string",
|
||||
);
|
||||
const latest = releases[0].replace(/releases\/\d+\/v/, "");
|
||||
return `<div class="navbar__item dropdown dropdown--hoverable dropdown--right">
|
||||
<div aria-haspopup="true" aria-expanded="false" role="button" class="navbar__link menu__link">
|
||||
Version: ${latest}
|
||||
</div>
|
||||
<ul class="dropdown__menu">
|
||||
${releases
|
||||
.map((release) => {
|
||||
const version = release.replace(/releases\/\d+\/v/, "");
|
||||
const subdomain = `version-${version.replace(".", "-")}`;
|
||||
const label = `Version: ${version}`;
|
||||
return `<li>
|
||||
<a
|
||||
href="https://${subdomain}.goauthentik.io/docs"
|
||||
target="_blank" rel="noopener noreferrer"
|
||||
class="dropdown__link">${label}</a>
|
||||
</li>`;
|
||||
})
|
||||
.join("")}
|
||||
</ul>
|
||||
</div>
|
||||
<hr>`;
|
||||
}
|
@ -1,54 +1,34 @@
|
||||
/**
|
||||
* @file Test suite for the sidebar configuration of the authentik integrations.
|
||||
*
|
||||
* @todo Enforce types.
|
||||
*/
|
||||
import FastGlob from "fast-glob";
|
||||
import assert from "node:assert";
|
||||
import test from "node:test";
|
||||
|
||||
import assert from "node:assert";
|
||||
import sidebar from "../sidebarsIntegrations.js";
|
||||
import glob from "glob";
|
||||
|
||||
const getSidebarItems = () => {
|
||||
/**
|
||||
* @type {any[]}
|
||||
*/
|
||||
const allItems = [];
|
||||
/**
|
||||
*
|
||||
* @param {any} category
|
||||
*/
|
||||
const mapper = (category) => {
|
||||
if (!category.items) {
|
||||
return;
|
||||
}
|
||||
|
||||
category.items.forEach(
|
||||
/**
|
||||
*
|
||||
* @param {any} item
|
||||
*/
|
||||
(item) => {
|
||||
if (typeof item === "string") {
|
||||
allItems.push(item);
|
||||
} else {
|
||||
mapper(item);
|
||||
}
|
||||
},
|
||||
);
|
||||
category.items.forEach((item) => {
|
||||
if (item.constructor === String) {
|
||||
allItems.push(item);
|
||||
} else {
|
||||
mapper(item);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
sidebar.integrations.forEach(mapper);
|
||||
return allItems.sort();
|
||||
};
|
||||
|
||||
test("ensure all services have a sidebar entry", (_t) => {
|
||||
test("ensure all services have a sidebar entry", (t) => {
|
||||
// All services in the sidebar
|
||||
const services = getSidebarItems()
|
||||
.filter((entry) => entry.startsWith("services/"))
|
||||
.map((entry) => entry.replace("/index", ""))
|
||||
.map((entry) => entry.replace("services/", ""));
|
||||
const servicesFiles = FastGlob.sync("integrations/**/*.+(md|mdx)")
|
||||
const servicesFiles = glob
|
||||
.sync("integrations/**/*.+(md|mdx)")
|
||||
.filter((entry) => entry.startsWith("integrations/services/"))
|
||||
.map((entry) => entry.replace("integrations/services/", ""))
|
||||
.map((entry) => entry.replace(/\/index\.mdx?/, ""))
|
||||
@ -59,19 +39,19 @@ test("ensure all services have a sidebar entry", (_t) => {
|
||||
});
|
||||
});
|
||||
|
||||
test("ensure all sources have a sidebar entry", (_t) => {
|
||||
test("ensure all sources have a sidebar entry", (t) => {
|
||||
// All sources in the sidebar
|
||||
const sources = getSidebarItems()
|
||||
.filter((entry) => entry.startsWith("sources/"))
|
||||
.map((entry) => entry.replace("/index", ""))
|
||||
.map((entry) => entry.replace("sources/", ""));
|
||||
const sourceFiles = FastGlob.sync("integrations/**/*.+(md|mdx)")
|
||||
const sourceFiles = glob
|
||||
.sync("integrations/**/*.+(md|mdx)")
|
||||
.filter((entry) => entry.startsWith("integrations/sources/"))
|
||||
.map((entry) => entry.replace("integrations/sources/", ""))
|
||||
.map((entry) => entry.replace(/\/index\.mdx?/, ""))
|
||||
.map((entry) => entry.replace(".md", ""))
|
||||
.sort();
|
||||
|
||||
sourceFiles.forEach((file, idx) => {
|
||||
assert.strictEqual(file, sources[idx]);
|
||||
});
|
@ -1,32 +1,8 @@
|
||||
// @file TSConfig used by the docs package during development.
|
||||
//
|
||||
// @remarks
|
||||
// While this configuration will influence the IDE experience,
|
||||
// Docusaurus will instead use an internal configuration to build the site.
|
||||
//
|
||||
// @see https://docusaurus.io/docs/typescript-support
|
||||
{
|
||||
"extends": "@goauthentik/tsconfig",
|
||||
// This file is not used in compilation. It is here just for a nice editor experience.
|
||||
"extends": "@docusaurus/tsconfig",
|
||||
"compilerOptions": {
|
||||
"noEmit": true,
|
||||
"checkJs": true,
|
||||
"allowJs": true,
|
||||
"composite": false,
|
||||
"esModuleInterop": true,
|
||||
"moduleResolution": "bundler",
|
||||
"module": "esnext",
|
||||
"jsx": "preserve",
|
||||
"lib": ["DOM", "DOM.Iterable", "ESNext"],
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@site/*": ["./*"]
|
||||
}
|
||||
"baseUrl": "."
|
||||
},
|
||||
"exclude": [
|
||||
// ---
|
||||
"./out/**/*",
|
||||
"./dist/**/*",
|
||||
".docusaurus",
|
||||
"build"
|
||||
]
|
||||
"exclude": [".docusaurus", "build", "node_modules"]
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user