root: Multi-tenancy (#7590)

* tenants -> brands, init new tenant model, migrate some config to tenants

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* setup logging for tenants

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* configure celery and cache

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* small fixes, runs

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* task fixes, creation of tenant now works by cloning a template schema, some other small stuff

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* lint

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* fix-tests

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* upstream fixes

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* fix-pylint

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* lint

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* fix tests

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* fix avatar tests

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* migrate config reputation_expiry as well

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* fix web rebase

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* lint

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* fix migrations for template schema

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* fix migrations for template schema

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* fix migrations for template schema 3

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* revert reputation expiry migration

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* fix type

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* fix some more tests

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* website: tenants -> brands

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* try fixing e2e tests

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* start frontend :help:

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* add ability to disable tenants api

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* delete embedded outpost if it is disabled

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* make sure embedded outpost is disabled when tenants are enabled

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* management commands: add --schema option where relevant

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* store files per-tenant

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* fix embedded outpost deletion

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* lint

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* fix files migration

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* add tenant api tests

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* add domain tests

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* add settings tests

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* make --schema-name default to public in mgmt commands

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* lint

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* sources/ldap: make sure lock is per-tenant

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* fix stuff I broke

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* fix remaining failing tests

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* lint

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* try fixing e2e tests

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* much better frontend, but save does not refresh form properly

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* update django-tenants with latest fixes

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* lint

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* i18n-extract

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* review comments

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* move event_retention from brands to tenants

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* wip

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* root: add support for storing media files in S3

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* use permissions for settings api

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* lint

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* blueprints: disable tenants management

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* fix tests

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* fix embedded outpost create/delete logic

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* make gen

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* make sure prometheus metrics are correctly served

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* makefile: don't delete the go api client when not regenerating it

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* tenants api: add recovery group and token creation endpoints

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* fix startup

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* fix prometheus metrics

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* fix tests

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* lint

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* fix web stuff

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix migrations from stable

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* fix oauth source type import

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* Revert "fix oauth source type import"

This reverts commit d015fd0244.

* try with setting_changed signal

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* try with connection_created signal

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* fix scim tests

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* fix web after merge

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* fix enterprise settings

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* Revert "try with connection_created signal"

This reverts commit 764a999db8.

* Revert "try with setting_changed signal"

This reverts commit 32b40a3bbb.

* lib/expression: refactor expression compilation

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* fix django version

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* fix web after merge

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* relock poetry

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* fix reconcile

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* try running tenant save in a transaction

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* black

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* test: export postgres logs for debugging and use failfast

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* test: fix container name for logs

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* do not copy tenant data

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* Revert "try running tenant save in a transaction"

This reverts commit da6dec5a61.

* Revert "do not copy tenant data"

This reverts commit d07ae9423672f068b0bd8be409ff9b58452a80f2.

* Revert "Revert "do not copy tenant data""

This reverts commit 4bffb19704.

* fix clone with nodata

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* why not

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* remove failfast

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* remove postgres query logging

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* update reconcile logic to clearly differentiate between tenant and global

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* fix

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix reconcile app decorator

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* enable django checks

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* actually nodata was unnecessary as we're cloning from template and not from public

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* pylint

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* update django-tenants with sequence fix

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* actually update

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* fix e2e tests

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* add tests for settings api

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* add tests for recovery api

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* lint

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* recovery tests: do them on a new tenant

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* web: fix system status being degraded when embedded outpost is disabled

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* fix recovery tests

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* fix tenants tests

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* lint-fix

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* lint-fix

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* update UI

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* add management command to create a tenant

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* add docs

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* release notes

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* more docs

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* checklist

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* self review

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* spelling

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* make web after upgrading

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* remove extra xlif file

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* prettier

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* Revert "add management command to create a tenant"

This reverts commit 39d13c0447.

* split api into smaller files, only import urls when tenants is enabled

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* rewite some things on the release notes

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* root: make sure install_id comes from public schema

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* require a license to use tenants

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* lint

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* fix tenants tests

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* fix files migration

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* release notes: add warning about user sessions being invalidated

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* remove api disabled test, we can't test for it

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

---------

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Jens Langhammer <jens@goauthentik.io>
This commit is contained in:
Marc 'risson' Schmitt
2024-01-23 14:28:06 +01:00
committed by GitHub
parent 73ddaf48be
commit abc0c2d2a2
227 changed files with 6554 additions and 2481 deletions

View File

@ -139,9 +139,10 @@ export class AkAdminSidebar extends WithCapabilitiesConfig(AKElement) {
["/core/tokens", msg("Tokens and App passwords")],
["/flow/stages/invitations", msg("Invitations")]]],
[null, msg("System"), null, [
["/core/tenants", msg("Tenants")],
["/core/brands", msg("Brands")],
["/crypto/certificates", msg("Certificates")],
["/outpost/integrations", msg("Outpost Integrations")]]]
["/outpost/integrations", msg("Outpost Integrations")],
["/admin/settings", msg("Settings")]]],
];
// Typescript requires the type here to correctly type the recursive path

View File

@ -52,9 +52,9 @@ export const ROUTES: Route[] = [
await import("@goauthentik/admin/tokens/TokenListPage");
return html`<ak-token-list></ak-token-list>`;
}),
new Route(new RegExp("^/core/tenants$"), async () => {
await import("@goauthentik/admin/tenants/TenantListPage");
return html`<ak-tenant-list></ak-tenant-list>`;
new Route(new RegExp("^/core/brands"), async () => {
await import("@goauthentik/admin/brands/BrandListPage");
return html`<ak-brand-list></ak-brand-list>`;
}),
new Route(new RegExp("^/policy/policies$"), async () => {
await import("@goauthentik/admin/policies/PolicyListPage");
@ -136,6 +136,10 @@ export const ROUTES: Route[] = [
await import("@goauthentik/admin/crypto/CertificateKeyPairListPage");
return html`<ak-crypto-certificate-list></ak-crypto-certificate-list>`;
}),
new Route(new RegExp("^/admin/settings$"), async () => {
await import("@goauthentik/admin/admin-settings/AdminSettingsPage");
return html`<ak-admin-settings></ak-admin-settings>`;
}),
new Route(new RegExp("^/blueprints/instances$"), async () => {
await import("@goauthentik/admin/blueprints/BlueprintListPage");
return html`<ak-blueprint-list></ak-blueprint-list>`;

View File

@ -57,7 +57,7 @@ export class RecentEventsCard extends Table<Event> {
new TableColumn(msg("User"), "user"),
new TableColumn(msg("Creation Date"), "created"),
new TableColumn(msg("Client IP"), "client_ip"),
new TableColumn(msg("Tenant"), "tenant_name"),
new TableColumn(msg("Brand"), "brand_name"),
];
}
@ -88,7 +88,7 @@ export class RecentEventsCard extends Table<Event> {
html`<span>${item.created?.toLocaleString()}</span>`,
html` <div>${item.clientIp || msg("-")}</div>
<small>${EventGeo(item)}</small>`,
html`<span>${item.tenant?.name || msg("-")}</span>`,
html`<span>${item.brand?.name || msg("-")}</span>`,
];
}

View File

@ -22,7 +22,10 @@ export class SystemStatusCard extends AdminStatusCard<SystemInfo> {
async getPrimaryValue(): Promise<SystemInfo> {
this.now = new Date();
let status = await new AdminApi(DEFAULT_CONFIG).adminSystemRetrieve();
if (status.embeddedOutpostHost === "" || !status.embeddedOutpostHost.includes("http")) {
if (
!status.embeddedOutpostDisabled &&
(status.embeddedOutpostHost === "" || !status.embeddedOutpostHost.includes("http"))
) {
// First install, ensure the embedded outpost host is set
// also run when outpost host does not contain http
// (yes it's called host and requires a URL, i know)
@ -51,7 +54,7 @@ export class SystemStatusCard extends AdminStatusCard<SystemInfo> {
}
getStatus(value: SystemInfo): Promise<AdminStatus> {
if (value.embeddedOutpostHost === "") {
if (!value.embeddedOutpostDisabled && value.embeddedOutpostHost === "") {
this.statusSummary = msg("Warning");
return Promise.resolve<AdminStatus>({
icon: "fa fa-exclamation-triangle pf-m-warning",

View File

@ -0,0 +1,187 @@
import { first } from "@goauthentik/app/common/utils";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import "@goauthentik/components/ak-switch-input";
import "@goauthentik/components/ak-text-input";
import "@goauthentik/elements/CodeMirror";
import { CodeMirrorMode } from "@goauthentik/elements/CodeMirror";
import { Form } from "@goauthentik/elements/forms/Form";
import "@goauthentik/elements/forms/FormGroup";
import "@goauthentik/elements/forms/HorizontalFormElement";
import "@goauthentik/elements/forms/Radio";
import "@goauthentik/elements/forms/SearchSelect";
import "@goauthentik/elements/utils/TimeDeltaHelp";
import { msg } from "@lit/localize";
import { CSSResult, TemplateResult, html } from "lit";
import { customElement, property } from "lit/decorators.js";
import { ifDefined } from "lit/directives/if-defined.js";
import PFList from "@patternfly/patternfly/components/List/list.css";
import { AdminApi, Settings, SettingsRequest } from "@goauthentik/api";
@customElement("ak-admin-settings-form")
export class AdminSettingsForm extends Form<SettingsRequest> {
@property({ attribute: false })
set settings(value: Settings) {
this._settings = value;
}
private _settings?: Settings;
getSuccessMessage(): string {
return msg("Successfully updated settings.");
}
async send(data: SettingsRequest): Promise<Settings> {
return new AdminApi(DEFAULT_CONFIG).adminSettingsUpdate({
settingsRequest: data,
});
}
static get styles(): CSSResult[] {
return super.styles.concat(PFList);
}
renderForm(): TemplateResult {
return html`
<ak-text-input
name="avatars"
label=${msg("Avatars")}
value="${ifDefined(this._settings?.avatars)}"
.bighelp=${html`
<p class="pf-c-form__helper-text">
${msg(
"Configure how authentik should show avatars for users. The following values can be set:",
)}
</p>
<ul class="pf-c-list">
<li class="pf-c-form__helper-text">
<code>none</code>:
${msg(
"Disables per-user avatars and just shows a 1x1 pixel transparent picture",
)}
</li>
<li class="pf-c-form__helper-text">
<code>gravatar</code>:
${msg("Uses gravatar with the user's email address")}
</li>
<li class="pf-c-form__helper-text">
<code>initials</code>:
${msg("Generated avatars based on the user's name")}
</li>
<li class="pf-c-form__helper-text">
${msg(
"Any URL: If you want to use images hosted on another server, you can set any URL. Additionally, these placeholders can be used:",
)}
<ul class="pf-c-list">
<li class="pf-c-form__helper-text">
<code>%(username)s</code>: ${msg("The user's username")}
</li>
<li class="pf-c-form__helper-text">
<code>%(mail_hash)s</code>:
${msg("The email address, md5 hashed")}
</li>
<li class="pf-c-form__helper-text">
<code>%(upn)s</code>:
${msg("The user's UPN, if set (otherwise an empty string)")}
</li>
</ul>
</li>
<li class="pf-c-form__helper-text">
${msg(
html`An attribute path like
<code>attributes.something.avatar</code>, which can be used in
combination with the file field to allow users to upload custom
avatars for themselves.`,
)}
</li>
</ul>
<p class="pf-c-form__helper-text">
${msg(
"Multiple values can be set, comma-separated, and authentik will fallback to the next mode when no avatar could be found.",
)}
${msg(
html`For example, setting this to <code>gravatar,initials</code> will
attempt to get an avatar from Gravatar, and if the user has not
configured on there, it will fallback to a generated avatar.`,
)}
</p>
`}
required
>
</ak-text-input>
<ak-switch-input
name="defaultUserChangeName"
label=${msg("Allow users to change name")}
?checked="${this._settings?.defaultUserChangeName}"
help=${msg("Enable the ability for users to change their name.")}
>
</ak-switch-input>
<ak-switch-input
name="defaultUserChangeEmail"
label=${msg("Allow users to change email")}
?checked="${this._settings?.defaultUserChangeEmail}"
help=${msg("Enable the ability for users to change their email.")}
>
</ak-switch-input>
<ak-switch-input
name="defaultUserChangeUsername"
label=${msg("Allow users to change username")}
?checked="${this._settings?.defaultUserChangeUsername}"
help=${msg("Enable the ability for users to change their username.")}
>
</ak-switch-input>
<ak-text-input
name="eventRetention"
label=${msg("Event retention")}
required
value="${ifDefined(this._settings?.eventRetention)}"
.bighelp=${html`<p class="pf-c-form__helper-text">
${msg("Duration after which events will be deleted from the database.")}
</p>
<p class="pf-c-form__helper-text">
${msg(
'When using an external logging solution for archiving, this can be set to "minutes=5".',
)}
</p>
<p class="pf-c-form__helper-text">
${msg(
"This setting only affects new Events, as the expiration is saved per-event.",
)}
</p>
<ak-utils-time-delta-help></ak-utils-time-delta-help>`}
>
</ak-text-input>
<ak-form-element-horizontal label=${msg("Footer links")} name="footerLinks">
<ak-codemirror
mode=${CodeMirrorMode.YAML}
.parseValue=${false}
value="${first(this._settings?.footerLinks, [])}"
></ak-codemirror>
<p class="pf-c-form__helper-text">
${msg(
"This option configures the footer links on the flow executor pages. It must be a valid JSON list and can be used as follows:",
)}
<code>[{"name": "Link Name","href":"https://goauthentik.io"}]</code>
</p>
</ak-form-element-horizontal>
<ak-switch-input
name="gdprCompliance"
label=${msg("GDPR compliance")}
?checked="${this._settings?.gdprCompliance}"
help=${msg(
"When enabled, all the events caused by a user will be deleted upon the user's deletion.",
)}
>
</ak-switch-input>
<ak-switch-input
name="impersonation"
label=${msg("Impersonation")}
?checked="${this._settings?.impersonation}"
help=${msg("Globally enable/disable impersonation.")}
>
</ak-switch-input>
`;
}
}

View File

@ -0,0 +1,115 @@
import "@goauthentik/admin/admin-settings/AdminSettingsForm";
import { AdminSettingsForm } from "@goauthentik/admin/admin-settings/AdminSettingsForm";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import "@goauthentik/components/events/ObjectChangelog";
import { AKElement } from "@goauthentik/elements/Base";
import "@goauthentik/elements/CodeMirror";
import "@goauthentik/elements/EmptyState";
import "@goauthentik/elements/Markdown";
import "@goauthentik/elements/PageHeader";
import "@goauthentik/elements/Tabs";
import "@goauthentik/elements/buttons/ModalButton";
import "@goauthentik/elements/buttons/SpinnerButton";
import "@goauthentik/elements/buttons/SpinnerButton";
import "@goauthentik/elements/forms/ModalForm";
import { msg } from "@lit/localize";
import { CSSResult, TemplateResult, html } from "lit";
import { customElement, property } from "lit/decorators.js";
import PFBanner from "@patternfly/patternfly/components/Banner/banner.css";
import PFButton from "@patternfly/patternfly/components/Button/button.css";
import PFCard from "@patternfly/patternfly/components/Card/card.css";
import PFContent from "@patternfly/patternfly/components/Content/content.css";
import PFDescriptionList from "@patternfly/patternfly/components/DescriptionList/description-list.css";
import PFForm from "@patternfly/patternfly/components/Form/form.css";
import PFFormControl from "@patternfly/patternfly/components/FormControl/form-control.css";
import PFPage from "@patternfly/patternfly/components/Page/page.css";
import PFGrid from "@patternfly/patternfly/layouts/Grid/grid.css";
import PFBase from "@patternfly/patternfly/patternfly-base.css";
import { AdminApi, Settings } from "@goauthentik/api";
@customElement("ak-admin-settings")
export class AdminSettingsPage extends AKElement {
static get styles(): CSSResult[] {
return [
PFBase,
PFButton,
PFPage,
PFGrid,
PFContent,
PFCard,
PFDescriptionList,
PFForm,
PFFormControl,
PFBanner,
];
}
@property({ attribute: false })
settings?: Settings;
loadSettings(): void {
new AdminApi(DEFAULT_CONFIG).adminSettingsRetrieve().then((settings) => {
this.settings = settings;
});
}
firstUpdated(): void {
this.loadSettings();
}
async save(): Promise<void> {
const form = this.shadowRoot?.querySelector<AdminSettingsForm>("ak-admin-settings-form");
if (!form) {
return;
}
await form.submit(new Event("submit"));
this.resetForm();
}
resetForm(): void {
const form = this.shadowRoot?.querySelector<AdminSettingsForm>("ak-admin-settings-form");
if (!form) {
return;
}
this.loadSettings();
form.settings = this.settings!;
form.resetForm();
}
render(): TemplateResult {
if (!this.settings) {
return html``;
}
return html`
<ak-page-header icon="fa fa-cog" header="" description="">
<span slot="header"> ${msg("System settings")} </span>
</ak-page-header>
<section class="pf-c-page__main-section pf-m-no-padding-mobile pf-l-grid pf-m-gutter">
<div class="pf-c-card">
<div class="pf-c-card__body">
<ak-admin-settings-form id="form" .settings=${this.settings}>
</ak-admin-settings-form>
</div>
<div class="pf-c-card__footer">
<ak-spinner-button
.callAction=${async () => {
await this.save();
}}
class="pf-m-primary"
>${msg("Save")}</ak-spinner-button
>
<ak-spinner-button
.callAction=${() => {
this.resetForm();
}}
class="pf-m-secondary"
>${msg("Cancel")}</ak-spinner-button
>
</div>
</div>
</section>
`;
}
}

View File

@ -1,13 +1,13 @@
import "@goauthentik/admin/applications/wizard/ak-wizard-title";
import "@goauthentik/admin/common/ak-core-group-search";
import "@goauthentik/admin/common/ak-crypto-certificate-search";
import "@goauthentik/admin/common/ak-flow-search/ak-tenanted-flow-search";
import "@goauthentik/admin/common/ak-flow-search/ak-branded-flow-search";
import { first } from "@goauthentik/common/utils";
import "@goauthentik/components/ak-number-input";
import "@goauthentik/components/ak-radio-input";
import "@goauthentik/components/ak-switch-input";
import "@goauthentik/components/ak-text-input";
import { WithTenantConfig } from "@goauthentik/elements/Interface/tenantProvider";
import { WithBrandConfig } from "@goauthentik/elements/Interface/brandProvider";
import "@goauthentik/elements/forms/FormGroup";
import "@goauthentik/elements/forms/HorizontalFormElement";
@ -32,7 +32,7 @@ import {
} from "./LDAPOptionsAndHelp";
@customElement("ak-application-wizard-authentication-by-ldap")
export class ApplicationWizardApplicationDetails extends WithTenantConfig(BaseProviderPanel) {
export class ApplicationWizardApplicationDetails extends WithBrandConfig(BaseProviderPanel) {
render() {
const provider = this.wizard.provider as LDAPProvider | undefined;
const errors = this.wizard.errors.provider;
@ -54,12 +54,12 @@ export class ApplicationWizardApplicationDetails extends WithTenantConfig(BasePr
name="authorizationFlow"
.errorMessages=${errors?.authorizationFlow ?? []}
>
<ak-tenanted-flow-search
<ak-branded-flow-search
flowType=${FlowsInstancesListDesignationEnum.Authentication}
.currentFlow=${provider?.authorizationFlow}
.tenantFlow=${this.tenant.flowAuthentication}
.brandFlow=${this.brand.flowAuthentication}
required
></ak-tenanted-flow-search>
></ak-branded-flow-search>
<p class="pf-c-form__helper-text">
${msg("Flow used for users to authenticate.")}
</p>

View File

@ -1,6 +1,6 @@
import "@goauthentik/admin/applications/wizard/ak-wizard-title";
import "@goauthentik/admin/common/ak-crypto-certificate-search";
import "@goauthentik/admin/common/ak-flow-search/ak-tenanted-flow-search";
import "@goauthentik/admin/common/ak-flow-search/ak-branded-flow-search";
import {
clientTypeOptions,
issuerModeOptions,

View File

@ -1,9 +1,9 @@
import "@goauthentik/admin/applications/wizard/ak-wizard-title";
import "@goauthentik/admin/common/ak-crypto-certificate-search";
import "@goauthentik/admin/common/ak-flow-search/ak-tenanted-flow-search";
import "@goauthentik/admin/common/ak-flow-search/ak-branded-flow-search";
import { ascii_letters, digits, first, randomString } from "@goauthentik/common/utils";
import "@goauthentik/components/ak-text-input";
import { WithTenantConfig } from "@goauthentik/elements/Interface/tenantProvider";
import { WithBrandConfig } from "@goauthentik/elements/Interface/brandProvider";
import "@goauthentik/elements/forms/FormGroup";
import "@goauthentik/elements/forms/HorizontalFormElement";
@ -17,7 +17,7 @@ import { FlowsInstancesListDesignationEnum, RadiusProvider } from "@goauthentik/
import BaseProviderPanel from "../BaseProviderPanel";
@customElement("ak-application-wizard-authentication-by-radius")
export class ApplicationWizardAuthenticationByRadius extends WithTenantConfig(BaseProviderPanel) {
export class ApplicationWizardAuthenticationByRadius extends WithBrandConfig(BaseProviderPanel) {
render() {
const provider = this.wizard.provider as RadiusProvider | undefined;
const errors = this.wizard.errors.provider;
@ -39,12 +39,12 @@ export class ApplicationWizardAuthenticationByRadius extends WithTenantConfig(Ba
name="authorizationFlow"
.errorMessages=${errors?.authorizationFlow ?? []}
>
<ak-tenanted-flow-search
<ak-branded-flow-search
flowType=${FlowsInstancesListDesignationEnum.Authentication}
.currentFlow=${provider?.authorizationFlow}
.tenantFlow=${this.tenant.flowAuthentication}
.brandFlow=${this.brand.flowAuthentication}
required
></ak-tenanted-flow-search>
></ak-branded-flow-search>
<p class="pf-c-form__helper-text">
${msg("Flow used for users to authenticate.")}
</p>

View File

@ -2,7 +2,7 @@ import "@goauthentik/admin/applications/wizard/ak-wizard-title";
import "@goauthentik/admin/applications/wizard/ak-wizard-title";
import "@goauthentik/admin/common/ak-core-group-search";
import "@goauthentik/admin/common/ak-crypto-certificate-search";
import "@goauthentik/admin/common/ak-flow-search/ak-tenanted-flow-search";
import "@goauthentik/admin/common/ak-flow-search/ak-branded-flow-search";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import "@goauthentik/components/ak-multi-select";
import "@goauthentik/components/ak-number-input";

View File

@ -1,7 +1,7 @@
import "@goauthentik/admin/applications/wizard/ak-wizard-title";
import "@goauthentik/admin/common/ak-core-group-search";
import "@goauthentik/admin/common/ak-crypto-certificate-search";
import "@goauthentik/admin/common/ak-flow-search/ak-tenanted-flow-search";
import "@goauthentik/admin/common/ak-flow-search/ak-branded-flow-search";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { first } from "@goauthentik/common/utils";
import "@goauthentik/components/ak-multi-select";

View File

@ -8,38 +8,38 @@ import "@goauthentik/elements/forms/FormGroup";
import "@goauthentik/elements/forms/HorizontalFormElement";
import { ModelForm } from "@goauthentik/elements/forms/ModelForm";
import "@goauthentik/elements/forms/SearchSelect";
import { DefaultTenant } from "@goauthentik/elements/sidebar/SidebarBrand";
import { DefaultBrand } from "@goauthentik/elements/sidebar/SidebarBrand";
import YAML from "yaml";
import { msg } from "@lit/localize";
import { TemplateResult, html } from "lit";
import { customElement } from "lit/decorators.js";
import { CoreApi, FlowsInstancesListDesignationEnum, Tenant } from "@goauthentik/api";
import { Brand, CoreApi, FlowsInstancesListDesignationEnum } from "@goauthentik/api";
@customElement("ak-tenant-form")
export class TenantForm extends ModelForm<Tenant, string> {
loadInstance(pk: string): Promise<Tenant> {
return new CoreApi(DEFAULT_CONFIG).coreTenantsRetrieve({
tenantUuid: pk,
@customElement("ak-brand-form")
export class BrandForm extends ModelForm<Brand, string> {
loadInstance(pk: string): Promise<Brand> {
return new CoreApi(DEFAULT_CONFIG).coreBrandsRetrieve({
brandUuid: pk,
});
}
getSuccessMessage(): string {
return this.instance
? msg("Successfully updated tenant.")
: msg("Successfully created tenant.");
? msg("Successfully updated brand.")
: msg("Successfully created brand.");
}
async send(data: Tenant): Promise<Tenant> {
if (this.instance?.tenantUuid) {
return new CoreApi(DEFAULT_CONFIG).coreTenantsUpdate({
tenantUuid: this.instance.tenantUuid,
tenantRequest: data,
async send(data: Brand): Promise<Brand> {
if (this.instance?.brandUuid) {
return new CoreApi(DEFAULT_CONFIG).coreBrandsUpdate({
brandUuid: this.instance.brandUuid,
brandRequest: data,
});
} else {
return new CoreApi(DEFAULT_CONFIG).coreTenantsCreate({
tenantRequest: data,
return new CoreApi(DEFAULT_CONFIG).coreBrandsCreate({
brandRequest: data,
});
}
}
@ -77,7 +77,7 @@ export class TenantForm extends ModelForm<Tenant, string> {
<span class="pf-c-switch__label">${msg("Default")}</span>
</label>
<p class="pf-c-form__helper-text">
${msg("Use this tenant for each domain that doesn't have a dedicated tenant.")}
${msg("Use this brand for each domain that doesn't have a dedicated brand.")}
</p>
</ak-form-element-horizontal>
@ -93,7 +93,7 @@ export class TenantForm extends ModelForm<Tenant, string> {
type="text"
value="${first(
this.instance?.brandingTitle,
DefaultTenant.brandingTitle,
DefaultBrand.brandingTitle,
)}"
class="pf-c-form-control"
required
@ -109,10 +109,7 @@ export class TenantForm extends ModelForm<Tenant, string> {
>
<input
type="text"
value="${first(
this.instance?.brandingLogo,
DefaultTenant.brandingLogo,
)}"
value="${first(this.instance?.brandingLogo, DefaultBrand.brandingLogo)}"
class="pf-c-form-control"
required
/>
@ -129,7 +126,7 @@ export class TenantForm extends ModelForm<Tenant, string> {
type="text"
value="${first(
this.instance?.brandingFavicon,
DefaultTenant.brandingFavicon,
DefaultBrand.brandingFavicon,
)}"
class="pf-c-form-control"
required
@ -236,34 +233,6 @@ export class TenantForm extends ModelForm<Tenant, string> {
.certificate=${this.instance?.webCertificate}
></ak-crypto-certificate-search>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${msg("Event retention")}
?required=${true}
name="eventRetention"
>
<input
type="text"
value="${first(this.instance?.eventRetention, "days=365")}"
class="pf-c-form-control"
required
/>
<p class="pf-c-form__helper-text">
${msg("Duration after which events will be deleted from the database.")}
</p>
<p class="pf-c-form__helper-text">
${msg(
'When using an external logging solution for archiving, this can be set to "minutes=5".',
)}
</p>
<p class="pf-c-form__helper-text">
${msg(
"This setting only affects new Events, as the expiration is saved per-event.",
)}
</p>
<p class="pf-c-form__helper-text">
${msg('Format: "weeks=3;days=2;hours=3,seconds=2".')}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal label=${msg("Attributes")} name="attributes">
<ak-codemirror
mode=${CodeMirrorMode.YAML}
@ -272,7 +241,7 @@ export class TenantForm extends ModelForm<Tenant, string> {
</ak-codemirror>
<p class="pf-c-form__helper-text">
${msg(
"Set custom attributes using YAML or JSON. Any attributes set here will be inherited by users, if the request is handled by this tenant.",
"Set custom attributes using YAML or JSON. Any attributes set here will be inherited by users, if the request is handled by this brand.",
)}
</p>
</ak-form-element-horizontal>

View File

@ -1,4 +1,4 @@
import "@goauthentik/admin/tenants/TenantForm";
import "@goauthentik/admin/brands/BrandForm";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { uiConfig } from "@goauthentik/common/ui/config";
import "@goauthentik/components/ak-status-label";
@ -16,21 +16,21 @@ import { msg } from "@lit/localize";
import { TemplateResult, html } from "lit";
import { customElement, property } from "lit/decorators.js";
import { CoreApi, RbacPermissionsAssignedByUsersListModelEnum, Tenant } from "@goauthentik/api";
import { Brand, CoreApi, RbacPermissionsAssignedByUsersListModelEnum } from "@goauthentik/api";
@customElement("ak-tenant-list")
export class TenantListPage extends TablePage<Tenant> {
@customElement("ak-brand-list")
export class BrandListPage extends TablePage<Brand> {
searchEnabled(): boolean {
return true;
}
pageTitle(): string {
return msg("Tenants");
return msg("Brands");
}
pageDescription(): string {
return msg("Configure visual settings and defaults for different domains.");
}
pageIcon(): string {
return "pf-icon pf-icon-tenant";
return "pf-icon pf-icon-brand";
}
checkbox = true;
@ -38,8 +38,8 @@ export class TenantListPage extends TablePage<Tenant> {
@property()
order = "domain";
async apiEndpoint(page: number): Promise<PaginatedResponse<Tenant>> {
return new CoreApi(DEFAULT_CONFIG).coreTenantsList({
async apiEndpoint(page: number): Promise<PaginatedResponse<Brand>> {
return new CoreApi(DEFAULT_CONFIG).coreBrandsList({
ordering: this.order,
page: page,
pageSize: (await uiConfig()).pagination.perPage,
@ -58,19 +58,19 @@ export class TenantListPage extends TablePage<Tenant> {
renderToolbarSelected(): TemplateResult {
const disabled = this.selectedElements.length < 1;
return html`<ak-forms-delete-bulk
objectLabel=${msg("Tenant(s)")}
objectLabel=${msg("Brand(s)")}
.objects=${this.selectedElements}
.metadata=${(item: Tenant) => {
.metadata=${(item: Brand) => {
return [{ key: msg("Domain"), value: item.domain }];
}}
.usedBy=${(item: Tenant) => {
return new CoreApi(DEFAULT_CONFIG).coreTenantsUsedByList({
tenantUuid: item.tenantUuid,
.usedBy=${(item: Brand) => {
return new CoreApi(DEFAULT_CONFIG).coreBrandsUsedByList({
brandUuid: item.brandUuid,
});
}}
.delete=${(item: Tenant) => {
return new CoreApi(DEFAULT_CONFIG).coreTenantsDestroy({
tenantUuid: item.tenantUuid,
.delete=${(item: Brand) => {
return new CoreApi(DEFAULT_CONFIG).coreBrandsDestroy({
brandUuid: item.brandUuid,
});
}}
>
@ -80,14 +80,14 @@ export class TenantListPage extends TablePage<Tenant> {
</ak-forms-delete-bulk>`;
}
row(item: Tenant): TemplateResult[] {
row(item: Brand): TemplateResult[] {
return [
html`${item.domain}`,
html`<ak-status-label ?good=${item._default}></ak-status-label>`,
html`<ak-forms-modal>
<span slot="submit"> ${msg("Update")} </span>
<span slot="header"> ${msg("Update Tenant")} </span>
<ak-tenant-form slot="form" .instancePk=${item.tenantUuid}> </ak-tenant-form>
<span slot="header"> ${msg("Update Brand")} </span>
<ak-brand-form slot="form" .instancePk=${item.brandUuid}> </ak-brand-form>
<button slot="trigger" class="pf-c-button pf-m-plain">
<pf-tooltip position="top" content=${msg("Edit")}>
<i class="fas fa-edit"></i>
@ -96,8 +96,8 @@ export class TenantListPage extends TablePage<Tenant> {
</ak-forms-modal>
<ak-rbac-object-permission-modal
model=${RbacPermissionsAssignedByUsersListModelEnum.TenantsTenant}
objectPk=${item.tenantUuid}
model=${RbacPermissionsAssignedByUsersListModelEnum.BrandsBrand}
objectPk=${item.brandUuid}
>
</ak-rbac-object-permission-modal>`,
];
@ -107,8 +107,8 @@ export class TenantListPage extends TablePage<Tenant> {
return html`
<ak-forms-modal>
<span slot="submit"> ${msg("Create")} </span>
<span slot="header"> ${msg("Create Tenant")} </span>
<ak-tenant-form slot="form"> </ak-tenant-form>
<span slot="header"> ${msg("Create Brand")} </span>
<ak-brand-form slot="form"> </ak-brand-form>
<button slot="trigger" class="pf-c-button pf-m-primary">${msg("Create")}</button>
</ak-forms-modal>
`;

View File

@ -28,7 +28,7 @@ export function getFlowValue(flow: Flow | undefined): string | undefined {
*
* A wrapper around SearchSelect that understands the basic semantics of querying about Flows. This
* code eliminates the long blocks of unreadable invocation that were embedded in every provider, as well as in
* sources, tenants, and applications.
* sources, brands, and applications.
*
*/
@ -93,7 +93,7 @@ export class FlowSearch<T extends Flow> extends CustomListenerElement(AKElement)
}
/* This is the most commonly overridden method of this class. About half of the Flow Searches
* use this method, but several have more complex needs, such as relating to the tenant, or just
* use this method, but several have more complex needs, such as relating to the brand, or just
* returning false.
*/

View File

@ -0,0 +1,34 @@
import { customElement, property } from "lit/decorators.js";
import type { Flow } from "@goauthentik/api";
import FlowSearch from "./FlowSearch";
/**
* Search for flows that may have a fallback specified by the brand settings
*
* @element ak-branded-flow-search
*
*/
@customElement("ak-branded-flow-search")
export class AkBrandedFlowSearch<T extends Flow> extends FlowSearch<T> {
/**
* The Associated ID of the flow the brand has, to compare if possible
*
* @attr
*/
@property({ attribute: false, type: String })
brandFlow?: string;
constructor() {
super();
this.selected = this.selected.bind(this);
}
selected(flow: Flow): boolean {
return super.selected(flow) || flow.pk === this.brandFlow;
}
}
export default AkBrandedFlowSearch;

View File

@ -1,34 +0,0 @@
import { customElement, property } from "lit/decorators.js";
import type { Flow } from "@goauthentik/api";
import FlowSearch from "./FlowSearch";
/**
* Search for flows that may have a fallback specified by the tenant settings
*
* @element ak-tenanted-flow-search
*
*/
@customElement("ak-tenanted-flow-search")
export class AkTenantedFlowSearch<T extends Flow> extends FlowSearch<T> {
/**
* The Associated ID of the flow the tenant has, to compare if possible
*
* @attr
*/
@property({ attribute: false, type: String })
tenantFlow?: string;
constructor() {
super();
this.selected = this.selected.bind(this);
}
selected(flow: Flow): boolean {
return super.selected(flow) || flow.pk === this.tenantFlow;
}
}
export default AkTenantedFlowSearch;

View File

@ -59,7 +59,7 @@ export class EventListPage extends TablePage<Event> {
new TableColumn(msg("User"), "user"),
new TableColumn(msg("Creation Date"), "created"),
new TableColumn(msg("Client IP"), "client_ip"),
new TableColumn(msg("Tenant"), "tenant_name"),
new TableColumn(msg("Brand"), "brand_name"),
new TableColumn(msg("Actions")),
];
}
@ -97,7 +97,7 @@ export class EventListPage extends TablePage<Event> {
html`<div>${item.clientIp || msg("-")}</div>
<small>${EventGeo(item)}</small>`,
html`<span>${item.tenant?.name || msg("-")}</span>`,
html`<span>${item.brand?.name || msg("-")}</span>`,
html`<a href="#/events/log/${item.pk}">
<pf-tooltip position="top" content=${msg("Show details")}>
<i class="fas fa-share-square"></i>

View File

@ -139,12 +139,12 @@ export class EventViewPage extends AKElement {
<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text"
>${msg("Tenant")}</span
>${msg("Brand")}</span
>
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">
${this.event.tenant?.name || msg("-")}
${this.event.brand?.name || msg("-")}
</div>
</dd>
</div>

View File

@ -9,11 +9,11 @@ import { MessageLevel } from "@goauthentik/common/messages";
import { uiConfig } from "@goauthentik/common/ui/config";
import { first } from "@goauthentik/common/utils";
import "@goauthentik/components/ak-status-label";
import { WithBrandConfig } from "@goauthentik/elements/Interface/brandProvider";
import {
CapabilitiesEnum,
WithCapabilitiesConfig,
} from "@goauthentik/elements/Interface/capabilitiesProvider";
import { WithTenantConfig } from "@goauthentik/elements/Interface/tenantProvider";
import "@goauthentik/elements/buttons/ActionButton";
import "@goauthentik/elements/buttons/Dropdown";
import "@goauthentik/elements/forms/DeleteBulkForm";
@ -110,7 +110,7 @@ export class RelatedUserAdd extends Form<{ users: number[] }> {
}
@customElement("ak-user-related-list")
export class RelatedUserList extends WithTenantConfig(WithCapabilitiesConfig(Table<User>)) {
export class RelatedUserList extends WithBrandConfig(WithCapabilitiesConfig(Table<User>)) {
expandable = true;
checkbox = true;
@ -295,7 +295,7 @@ export class RelatedUserList extends WithTenantConfig(WithCapabilitiesConfig(Tab
${msg("Set password")}
</button>
</ak-forms-modal>
${this.tenant?.flowRecovery
${this.brand?.flowRecovery
? html`
<ak-action-button
class="pf-m-secondary"
@ -357,7 +357,7 @@ export class RelatedUserList extends WithTenantConfig(WithCapabilitiesConfig(Tab
`
: html` <p>
${msg(
"To let a user directly reset a their password, configure a recovery flow on the currently active tenant.",
"To let a user directly reset a their password, configure a recovery flow on the currently active brand.",
)}
</p>`}
</div>

View File

@ -1,9 +1,9 @@
import "@goauthentik/admin/common/ak-crypto-certificate-search";
import "@goauthentik/admin/common/ak-flow-search/ak-tenanted-flow-search";
import "@goauthentik/admin/common/ak-flow-search/ak-branded-flow-search";
import { BaseProviderForm } from "@goauthentik/admin/providers/BaseProviderForm";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { first } from "@goauthentik/common/utils";
import { WithTenantConfig } from "@goauthentik/elements/Interface/tenantProvider";
import { WithBrandConfig } from "@goauthentik/elements/Interface/brandProvider";
import "@goauthentik/elements/forms/FormGroup";
import "@goauthentik/elements/forms/HorizontalFormElement";
import "@goauthentik/elements/forms/Radio";
@ -25,7 +25,7 @@ import {
} from "@goauthentik/api";
@customElement("ak-provider-ldap-form")
export class LDAPProviderFormPage extends WithTenantConfig(BaseProviderForm<LDAPProvider>) {
export class LDAPProviderFormPage extends WithBrandConfig(BaseProviderForm<LDAPProvider>) {
async loadInstance(pk: number): Promise<LDAPProvider> {
return new ProvidersApi(DEFAULT_CONFIG).providersLdapRetrieve({
id: pk,
@ -48,7 +48,7 @@ export class LDAPProviderFormPage extends WithTenantConfig(BaseProviderForm<LDAP
// All Provider objects have an Authorization flow, but not all providers have an Authentication
// flow. LDAP needs only one field, but it is not an Authorization field, it is an
// Authentication field. So, yeah, we're using the authorization field to store the
// authentication information, which is why the ak-tenanted-flow-search call down there looks so
// authentication information, which is why the ak-branded-flow-search call down there looks so
// weird-- we're looking up Authentication flows, but we're storing them in the Authorization
// field of the target Provider.
renderForm(): TemplateResult {
@ -65,12 +65,12 @@ export class LDAPProviderFormPage extends WithTenantConfig(BaseProviderForm<LDAP
?required=${true}
name="authorizationFlow"
>
<ak-tenanted-flow-search
<ak-branded-flow-search
flowType=${FlowsInstancesListDesignationEnum.Authentication}
.currentFlow=${this.instance?.authorizationFlow}
.tenantFlow=${this.tenant?.flowAuthentication}
.brandFlow=${this.brand?.flowAuthentication}
required
></ak-tenanted-flow-search>
></ak-branded-flow-search>
<p class="pf-c-form__helper-text">${msg("Flow used for users to authenticate.")}</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal label=${msg("Search group")} name="searchGroup">

View File

@ -1,5 +1,5 @@
import "@goauthentik/admin/common/ak-crypto-certificate-search";
import "@goauthentik/admin/common/ak-flow-search/ak-tenanted-flow-search";
import "@goauthentik/admin/common/ak-flow-search/ak-branded-flow-search";
import { first } from "@goauthentik/app/common/utils";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import "@goauthentik/elements/CodeMirror";

View File

@ -1,7 +1,7 @@
import { BaseProviderForm } from "@goauthentik/admin/providers/BaseProviderForm";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { ascii_letters, digits, first, randomString } from "@goauthentik/common/utils";
import { WithTenantConfig } from "@goauthentik/elements/Interface/tenantProvider";
import { WithBrandConfig } from "@goauthentik/elements/Interface/brandProvider";
import "@goauthentik/elements/forms/FormGroup";
import "@goauthentik/elements/forms/HorizontalFormElement";
import "@goauthentik/elements/forms/SearchSelect";
@ -14,7 +14,7 @@ import { customElement } from "lit/decorators.js";
import { FlowsInstancesListDesignationEnum, ProvidersApi, RadiusProvider } from "@goauthentik/api";
@customElement("ak-provider-radius-form")
export class RadiusProviderFormPage extends WithTenantConfig(BaseProviderForm<RadiusProvider>) {
export class RadiusProviderFormPage extends WithBrandConfig(BaseProviderForm<RadiusProvider>) {
loadInstance(pk: number): Promise<RadiusProvider> {
return new ProvidersApi(DEFAULT_CONFIG).providersRadiusRetrieve({
id: pk,
@ -37,7 +37,7 @@ export class RadiusProviderFormPage extends WithTenantConfig(BaseProviderForm<Ra
// All Provider objects have an Authorization flow, but not all providers have an Authentication
// flow. Radius needs only one field, but it is not the Authorization field, it is an
// Authentication field. So, yeah, we're using the authorization field to store the
// authentication information, which is why the ak-tenanted-flow-search call down there looks so
// authentication information, which is why the ak-branded-flow-search call down there looks so
// weird-- we're looking up Authentication flows, but we're storing them in the Authorization
// field of the target Provider.
renderForm(): TemplateResult {
@ -54,12 +54,12 @@ export class RadiusProviderFormPage extends WithTenantConfig(BaseProviderForm<Ra
?required=${true}
name="authorizationFlow"
>
<ak-tenanted-flow-search
<ak-branded-flow-search
flowType=${FlowsInstancesListDesignationEnum.Authentication}
.currentFlow=${this.instance?.authorizationFlow}
.tenantFlow=${this.tenant?.flowAuthentication}
.brandFlow=${this.brand?.flowAuthentication}
required
></ak-tenanted-flow-search>
></ak-branded-flow-search>
<p class="pf-c-form__helper-text">${msg("Flow used for users to authenticate.")}</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal name="mfaSupport">

View File

@ -28,7 +28,7 @@ class PreviewStageHost implements StageHost {
challenge = undefined;
flowSlug = undefined;
loading = false;
tenant = undefined;
brand = undefined;
async submit(payload: unknown): Promise<boolean> {
this.promptForm.previewResult = payload;
return false;

View File

@ -12,11 +12,11 @@ import { DefaultUIConfig, uiConfig } from "@goauthentik/common/ui/config";
import { first } from "@goauthentik/common/utils";
import "@goauthentik/components/ak-status-label";
import { rootInterface } from "@goauthentik/elements/Base";
import { WithBrandConfig } from "@goauthentik/elements/Interface/brandProvider";
import {
CapabilitiesEnum,
WithCapabilitiesConfig,
} from "@goauthentik/elements/Interface/capabilitiesProvider";
import { WithTenantConfig } from "@goauthentik/elements/Interface/tenantProvider";
import { PFSize } from "@goauthentik/elements/Spinner";
import "@goauthentik/elements/TreeView";
import "@goauthentik/elements/buttons/ActionButton";
@ -61,7 +61,7 @@ export const requestRecoveryLink = (user: User) =>
showMessage({
level: MessageLevel.error,
message: msg(
"The current tenant must have a recovery flow configured to use a recovery link",
"The current brand must have a recovery flow configured to use a recovery link",
),
}),
),
@ -91,7 +91,7 @@ const recoveryButtonStyles = css`
`;
@customElement("ak-user-list")
export class UserListPage extends WithTenantConfig(WithCapabilitiesConfig(TablePage<User>)) {
export class UserListPage extends WithBrandConfig(WithCapabilitiesConfig(TablePage<User>)) {
expandable = true;
checkbox = true;
@ -352,7 +352,7 @@ export class UserListPage extends WithTenantConfig(WithCapabilitiesConfig(TableP
${msg("Set password")}
</button>
</ak-forms-modal>
${this.tenant.flowRecovery
${this.brand.flowRecovery
? html`
<ak-action-button
class="pf-m-secondary"
@ -370,7 +370,7 @@ export class UserListPage extends WithTenantConfig(WithCapabilitiesConfig(TableP
`
: html` <p>
${msg(
"To let a user directly reset a their password, configure a recovery flow on the currently active tenant.",
"To let a user directly reset a their password, configure a recovery flow on the currently active brand.",
)}
</p>`}
</div>

View File

@ -6,7 +6,7 @@ import {
import { EVENT_LOCALE_REQUEST, EVENT_REFRESH, VERSION } from "@goauthentik/common/constants";
import { globalAK } from "@goauthentik/common/global";
import { Config, Configuration, CoreApi, CurrentTenant, RootApi } from "@goauthentik/api";
import { Config, Configuration, CoreApi, CurrentBrand, RootApi } from "@goauthentik/api";
let globalConfigPromise: Promise<Config> | undefined = Promise.resolve(globalAK().config);
export function config(): Promise<Config> {
@ -16,7 +16,7 @@ export function config(): Promise<Config> {
return globalConfigPromise;
}
export function tenantSetFavicon(tenant: CurrentTenant) {
export function brandSetFavicon(brand: CurrentBrand) {
/**
* <link rel="icon" href="/static/dist/assets/icons/icon.png">
* <link rel="shortcut icon" href="/static/dist/assets/icons/icon.png">
@ -29,36 +29,36 @@ export function tenantSetFavicon(tenant: CurrentTenant) {
relIcon.rel = rel;
document.getElementsByTagName("head")[0].appendChild(relIcon);
}
relIcon.href = tenant.brandingFavicon;
relIcon.href = brand.brandingFavicon;
});
}
export function tenantSetLocale(tenant: CurrentTenant) {
if (tenant.defaultLocale === "") {
export function brandSetLocale(brand: CurrentBrand) {
if (brand.defaultLocale === "") {
return;
}
console.debug("authentik/locale: setting locale from tenant default");
console.debug("authentik/locale: setting locale from brand default");
window.dispatchEvent(
new CustomEvent(EVENT_LOCALE_REQUEST, {
composed: true,
bubbles: true,
detail: { locale: tenant.defaultLocale },
detail: { locale: brand.defaultLocale },
}),
);
}
let globalTenantPromise: Promise<CurrentTenant> | undefined = Promise.resolve(globalAK().tenant);
export function tenant(): Promise<CurrentTenant> {
if (!globalTenantPromise) {
globalTenantPromise = new CoreApi(DEFAULT_CONFIG)
.coreTenantsCurrentRetrieve()
.then((tenant) => {
tenantSetFavicon(tenant);
tenantSetLocale(tenant);
return tenant;
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 globalTenantPromise;
return globalBrandPromise;
}
export function getMetaContent(key: string): string {
@ -75,7 +75,7 @@ export const DEFAULT_CONFIG = new Configuration({
middleware: [
new CSRFMiddleware(),
new EventMiddleware(),
new LoggingMiddleware(globalAK().tenant),
new LoggingMiddleware(globalAK().brand),
],
});
@ -90,9 +90,9 @@ window.addEventListener(EVENT_REFRESH, () => {
// Upon global refresh, disregard whatever was pre-hydrated and
// actually load info from API
globalConfigPromise = undefined;
globalTenantPromise = undefined;
globalBrandPromise = undefined;
config();
tenant();
brand();
});
console.debug(`authentik(early): version ${VERSION}, apiBase ${DEFAULT_CONFIG.basePath}`);

View File

@ -2,7 +2,7 @@ import { EVENT_REQUEST_POST } from "@goauthentik/common/constants";
import { getCookie } from "@goauthentik/common/utils";
import {
CurrentTenant,
CurrentBrand,
FetchParams,
Middleware,
RequestContext,
@ -18,13 +18,13 @@ export interface RequestInfo {
}
export class LoggingMiddleware implements Middleware {
tenant: CurrentTenant;
constructor(tenant: CurrentTenant) {
this.tenant = tenant;
brand: CurrentBrand;
constructor(brand: CurrentBrand) {
this.brand = brand;
}
post(context: ResponseContext): Promise<Response | void> {
let msg = `authentik/api[${this.tenant.matchedDomain}]: `;
let msg = `authentik/api[${this.brand.matchedDomain}]: `;
// https://developer.mozilla.org/en-US/docs/Web/API/console#styling_console_output
msg += `%c${context.response.status}%c ${context.init.method} ${context.url}`;
let style = "";

View File

@ -1,4 +1,4 @@
import { Config, ConfigFromJSON, CurrentTenant, CurrentTenantFromJSON } from "@goauthentik/api";
import { Config, ConfigFromJSON, CurrentBrand, CurrentBrandFromJSON } from "@goauthentik/api";
export interface GlobalAuthentik {
_converted?: boolean;
@ -7,7 +7,7 @@ export interface GlobalAuthentik {
layout: string;
};
config: Config;
tenant: CurrentTenant;
brand: CurrentBrand;
versionFamily: string;
versionSubdomain: string;
build: string;
@ -21,7 +21,7 @@ export function globalAK(): GlobalAuthentik {
const ak = (window as unknown as AuthentikWindow).authentik;
if (ak && !ak._converted) {
ak._converted = true;
ak.tenant = CurrentTenantFromJSON(ak.tenant);
ak.brand = CurrentBrandFromJSON(ak.brand);
ak.config = ConfigFromJSON(ak.config);
}
if (!ak) {
@ -29,7 +29,7 @@ export function globalAK(): GlobalAuthentik {
config: ConfigFromJSON({
capabilities: [],
}),
tenant: CurrentTenantFromJSON({
brand: CurrentBrandFromJSON({
ui_footer_links: [],
}),
versionFamily: "",

View File

@ -1,11 +1,9 @@
import { createContext } from "@lit-labs/context";
import type { Config, CurrentTenant } from "@goauthentik/api";
import type { Config, CurrentBrand } from "@goauthentik/api";
export const authentikConfigContext = createContext<Config>(Symbol("authentik-config-context"));
export const authentikTenantContext = createContext<CurrentTenant>(
Symbol("authentik-tenant-context"),
);
export const authentikBrandContext = createContext<CurrentBrand>(Symbol("authentik-brand-context"));
export default authentikConfigContext;

View File

@ -9,13 +9,13 @@ import { LitElement } from "lit";
import AKGlobal from "@goauthentik/common/styles/authentik.css";
import ThemeDark from "@goauthentik/common/styles/theme-dark.css";
import { Config, CurrentTenant, UiThemeEnum } from "@goauthentik/api";
import { Config, CurrentBrand, UiThemeEnum } from "@goauthentik/api";
import { AdoptedStyleSheetsElement } from "./types";
type AkInterface = HTMLElement & {
getTheme: () => Promise<UiThemeEnum>;
tenant?: CurrentTenant;
brand?: CurrentBrand;
uiConfig?: UIConfig;
config?: Config;
};

View File

@ -1,8 +1,8 @@
import { config, tenant } from "@goauthentik/common/api/config";
import { brand, config } from "@goauthentik/common/api/config";
import { UIConfig, uiConfig } from "@goauthentik/common/ui/config";
import {
authentikBrandContext,
authentikConfigContext,
authentikTenantContext,
} from "@goauthentik/elements/AuthentikContexts";
import type { AdoptedStyleSheetsElement } from "@goauthentik/elements/types";
import { ensureCSSStyleSheet } from "@goauthentik/elements/utils/ensureCSSStyleSheet";
@ -12,13 +12,13 @@ import { state } from "lit/decorators.js";
import PFBase from "@patternfly/patternfly/patternfly-base.css";
import { Config, CurrentTenant, UiThemeEnum } from "@goauthentik/api";
import { Config, CurrentBrand, UiThemeEnum } from "@goauthentik/api";
import { AKElement } from "../Base";
type AkInterface = HTMLElement & {
getTheme: () => Promise<UiThemeEnum>;
tenant?: CurrentTenant;
brand?: CurrentBrand;
uiConfig?: UIConfig;
config?: Config;
};
@ -45,28 +45,28 @@ export class Interface extends AKElement implements AkInterface {
return this._config;
}
_tenantContext = new ContextProvider(this, {
context: authentikTenantContext,
_brandContext = new ContextProvider(this, {
context: authentikBrandContext,
initialValue: undefined,
});
_tenant?: CurrentTenant;
_brand?: CurrentBrand;
@state()
set tenant(c: CurrentTenant) {
this._tenant = c;
this._tenantContext.setValue(c);
set brand(c: CurrentBrand) {
this._brand = c;
this._brandContext.setValue(c);
this.requestUpdate();
}
get tenant(): CurrentTenant | undefined {
return this._tenant;
get brand(): CurrentBrand | undefined {
return this._brand;
}
constructor() {
super();
document.adoptedStyleSheets = [...document.adoptedStyleSheets, ensureCSSStyleSheet(PFBase)];
tenant().then((tenant) => (this.tenant = tenant));
brand().then((brand) => (this.brand = brand));
config().then((config) => (this.config = config));
this.dataset.akInterfaceRoot = "true";
}

View File

@ -0,0 +1,20 @@
import { authentikBrandContext } from "@goauthentik/elements/AuthentikContexts";
import { consume } from "@lit-labs/context";
import type { LitElement } from "lit";
import type { CurrentBrand } from "@goauthentik/api";
// eslint-disable-next-line @typescript-eslint/no-explicit-any
type Constructor<T = object> = abstract new (...args: any[]) => T;
export function WithBrandConfig<T extends Constructor<LitElement>>(
superclass: T,
subscribe = true,
) {
abstract class WithBrandProvider extends superclass {
@consume({ context: authentikBrandContext, subscribe })
public brand!: CurrentBrand;
}
return WithBrandProvider;
}

View File

@ -1,20 +0,0 @@
import { authentikTenantContext } from "@goauthentik/elements/AuthentikContexts";
import { consume } from "@lit-labs/context";
import type { LitElement } from "lit";
import type { CurrentTenant } from "@goauthentik/api";
// eslint-disable-next-line @typescript-eslint/no-explicit-any
type Constructor<T = object> = abstract new (...args: any[]) => T;
export function WithTenantConfig<T extends Constructor<LitElement>>(
superclass: T,
subscribe = true,
) {
abstract class WithTenantProvider extends superclass {
@consume({ context: authentikTenantContext, subscribe })
public tenant!: CurrentTenant;
}
return WithTenantProvider;
}

View File

@ -9,7 +9,7 @@ import {
import { currentInterface } from "@goauthentik/common/sentry";
import { me } from "@goauthentik/common/users";
import { AKElement } from "@goauthentik/elements/Base";
import { WithTenantConfig } from "@goauthentik/elements/Interface/tenantProvider";
import { WithBrandConfig } from "@goauthentik/elements/Interface/brandProvider";
import "@patternfly/elements/pf-tooltip/pf-tooltip.js";
import { msg } from "@lit/localize";
@ -24,7 +24,7 @@ import PFBase from "@patternfly/patternfly/patternfly-base.css";
import { EventsApi } from "@goauthentik/api";
@customElement("ak-page-header")
export class PageHeader extends WithTenantConfig(AKElement) {
export class PageHeader extends WithBrandConfig(AKElement) {
@property()
icon?: string;
@ -37,7 +37,7 @@ export class PageHeader extends WithTenantConfig(AKElement) {
@property()
set header(value: string) {
const currentIf = currentInterface();
let title = this.tenant?.brandingTitle || TITLE_DEFAULT;
let title = this.brand?.brandingTitle || TITLE_DEFAULT;
if (currentIf === "admin") {
title = `${msg("Admin")} - ${title}`;
}

View File

@ -1,6 +1,6 @@
import { EVENT_SIDEBAR_TOGGLE } from "@goauthentik/common/constants";
import { AKElement } from "@goauthentik/elements/Base";
import { WithTenantConfig } from "@goauthentik/elements/Interface/tenantProvider";
import { WithBrandConfig } from "@goauthentik/elements/Interface/brandProvider";
import { CSSResult, TemplateResult, css, html } from "lit";
import { customElement } from "lit/decorators.js";
@ -10,13 +10,13 @@ import PFPage from "@patternfly/patternfly/components/Page/page.css";
import PFGlobal from "@patternfly/patternfly/patternfly-base.css";
import PFBase from "@patternfly/patternfly/patternfly-base.css";
import { CurrentTenant, UiThemeEnum } from "@goauthentik/api";
import { CurrentBrand, UiThemeEnum } from "@goauthentik/api";
// If the viewport is wider than MIN_WIDTH, the sidebar
// is shown besides the content, and not overlaid.
export const MIN_WIDTH = 1200;
export const DefaultTenant: CurrentTenant = {
export const DefaultBrand: CurrentBrand = {
brandingLogo: "/static/dist/assets/icons/icon_left_brand.svg",
brandingFavicon: "/static/dist/assets/icons/icon.png",
brandingTitle: "authentik",
@ -27,7 +27,7 @@ export const DefaultTenant: CurrentTenant = {
};
@customElement("ak-sidebar-brand")
export class SidebarBrand extends WithTenantConfig(AKElement) {
export class SidebarBrand extends WithBrandConfig(AKElement) {
static get styles(): CSSResult[] {
return [
PFBase,
@ -84,7 +84,7 @@ export class SidebarBrand extends WithTenantConfig(AKElement) {
<a href="#/" class="pf-c-page__header-brand-link">
<div class="pf-c-brand ak-brand">
<img
src=${this.tenant?.brandingLogo ?? DefaultTenant.brandingLogo}
src=${this.brand?.brandingLogo ?? DefaultBrand.brandingLogo}
alt="authentik Logo"
loading="lazy"
/>

View File

@ -209,7 +209,7 @@ export class RacInterface extends Interface {
}
updateTitle(): void {
let title = this.tenant?.brandingTitle || TITLE_DEFAULT;
let title = this.brand?.brandingTitle || TITLE_DEFAULT;
if (this.endpointName) {
title = `${this.endpointName} - ${title}`;
}

View File

@ -55,9 +55,9 @@ export class FlowExecutor extends Interface implements StageHost {
set challenge(value: ChallengeTypes | undefined) {
this._challenge = value;
if (value?.flowInfo?.title) {
document.title = `${value.flowInfo?.title} - ${this.tenant?.brandingTitle}`;
document.title = `${value.flowInfo?.title} - ${this.brand?.brandingTitle}`;
} else {
document.title = this.tenant?.brandingTitle || TITLE_DEFAULT;
document.title = this.brand?.brandingTitle || TITLE_DEFAULT;
}
this.requestUpdate();
}
@ -186,7 +186,7 @@ export class FlowExecutor extends Interface implements StageHost {
}
async getTheme(): Promise<UiThemeEnum> {
return globalAK()?.tenant.uiTheme || UiThemeEnum.Automatic;
return globalAK()?.brand.uiTheme || UiThemeEnum.Automatic;
}
async submit(payload?: FlowChallengeResponseRequest): Promise<boolean> {
@ -430,7 +430,7 @@ export class FlowExecutor extends Interface implements StageHost {
renderChallengeWrapper(): TemplateResult {
const logo = html`<div class="pf-c-login__main-header pf-c-brand ak-brand">
<img src="${first(this.tenant?.brandingLogo, "")}" alt="authentik Logo" />
<img src="${first(this.brand?.brandingLogo, "")}" alt="authentik Logo" />
</div>`;
if (!this.challenge) {
return html`${logo}<ak-empty-state ?loading=${true} header=${msg("Loading")}>
@ -488,7 +488,7 @@ export class FlowExecutor extends Interface implements StageHost {
</div>
<footer class="pf-c-login__footer">
<ul class="pf-c-list pf-m-inline">
${this.tenant?.uiFooterLinks?.map((link) => {
${this.brand?.uiFooterLinks?.map((link) => {
return html`<li>
<a href="${link.href || ""}"
>${link.name}</a

View File

@ -20,7 +20,7 @@ import PFBase from "@patternfly/patternfly/patternfly-base.css";
import {
AuthenticatorValidationChallenge,
AuthenticatorValidationChallengeResponseRequest,
CurrentTenant,
CurrentBrand,
DeviceChallenge,
DeviceClassesEnum,
FlowsApi,
@ -44,8 +44,8 @@ export class AuthenticatorValidateStage
return this.host.loading;
}
get tenant(): CurrentTenant | undefined {
return this.host.tenant;
get brand(): CurrentBrand | undefined {
return this.host.brand;
}
@state()

View File

@ -4,7 +4,7 @@ import { KeyUnknown } from "@goauthentik/elements/forms/Form";
import { TemplateResult, html } from "lit";
import { property } from "lit/decorators.js";
import { CurrentTenant, ErrorDetail } from "@goauthentik/api";
import { CurrentBrand, ErrorDetail } from "@goauthentik/api";
export interface StageHost {
challenge?: unknown;
@ -12,7 +12,7 @@ export interface StageHost {
loading: boolean;
submit(payload: unknown): Promise<boolean>;
readonly tenant?: CurrentTenant;
readonly brand?: CurrentBrand;
}
export function readFileAsync(file: Blob) {

View File

@ -4,7 +4,7 @@ import { globalAK } from "@goauthentik/common/global";
import { first, getCookie } from "@goauthentik/common/utils";
import { Interface } from "@goauthentik/elements/Interface";
import "@goauthentik/elements/ak-locale-context";
import { DefaultTenant } from "@goauthentik/elements/sidebar/SidebarBrand";
import { DefaultBrand } from "@goauthentik/elements/sidebar/SidebarBrand";
import "rapidoc";
import { CSSResult, TemplateResult, css, html } from "lit";
@ -59,7 +59,7 @@ export class APIBrowser extends Interface {
}
async getTheme(): Promise<UiThemeEnum> {
return globalAK()?.tenant.uiTheme || UiThemeEnum.Automatic;
return globalAK()?.brand.uiTheme || UiThemeEnum.Automatic;
}
render(): TemplateResult {
@ -103,7 +103,7 @@ export class APIBrowser extends Interface {
<img
alt="authentik Logo"
class="logo"
src="${first(this.tenant?.brandingLogo, DefaultTenant.brandingLogo)}"
src="${first(this.brand?.brandingLogo, DefaultBrand.brandingLogo)}"
/>
</div>
</rapi-doc>

View File

@ -29,7 +29,7 @@ export class Loading extends Interface {
}
async getTheme(): Promise<UiThemeEnum> {
return globalAK()?.tenant.uiTheme || UiThemeEnum.Automatic;
return globalAK()?.brand.uiTheme || UiThemeEnum.Automatic;
}
render(): TemplateResult {

View File

@ -19,7 +19,7 @@ import "@goauthentik/elements/notifications/NotificationDrawer";
import { getURLParam, updateURLParams } from "@goauthentik/elements/router/RouteMatch";
import "@goauthentik/elements/router/RouterOutlet";
import "@goauthentik/elements/sidebar/Sidebar";
import { DefaultTenant } from "@goauthentik/elements/sidebar/SidebarBrand";
import { DefaultBrand } from "@goauthentik/elements/sidebar/SidebarBrand";
import "@goauthentik/elements/sidebar/SidebarItem";
import { ROUTES } from "@goauthentik/user/Routes";
import "@patternfly/elements/pf-tooltip/pf-tooltip.js";
@ -192,11 +192,8 @@ export class UserInterface extends Interface {
<a href="#/" class="pf-c-page__header-brand-link">
<img
class="pf-c-brand"
src="${first(
this.tenant?.brandingLogo,
DefaultTenant.brandingLogo,
)}"
alt="${(this.tenant?.brandingTitle, DefaultTenant.brandingTitle)}"
src="${first(this.brand?.brandingLogo, DefaultBrand.brandingLogo)}"
alt="${(this.brand?.brandingTitle, DefaultBrand.brandingTitle)}"
/>
</a>
</div>

View File

@ -3,7 +3,7 @@ import { EVENT_REFRESH } from "@goauthentik/common/constants";
import { MessageLevel } from "@goauthentik/common/messages";
import { refreshMe } from "@goauthentik/common/users";
import { AKElement } from "@goauthentik/elements/Base";
import { WithTenantConfig } from "@goauthentik/elements/Interface/tenantProvider";
import { WithBrandConfig } from "@goauthentik/elements/Interface/brandProvider";
import { showMessage } from "@goauthentik/elements/messages/MessageContainer";
import { StageHost } from "@goauthentik/flow/stages/base";
import "@goauthentik/user/user-settings/details/stages/prompt/PromptStage";
@ -32,7 +32,7 @@ import {
@customElement("ak-user-settings-flow-executor")
export class UserSettingsFlowExecutor
extends WithTenantConfig(AKElement, true)
extends WithBrandConfig(AKElement, true)
implements StageHost
{
@property()
@ -87,7 +87,7 @@ export class UserSettingsFlowExecutor
}
firstUpdated(): void {
this.flowSlug = this.tenant?.flowUserSettings;
this.flowSlug = this.brand?.flowUserSettings;
if (!this.flowSlug) {
return;
}

View File

@ -51,10 +51,10 @@ export class UserSettingsPromptStage extends PromptStage {
<div class="pf-c-form__horizontal-group">
<div class="pf-c-form__actions">
<button type="submit" class="pf-c-button pf-m-primary">${msg("Save")}</button>
${this.host.tenant?.flowUnenrollment
${this.host.brand?.flowUnenrollment
? html` <a
class="pf-c-button pf-m-danger"
href="/if/flow/${this.host.tenant.flowUnenrollment}/"
href="/if/flow/${this.host.brand.flowUnenrollment}/"
>
${msg("Delete account")}
</a>`