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 commitd015fd0244
. * 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 commit764a999db8
. * Revert "try with setting_changed signal" This reverts commit32b40a3bbb
. * 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 commitda6dec5a61
. * Revert "do not copy tenant data" This reverts commit d07ae9423672f068b0bd8be409ff9b58452a80f2. * Revert "Revert "do not copy tenant data"" This reverts commit4bffb19704
. * 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 commit39d13c0447
. * 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:

committed by
GitHub

parent
73ddaf48be
commit
abc0c2d2a2
@ -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
|
||||
|
@ -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>`;
|
||||
|
@ -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>`,
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -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",
|
||||
|
187
web/src/admin/admin-settings/AdminSettingsForm.ts
Normal file
187
web/src/admin/admin-settings/AdminSettingsForm.ts
Normal 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>
|
||||
`;
|
||||
}
|
||||
}
|
115
web/src/admin/admin-settings/AdminSettingsPage.ts
Normal file
115
web/src/admin/admin-settings/AdminSettingsPage.ts
Normal 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>
|
||||
`;
|
||||
}
|
||||
}
|
@ -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>
|
||||
|
@ -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,
|
||||
|
@ -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>
|
||||
|
@ -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";
|
||||
|
@ -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";
|
||||
|
@ -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>
|
@ -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>
|
||||
`;
|
@ -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.
|
||||
*/
|
||||
|
||||
|
@ -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;
|
@ -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;
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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">
|
||||
|
@ -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";
|
||||
|
@ -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">
|
||||
|
@ -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;
|
||||
|
@ -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>
|
||||
|
@ -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}`);
|
||||
|
@ -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 = "";
|
||||
|
@ -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: "",
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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";
|
||||
}
|
||||
|
20
web/src/elements/Interface/brandProvider.ts
Normal file
20
web/src/elements/Interface/brandProvider.ts
Normal 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;
|
||||
}
|
@ -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;
|
||||
}
|
@ -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}`;
|
||||
}
|
||||
|
@ -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"
|
||||
/>
|
||||
|
@ -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}`;
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
|
@ -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) {
|
||||
|
@ -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>
|
||||
|
@ -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 {
|
||||
|
@ -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>
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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>`
|
||||
|
Reference in New Issue
Block a user