Merge branch 'dev' into web/sidebar-with-live-content-3
* dev: (72 commits) web/flows: show logo in card (#7824) blueprints: improve file change handler (#7813) web/user: fix search not updating app (#7825) web: bump the storybook group in /web with 5 updates (#7819) core: compile backend translations (#7827) translate: Updates for file locale/en/LC_MESSAGES/django.po in de (#7812) core: bump github.com/go-openapi/strfmt from 0.21.8 to 0.21.9 (#7814) ci: bump actions/stale from 8 to 9 (#7815) web: bump the wdio group in /tests/wdio with 1 update (#7816) translate: Updates for file web/xliff/en.xlf in zh_CN (#7820) web: bump the sentry group in /web with 2 updates (#7817) web: bump vite-tsconfig-paths from 4.2.1 to 4.2.2 in /web (#7818) translate: Updates for file web/xliff/en.xlf in zh-Hans (#7821) translate: Updates for file locale/en/LC_MESSAGES/django.po in zh-Hans (#7822) translate: Updates for file locale/en/LC_MESSAGES/django.po in zh_CN (#7823) web: bump typescript from 5.3.2 to 5.3.3 in /web (#7806) website: bump typescript from 5.3.2 to 5.3.3 in /website (#7807) web: bump typescript from 5.3.2 to 5.3.3 in /tests/wdio (#7808) core: bump goauthentik.io/api/v3 from 3.2023104.1 to 3.2023104.2 (#7809) ci: bump actions/setup-go from 4 to 5 ...
This commit is contained in:
		@ -89,17 +89,15 @@ export class ApplicationListPage extends TablePage<Application> {
 | 
			
		||||
        ];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    renderSectionBefore(): TemplateResult {
 | 
			
		||||
        return html`<ak-application-wizard-hint></ak-application-wizard-hint>`;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    renderSidebarAfter(): TemplateResult {
 | 
			
		||||
        // Rendering the wizard with .open here, as if we set the attribute in
 | 
			
		||||
        // renderObjectCreate() it'll open two wizards, since that function gets called twice
 | 
			
		||||
 | 
			
		||||
        /* Re-enable the wizard later:
 | 
			
		||||
          <ak-application-wizard
 | 
			
		||||
                .open=${getURLParam("createWizard", false)}
 | 
			
		||||
                .showButton=${false}
 | 
			
		||||
                ></ak-application-wizard>*/
 | 
			
		||||
 | 
			
		||||
        return html` <div class="pf-c-sidebar__panel pf-m-width-25">
 | 
			
		||||
        return html`<div class="pf-c-sidebar__panel pf-m-width-25">
 | 
			
		||||
            <div class="pf-c-card">
 | 
			
		||||
                <div class="pf-c-card__body">
 | 
			
		||||
                    <ak-markdown .md=${MDApplication}></ak-markdown>
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,7 @@
 | 
			
		||||
import { WizardPanel } from "@goauthentik/components/ak-wizard-main/types";
 | 
			
		||||
import { AKElement } from "@goauthentik/elements/Base";
 | 
			
		||||
import { KeyUnknown, serializeForm } from "@goauthentik/elements/forms/Form";
 | 
			
		||||
import { HorizontalFormElement } from "@goauthentik/elements/forms/HorizontalFormElement";
 | 
			
		||||
import { CustomEmitterElement } from "@goauthentik/elements/utils/eventEmitter";
 | 
			
		||||
 | 
			
		||||
import { consume } from "@lit-labs/context";
 | 
			
		||||
@ -10,6 +12,15 @@ import { styles as AwadStyles } from "./BasePanel.css";
 | 
			
		||||
import { applicationWizardContext } from "./ContextIdentity";
 | 
			
		||||
import type { ApplicationWizardState, ApplicationWizardStateUpdate } from "./types";
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Application Wizard Base Panel
 | 
			
		||||
 *
 | 
			
		||||
 * All of the displays in our system inherit from this object, which supplies the basic CSS for all
 | 
			
		||||
 * the inputs we display, as well as the values and validity state for the form currently being
 | 
			
		||||
 * displayed.
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
export class ApplicationWizardPageBase
 | 
			
		||||
    extends CustomEmitterElement(AKElement)
 | 
			
		||||
    implements WizardPanel
 | 
			
		||||
@ -18,15 +29,41 @@ export class ApplicationWizardPageBase
 | 
			
		||||
        return AwadStyles;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @query("form")
 | 
			
		||||
    form!: HTMLFormElement;
 | 
			
		||||
 | 
			
		||||
    rendered = false;
 | 
			
		||||
 | 
			
		||||
    @consume({ context: applicationWizardContext })
 | 
			
		||||
    public wizard!: ApplicationWizardState;
 | 
			
		||||
 | 
			
		||||
    // This used to be more complex; now it just establishes the event name.
 | 
			
		||||
    @query("form")
 | 
			
		||||
    form!: HTMLFormElement;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Provide access to the values on the current form. Child implementations use this to craft the
 | 
			
		||||
     * update that will be sent using `dispatchWizardUpdate` below.
 | 
			
		||||
     */
 | 
			
		||||
    get formValues(): KeyUnknown | undefined {
 | 
			
		||||
        const elements = [
 | 
			
		||||
            ...Array.from(
 | 
			
		||||
                this.form.querySelectorAll<HorizontalFormElement>("ak-form-element-horizontal"),
 | 
			
		||||
            ),
 | 
			
		||||
            ...Array.from(this.form.querySelectorAll<HTMLElement>("[data-ak-control=true]")),
 | 
			
		||||
        ];
 | 
			
		||||
        return serializeForm(elements as unknown as NodeListOf<HorizontalFormElement>);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Provide access to the validity of the current form. Child implementations use this to craft
 | 
			
		||||
     * the update that will be sent using `dispatchWizardUpdate` below.
 | 
			
		||||
     */
 | 
			
		||||
    get valid() {
 | 
			
		||||
        return this.form.checkValidity();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    rendered = false;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Provide a single source of truth for the token used to notify the orchestrator that an event
 | 
			
		||||
     * happens. The token `ak-wizard-update` is used by the Wizard framework's reactive controller
 | 
			
		||||
     * to route "data on the current step has changed" events to the orchestrator.
 | 
			
		||||
     */
 | 
			
		||||
    dispatchWizardUpdate(update: ApplicationWizardStateUpdate) {
 | 
			
		||||
        this.dispatchCustomEvent("ak-wizard-update", update);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -5,5 +5,3 @@ import { ApplicationWizardState } from "./types";
 | 
			
		||||
export const applicationWizardContext = createContext<ApplicationWizardState>(
 | 
			
		||||
    Symbol("ak-application-wizard-state-context"),
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
export default applicationWizardContext;
 | 
			
		||||
 | 
			
		||||
@ -1,4 +1,3 @@
 | 
			
		||||
import { merge } from "@goauthentik/common/merge";
 | 
			
		||||
import { AkWizard } from "@goauthentik/components/ak-wizard-main/AkWizard";
 | 
			
		||||
import { CustomListenerElement } from "@goauthentik/elements/utils/eventEmitter";
 | 
			
		||||
 | 
			
		||||
@ -6,7 +5,7 @@ import { ContextProvider } from "@lit-labs/context";
 | 
			
		||||
import { msg } from "@lit/localize";
 | 
			
		||||
import { customElement, state } from "lit/decorators.js";
 | 
			
		||||
 | 
			
		||||
import applicationWizardContext from "./ContextIdentity";
 | 
			
		||||
import { applicationWizardContext } from "./ContextIdentity";
 | 
			
		||||
import { newSteps } from "./steps";
 | 
			
		||||
import {
 | 
			
		||||
    ApplicationStep,
 | 
			
		||||
@ -15,10 +14,11 @@ import {
 | 
			
		||||
    OneOfProvider,
 | 
			
		||||
} from "./types";
 | 
			
		||||
 | 
			
		||||
const freshWizardState = () => ({
 | 
			
		||||
const freshWizardState = (): ApplicationWizardState => ({
 | 
			
		||||
    providerModel: "",
 | 
			
		||||
    app: {},
 | 
			
		||||
    provider: {},
 | 
			
		||||
    errors: {},
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
@customElement("ak-application-wizard")
 | 
			
		||||
@ -56,28 +56,6 @@ export class ApplicationWizard extends CustomListenerElement(
 | 
			
		||||
     */
 | 
			
		||||
    providerCache: Map<string, OneOfProvider> = new Map();
 | 
			
		||||
 | 
			
		||||
    maybeProviderSwap(providerModel: string | undefined): boolean {
 | 
			
		||||
        if (
 | 
			
		||||
            providerModel === undefined ||
 | 
			
		||||
            typeof providerModel !== "string" ||
 | 
			
		||||
            providerModel === this.wizardState.providerModel
 | 
			
		||||
        ) {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.providerCache.set(this.wizardState.providerModel, this.wizardState.provider);
 | 
			
		||||
        const prevProvider = this.providerCache.get(providerModel);
 | 
			
		||||
        this.wizardState.provider = prevProvider ?? {
 | 
			
		||||
            name: `Provider for ${this.wizardState.app.name}`,
 | 
			
		||||
        };
 | 
			
		||||
        const method = this.steps.find(({ id }) => id === "provider-details");
 | 
			
		||||
        if (!method) {
 | 
			
		||||
            throw new Error("Could not find Authentication Method page?");
 | 
			
		||||
        }
 | 
			
		||||
        method.disabled = false;
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // And this is where all the special cases go...
 | 
			
		||||
    handleUpdate(detail: ApplicationWizardStateUpdate) {
 | 
			
		||||
        if (detail.status === "submitted") {
 | 
			
		||||
@ -87,17 +65,26 @@ export class ApplicationWizard extends CustomListenerElement(
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.step.valid = this.step.valid || detail.status === "valid";
 | 
			
		||||
 | 
			
		||||
        const update = detail.update;
 | 
			
		||||
 | 
			
		||||
        if (!update) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (this.maybeProviderSwap(update.providerModel)) {
 | 
			
		||||
            this.requestUpdate();
 | 
			
		||||
        // When the providerModel enum changes, retrieve the customer's prior work for *this* wizard
 | 
			
		||||
        // session (and only this wizard session) or provide an empty model with a default provider
 | 
			
		||||
        // name.
 | 
			
		||||
        if (update.providerModel && update.providerModel !== this.wizardState.providerModel) {
 | 
			
		||||
            const requestedProvider = this.providerCache.get(update.providerModel) ?? {
 | 
			
		||||
                name: `Provider for ${this.wizardState.app.name}`,
 | 
			
		||||
            };
 | 
			
		||||
            if (this.wizardState.providerModel) {
 | 
			
		||||
                this.providerCache.set(this.wizardState.providerModel, this.wizardState.provider);
 | 
			
		||||
            }
 | 
			
		||||
            update.provider = requestedProvider;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.wizardState = merge(this.wizardState, update) as ApplicationWizardState;
 | 
			
		||||
        this.wizardState = update as ApplicationWizardState;
 | 
			
		||||
        this.wizardStateProvider.setValue(this.wizardState);
 | 
			
		||||
        this.requestUpdate();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										30
									
								
								web/src/admin/applications/wizard/ak-wizard-title.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								web/src/admin/applications/wizard/ak-wizard-title.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,30 @@
 | 
			
		||||
import { AKElement } from "@goauthentik/elements/Base";
 | 
			
		||||
 | 
			
		||||
import { css, html } from "lit";
 | 
			
		||||
import { customElement } from "lit/decorators.js";
 | 
			
		||||
 | 
			
		||||
import PFContent from "@patternfly/patternfly/components/Content/content.css";
 | 
			
		||||
import PFTitle from "@patternfly/patternfly/components/Title/title.css";
 | 
			
		||||
 | 
			
		||||
@customElement("ak-wizard-title")
 | 
			
		||||
export class AkWizardTitle extends AKElement {
 | 
			
		||||
    static get styles() {
 | 
			
		||||
        return [
 | 
			
		||||
            PFContent,
 | 
			
		||||
            PFTitle,
 | 
			
		||||
            css`
 | 
			
		||||
                .ak-bottom-spacing {
 | 
			
		||||
                    padding-bottom: var(--pf-global--spacer--lg);
 | 
			
		||||
                }
 | 
			
		||||
            `,
 | 
			
		||||
        ];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    render() {
 | 
			
		||||
        return html`<div class="ak-bottom-spacing pf-c-content">
 | 
			
		||||
            <h3><slot></slot></h3>
 | 
			
		||||
        </div>`;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default AkWizardTitle;
 | 
			
		||||
@ -5,7 +5,6 @@ import "@goauthentik/components/ak-slug-input";
 | 
			
		||||
import "@goauthentik/components/ak-switch-input";
 | 
			
		||||
import "@goauthentik/components/ak-text-input";
 | 
			
		||||
import "@goauthentik/elements/forms/FormGroup";
 | 
			
		||||
import "@goauthentik/elements/forms/FormGroup";
 | 
			
		||||
import "@goauthentik/elements/forms/HorizontalFormElement";
 | 
			
		||||
 | 
			
		||||
import { msg } from "@lit/localize";
 | 
			
		||||
@ -17,28 +16,20 @@ import BasePanel from "../BasePanel";
 | 
			
		||||
 | 
			
		||||
@customElement("ak-application-wizard-application-details")
 | 
			
		||||
export class ApplicationWizardApplicationDetails extends BasePanel {
 | 
			
		||||
    handleChange(ev: Event) {
 | 
			
		||||
        if (!ev.target) {
 | 
			
		||||
            console.warn(`Received event with no target: ${ev}`);
 | 
			
		||||
            return;
 | 
			
		||||
    handleChange(_ev: Event) {
 | 
			
		||||
        const formValues = this.formValues;
 | 
			
		||||
        if (!formValues) {
 | 
			
		||||
            throw new Error("No application values on form?");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const target = ev.target as HTMLInputElement;
 | 
			
		||||
        const value = target.type === "checkbox" ? target.checked : target.value;
 | 
			
		||||
        this.dispatchWizardUpdate({
 | 
			
		||||
            update: {
 | 
			
		||||
                app: {
 | 
			
		||||
                    [target.name]: value,
 | 
			
		||||
                },
 | 
			
		||||
                ...this.wizard,
 | 
			
		||||
                app: formValues,
 | 
			
		||||
            },
 | 
			
		||||
            status: this.form.checkValidity() ? "valid" : "invalid",
 | 
			
		||||
            status: this.valid ? "valid" : "invalid",
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    validator() {
 | 
			
		||||
        return this.form.reportValidity();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    render(): TemplateResult {
 | 
			
		||||
        return html` <form class="pf-c-form pf-m-horizontal" @input=${this.handleChange}>
 | 
			
		||||
            <ak-text-input
 | 
			
		||||
@ -48,6 +39,7 @@ export class ApplicationWizardApplicationDetails extends BasePanel {
 | 
			
		||||
                required
 | 
			
		||||
                help=${msg("Application's display Name.")}
 | 
			
		||||
                id="ak-application-wizard-details-name"
 | 
			
		||||
                .errorMessages=${this.wizard.errors.app?.name ?? []}
 | 
			
		||||
            ></ak-text-input>
 | 
			
		||||
            <ak-slug-input
 | 
			
		||||
                name="slug"
 | 
			
		||||
@ -56,11 +48,13 @@ export class ApplicationWizardApplicationDetails extends BasePanel {
 | 
			
		||||
                source="#ak-application-wizard-details-name"
 | 
			
		||||
                required
 | 
			
		||||
                help=${msg("Internal application name used in URLs.")}
 | 
			
		||||
                .errorMessages=${this.wizard.errors.app?.slug ?? []}
 | 
			
		||||
            ></ak-slug-input>
 | 
			
		||||
            <ak-text-input
 | 
			
		||||
                name="group"
 | 
			
		||||
                value=${ifDefined(this.wizard.app?.group)}
 | 
			
		||||
                label=${msg("Group")}
 | 
			
		||||
                .errorMessages=${this.wizard.errors.app?.group ?? []}
 | 
			
		||||
                help=${msg(
 | 
			
		||||
                    "Optionally enter a group name. Applications with identical groups are shown grouped together.",
 | 
			
		||||
                )}
 | 
			
		||||
@ -71,6 +65,7 @@ export class ApplicationWizardApplicationDetails extends BasePanel {
 | 
			
		||||
                name="policyEngineMode"
 | 
			
		||||
                .options=${policyOptions}
 | 
			
		||||
                .value=${this.wizard.app?.policyEngineMode}
 | 
			
		||||
                .errorMessages=${this.wizard.errors.app?.policyEngineMode ?? []}
 | 
			
		||||
            ></ak-radio-input>
 | 
			
		||||
            <ak-form-group aria-label="UI Settings">
 | 
			
		||||
                <span slot="header"> ${msg("UI Settings")} </span>
 | 
			
		||||
@ -82,6 +77,7 @@ export class ApplicationWizardApplicationDetails extends BasePanel {
 | 
			
		||||
                        help=${msg(
 | 
			
		||||
                            "If left empty, authentik will try to extract the launch URL based on the selected provider.",
 | 
			
		||||
                        )}
 | 
			
		||||
                        .errorMessages=${this.wizard.errors.app?.metaLaunchUrl ?? []}
 | 
			
		||||
                    ></ak-text-input>
 | 
			
		||||
                    <ak-switch-input
 | 
			
		||||
                        name="openInNewTab"
 | 
			
		||||
 | 
			
		||||
@ -19,13 +19,17 @@ type ProviderRenderer = () => TemplateResult;
 | 
			
		||||
 | 
			
		||||
type ModelConverter = (provider: OneOfProvider) => ModelRequest;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * There's an internal key and an API key because "Proxy" has three different subtypes.
 | 
			
		||||
 */
 | 
			
		||||
// prettier-ignore
 | 
			
		||||
type ProviderType = [
 | 
			
		||||
    string,
 | 
			
		||||
    string,
 | 
			
		||||
    string,
 | 
			
		||||
    ProviderRenderer,
 | 
			
		||||
    ProviderModelEnumType,
 | 
			
		||||
    ModelConverter,
 | 
			
		||||
    string,                // internal key used by the wizard to distinguish between providers
 | 
			
		||||
    string,                // Name of the provider
 | 
			
		||||
    string,                // Description
 | 
			
		||||
    ProviderRenderer,      // Function that returns the provider's wizard panel as a TemplateResult
 | 
			
		||||
    ProviderModelEnumType, // key used by the API to distinguish between providers
 | 
			
		||||
    ModelConverter,        // Handler that takes a generic provider and returns one specifically typed to its panel
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
export type LocalTypeCreate = TypeCreate & {
 | 
			
		||||
 | 
			
		||||
@ -25,19 +25,15 @@ export class ApplicationWizardAuthenticationMethodChoice extends BasePanel {
 | 
			
		||||
    handleChoice(ev: InputEvent) {
 | 
			
		||||
        const target = ev.target as HTMLInputElement;
 | 
			
		||||
        this.dispatchWizardUpdate({
 | 
			
		||||
            update: { providerModel: target.value },
 | 
			
		||||
            status: this.validator() ? "valid" : "invalid",
 | 
			
		||||
            update: {
 | 
			
		||||
                ...this.wizard,
 | 
			
		||||
                providerModel: target.value,
 | 
			
		||||
                errors: {},
 | 
			
		||||
            },
 | 
			
		||||
            status: this.valid ? "valid" : "invalid",
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    validator() {
 | 
			
		||||
        const radios = Array.from(this.form.querySelectorAll('input[type="radio"]'));
 | 
			
		||||
        const chosen = radios.find(
 | 
			
		||||
            (radio: Element) => radio instanceof HTMLInputElement && radio.checked,
 | 
			
		||||
        );
 | 
			
		||||
        return !!chosen;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    renderProvider(type: LocalTypeCreate) {
 | 
			
		||||
        const method = this.wizard.providerModel;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -18,12 +18,14 @@ import PFTitle from "@patternfly/patternfly/components/Title/title.css";
 | 
			
		||||
import PFBullseye from "@patternfly/patternfly/layouts/Bullseye/bullseye.css";
 | 
			
		||||
 | 
			
		||||
import {
 | 
			
		||||
    ApplicationRequest,
 | 
			
		||||
    type ApplicationRequest,
 | 
			
		||||
    CoreApi,
 | 
			
		||||
    TransactionApplicationRequest,
 | 
			
		||||
    TransactionApplicationResponse,
 | 
			
		||||
    type ModelRequest,
 | 
			
		||||
    type TransactionApplicationRequest,
 | 
			
		||||
    type TransactionApplicationResponse,
 | 
			
		||||
    ValidationError,
 | 
			
		||||
    ValidationErrorFromJSON,
 | 
			
		||||
} from "@goauthentik/api";
 | 
			
		||||
import type { ModelRequest } from "@goauthentik/api";
 | 
			
		||||
 | 
			
		||||
import BasePanel from "../BasePanel";
 | 
			
		||||
import providerModelsList from "../auth-method-choice/ak-application-wizard-authentication-method-choice.choices";
 | 
			
		||||
@ -88,7 +90,7 @@ export class ApplicationWizardCommitApplication extends BasePanel {
 | 
			
		||||
    commitState: State = idleState;
 | 
			
		||||
 | 
			
		||||
    @state()
 | 
			
		||||
    errors: string[] = [];
 | 
			
		||||
    errors?: ValidationError;
 | 
			
		||||
 | 
			
		||||
    response?: TransactionApplicationResponse;
 | 
			
		||||
 | 
			
		||||
@ -121,27 +123,10 @@ export class ApplicationWizardCommitApplication extends BasePanel {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
 | 
			
		||||
    decodeErrors(body: Record<string, any>) {
 | 
			
		||||
        const spaceify = (src: Record<string, string>) =>
 | 
			
		||||
            Object.values(src).map((msg) => `\u00a0\u00a0\u00a0\u00a0${msg}`);
 | 
			
		||||
 | 
			
		||||
        let errs: string[] = [];
 | 
			
		||||
        if (body["app"] !== undefined) {
 | 
			
		||||
            errs = [...errs, msg("In the Application:"), ...spaceify(body["app"])];
 | 
			
		||||
        }
 | 
			
		||||
        if (body["provider"] !== undefined) {
 | 
			
		||||
            errs = [...errs, msg("In the Provider:"), ...spaceify(body["provider"])];
 | 
			
		||||
        }
 | 
			
		||||
        console.log(body, errs);
 | 
			
		||||
        return errs;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async send(
 | 
			
		||||
        data: TransactionApplicationRequest,
 | 
			
		||||
    ): Promise<TransactionApplicationResponse | void> {
 | 
			
		||||
        this.errors = [];
 | 
			
		||||
 | 
			
		||||
        this.errors = undefined;
 | 
			
		||||
        new CoreApi(DEFAULT_CONFIG)
 | 
			
		||||
            .coreTransactionalApplicationsUpdate({
 | 
			
		||||
                transactionApplicationRequest: data,
 | 
			
		||||
@ -153,18 +138,57 @@ export class ApplicationWizardCommitApplication extends BasePanel {
 | 
			
		||||
                this.commitState = successState;
 | 
			
		||||
            })
 | 
			
		||||
            // eslint-disable-next-line @typescript-eslint/no-explicit-any
 | 
			
		||||
            .catch((resolution: any) => {
 | 
			
		||||
                resolution.response.json().then(
 | 
			
		||||
                    // eslint-disable-next-line @typescript-eslint/no-explicit-any
 | 
			
		||||
                    (body: Record<string, any>) => {
 | 
			
		||||
                        this.errors = this.decodeErrors(body);
 | 
			
		||||
            .catch(async (resolution: any) => {
 | 
			
		||||
                const errors = (this.errors = ValidationErrorFromJSON(
 | 
			
		||||
                    await resolution.response.json(),
 | 
			
		||||
                ));
 | 
			
		||||
                this.dispatchWizardUpdate({
 | 
			
		||||
                    update: {
 | 
			
		||||
                        ...this.wizard,
 | 
			
		||||
                        errors,
 | 
			
		||||
                    },
 | 
			
		||||
                );
 | 
			
		||||
                    status: "failed",
 | 
			
		||||
                });
 | 
			
		||||
                this.commitState = errorState;
 | 
			
		||||
            });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    render(): TemplateResult {
 | 
			
		||||
    renderErrors(errors?: ValidationError) {
 | 
			
		||||
        if (!errors) {
 | 
			
		||||
            return nothing;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const navTo = (step: number) => () =>
 | 
			
		||||
            this.dispatchCustomEvent("ak-wizard-nav", {
 | 
			
		||||
                command: "goto",
 | 
			
		||||
                step,
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
        if (errors.app) {
 | 
			
		||||
            return html`<p>${msg("There was an error in the application.")}</p>
 | 
			
		||||
                <p><a @click=${navTo(0)}>${msg("Review the application.")}</a></p>`;
 | 
			
		||||
        }
 | 
			
		||||
        if (errors.provider) {
 | 
			
		||||
            return html`<p>${msg("There was an error in the provider.")}</p>
 | 
			
		||||
                <p><a @click=${navTo(2)}>${msg("Review the provider.")}</a></p>`;
 | 
			
		||||
        }
 | 
			
		||||
        if (errors.detail) {
 | 
			
		||||
            return html`<p>${msg("There was an error")}: ${errors.detail}</p>`;
 | 
			
		||||
        }
 | 
			
		||||
        if ((errors?.nonFieldErrors ?? []).length > 0) {
 | 
			
		||||
            return html`<p>$(msg("There was an error")}:</p>
 | 
			
		||||
                <ul>
 | 
			
		||||
                    ${(errors.nonFieldErrors ?? []).map((e: string) => html`<li>${e}</li>`)}
 | 
			
		||||
                </ul>`;
 | 
			
		||||
        }
 | 
			
		||||
        return html`<p>
 | 
			
		||||
            ${msg(
 | 
			
		||||
                "There was an error creating the application, but no error message was sent. Please review the server logs.",
 | 
			
		||||
            )}
 | 
			
		||||
        </p>`;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    render() {
 | 
			
		||||
        const icon = classMap(
 | 
			
		||||
            this.commitState.icon.reduce((acc, icon) => ({ ...acc, [icon]: true }), {}),
 | 
			
		||||
        );
 | 
			
		||||
@ -184,13 +208,7 @@ export class ApplicationWizardCommitApplication extends BasePanel {
 | 
			
		||||
                            >
 | 
			
		||||
                                ${this.commitState.label}
 | 
			
		||||
                            </h1>
 | 
			
		||||
                            ${this.errors.length > 0
 | 
			
		||||
                                ? html`<ul>
 | 
			
		||||
                                      ${this.errors.map(
 | 
			
		||||
                                          (msg) => html`<li><code>${msg}</code></li>`,
 | 
			
		||||
                                      )}
 | 
			
		||||
                                  </ul>`
 | 
			
		||||
                                : nothing}
 | 
			
		||||
                            ${this.renderErrors(this.errors)}
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
 | 
			
		||||
@ -1,26 +1,19 @@
 | 
			
		||||
import BasePanel from "../BasePanel";
 | 
			
		||||
 | 
			
		||||
export class ApplicationWizardProviderPageBase extends BasePanel {
 | 
			
		||||
    handleChange(ev: InputEvent) {
 | 
			
		||||
        if (!ev.target) {
 | 
			
		||||
            console.warn(`Received event with no target: ${ev}`);
 | 
			
		||||
            return;
 | 
			
		||||
    handleChange(_ev: InputEvent) {
 | 
			
		||||
        const formValues = this.formValues;
 | 
			
		||||
        if (!formValues) {
 | 
			
		||||
            throw new Error("No provider values on form?");
 | 
			
		||||
        }
 | 
			
		||||
        const target = ev.target as HTMLInputElement;
 | 
			
		||||
        const value = target.type === "checkbox" ? target.checked : target.value;
 | 
			
		||||
        this.dispatchWizardUpdate({
 | 
			
		||||
            update: {
 | 
			
		||||
                provider: {
 | 
			
		||||
                    [target.name]: value,
 | 
			
		||||
                },
 | 
			
		||||
                ...this.wizard,
 | 
			
		||||
                provider: formValues,
 | 
			
		||||
            },
 | 
			
		||||
            status: this.form.checkValidity() ? "valid" : "invalid",
 | 
			
		||||
            status: this.valid ? "valid" : "invalid",
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    validator() {
 | 
			
		||||
        return this.form.reportValidity();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default ApplicationWizardProviderPageBase;
 | 
			
		||||
 | 
			
		||||
@ -1,3 +1,4 @@
 | 
			
		||||
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";
 | 
			
		||||
@ -34,112 +35,132 @@ import {
 | 
			
		||||
export class ApplicationWizardApplicationDetails extends BaseProviderPanel {
 | 
			
		||||
    render() {
 | 
			
		||||
        const provider = this.wizard.provider as LDAPProvider | undefined;
 | 
			
		||||
        const errors = this.wizard.errors.provider;
 | 
			
		||||
 | 
			
		||||
        return html` <form class="pf-c-form pf-m-horizontal" @input=${this.handleChange}>
 | 
			
		||||
            <ak-text-input
 | 
			
		||||
                name="name"
 | 
			
		||||
                value=${ifDefined(provider?.name)}
 | 
			
		||||
                label=${msg("Name")}
 | 
			
		||||
                required
 | 
			
		||||
                help=${msg("Method's display Name.")}
 | 
			
		||||
            ></ak-text-input>
 | 
			
		||||
 | 
			
		||||
            <ak-form-element-horizontal
 | 
			
		||||
                label=${msg("Bind flow")}
 | 
			
		||||
                ?required=${true}
 | 
			
		||||
                name="authorizationFlow"
 | 
			
		||||
            >
 | 
			
		||||
                <ak-tenanted-flow-search
 | 
			
		||||
                    flowType=${FlowsInstancesListDesignationEnum.Authentication}
 | 
			
		||||
                    .currentFlow=${provider?.authorizationFlow}
 | 
			
		||||
                    .tenantFlow=${rootInterface()?.tenant?.flowAuthentication}
 | 
			
		||||
        return html` <ak-wizard-title>${msg("Configure LDAP Provider")}</ak-wizard-title>
 | 
			
		||||
            <form class="pf-c-form pf-m-horizontal" @input=${this.handleChange}>
 | 
			
		||||
                <ak-text-input
 | 
			
		||||
                    name="name"
 | 
			
		||||
                    value=${ifDefined(provider?.name)}
 | 
			
		||||
                    label=${msg("Name")}
 | 
			
		||||
                    .errorMessages=${errors?.name ?? []}
 | 
			
		||||
                    required
 | 
			
		||||
                ></ak-tenanted-flow-search>
 | 
			
		||||
                <p class="pf-c-form__helper-text">${msg("Flow used for users to authenticate.")}</p>
 | 
			
		||||
            </ak-form-element-horizontal>
 | 
			
		||||
                    help=${msg("Method's display Name.")}
 | 
			
		||||
                ></ak-text-input>
 | 
			
		||||
 | 
			
		||||
            <ak-form-element-horizontal label=${msg("Search group")} name="searchGroup">
 | 
			
		||||
                <ak-core-group-search
 | 
			
		||||
                <ak-form-element-horizontal
 | 
			
		||||
                    label=${msg("Bind flow")}
 | 
			
		||||
                    ?required=${true}
 | 
			
		||||
                    name="authorizationFlow"
 | 
			
		||||
                    .errorMessages=${errors?.authorizationFlow ?? []}
 | 
			
		||||
                >
 | 
			
		||||
                    <ak-tenanted-flow-search
 | 
			
		||||
                        flowType=${FlowsInstancesListDesignationEnum.Authentication}
 | 
			
		||||
                        .currentFlow=${provider?.authorizationFlow}
 | 
			
		||||
                        .tenantFlow=${rootInterface()?.tenant?.flowAuthentication}
 | 
			
		||||
                        required
 | 
			
		||||
                    ></ak-tenanted-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"
 | 
			
		||||
                    group=${ifDefined(provider?.searchGroup ?? nothing)}
 | 
			
		||||
                ></ak-core-group-search>
 | 
			
		||||
                <p class="pf-c-form__helper-text">${groupHelp}</p>
 | 
			
		||||
            </ak-form-element-horizontal>
 | 
			
		||||
                    .errorMessages=${errors?.searchGroup ?? []}
 | 
			
		||||
                >
 | 
			
		||||
                    <ak-core-group-search
 | 
			
		||||
                        name="searchGroup"
 | 
			
		||||
                        group=${ifDefined(provider?.searchGroup ?? nothing)}
 | 
			
		||||
                    ></ak-core-group-search>
 | 
			
		||||
                    <p class="pf-c-form__helper-text">${groupHelp}</p>
 | 
			
		||||
                </ak-form-element-horizontal>
 | 
			
		||||
 | 
			
		||||
            <ak-radio-input
 | 
			
		||||
                label=${msg("Bind mode")}
 | 
			
		||||
                name="bindMode"
 | 
			
		||||
                .options=${bindModeOptions}
 | 
			
		||||
                .value=${provider?.bindMode}
 | 
			
		||||
                help=${msg("Configure how the outpost authenticates requests.")}
 | 
			
		||||
            >
 | 
			
		||||
            </ak-radio-input>
 | 
			
		||||
                <ak-radio-input
 | 
			
		||||
                    label=${msg("Bind mode")}
 | 
			
		||||
                    name="bindMode"
 | 
			
		||||
                    .options=${bindModeOptions}
 | 
			
		||||
                    .value=${provider?.bindMode}
 | 
			
		||||
                    help=${msg("Configure how the outpost authenticates requests.")}
 | 
			
		||||
                >
 | 
			
		||||
                </ak-radio-input>
 | 
			
		||||
 | 
			
		||||
            <ak-radio-input
 | 
			
		||||
                label=${msg("Search mode")}
 | 
			
		||||
                name="searchMode"
 | 
			
		||||
                .options=${searchModeOptions}
 | 
			
		||||
                .value=${provider?.searchMode}
 | 
			
		||||
                help=${msg("Configure how the outpost queries the core authentik server's users.")}
 | 
			
		||||
            >
 | 
			
		||||
            </ak-radio-input>
 | 
			
		||||
                <ak-radio-input
 | 
			
		||||
                    label=${msg("Search mode")}
 | 
			
		||||
                    name="searchMode"
 | 
			
		||||
                    .options=${searchModeOptions}
 | 
			
		||||
                    .value=${provider?.searchMode}
 | 
			
		||||
                    help=${msg(
 | 
			
		||||
                        "Configure how the outpost queries the core authentik server's users.",
 | 
			
		||||
                    )}
 | 
			
		||||
                >
 | 
			
		||||
                </ak-radio-input>
 | 
			
		||||
 | 
			
		||||
            <ak-switch-input
 | 
			
		||||
                name="openInNewTab"
 | 
			
		||||
                label=${msg("Code-based MFA Support")}
 | 
			
		||||
                ?checked=${provider?.mfaSupport}
 | 
			
		||||
                help=${mfaSupportHelp}
 | 
			
		||||
            >
 | 
			
		||||
            </ak-switch-input>
 | 
			
		||||
                <ak-switch-input
 | 
			
		||||
                    name="openInNewTab"
 | 
			
		||||
                    label=${msg("Code-based MFA Support")}
 | 
			
		||||
                    ?checked=${provider?.mfaSupport ?? true}
 | 
			
		||||
                    help=${mfaSupportHelp}
 | 
			
		||||
                >
 | 
			
		||||
                </ak-switch-input>
 | 
			
		||||
 | 
			
		||||
            <ak-form-group .expanded=${true}>
 | 
			
		||||
                <span slot="header"> ${msg("Protocol settings")} </span>
 | 
			
		||||
                <div slot="body" class="pf-c-form">
 | 
			
		||||
                    <ak-text-input
 | 
			
		||||
                        name="baseDn"
 | 
			
		||||
                        label=${msg("Base DN")}
 | 
			
		||||
                        required
 | 
			
		||||
                        value="${first(provider?.baseDn, "DC=ldap,DC=goauthentik,DC=io")}"
 | 
			
		||||
                        help=${msg(
 | 
			
		||||
                            "LDAP DN under which bind requests and search requests can be made.",
 | 
			
		||||
                        )}
 | 
			
		||||
                    >
 | 
			
		||||
                    </ak-text-input>
 | 
			
		||||
 | 
			
		||||
                    <ak-form-element-horizontal label=${msg("Certificate")} name="certificate">
 | 
			
		||||
                        <ak-crypto-certificate-search
 | 
			
		||||
                            certificate=${ifDefined(provider?.certificate ?? nothing)}
 | 
			
		||||
                            name="certificate"
 | 
			
		||||
                <ak-form-group .expanded=${true}>
 | 
			
		||||
                    <span slot="header"> ${msg("Protocol settings")} </span>
 | 
			
		||||
                    <div slot="body" class="pf-c-form">
 | 
			
		||||
                        <ak-text-input
 | 
			
		||||
                            name="baseDn"
 | 
			
		||||
                            label=${msg("Base DN")}
 | 
			
		||||
                            required
 | 
			
		||||
                            value="${first(provider?.baseDn, "DC=ldap,DC=goauthentik,DC=io")}"
 | 
			
		||||
                            .errorMessages=${errors?.baseDn ?? []}
 | 
			
		||||
                            help=${msg(
 | 
			
		||||
                                "LDAP DN under which bind requests and search requests can be made.",
 | 
			
		||||
                            )}
 | 
			
		||||
                        >
 | 
			
		||||
                        </ak-crypto-certificate-search>
 | 
			
		||||
                        <p class="pf-c-form__helper-text">${cryptoCertificateHelp}</p>
 | 
			
		||||
                    </ak-form-element-horizontal>
 | 
			
		||||
                        </ak-text-input>
 | 
			
		||||
 | 
			
		||||
                    <ak-text-input
 | 
			
		||||
                        label=${msg("TLS Server name")}
 | 
			
		||||
                        name="tlsServerName"
 | 
			
		||||
                        value="${first(provider?.tlsServerName, "")}"
 | 
			
		||||
                        help=${tlsServerNameHelp}
 | 
			
		||||
                    ></ak-text-input>
 | 
			
		||||
                        <ak-form-element-horizontal
 | 
			
		||||
                            label=${msg("Certificate")}
 | 
			
		||||
                            name="certificate"
 | 
			
		||||
                            .errorMessages=${errors?.certificate ?? []}
 | 
			
		||||
                        >
 | 
			
		||||
                            <ak-crypto-certificate-search
 | 
			
		||||
                                certificate=${ifDefined(provider?.certificate ?? nothing)}
 | 
			
		||||
                                name="certificate"
 | 
			
		||||
                            >
 | 
			
		||||
                            </ak-crypto-certificate-search>
 | 
			
		||||
                            <p class="pf-c-form__helper-text">${cryptoCertificateHelp}</p>
 | 
			
		||||
                        </ak-form-element-horizontal>
 | 
			
		||||
 | 
			
		||||
                    <ak-number-input
 | 
			
		||||
                        label=${msg("UID start number")}
 | 
			
		||||
                        required
 | 
			
		||||
                        name="uidStartNumber"
 | 
			
		||||
                        value="${first(provider?.uidStartNumber, 2000)}"
 | 
			
		||||
                        help=${uidStartNumberHelp}
 | 
			
		||||
                    ></ak-number-input>
 | 
			
		||||
                        <ak-text-input
 | 
			
		||||
                            label=${msg("TLS Server name")}
 | 
			
		||||
                            name="tlsServerName"
 | 
			
		||||
                            value="${first(provider?.tlsServerName, "")}"
 | 
			
		||||
                            .errorMessages=${errors?.tlsServerName ?? []}
 | 
			
		||||
                            help=${tlsServerNameHelp}
 | 
			
		||||
                        ></ak-text-input>
 | 
			
		||||
 | 
			
		||||
                    <ak-number-input
 | 
			
		||||
                        label=${msg("GID start number")}
 | 
			
		||||
                        required
 | 
			
		||||
                        name="gidStartNumber"
 | 
			
		||||
                        value="${first(provider?.gidStartNumber, 4000)}"
 | 
			
		||||
                        help=${gidStartNumberHelp}
 | 
			
		||||
                    ></ak-number-input>
 | 
			
		||||
                </div>
 | 
			
		||||
            </ak-form-group>
 | 
			
		||||
        </form>`;
 | 
			
		||||
                        <ak-number-input
 | 
			
		||||
                            label=${msg("UID start number")}
 | 
			
		||||
                            required
 | 
			
		||||
                            name="uidStartNumber"
 | 
			
		||||
                            value="${first(provider?.uidStartNumber, 2000)}"
 | 
			
		||||
                            .errorMessages=${errors?.uidStartNumber ?? []}
 | 
			
		||||
                            help=${uidStartNumberHelp}
 | 
			
		||||
                        ></ak-number-input>
 | 
			
		||||
 | 
			
		||||
                        <ak-number-input
 | 
			
		||||
                            label=${msg("GID start number")}
 | 
			
		||||
                            required
 | 
			
		||||
                            name="gidStartNumber"
 | 
			
		||||
                            value="${first(provider?.gidStartNumber, 4000)}"
 | 
			
		||||
                            .errorMessages=${errors?.gidStartNumber ?? []}
 | 
			
		||||
                            help=${gidStartNumberHelp}
 | 
			
		||||
                        ></ak-number-input>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </ak-form-group>
 | 
			
		||||
            </form>`;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,3 +1,4 @@
 | 
			
		||||
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 {
 | 
			
		||||
@ -27,10 +28,10 @@ import {
 | 
			
		||||
    PropertymappingsApi,
 | 
			
		||||
    SourcesApi,
 | 
			
		||||
} from "@goauthentik/api";
 | 
			
		||||
import type {
 | 
			
		||||
    OAuth2Provider,
 | 
			
		||||
    PaginatedOAuthSourceList,
 | 
			
		||||
    PaginatedScopeMappingList,
 | 
			
		||||
import {
 | 
			
		||||
    type OAuth2Provider,
 | 
			
		||||
    type PaginatedOAuthSourceList,
 | 
			
		||||
    type PaginatedScopeMappingList,
 | 
			
		||||
} from "@goauthentik/api";
 | 
			
		||||
 | 
			
		||||
import BaseProviderPanel from "../BaseProviderPanel";
 | 
			
		||||
@ -38,7 +39,7 @@ import BaseProviderPanel from "../BaseProviderPanel";
 | 
			
		||||
@customElement("ak-application-wizard-authentication-by-oauth")
 | 
			
		||||
export class ApplicationWizardAuthenticationByOauth extends BaseProviderPanel {
 | 
			
		||||
    @state()
 | 
			
		||||
    showClientSecret = false;
 | 
			
		||||
    showClientSecret = true;
 | 
			
		||||
 | 
			
		||||
    @state()
 | 
			
		||||
    propertyMappings?: PaginatedScopeMappingList;
 | 
			
		||||
@ -68,234 +69,254 @@ export class ApplicationWizardAuthenticationByOauth extends BaseProviderPanel {
 | 
			
		||||
 | 
			
		||||
    render() {
 | 
			
		||||
        const provider = this.wizard.provider as OAuth2Provider | undefined;
 | 
			
		||||
        const errors = this.wizard.errors.provider;
 | 
			
		||||
 | 
			
		||||
        return html`<form class="pf-c-form pf-m-horizontal" @input=${this.handleChange}>
 | 
			
		||||
            <ak-text-input
 | 
			
		||||
                name="name"
 | 
			
		||||
                label=${msg("Name")}
 | 
			
		||||
                value=${ifDefined(provider?.name)}
 | 
			
		||||
                required
 | 
			
		||||
            ></ak-text-input>
 | 
			
		||||
 | 
			
		||||
            <ak-form-element-horizontal
 | 
			
		||||
                name="authenticationFlow"
 | 
			
		||||
                label=${msg("Authentication flow")}
 | 
			
		||||
            >
 | 
			
		||||
                <ak-flow-search
 | 
			
		||||
                    flowType=${FlowsInstancesListDesignationEnum.Authentication}
 | 
			
		||||
                    .currentFlow=${provider?.authenticationFlow}
 | 
			
		||||
        return html`<ak-wizard-title>${msg("Configure OAuth2/OpenId Provider")}</ak-wizard-title>
 | 
			
		||||
            <form class="pf-c-form pf-m-horizontal" @input=${this.handleChange}>
 | 
			
		||||
                <ak-text-input
 | 
			
		||||
                    name="name"
 | 
			
		||||
                    label=${msg("Name")}
 | 
			
		||||
                    value=${ifDefined(provider?.name)}
 | 
			
		||||
                    .errorMessages=${errors?.name ?? []}
 | 
			
		||||
                    required
 | 
			
		||||
                ></ak-flow-search>
 | 
			
		||||
                <p class="pf-c-form__helper-text">
 | 
			
		||||
                    ${msg("Flow used when a user access this provider and is not authenticated.")}
 | 
			
		||||
                </p>
 | 
			
		||||
            </ak-form-element-horizontal>
 | 
			
		||||
                ></ak-text-input>
 | 
			
		||||
 | 
			
		||||
            <ak-form-element-horizontal
 | 
			
		||||
                name="authorizationFlow"
 | 
			
		||||
                label=${msg("Authorization flow")}
 | 
			
		||||
                ?required=${true}
 | 
			
		||||
            >
 | 
			
		||||
                <ak-flow-search
 | 
			
		||||
                    flowType=${FlowsInstancesListDesignationEnum.Authorization}
 | 
			
		||||
                    .currentFlow=${provider?.authorizationFlow}
 | 
			
		||||
                    required
 | 
			
		||||
                ></ak-flow-search>
 | 
			
		||||
                <p class="pf-c-form__helper-text">
 | 
			
		||||
                    ${msg("Flow used when authorizing this provider.")}
 | 
			
		||||
                </p>
 | 
			
		||||
            </ak-form-element-horizontal>
 | 
			
		||||
 | 
			
		||||
            <ak-form-group .expanded=${true}>
 | 
			
		||||
                <span slot="header"> ${msg("Protocol settings")} </span>
 | 
			
		||||
                <div slot="body" class="pf-c-form">
 | 
			
		||||
                    <ak-radio-input
 | 
			
		||||
                        name="clientType"
 | 
			
		||||
                        label=${msg("Client type")}
 | 
			
		||||
                        .value=${provider?.clientType}
 | 
			
		||||
                <ak-form-element-horizontal
 | 
			
		||||
                    name="authenticationFlow"
 | 
			
		||||
                    label=${msg("Authentication flow")}
 | 
			
		||||
                    .errorMessages=${errors?.authenticationFlow ?? []}
 | 
			
		||||
                >
 | 
			
		||||
                    <ak-flow-search
 | 
			
		||||
                        flowType=${FlowsInstancesListDesignationEnum.Authentication}
 | 
			
		||||
                        .currentFlow=${provider?.authenticationFlow}
 | 
			
		||||
                        required
 | 
			
		||||
                        @change=${(ev: CustomEvent<{ value: ClientTypeEnum }>) => {
 | 
			
		||||
                            this.showClientSecret = ev.detail.value !== ClientTypeEnum.Public;
 | 
			
		||||
                        }}
 | 
			
		||||
                        .options=${clientTypeOptions}
 | 
			
		||||
                    >
 | 
			
		||||
                    </ak-radio-input>
 | 
			
		||||
 | 
			
		||||
                    <ak-text-input
 | 
			
		||||
                        name="clientId"
 | 
			
		||||
                        label=${msg("Client ID")}
 | 
			
		||||
                        value="${first(
 | 
			
		||||
                            provider?.clientId,
 | 
			
		||||
                            randomString(40, ascii_letters + digits),
 | 
			
		||||
                        )}"
 | 
			
		||||
                    ></ak-flow-search>
 | 
			
		||||
                    <p class="pf-c-form__helper-text">
 | 
			
		||||
                        ${msg(
 | 
			
		||||
                            "Flow used when a user access this provider and is not authenticated.",
 | 
			
		||||
                        )}
 | 
			
		||||
                    </p>
 | 
			
		||||
                </ak-form-element-horizontal>
 | 
			
		||||
                <ak-form-element-horizontal
 | 
			
		||||
                    name="authorizationFlow"
 | 
			
		||||
                    label=${msg("Authorization flow")}
 | 
			
		||||
                    .errorMessages=${errors?.authorizationFlow ?? []}
 | 
			
		||||
                    ?required=${true}
 | 
			
		||||
                >
 | 
			
		||||
                    <ak-flow-search
 | 
			
		||||
                        flowType=${FlowsInstancesListDesignationEnum.Authorization}
 | 
			
		||||
                        .currentFlow=${provider?.authorizationFlow}
 | 
			
		||||
                        required
 | 
			
		||||
                    >
 | 
			
		||||
                    </ak-text-input>
 | 
			
		||||
                    ></ak-flow-search>
 | 
			
		||||
                    <p class="pf-c-form__helper-text">
 | 
			
		||||
                        ${msg("Flow used when authorizing this provider.")}
 | 
			
		||||
                    </p>
 | 
			
		||||
                </ak-form-element-horizontal>
 | 
			
		||||
 | 
			
		||||
                    <ak-text-input
 | 
			
		||||
                        name="clientSecret"
 | 
			
		||||
                        label=${msg("Client Secret")}
 | 
			
		||||
                        value="${first(
 | 
			
		||||
                            provider?.clientSecret,
 | 
			
		||||
                            randomString(128, ascii_letters + digits),
 | 
			
		||||
                        )}"
 | 
			
		||||
                        ?hidden=${!this.showClientSecret}
 | 
			
		||||
                    >
 | 
			
		||||
                    </ak-text-input>
 | 
			
		||||
 | 
			
		||||
                    <ak-textarea-input
 | 
			
		||||
                        name="redirectUris"
 | 
			
		||||
                        label=${msg("Redirect URIs/Origins (RegEx)")}
 | 
			
		||||
                        .value=${provider?.redirectUris}
 | 
			
		||||
                        .bighelp=${redirectUriHelp}
 | 
			
		||||
                    >
 | 
			
		||||
                    </ak-textarea-input>
 | 
			
		||||
 | 
			
		||||
                    <ak-form-element-horizontal label=${msg("Signing Key")} name="signingKey">
 | 
			
		||||
                        <ak-crypto-certificate-search
 | 
			
		||||
                            certificate=${ifDefined(provider?.signingKey ?? nothing)}
 | 
			
		||||
                            name="certificate"
 | 
			
		||||
                            singleton
 | 
			
		||||
                <ak-form-group .expanded=${true}>
 | 
			
		||||
                    <span slot="header"> ${msg("Protocol settings")} </span>
 | 
			
		||||
                    <div slot="body" class="pf-c-form">
 | 
			
		||||
                        <ak-radio-input
 | 
			
		||||
                            name="clientType"
 | 
			
		||||
                            label=${msg("Client type")}
 | 
			
		||||
                            .value=${provider?.clientType}
 | 
			
		||||
                            required
 | 
			
		||||
                            @change=${(ev: CustomEvent<{ value: ClientTypeEnum }>) => {
 | 
			
		||||
                                this.showClientSecret = ev.detail.value !== ClientTypeEnum.Public;
 | 
			
		||||
                            }}
 | 
			
		||||
                            .options=${clientTypeOptions}
 | 
			
		||||
                        >
 | 
			
		||||
                        </ak-crypto-certificate-search>
 | 
			
		||||
                        <p class="pf-c-form__helper-text">${msg("Key used to sign the tokens.")}</p>
 | 
			
		||||
                    </ak-form-element-horizontal>
 | 
			
		||||
                </div>
 | 
			
		||||
            </ak-form-group>
 | 
			
		||||
                        </ak-radio-input>
 | 
			
		||||
 | 
			
		||||
            <ak-form-group>
 | 
			
		||||
                <span slot="header"> ${msg("Advanced protocol settings")} </span>
 | 
			
		||||
                <div slot="body" class="pf-c-form">
 | 
			
		||||
                    <ak-text-input
 | 
			
		||||
                        name="accessCodeValidity"
 | 
			
		||||
                        label=${msg("Access code validity")}
 | 
			
		||||
                        required
 | 
			
		||||
                        value="${first(provider?.accessCodeValidity, "minutes=1")}"
 | 
			
		||||
                        .bighelp=${html`<p class="pf-c-form__helper-text">
 | 
			
		||||
                                ${msg("Configure how long access codes are valid for.")}
 | 
			
		||||
                        <ak-text-input
 | 
			
		||||
                            name="clientId"
 | 
			
		||||
                            label=${msg("Client ID")}
 | 
			
		||||
                            value=${provider?.clientId ?? randomString(40, ascii_letters + digits)}
 | 
			
		||||
                            .errorMessages=${errors?.clientId ?? []}
 | 
			
		||||
                            required
 | 
			
		||||
                        >
 | 
			
		||||
                        </ak-text-input>
 | 
			
		||||
 | 
			
		||||
                        <ak-text-input
 | 
			
		||||
                            name="clientSecret"
 | 
			
		||||
                            label=${msg("Client Secret")}
 | 
			
		||||
                            value=${provider?.clientSecret ??
 | 
			
		||||
                            randomString(128, ascii_letters + digits)}
 | 
			
		||||
                            .errorMessages=${errors?.clientSecret ?? []}
 | 
			
		||||
                            ?hidden=${!this.showClientSecret}
 | 
			
		||||
                        >
 | 
			
		||||
                        </ak-text-input>
 | 
			
		||||
 | 
			
		||||
                        <ak-textarea-input
 | 
			
		||||
                            name="redirectUris"
 | 
			
		||||
                            label=${msg("Redirect URIs/Origins (RegEx)")}
 | 
			
		||||
                            .value=${provider?.redirectUris}
 | 
			
		||||
                            .errorMessages=${errors?.redirectUriHelp ?? []}
 | 
			
		||||
                            .bighelp=${redirectUriHelp}
 | 
			
		||||
                        >
 | 
			
		||||
                        </ak-textarea-input>
 | 
			
		||||
 | 
			
		||||
                        <ak-form-element-horizontal
 | 
			
		||||
                            label=${msg("Signing Key")}
 | 
			
		||||
                            name="signingKey"
 | 
			
		||||
                            .errorMessages=${errors?.signingKey ?? []}
 | 
			
		||||
                        >
 | 
			
		||||
                            <ak-crypto-certificate-search
 | 
			
		||||
                                certificate=${ifDefined(provider?.signingKey ?? nothing)}
 | 
			
		||||
                                name="certificate"
 | 
			
		||||
                                singleton
 | 
			
		||||
                            >
 | 
			
		||||
                            </ak-crypto-certificate-search>
 | 
			
		||||
                            <p class="pf-c-form__helper-text">
 | 
			
		||||
                                ${msg("Key used to sign the tokens.")}
 | 
			
		||||
                            </p>
 | 
			
		||||
                            <ak-utils-time-delta-help></ak-utils-time-delta-help>`}
 | 
			
		||||
                    >
 | 
			
		||||
                    </ak-text-input>
 | 
			
		||||
                        </ak-form-element-horizontal>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </ak-form-group>
 | 
			
		||||
 | 
			
		||||
                    <ak-text-input
 | 
			
		||||
                        name="accessTokenValidity"
 | 
			
		||||
                        label=${msg("Access Token validity")}
 | 
			
		||||
                        value="${first(provider?.accessTokenValidity, "minutes=5")}"
 | 
			
		||||
                        required
 | 
			
		||||
                        .bighelp=${html` <p class="pf-c-form__helper-text">
 | 
			
		||||
                                ${msg("Configure how long access tokens are valid for.")}
 | 
			
		||||
                <ak-form-group>
 | 
			
		||||
                    <span slot="header"> ${msg("Advanced protocol settings")} </span>
 | 
			
		||||
                    <div slot="body" class="pf-c-form">
 | 
			
		||||
                        <ak-text-input
 | 
			
		||||
                            name="accessCodeValidity"
 | 
			
		||||
                            label=${msg("Access code validity")}
 | 
			
		||||
                            required
 | 
			
		||||
                            value="${first(provider?.accessCodeValidity, "minutes=1")}"
 | 
			
		||||
                            .errorMessages=${errors?.accessCodeValidity ?? []}
 | 
			
		||||
                            .bighelp=${html`<p class="pf-c-form__helper-text">
 | 
			
		||||
                                    ${msg("Configure how long access codes are valid for.")}
 | 
			
		||||
                                </p>
 | 
			
		||||
                                <ak-utils-time-delta-help></ak-utils-time-delta-help>`}
 | 
			
		||||
                        >
 | 
			
		||||
                        </ak-text-input>
 | 
			
		||||
 | 
			
		||||
                        <ak-text-input
 | 
			
		||||
                            name="accessTokenValidity"
 | 
			
		||||
                            label=${msg("Access Token validity")}
 | 
			
		||||
                            value="${first(provider?.accessTokenValidity, "minutes=5")}"
 | 
			
		||||
                            required
 | 
			
		||||
                            .errorMessages=${errors?.accessTokenValidity ?? []}
 | 
			
		||||
                            .bighelp=${html` <p class="pf-c-form__helper-text">
 | 
			
		||||
                                    ${msg("Configure how long access tokens are valid for.")}
 | 
			
		||||
                                </p>
 | 
			
		||||
                                <ak-utils-time-delta-help></ak-utils-time-delta-help>`}
 | 
			
		||||
                        >
 | 
			
		||||
                        </ak-text-input>
 | 
			
		||||
 | 
			
		||||
                        <ak-text-input
 | 
			
		||||
                            name="refreshTokenValidity"
 | 
			
		||||
                            label=${msg("Refresh Token validity")}
 | 
			
		||||
                            value="${first(provider?.refreshTokenValidity, "days=30")}"
 | 
			
		||||
                            .errorMessages=${errors?.refreshTokenValidity ?? []}
 | 
			
		||||
                            ?required=${true}
 | 
			
		||||
                            .bighelp=${html` <p class="pf-c-form__helper-text">
 | 
			
		||||
                                    ${msg("Configure how long refresh tokens are valid for.")}
 | 
			
		||||
                                </p>
 | 
			
		||||
                                <ak-utils-time-delta-help></ak-utils-time-delta-help>`}
 | 
			
		||||
                        >
 | 
			
		||||
                        </ak-text-input>
 | 
			
		||||
 | 
			
		||||
                        <ak-form-element-horizontal
 | 
			
		||||
                            label=${msg("Scopes")}
 | 
			
		||||
                            name="propertyMappings"
 | 
			
		||||
                            .errorMessages=${errors?.propertyMappings ?? []}
 | 
			
		||||
                        >
 | 
			
		||||
                            <select class="pf-c-form-control" multiple>
 | 
			
		||||
                                ${this.propertyMappings?.results.map((scope) => {
 | 
			
		||||
                                    let selected = false;
 | 
			
		||||
                                    if (!provider?.propertyMappings) {
 | 
			
		||||
                                        selected =
 | 
			
		||||
                                            scope.managed?.startsWith(
 | 
			
		||||
                                                "goauthentik.io/providers/oauth2/scope-",
 | 
			
		||||
                                            ) || false;
 | 
			
		||||
                                    } else {
 | 
			
		||||
                                        selected = Array.from(provider?.propertyMappings).some(
 | 
			
		||||
                                            (su) => {
 | 
			
		||||
                                                return su == scope.pk;
 | 
			
		||||
                                            },
 | 
			
		||||
                                        );
 | 
			
		||||
                                    }
 | 
			
		||||
                                    return html`<option
 | 
			
		||||
                                        value=${ifDefined(scope.pk)}
 | 
			
		||||
                                        ?selected=${selected}
 | 
			
		||||
                                    >
 | 
			
		||||
                                        ${scope.name}
 | 
			
		||||
                                    </option>`;
 | 
			
		||||
                                })}
 | 
			
		||||
                            </select>
 | 
			
		||||
                            <p class="pf-c-form__helper-text">
 | 
			
		||||
                                ${msg(
 | 
			
		||||
                                    "Select which scopes can be used by the client. The client still has to specify the scope to access the data.",
 | 
			
		||||
                                )}
 | 
			
		||||
                            </p>
 | 
			
		||||
                            <ak-utils-time-delta-help></ak-utils-time-delta-help>`}
 | 
			
		||||
                    >
 | 
			
		||||
                    </ak-text-input>
 | 
			
		||||
 | 
			
		||||
                    <ak-text-input
 | 
			
		||||
                        name="refreshTokenValidity"
 | 
			
		||||
                        label=${msg("Refresh Token validity")}
 | 
			
		||||
                        value="${first(provider?.refreshTokenValidity, "days=30")}"
 | 
			
		||||
                        ?required=${true}
 | 
			
		||||
                        .bighelp=${html` <p class="pf-c-form__helper-text">
 | 
			
		||||
                                ${msg("Configure how long refresh tokens are valid for.")}
 | 
			
		||||
                            <p class="pf-c-form__helper-text">
 | 
			
		||||
                                ${msg("Hold control/command to select multiple items.")}
 | 
			
		||||
                            </p>
 | 
			
		||||
                            <ak-utils-time-delta-help></ak-utils-time-delta-help>`}
 | 
			
		||||
                    >
 | 
			
		||||
                    </ak-text-input>
 | 
			
		||||
                        </ak-form-element-horizontal>
 | 
			
		||||
 | 
			
		||||
                    <ak-form-element-horizontal label=${msg("Scopes")} name="propertyMappings">
 | 
			
		||||
                        <select class="pf-c-form-control" multiple>
 | 
			
		||||
                            ${this.propertyMappings?.results.map((scope) => {
 | 
			
		||||
                                let selected = false;
 | 
			
		||||
                                if (!provider?.propertyMappings) {
 | 
			
		||||
                                    selected =
 | 
			
		||||
                                        scope.managed?.startsWith(
 | 
			
		||||
                                            "goauthentik.io/providers/oauth2/scope-",
 | 
			
		||||
                                        ) || false;
 | 
			
		||||
                                } else {
 | 
			
		||||
                                    selected = Array.from(provider?.propertyMappings).some((su) => {
 | 
			
		||||
                                        return su == scope.pk;
 | 
			
		||||
                        <ak-radio-input
 | 
			
		||||
                            name="subMode"
 | 
			
		||||
                            label=${msg("Subject mode")}
 | 
			
		||||
                            required
 | 
			
		||||
                            .options=${subjectModeOptions}
 | 
			
		||||
                            .value=${provider?.subMode}
 | 
			
		||||
                            help=${msg(
 | 
			
		||||
                                "Configure what data should be used as unique User Identifier. For most cases, the default should be fine.",
 | 
			
		||||
                            )}
 | 
			
		||||
                        >
 | 
			
		||||
                        </ak-radio-input>
 | 
			
		||||
                        <ak-switch-input
 | 
			
		||||
                            name="includeClaimsInIdToken"
 | 
			
		||||
                            label=${msg("Include claims in id_token")}
 | 
			
		||||
                            ?checked=${first(provider?.includeClaimsInIdToken, true)}
 | 
			
		||||
                            help=${msg(
 | 
			
		||||
                                "Include User claims from scopes in the id_token, for applications that don't access the userinfo endpoint.",
 | 
			
		||||
                            )}
 | 
			
		||||
                        ></ak-switch-input>
 | 
			
		||||
                        <ak-radio-input
 | 
			
		||||
                            name="issuerMode"
 | 
			
		||||
                            label=${msg("Issuer mode")}
 | 
			
		||||
                            required
 | 
			
		||||
                            .options=${issuerModeOptions}
 | 
			
		||||
                            .value=${provider?.issuerMode}
 | 
			
		||||
                            help=${msg(
 | 
			
		||||
                                "Configure how the issuer field of the ID Token should be filled.",
 | 
			
		||||
                            )}
 | 
			
		||||
                        >
 | 
			
		||||
                        </ak-radio-input>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </ak-form-group>
 | 
			
		||||
 | 
			
		||||
                <ak-form-group>
 | 
			
		||||
                    <span slot="header">${msg("Machine-to-Machine authentication settings")}</span>
 | 
			
		||||
                    <div slot="body" class="pf-c-form">
 | 
			
		||||
                        <ak-form-element-horizontal
 | 
			
		||||
                            label=${msg("Trusted OIDC Sources")}
 | 
			
		||||
                            name="jwksSources"
 | 
			
		||||
                            .errorMessages=${errors?.jwksSources ?? []}
 | 
			
		||||
                        >
 | 
			
		||||
                            <select class="pf-c-form-control" multiple>
 | 
			
		||||
                                ${this.oauthSources?.results.map((source) => {
 | 
			
		||||
                                    const selected = (provider?.jwksSources || []).some((su) => {
 | 
			
		||||
                                        return su == source.pk;
 | 
			
		||||
                                    });
 | 
			
		||||
                                }
 | 
			
		||||
                                return html`<option
 | 
			
		||||
                                    value=${ifDefined(scope.pk)}
 | 
			
		||||
                                    ?selected=${selected}
 | 
			
		||||
                                >
 | 
			
		||||
                                    ${scope.name}
 | 
			
		||||
                                </option>`;
 | 
			
		||||
                            })}
 | 
			
		||||
                        </select>
 | 
			
		||||
                        <p class="pf-c-form__helper-text">
 | 
			
		||||
                            ${msg(
 | 
			
		||||
                                "Select which scopes can be used by the client. The client still has to specify the scope to access the data.",
 | 
			
		||||
                            )}
 | 
			
		||||
                        </p>
 | 
			
		||||
                        <p class="pf-c-form__helper-text">
 | 
			
		||||
                            ${msg("Hold control/command to select multiple items.")}
 | 
			
		||||
                        </p>
 | 
			
		||||
                    </ak-form-element-horizontal>
 | 
			
		||||
 | 
			
		||||
                    <ak-radio-input
 | 
			
		||||
                        name="subMode"
 | 
			
		||||
                        label=${msg("Subject mode")}
 | 
			
		||||
                        required
 | 
			
		||||
                        .options=${subjectModeOptions}
 | 
			
		||||
                        .value=${provider?.subMode}
 | 
			
		||||
                        help=${msg(
 | 
			
		||||
                            "Configure what data should be used as unique User Identifier. For most cases, the default should be fine.",
 | 
			
		||||
                        )}
 | 
			
		||||
                    >
 | 
			
		||||
                    </ak-radio-input>
 | 
			
		||||
                    <ak-switch-input
 | 
			
		||||
                        name="includeClaimsInIdToken"
 | 
			
		||||
                        label=${msg("Include claims in id_token")}
 | 
			
		||||
                        ?checked=${first(provider?.includeClaimsInIdToken, true)}
 | 
			
		||||
                        help=${msg(
 | 
			
		||||
                            "Include User claims from scopes in the id_token, for applications that don't access the userinfo endpoint.",
 | 
			
		||||
                        )}
 | 
			
		||||
                    ></ak-switch-input>
 | 
			
		||||
                    <ak-radio-input
 | 
			
		||||
                        name="issuerMode"
 | 
			
		||||
                        label=${msg("Issuer mode")}
 | 
			
		||||
                        required
 | 
			
		||||
                        .options=${issuerModeOptions}
 | 
			
		||||
                        .value=${provider?.issuerMode}
 | 
			
		||||
                        help=${msg(
 | 
			
		||||
                            "Configure how the issuer field of the ID Token should be filled.",
 | 
			
		||||
                        )}
 | 
			
		||||
                    >
 | 
			
		||||
                    </ak-radio-input>
 | 
			
		||||
                </div>
 | 
			
		||||
            </ak-form-group>
 | 
			
		||||
 | 
			
		||||
            <ak-form-group>
 | 
			
		||||
                <span slot="header">${msg("Machine-to-Machine authentication settings")}</span>
 | 
			
		||||
                <div slot="body" class="pf-c-form">
 | 
			
		||||
                    <ak-form-element-horizontal
 | 
			
		||||
                        label=${msg("Trusted OIDC Sources")}
 | 
			
		||||
                        name="jwksSources"
 | 
			
		||||
                    >
 | 
			
		||||
                        <select class="pf-c-form-control" multiple>
 | 
			
		||||
                            ${this.oauthSources?.results.map((source) => {
 | 
			
		||||
                                const selected = (provider?.jwksSources || []).some((su) => {
 | 
			
		||||
                                    return su == source.pk;
 | 
			
		||||
                                });
 | 
			
		||||
                                return html`<option value=${source.pk} ?selected=${selected}>
 | 
			
		||||
                                    ${source.name} (${source.slug})
 | 
			
		||||
                                </option>`;
 | 
			
		||||
                            })}
 | 
			
		||||
                        </select>
 | 
			
		||||
                        <p class="pf-c-form__helper-text">
 | 
			
		||||
                            ${msg(
 | 
			
		||||
                                "JWTs signed by certificates configured in the selected sources can be used to authenticate to this provider.",
 | 
			
		||||
                            )}
 | 
			
		||||
                        </p>
 | 
			
		||||
                        <p class="pf-c-form__helper-text">
 | 
			
		||||
                            ${msg("Hold control/command to select multiple items.")}
 | 
			
		||||
                        </p>
 | 
			
		||||
                    </ak-form-element-horizontal>
 | 
			
		||||
                </div>
 | 
			
		||||
            </ak-form-group>
 | 
			
		||||
        </form>`;
 | 
			
		||||
                                    return html`<option value=${source.pk} ?selected=${selected}>
 | 
			
		||||
                                        ${source.name} (${source.slug})
 | 
			
		||||
                                    </option>`;
 | 
			
		||||
                                })}
 | 
			
		||||
                            </select>
 | 
			
		||||
                            <p class="pf-c-form__helper-text">
 | 
			
		||||
                                ${msg(
 | 
			
		||||
                                    "JWTs signed by certificates configured in the selected sources can be used to authenticate to this provider.",
 | 
			
		||||
                                )}
 | 
			
		||||
                            </p>
 | 
			
		||||
                            <p class="pf-c-form__helper-text">
 | 
			
		||||
                                ${msg("Hold control/command to select multiple items.")}
 | 
			
		||||
                            </p>
 | 
			
		||||
                        </ak-form-element-horizontal>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </ak-form-group>
 | 
			
		||||
            </form>`;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,3 +1,4 @@
 | 
			
		||||
import "@goauthentik/admin/applications/wizard/ak-wizard-title";
 | 
			
		||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
 | 
			
		||||
import { first } from "@goauthentik/common/utils";
 | 
			
		||||
import "@goauthentik/components/ak-switch-input";
 | 
			
		||||
@ -61,11 +62,11 @@ export class AkTypeProxyApplicationWizardPage extends BaseProviderPanel {
 | 
			
		||||
        return nothing;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    renderProxyMode() {
 | 
			
		||||
        return html`<h2>This space intentionally left blank</h2>`;
 | 
			
		||||
    renderProxyMode(): TemplateResult {
 | 
			
		||||
        throw new Error("Must be implemented in a child class.");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    renderHttpBasic(): TemplateResult {
 | 
			
		||||
    renderHttpBasic() {
 | 
			
		||||
        return html`<ak-text-input
 | 
			
		||||
                name="basicAuthUserAttribute"
 | 
			
		||||
                label=${msg("HTTP-Basic Username Key")}
 | 
			
		||||
@ -87,168 +88,194 @@ export class AkTypeProxyApplicationWizardPage extends BaseProviderPanel {
 | 
			
		||||
            </ak-text-input>`;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    scopeMappingConfiguration(provider?: ProxyProvider) {
 | 
			
		||||
        const propertyMappings = this.propertyMappings?.results ?? [];
 | 
			
		||||
 | 
			
		||||
        const defaultScopes = () =>
 | 
			
		||||
            propertyMappings
 | 
			
		||||
                .filter((scope) => !(scope?.managed ?? "").startsWith("goauthentik.io/providers"))
 | 
			
		||||
                .map((pm) => pm.pk);
 | 
			
		||||
 | 
			
		||||
        const configuredScopes = (providerMappings: string[]) =>
 | 
			
		||||
            propertyMappings.map((scope) => scope.pk).filter((pk) => providerMappings.includes(pk));
 | 
			
		||||
 | 
			
		||||
        const scopeValues = provider?.propertyMappings
 | 
			
		||||
            ? configuredScopes(provider?.propertyMappings ?? [])
 | 
			
		||||
            : defaultScopes();
 | 
			
		||||
 | 
			
		||||
        const scopePairs = propertyMappings.map((scope) => [scope.pk, scope.name]);
 | 
			
		||||
 | 
			
		||||
        return { scopePairs, scopeValues };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    render() {
 | 
			
		||||
        return html`<form class="pf-c-form pf-m-horizontal" @input=${this.handleChange}>
 | 
			
		||||
            ${this.renderModeDescription()}
 | 
			
		||||
            <ak-text-input
 | 
			
		||||
                name="name"
 | 
			
		||||
                value=${ifDefined(this.instance?.name)}
 | 
			
		||||
                required
 | 
			
		||||
                label=${msg("Name")}
 | 
			
		||||
            ></ak-text-input>
 | 
			
		||||
        const errors = this.wizard.errors.provider;
 | 
			
		||||
        const { scopePairs, scopeValues } = this.scopeMappingConfiguration(this.instance);
 | 
			
		||||
 | 
			
		||||
            <ak-form-element-horizontal
 | 
			
		||||
                label=${msg("Authentication flow")}
 | 
			
		||||
                ?required=${false}
 | 
			
		||||
                name="authenticationFlow"
 | 
			
		||||
            >
 | 
			
		||||
                <ak-flow-search
 | 
			
		||||
                    flowType=${FlowsInstancesListDesignationEnum.Authentication}
 | 
			
		||||
                    .currentFlow=${this.instance?.authenticationFlow}
 | 
			
		||||
        return html` <ak-wizard-title>${msg("Configure Proxy Provider")}</ak-wizard-title>
 | 
			
		||||
            <form class="pf-c-form pf-m-horizontal" @input=${this.handleChange}>
 | 
			
		||||
                ${this.renderModeDescription()}
 | 
			
		||||
                <ak-text-input
 | 
			
		||||
                    name="name"
 | 
			
		||||
                    value=${ifDefined(this.instance?.name)}
 | 
			
		||||
                    required
 | 
			
		||||
                ></ak-flow-search>
 | 
			
		||||
                <p class="pf-c-form__helper-text">
 | 
			
		||||
                    ${msg("Flow used when a user access this provider and is not authenticated.")}
 | 
			
		||||
                </p>
 | 
			
		||||
            </ak-form-element-horizontal>
 | 
			
		||||
                    .errorMessages=${errors?.name ?? []}
 | 
			
		||||
                    label=${msg("Name")}
 | 
			
		||||
                ></ak-text-input>
 | 
			
		||||
 | 
			
		||||
            <ak-form-element-horizontal
 | 
			
		||||
                label=${msg("Authorization flow")}
 | 
			
		||||
                ?required=${true}
 | 
			
		||||
                name="authorizationFlow"
 | 
			
		||||
            >
 | 
			
		||||
                <ak-flow-search
 | 
			
		||||
                    flowType=${FlowsInstancesListDesignationEnum.Authorization}
 | 
			
		||||
                    .currentFlow=${this.instance?.authorizationFlow}
 | 
			
		||||
                    required
 | 
			
		||||
                ></ak-flow-search>
 | 
			
		||||
                <p class="pf-c-form__helper-text">
 | 
			
		||||
                    ${msg("Flow used when authorizing this provider.")}
 | 
			
		||||
                </p>
 | 
			
		||||
            </ak-form-element-horizontal>
 | 
			
		||||
                <ak-form-element-horizontal
 | 
			
		||||
                    label=${msg("Authentication flow")}
 | 
			
		||||
                    ?required=${false}
 | 
			
		||||
                    .errorMessages=${errors?.authenticationFlow ?? []}
 | 
			
		||||
                    name="authenticationFlow"
 | 
			
		||||
                >
 | 
			
		||||
                    <ak-flow-search
 | 
			
		||||
                        flowType=${FlowsInstancesListDesignationEnum.Authentication}
 | 
			
		||||
                        .currentFlow=${this.instance?.authenticationFlow}
 | 
			
		||||
                        required
 | 
			
		||||
                    ></ak-flow-search>
 | 
			
		||||
                    <p class="pf-c-form__helper-text">
 | 
			
		||||
                        ${msg(
 | 
			
		||||
                            "Flow used when a user access this provider and is not authenticated.",
 | 
			
		||||
                        )}
 | 
			
		||||
                    </p>
 | 
			
		||||
                </ak-form-element-horizontal>
 | 
			
		||||
 | 
			
		||||
            ${this.renderProxyMode()}
 | 
			
		||||
                <ak-form-element-horizontal
 | 
			
		||||
                    label=${msg("Authorization flow")}
 | 
			
		||||
                    ?required=${true}
 | 
			
		||||
                    name="authorizationFlow"
 | 
			
		||||
                    .errorMessages=${errors?.authorizationFlow ?? []}
 | 
			
		||||
                >
 | 
			
		||||
                    <ak-flow-search
 | 
			
		||||
                        flowType=${FlowsInstancesListDesignationEnum.Authorization}
 | 
			
		||||
                        .currentFlow=${this.instance?.authorizationFlow}
 | 
			
		||||
                        required
 | 
			
		||||
                    ></ak-flow-search>
 | 
			
		||||
                    <p class="pf-c-form__helper-text">
 | 
			
		||||
                        ${msg("Flow used when authorizing this provider.")}
 | 
			
		||||
                    </p>
 | 
			
		||||
                </ak-form-element-horizontal>
 | 
			
		||||
 | 
			
		||||
            <ak-text-input
 | 
			
		||||
                name="accessTokenValidity"
 | 
			
		||||
                value=${first(this.instance?.accessTokenValidity, "hours=24")}
 | 
			
		||||
                label=${msg("Token validity")}
 | 
			
		||||
                help=${msg("Configure how long tokens are valid for.")}
 | 
			
		||||
            ></ak-text-input>
 | 
			
		||||
                ${this.renderProxyMode()}
 | 
			
		||||
 | 
			
		||||
            <ak-form-group>
 | 
			
		||||
                <span slot="header">${msg("Advanced protocol settings")}</span>
 | 
			
		||||
                <div slot="body" class="pf-c-form">
 | 
			
		||||
                    <ak-form-element-horizontal label=${msg("Certificate")} name="certificate">
 | 
			
		||||
                        <ak-crypto-certificate-search
 | 
			
		||||
                            certificate=${ifDefined(this.instance?.certificate ?? undefined)}
 | 
			
		||||
                        ></ak-crypto-certificate-search>
 | 
			
		||||
                    </ak-form-element-horizontal>
 | 
			
		||||
                <ak-text-input
 | 
			
		||||
                    name="accessTokenValidity"
 | 
			
		||||
                    value=${first(this.instance?.accessTokenValidity, "hours=24")}
 | 
			
		||||
                    label=${msg("Token validity")}
 | 
			
		||||
                    help=${msg("Configure how long tokens are valid for.")}
 | 
			
		||||
                    .errorMessages=${errors?.accessTokenValidity ?? []}
 | 
			
		||||
                ></ak-text-input>
 | 
			
		||||
 | 
			
		||||
                    <ak-form-element-horizontal
 | 
			
		||||
                        label=${msg("Additional scopes")}
 | 
			
		||||
                        name="propertyMappings"
 | 
			
		||||
                    >
 | 
			
		||||
                        <select class="pf-c-form-control" multiple>
 | 
			
		||||
                            ${this.propertyMappings?.results
 | 
			
		||||
                                .filter((scope) => {
 | 
			
		||||
                                    return !scope.managed?.startsWith("goauthentik.io/providers");
 | 
			
		||||
                                })
 | 
			
		||||
                                .map((scope) => {
 | 
			
		||||
                                    const selected = (this.instance?.propertyMappings || []).some(
 | 
			
		||||
                <ak-form-group>
 | 
			
		||||
                    <span slot="header">${msg("Advanced protocol settings")}</span>
 | 
			
		||||
                    <div slot="body" class="pf-c-form">
 | 
			
		||||
                        <ak-form-element-horizontal
 | 
			
		||||
                            label=${msg("Certificate")}
 | 
			
		||||
                            name="certificate"
 | 
			
		||||
                            .errorMessages=${errors?.certificate ?? []}
 | 
			
		||||
                        >
 | 
			
		||||
                            <ak-crypto-certificate-search
 | 
			
		||||
                                certificate=${ifDefined(this.instance?.certificate ?? undefined)}
 | 
			
		||||
                            ></ak-crypto-certificate-search>
 | 
			
		||||
                        </ak-form-element-horizontal>
 | 
			
		||||
 | 
			
		||||
                        <ak-multi-select
 | 
			
		||||
                            label=${msg("AdditionalScopes")}
 | 
			
		||||
                            name="propertyMappings"
 | 
			
		||||
                            required
 | 
			
		||||
                            .options=${scopePairs}
 | 
			
		||||
                            .values=${scopeValues}
 | 
			
		||||
                            .errorMessages=${errors?.propertyMappings ?? []}
 | 
			
		||||
                            .richhelp=${html`
 | 
			
		||||
                                <p class="pf-c-form__helper-text">
 | 
			
		||||
                                    ${msg(
 | 
			
		||||
                                        "Additional scope mappings, which are passed to the proxy.",
 | 
			
		||||
                                    )}
 | 
			
		||||
                                </p>
 | 
			
		||||
                                <p class="pf-c-form__helper-text">
 | 
			
		||||
                                    ${msg("Hold control/command to select multiple items.")}
 | 
			
		||||
                                </p>
 | 
			
		||||
                            `}
 | 
			
		||||
                        ></ak-multi-select>
 | 
			
		||||
 | 
			
		||||
                        <ak-textarea-input
 | 
			
		||||
                            name="skipPathRegex"
 | 
			
		||||
                            label=${this.mode === ProxyMode.ForwardDomain
 | 
			
		||||
                                ? msg("Unauthenticated URLs")
 | 
			
		||||
                                : msg("Unauthenticated Paths")}
 | 
			
		||||
                            value=${ifDefined(this.instance?.skipPathRegex)}
 | 
			
		||||
                            .errorMessages=${errors?.skipPathRegex ?? []}
 | 
			
		||||
                            .bighelp=${html` <p class="pf-c-form__helper-text">
 | 
			
		||||
                                    ${msg(
 | 
			
		||||
                                        "Regular expressions for which authentication is not required. Each new line is interpreted as a new expression.",
 | 
			
		||||
                                    )}
 | 
			
		||||
                                </p>
 | 
			
		||||
                                <p class="pf-c-form__helper-text">
 | 
			
		||||
                                    ${msg(
 | 
			
		||||
                                        "When using proxy or forward auth (single application) mode, the requested URL Path is checked against the regular expressions. When using forward auth (domain mode), the full requested URL including scheme and host is matched against the regular expressions.",
 | 
			
		||||
                                    )}
 | 
			
		||||
                                </p>`}
 | 
			
		||||
                        >
 | 
			
		||||
                        </ak-textarea-input>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </ak-form-group>
 | 
			
		||||
                <ak-form-group>
 | 
			
		||||
                    <span slot="header">${msg("Authentication settings")}</span>
 | 
			
		||||
                    <div slot="body" class="pf-c-form">
 | 
			
		||||
                        <ak-switch-input
 | 
			
		||||
                            name="interceptHeaderAuth"
 | 
			
		||||
                            ?checked=${first(this.instance?.interceptHeaderAuth, true)}
 | 
			
		||||
                            label=${msg("Intercept header authentication")}
 | 
			
		||||
                            help=${msg(
 | 
			
		||||
                                "When enabled, authentik will intercept the Authorization header to authenticate the request.",
 | 
			
		||||
                            )}
 | 
			
		||||
                        ></ak-switch-input>
 | 
			
		||||
 | 
			
		||||
                        <ak-switch-input
 | 
			
		||||
                            name="basicAuthEnabled"
 | 
			
		||||
                            ?checked=${first(this.instance?.basicAuthEnabled, false)}
 | 
			
		||||
                            @change=${(ev: Event) => {
 | 
			
		||||
                                const el = ev.target as HTMLInputElement;
 | 
			
		||||
                                this.showHttpBasic = el.checked;
 | 
			
		||||
                            }}
 | 
			
		||||
                            label=${msg("Send HTTP-Basic Authentication")}
 | 
			
		||||
                            help=${msg(
 | 
			
		||||
                                "Send a custom HTTP-Basic Authentication header based on values from authentik.",
 | 
			
		||||
                            )}
 | 
			
		||||
                        ></ak-switch-input>
 | 
			
		||||
 | 
			
		||||
                        ${this.showHttpBasic ? this.renderHttpBasic() : html``}
 | 
			
		||||
 | 
			
		||||
                        <ak-form-element-horizontal
 | 
			
		||||
                            label=${msg("Trusted OIDC Sources")}
 | 
			
		||||
                            name="jwksSources"
 | 
			
		||||
                            .errorMessages=${errors?.jwksSources ?? []}
 | 
			
		||||
                        >
 | 
			
		||||
                            <select class="pf-c-form-control" multiple>
 | 
			
		||||
                                ${this.oauthSources?.results.map((source) => {
 | 
			
		||||
                                    const selected = (this.instance?.jwksSources || []).some(
 | 
			
		||||
                                        (su) => {
 | 
			
		||||
                                            return su == scope.pk;
 | 
			
		||||
                                            return su == source.pk;
 | 
			
		||||
                                        },
 | 
			
		||||
                                    );
 | 
			
		||||
                                    return html`<option
 | 
			
		||||
                                        value=${ifDefined(scope.pk)}
 | 
			
		||||
                                        ?selected=${selected}
 | 
			
		||||
                                    >
 | 
			
		||||
                                        ${scope.name}
 | 
			
		||||
                                    return html`<option value=${source.pk} ?selected=${selected}>
 | 
			
		||||
                                        ${source.name} (${source.slug})
 | 
			
		||||
                                    </option>`;
 | 
			
		||||
                                })}
 | 
			
		||||
                        </select>
 | 
			
		||||
                        <p class="pf-c-form__helper-text">
 | 
			
		||||
                            ${msg("Additional scope mappings, which are passed to the proxy.")}
 | 
			
		||||
                        </p>
 | 
			
		||||
                        <p class="pf-c-form__helper-text">
 | 
			
		||||
                            ${msg("Hold control/command to select multiple items.")}
 | 
			
		||||
                        </p>
 | 
			
		||||
                    </ak-form-element-horizontal>
 | 
			
		||||
 | 
			
		||||
                    <ak-textarea-input
 | 
			
		||||
                        name="skipPathRegex"
 | 
			
		||||
                        label=${this.mode === ProxyMode.ForwardDomain
 | 
			
		||||
                            ? msg("Unauthenticated URLs")
 | 
			
		||||
                            : msg("Unauthenticated Paths")}
 | 
			
		||||
                        value=${ifDefined(this.instance?.skipPathRegex)}
 | 
			
		||||
                        .bighelp=${html` <p class="pf-c-form__helper-text">
 | 
			
		||||
                            </select>
 | 
			
		||||
                            <p class="pf-c-form__helper-text">
 | 
			
		||||
                                ${msg(
 | 
			
		||||
                                    "Regular expressions for which authentication is not required. Each new line is interpreted as a new expression.",
 | 
			
		||||
                                    "JWTs signed by certificates configured in the selected sources can be used to authenticate to this provider.",
 | 
			
		||||
                                )}
 | 
			
		||||
                            </p>
 | 
			
		||||
                            <p class="pf-c-form__helper-text">
 | 
			
		||||
                                ${msg(
 | 
			
		||||
                                    "When using proxy or forward auth (single application) mode, the requested URL Path is checked against the regular expressions. When using forward auth (domain mode), the full requested URL including scheme and host is matched against the regular expressions.",
 | 
			
		||||
                                )}
 | 
			
		||||
                            </p>`}
 | 
			
		||||
                    >
 | 
			
		||||
                    </ak-textarea-input>
 | 
			
		||||
                </div>
 | 
			
		||||
            </ak-form-group>
 | 
			
		||||
            <ak-form-group>
 | 
			
		||||
                <span slot="header">${msg("Authentication settings")}</span>
 | 
			
		||||
                <div slot="body" class="pf-c-form">
 | 
			
		||||
                    <ak-switch-input
 | 
			
		||||
                        name="interceptHeaderAuth"
 | 
			
		||||
                        ?checked=${first(this.instance?.interceptHeaderAuth, true)}
 | 
			
		||||
                        label=${msg("Intercept header authentication")}
 | 
			
		||||
                        help=${msg(
 | 
			
		||||
                            "When enabled, authentik will intercept the Authorization header to authenticate the request.",
 | 
			
		||||
                        )}
 | 
			
		||||
                    ></ak-switch-input>
 | 
			
		||||
 | 
			
		||||
                    <ak-switch-input
 | 
			
		||||
                        name="basicAuthEnabled"
 | 
			
		||||
                        ?checked=${first(this.instance?.basicAuthEnabled, false)}
 | 
			
		||||
                        @change=${(ev: Event) => {
 | 
			
		||||
                            const el = ev.target as HTMLInputElement;
 | 
			
		||||
                            this.showHttpBasic = el.checked;
 | 
			
		||||
                        }}
 | 
			
		||||
                        label=${msg("Send HTTP-Basic Authentication")}
 | 
			
		||||
                        help=${msg(
 | 
			
		||||
                            "Send a custom HTTP-Basic Authentication header based on values from authentik.",
 | 
			
		||||
                        )}
 | 
			
		||||
                    ></ak-switch-input>
 | 
			
		||||
 | 
			
		||||
                    ${this.showHttpBasic ? this.renderHttpBasic() : html``}
 | 
			
		||||
 | 
			
		||||
                    <ak-form-element-horizontal
 | 
			
		||||
                        label=${msg("Trusted OIDC Sources")}
 | 
			
		||||
                        name="jwksSources"
 | 
			
		||||
                    >
 | 
			
		||||
                        <select class="pf-c-form-control" multiple>
 | 
			
		||||
                            ${this.oauthSources?.results.map((source) => {
 | 
			
		||||
                                const selected = (this.instance?.jwksSources || []).some((su) => {
 | 
			
		||||
                                    return su == source.pk;
 | 
			
		||||
                                });
 | 
			
		||||
                                return html`<option value=${source.pk} ?selected=${selected}>
 | 
			
		||||
                                    ${source.name} (${source.slug})
 | 
			
		||||
                                </option>`;
 | 
			
		||||
                            })}
 | 
			
		||||
                        </select>
 | 
			
		||||
                        <p class="pf-c-form__helper-text">
 | 
			
		||||
                            ${msg(
 | 
			
		||||
                                "JWTs signed by certificates configured in the selected sources can be used to authenticate to this provider.",
 | 
			
		||||
                            )}
 | 
			
		||||
                        </p>
 | 
			
		||||
                        <p class="pf-c-form__helper-text">
 | 
			
		||||
                            ${msg("Hold control/command to select multiple items.")}
 | 
			
		||||
                        </p>
 | 
			
		||||
                    </ak-form-element-horizontal>
 | 
			
		||||
                </div>
 | 
			
		||||
            </ak-form-group>
 | 
			
		||||
        </form>`;
 | 
			
		||||
                                ${msg("Hold control/command to select multiple items.")}
 | 
			
		||||
                            </p>
 | 
			
		||||
                        </ak-form-element-horizontal>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </ak-form-group>
 | 
			
		||||
            </form>`;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -5,6 +5,8 @@ import { customElement } from "@lit/reactive-element/decorators.js";
 | 
			
		||||
import { html } from "lit";
 | 
			
		||||
import { ifDefined } from "lit/directives/if-defined.js";
 | 
			
		||||
 | 
			
		||||
import { ProxyProvider } from "@goauthentik/api";
 | 
			
		||||
 | 
			
		||||
import AkTypeProxyApplicationWizardPage from "./AuthenticationByProxyPage";
 | 
			
		||||
 | 
			
		||||
@customElement("ak-application-wizard-authentication-for-forward-proxy-domain")
 | 
			
		||||
@ -28,11 +30,15 @@ export class AkForwardDomainProxyApplicationWizardPage extends AkTypeProxyApplic
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    renderProxyMode() {
 | 
			
		||||
        const provider = this.wizard.provider as ProxyProvider | undefined;
 | 
			
		||||
        const errors = this.wizard.errors.provider;
 | 
			
		||||
 | 
			
		||||
        return html`
 | 
			
		||||
            <ak-text-input
 | 
			
		||||
                name="externalHost"
 | 
			
		||||
                label=${msg("External host")}
 | 
			
		||||
                value=${ifDefined(this.instance?.externalHost)}
 | 
			
		||||
                value=${ifDefined(provider?.externalHost)}
 | 
			
		||||
                .errorMessages=${errors?.externalHost ?? []}
 | 
			
		||||
                required
 | 
			
		||||
                help=${msg(
 | 
			
		||||
                    "The external URL you'll authenticate at. The authentik core server should be reachable under this URL.",
 | 
			
		||||
@ -42,7 +48,8 @@ export class AkForwardDomainProxyApplicationWizardPage extends AkTypeProxyApplic
 | 
			
		||||
            <ak-text-input
 | 
			
		||||
                name="cookieDomain"
 | 
			
		||||
                label=${msg("Cookie domain")}
 | 
			
		||||
                value="${ifDefined(this.instance?.cookieDomain)}"
 | 
			
		||||
                value="${ifDefined(provider?.cookieDomain)}"
 | 
			
		||||
                .errorMessages=${errors?.cookieDomain ?? []}
 | 
			
		||||
                required
 | 
			
		||||
                help=${msg(
 | 
			
		||||
                    "Set this to the domain you wish the authentication to be valid for. Must be a parent domain of the URL above. If you're running applications as app1.domain.tld, app2.domain.tld, set this to 'domain.tld'.",
 | 
			
		||||
 | 
			
		||||
@ -7,6 +7,8 @@ import { customElement } from "@lit/reactive-element/decorators.js";
 | 
			
		||||
import { html } from "lit";
 | 
			
		||||
import { ifDefined } from "lit/directives/if-defined.js";
 | 
			
		||||
 | 
			
		||||
import { ProxyProvider } from "@goauthentik/api";
 | 
			
		||||
 | 
			
		||||
import AkTypeProxyApplicationWizardPage from "./AuthenticationByProxyPage";
 | 
			
		||||
 | 
			
		||||
@customElement("ak-application-wizard-authentication-for-reverse-proxy")
 | 
			
		||||
@ -20,25 +22,30 @@ export class AkReverseProxyApplicationWizardPage extends AkTypeProxyApplicationW
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    renderProxyMode() {
 | 
			
		||||
        const provider = this.wizard.provider as ProxyProvider | undefined;
 | 
			
		||||
        const errors = this.wizard.errors.provider;
 | 
			
		||||
 | 
			
		||||
        return html` <ak-text-input
 | 
			
		||||
                name="externalHost"
 | 
			
		||||
                value=${ifDefined(this.instance?.externalHost)}
 | 
			
		||||
                value=${ifDefined(provider?.externalHost)}
 | 
			
		||||
                required
 | 
			
		||||
                label=${msg("External host")}
 | 
			
		||||
                .errorMessages=${errors?.externalHost ?? []}
 | 
			
		||||
                help=${msg(
 | 
			
		||||
                    "The external URL you'll access the application at. Include any non-standard port.",
 | 
			
		||||
                )}
 | 
			
		||||
            ></ak-text-input>
 | 
			
		||||
            <ak-text-input
 | 
			
		||||
                name="internalHost"
 | 
			
		||||
                value=${ifDefined(this.instance?.internalHost)}
 | 
			
		||||
                value=${ifDefined(provider?.internalHost)}
 | 
			
		||||
                .errorMessages=${errors?.internalHost ?? []}
 | 
			
		||||
                required
 | 
			
		||||
                label=${msg("Internal host")}
 | 
			
		||||
                help=${msg("Upstream host that the requests are forwarded to.")}
 | 
			
		||||
            ></ak-text-input>
 | 
			
		||||
            <ak-switch-input
 | 
			
		||||
                name="internalHostSslValidation"
 | 
			
		||||
                ?checked=${first(this.instance?.internalHostSslValidation, true)}
 | 
			
		||||
                ?checked=${first(provider?.internalHostSslValidation, true)}
 | 
			
		||||
                label=${msg("Internal host SSL Validation")}
 | 
			
		||||
                help=${msg("Validate SSL Certificates of upstream servers.")}
 | 
			
		||||
            >
 | 
			
		||||
 | 
			
		||||
@ -5,6 +5,8 @@ import { customElement } from "@lit/reactive-element/decorators.js";
 | 
			
		||||
import { html } from "lit";
 | 
			
		||||
import { ifDefined } from "lit/directives/if-defined.js";
 | 
			
		||||
 | 
			
		||||
import { ProxyProvider } from "@goauthentik/api";
 | 
			
		||||
 | 
			
		||||
import AkTypeProxyApplicationWizardPage from "./AuthenticationByProxyPage";
 | 
			
		||||
 | 
			
		||||
@customElement("ak-application-wizard-authentication-for-single-forward-proxy")
 | 
			
		||||
@ -21,11 +23,15 @@ export class AkForwardSingleProxyApplicationWizardPage extends AkTypeProxyApplic
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    renderProxyMode() {
 | 
			
		||||
        const provider = this.wizard.provider as ProxyProvider | undefined;
 | 
			
		||||
        const errors = this.wizard.errors.provider;
 | 
			
		||||
 | 
			
		||||
        return html`<ak-text-input
 | 
			
		||||
            name="externalHost"
 | 
			
		||||
            value=${ifDefined(this.instance?.externalHost)}
 | 
			
		||||
            value=${ifDefined(provider?.externalHost)}
 | 
			
		||||
            required
 | 
			
		||||
            label=${msg("External host")}
 | 
			
		||||
            .errorMessages=${errors?.externalHost ?? []}
 | 
			
		||||
            help=${msg(
 | 
			
		||||
                "The external URL you'll access the application at. Include any non-standard port.",
 | 
			
		||||
            )}
 | 
			
		||||
 | 
			
		||||
@ -1,3 +1,4 @@
 | 
			
		||||
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 { ascii_letters, digits, first, randomString } from "@goauthentik/common/utils";
 | 
			
		||||
@ -19,54 +20,62 @@ import BaseProviderPanel from "../BaseProviderPanel";
 | 
			
		||||
export class ApplicationWizardAuthenticationByRadius extends BaseProviderPanel {
 | 
			
		||||
    render() {
 | 
			
		||||
        const provider = this.wizard.provider as RadiusProvider | undefined;
 | 
			
		||||
        const errors = this.wizard.errors.provider;
 | 
			
		||||
 | 
			
		||||
        return html`<form class="pf-c-form pf-m-horizontal" @input=${this.handleChange}>
 | 
			
		||||
            <ak-text-input
 | 
			
		||||
                name="name"
 | 
			
		||||
                label=${msg("Name")}
 | 
			
		||||
                value=${ifDefined(provider?.name)}
 | 
			
		||||
                required
 | 
			
		||||
            >
 | 
			
		||||
            </ak-text-input>
 | 
			
		||||
 | 
			
		||||
            <ak-form-element-horizontal
 | 
			
		||||
                label=${msg("Authentication flow")}
 | 
			
		||||
                ?required=${true}
 | 
			
		||||
                name="authorizationFlow"
 | 
			
		||||
            >
 | 
			
		||||
                <ak-tenanted-flow-search
 | 
			
		||||
                    flowType=${FlowsInstancesListDesignationEnum.Authentication}
 | 
			
		||||
                    .currentFlow=${provider?.authorizationFlow}
 | 
			
		||||
                    .tenantFlow=${rootInterface()?.tenant?.flowAuthentication}
 | 
			
		||||
        return html`<ak-wizard-title>${msg("Configure Radius Provider")}</ak-wizard-title>
 | 
			
		||||
            <form class="pf-c-form pf-m-horizontal" @input=${this.handleChange}>
 | 
			
		||||
                <ak-text-input
 | 
			
		||||
                    name="name"
 | 
			
		||||
                    label=${msg("Name")}
 | 
			
		||||
                    value=${ifDefined(provider?.name)}
 | 
			
		||||
                    .errorMessages=${errors?.name ?? []}
 | 
			
		||||
                    required
 | 
			
		||||
                ></ak-tenanted-flow-search>
 | 
			
		||||
                <p class="pf-c-form__helper-text">${msg("Flow used for users to authenticate.")}</p>
 | 
			
		||||
            </ak-form-element-horizontal>
 | 
			
		||||
                >
 | 
			
		||||
                </ak-text-input>
 | 
			
		||||
 | 
			
		||||
            <ak-form-group expanded>
 | 
			
		||||
                <span slot="header"> ${msg("Protocol settings")} </span>
 | 
			
		||||
                <div slot="body" class="pf-c-form">
 | 
			
		||||
                    <ak-text-input
 | 
			
		||||
                        name="sharedSecret"
 | 
			
		||||
                        label=${msg("Shared secret")}
 | 
			
		||||
                        value=${first(
 | 
			
		||||
                            provider?.sharedSecret,
 | 
			
		||||
                            randomString(128, ascii_letters + digits),
 | 
			
		||||
                        )}
 | 
			
		||||
                <ak-form-element-horizontal
 | 
			
		||||
                    label=${msg("Authentication flow")}
 | 
			
		||||
                    ?required=${true}
 | 
			
		||||
                    name="authorizationFlow"
 | 
			
		||||
                    .errorMessages=${errors?.authorizationFlow ?? []}
 | 
			
		||||
                >
 | 
			
		||||
                    <ak-tenanted-flow-search
 | 
			
		||||
                        flowType=${FlowsInstancesListDesignationEnum.Authentication}
 | 
			
		||||
                        .currentFlow=${provider?.authorizationFlow}
 | 
			
		||||
                        .tenantFlow=${rootInterface()?.tenant?.flowAuthentication}
 | 
			
		||||
                        required
 | 
			
		||||
                    ></ak-text-input>
 | 
			
		||||
                    <ak-text-input
 | 
			
		||||
                        name="clientNetworks"
 | 
			
		||||
                        label=${msg("Client Networks")}
 | 
			
		||||
                        value=${first(provider?.clientNetworks, "0.0.0.0/0, ::/0")}
 | 
			
		||||
                        required
 | 
			
		||||
                        help=${msg(`List of CIDRs (comma-seperated) that clients can connect from. A more specific
 | 
			
		||||
                    ></ak-tenanted-flow-search>
 | 
			
		||||
                    <p class="pf-c-form__helper-text">
 | 
			
		||||
                        ${msg("Flow used for users to authenticate.")}
 | 
			
		||||
                    </p>
 | 
			
		||||
                </ak-form-element-horizontal>
 | 
			
		||||
 | 
			
		||||
                <ak-form-group expanded>
 | 
			
		||||
                    <span slot="header"> ${msg("Protocol settings")} </span>
 | 
			
		||||
                    <div slot="body" class="pf-c-form">
 | 
			
		||||
                        <ak-text-input
 | 
			
		||||
                            name="sharedSecret"
 | 
			
		||||
                            label=${msg("Shared secret")}
 | 
			
		||||
                            .errorMessages=${errors?.sharedSecret ?? []}
 | 
			
		||||
                            value=${first(
 | 
			
		||||
                                provider?.sharedSecret,
 | 
			
		||||
                                randomString(128, ascii_letters + digits),
 | 
			
		||||
                            )}
 | 
			
		||||
                            required
 | 
			
		||||
                        ></ak-text-input>
 | 
			
		||||
                        <ak-text-input
 | 
			
		||||
                            name="clientNetworks"
 | 
			
		||||
                            label=${msg("Client Networks")}
 | 
			
		||||
                            value=${first(provider?.clientNetworks, "0.0.0.0/0, ::/0")}
 | 
			
		||||
                            .errorMessages=${errors?.clientNetworks ?? []}
 | 
			
		||||
                            required
 | 
			
		||||
                            help=${msg(`List of CIDRs (comma-seperated) that clients can connect from. A more specific
 | 
			
		||||
                            CIDR will match before a looser one. Clients connecting from a non-specified CIDR
 | 
			
		||||
                            will be dropped.`)}
 | 
			
		||||
                    ></ak-text-input>
 | 
			
		||||
                </div>
 | 
			
		||||
            </ak-form-group>
 | 
			
		||||
        </form>`;
 | 
			
		||||
                        ></ak-text-input>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </ak-form-group>
 | 
			
		||||
            </form>`;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,7 +1,10 @@
 | 
			
		||||
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 { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
 | 
			
		||||
import "@goauthentik/components/ak-multi-select";
 | 
			
		||||
import "@goauthentik/components/ak-number-input";
 | 
			
		||||
import "@goauthentik/components/ak-radio-input";
 | 
			
		||||
import "@goauthentik/components/ak-switch-input";
 | 
			
		||||
@ -10,7 +13,7 @@ import "@goauthentik/elements/forms/FormGroup";
 | 
			
		||||
import "@goauthentik/elements/forms/HorizontalFormElement";
 | 
			
		||||
 | 
			
		||||
import { msg } from "@lit/localize";
 | 
			
		||||
import { customElement } from "@lit/reactive-element/decorators/custom-element.js";
 | 
			
		||||
import { customElement, state } from "@lit/reactive-element/decorators.js";
 | 
			
		||||
import { html } from "lit";
 | 
			
		||||
import { ifDefined } from "lit/directives/if-defined.js";
 | 
			
		||||
 | 
			
		||||
@ -27,9 +30,11 @@ import {
 | 
			
		||||
    signatureAlgorithmOptions,
 | 
			
		||||
    spBindingOptions,
 | 
			
		||||
} from "./SamlProviderOptions";
 | 
			
		||||
import "./saml-property-mappings-search";
 | 
			
		||||
 | 
			
		||||
@customElement("ak-application-wizard-authentication-by-saml-configuration")
 | 
			
		||||
export class ApplicationWizardProviderSamlConfiguration extends BaseProviderPanel {
 | 
			
		||||
    @state()
 | 
			
		||||
    propertyMappings?: PaginatedSAMLPropertyMappingList;
 | 
			
		||||
 | 
			
		||||
    constructor() {
 | 
			
		||||
@ -43,207 +48,229 @@ export class ApplicationWizardProviderSamlConfiguration extends BaseProviderPane
 | 
			
		||||
            });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    propertyMappingConfiguration(provider?: SAMLProvider) {
 | 
			
		||||
        const propertyMappings = this.propertyMappings?.results ?? [];
 | 
			
		||||
 | 
			
		||||
        const configuredMappings = (providerMappings: string[]) =>
 | 
			
		||||
            propertyMappings.map((pm) => pm.pk).filter((pmpk) => providerMappings.includes(pmpk));
 | 
			
		||||
 | 
			
		||||
        const managedMappings = () =>
 | 
			
		||||
            propertyMappings
 | 
			
		||||
                .filter((pm) => (pm?.managed ?? "").startsWith("goauthentik.io/providers/saml"))
 | 
			
		||||
                .map((pm) => pm.pk);
 | 
			
		||||
 | 
			
		||||
        const pmValues = provider?.propertyMappings
 | 
			
		||||
            ? configuredMappings(provider?.propertyMappings ?? [])
 | 
			
		||||
            : managedMappings();
 | 
			
		||||
 | 
			
		||||
        const propertyPairs = propertyMappings.map((pm) => [pm.pk, pm.name]);
 | 
			
		||||
 | 
			
		||||
        return { pmValues, propertyPairs };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    render() {
 | 
			
		||||
        const provider = this.wizard.provider as SAMLProvider | undefined;
 | 
			
		||||
        const errors = this.wizard.errors.provider;
 | 
			
		||||
 | 
			
		||||
        return html` <form class="pf-c-form pf-m-horizontal" @input=${this.handleChange}>
 | 
			
		||||
            <ak-text-input
 | 
			
		||||
                name="name"
 | 
			
		||||
                value=${ifDefined(provider?.name)}
 | 
			
		||||
                required
 | 
			
		||||
                label=${msg("Name")}
 | 
			
		||||
            ></ak-text-input>
 | 
			
		||||
        const { pmValues, propertyPairs } = this.propertyMappingConfiguration(provider);
 | 
			
		||||
 | 
			
		||||
            <ak-form-element-horizontal
 | 
			
		||||
                label=${msg("Authentication flow")}
 | 
			
		||||
                ?required=${false}
 | 
			
		||||
                name="authenticationFlow"
 | 
			
		||||
            >
 | 
			
		||||
                <ak-flow-search
 | 
			
		||||
                    flowType=${FlowsInstancesListDesignationEnum.Authentication}
 | 
			
		||||
                    .currentFlow=${provider?.authenticationFlow}
 | 
			
		||||
        return html` <ak-wizard-title>${msg("Configure SAML Provider")}</ak-wizard-title>
 | 
			
		||||
            <form class="pf-c-form pf-m-horizontal" @input=${this.handleChange}>
 | 
			
		||||
                <ak-text-input
 | 
			
		||||
                    name="name"
 | 
			
		||||
                    value=${ifDefined(provider?.name)}
 | 
			
		||||
                    required
 | 
			
		||||
                ></ak-flow-search>
 | 
			
		||||
                <p class="pf-c-form__helper-text">
 | 
			
		||||
                    ${msg("Flow used when a user access this provider and is not authenticated.")}
 | 
			
		||||
                </p>
 | 
			
		||||
            </ak-form-element-horizontal>
 | 
			
		||||
                    label=${msg("Name")}
 | 
			
		||||
                    .errorMessages=${errors?.name ?? []}
 | 
			
		||||
                ></ak-text-input>
 | 
			
		||||
 | 
			
		||||
            <ak-form-element-horizontal
 | 
			
		||||
                label=${msg("Authorization flow")}
 | 
			
		||||
                ?required=${true}
 | 
			
		||||
                name="authorizationFlow"
 | 
			
		||||
            >
 | 
			
		||||
                <ak-flow-search
 | 
			
		||||
                    flowType=${FlowsInstancesListDesignationEnum.Authorization}
 | 
			
		||||
                    .currentFlow=${provider?.authorizationFlow}
 | 
			
		||||
                    required
 | 
			
		||||
                ></ak-flow-search>
 | 
			
		||||
                <p class="pf-c-form__helper-text">
 | 
			
		||||
                    ${msg("Flow used when authorizing this provider.")}
 | 
			
		||||
                </p>
 | 
			
		||||
            </ak-form-element-horizontal>
 | 
			
		||||
 | 
			
		||||
            <ak-form-group .expanded=${true}>
 | 
			
		||||
                <span slot="header"> ${msg("Protocol settings")} </span>
 | 
			
		||||
                <div slot="body" class="pf-c-form">
 | 
			
		||||
                    <ak-text-input
 | 
			
		||||
                        name="acsUrl"
 | 
			
		||||
                        value=${ifDefined(provider?.acsUrl)}
 | 
			
		||||
                <ak-form-element-horizontal
 | 
			
		||||
                    label=${msg("Authentication flow")}
 | 
			
		||||
                    ?required=${false}
 | 
			
		||||
                    name="authenticationFlow"
 | 
			
		||||
                    .errorMessages=${errors?.authenticationFlow ?? []}
 | 
			
		||||
                >
 | 
			
		||||
                    <ak-flow-search
 | 
			
		||||
                        flowType=${FlowsInstancesListDesignationEnum.Authentication}
 | 
			
		||||
                        .currentFlow=${provider?.authenticationFlow}
 | 
			
		||||
                        required
 | 
			
		||||
                        label=${msg("ACS URL")}
 | 
			
		||||
                    ></ak-text-input>
 | 
			
		||||
 | 
			
		||||
                    <ak-text-input
 | 
			
		||||
                        name="issuer"
 | 
			
		||||
                        value=${provider?.issuer || "authentik"}
 | 
			
		||||
                        required
 | 
			
		||||
                        label=${msg("Issuer")}
 | 
			
		||||
                        help=${msg("Also known as EntityID.")}
 | 
			
		||||
                    ></ak-text-input>
 | 
			
		||||
 | 
			
		||||
                    <ak-radio-input
 | 
			
		||||
                        name="spBinding"
 | 
			
		||||
                        label=${msg("Service Provider Binding")}
 | 
			
		||||
                        required
 | 
			
		||||
                        .options=${spBindingOptions}
 | 
			
		||||
                        .value=${provider?.spBinding}
 | 
			
		||||
                        help=${msg(
 | 
			
		||||
                            "Determines how authentik sends the response back to the Service Provider.",
 | 
			
		||||
                    ></ak-flow-search>
 | 
			
		||||
                    <p class="pf-c-form__helper-text">
 | 
			
		||||
                        ${msg(
 | 
			
		||||
                            "Flow used when a user access this provider and is not authenticated.",
 | 
			
		||||
                        )}
 | 
			
		||||
                    >
 | 
			
		||||
                    </ak-radio-input>
 | 
			
		||||
                    </p>
 | 
			
		||||
                </ak-form-element-horizontal>
 | 
			
		||||
 | 
			
		||||
                    <ak-text-input
 | 
			
		||||
                        name="audience"
 | 
			
		||||
                        value=${ifDefined(provider?.audience)}
 | 
			
		||||
                        label=${msg("Audience")}
 | 
			
		||||
                    ></ak-text-input>
 | 
			
		||||
                </div>
 | 
			
		||||
            </ak-form-group>
 | 
			
		||||
                <ak-form-element-horizontal
 | 
			
		||||
                    label=${msg("Authorization flow")}
 | 
			
		||||
                    ?required=${true}
 | 
			
		||||
                    name="authorizationFlow"
 | 
			
		||||
                    .errorMessages=${errors?.authorizationFlow ?? []}
 | 
			
		||||
                >
 | 
			
		||||
                    <ak-flow-search
 | 
			
		||||
                        flowType=${FlowsInstancesListDesignationEnum.Authorization}
 | 
			
		||||
                        .currentFlow=${provider?.authorizationFlow}
 | 
			
		||||
                        required
 | 
			
		||||
                    ></ak-flow-search>
 | 
			
		||||
                    <p class="pf-c-form__helper-text">
 | 
			
		||||
                        ${msg("Flow used when authorizing this provider.")}
 | 
			
		||||
                    </p>
 | 
			
		||||
                </ak-form-element-horizontal>
 | 
			
		||||
 | 
			
		||||
            <ak-form-group>
 | 
			
		||||
                <span slot="header"> ${msg("Advanced protocol settings")} </span>
 | 
			
		||||
                <div slot="body" class="pf-c-form">
 | 
			
		||||
                    <ak-form-element-horizontal
 | 
			
		||||
                        label=${msg("Signing Certificate")}
 | 
			
		||||
                        name="signingKp"
 | 
			
		||||
                    >
 | 
			
		||||
                        <ak-crypto-certificate-search
 | 
			
		||||
                            certificate=${ifDefined(provider?.signingKp ?? undefined)}
 | 
			
		||||
                        ></ak-crypto-certificate-search>
 | 
			
		||||
                        <p class="pf-c-form__helper-text">
 | 
			
		||||
                            ${msg(
 | 
			
		||||
                                "Certificate used to sign outgoing Responses going to the Service Provider.",
 | 
			
		||||
                <ak-form-group .expanded=${true}>
 | 
			
		||||
                    <span slot="header"> ${msg("Protocol settings")} </span>
 | 
			
		||||
                    <div slot="body" class="pf-c-form">
 | 
			
		||||
                        <ak-text-input
 | 
			
		||||
                            name="acsUrl"
 | 
			
		||||
                            value=${ifDefined(provider?.acsUrl)}
 | 
			
		||||
                            required
 | 
			
		||||
                            label=${msg("ACS URL")}
 | 
			
		||||
                            .errorMessages=${errors?.acsUrl ?? []}
 | 
			
		||||
                        ></ak-text-input>
 | 
			
		||||
 | 
			
		||||
                        <ak-text-input
 | 
			
		||||
                            name="issuer"
 | 
			
		||||
                            value=${provider?.issuer || "authentik"}
 | 
			
		||||
                            required
 | 
			
		||||
                            label=${msg("Issuer")}
 | 
			
		||||
                            help=${msg("Also known as EntityID.")}
 | 
			
		||||
                            .errorMessages=${errors?.issuer ?? []}
 | 
			
		||||
                        ></ak-text-input>
 | 
			
		||||
 | 
			
		||||
                        <ak-radio-input
 | 
			
		||||
                            name="spBinding"
 | 
			
		||||
                            label=${msg("Service Provider Binding")}
 | 
			
		||||
                            required
 | 
			
		||||
                            .options=${spBindingOptions}
 | 
			
		||||
                            .value=${provider?.spBinding}
 | 
			
		||||
                            help=${msg(
 | 
			
		||||
                                "Determines how authentik sends the response back to the Service Provider.",
 | 
			
		||||
                            )}
 | 
			
		||||
                        </p>
 | 
			
		||||
                    </ak-form-element-horizontal>
 | 
			
		||||
                        >
 | 
			
		||||
                        </ak-radio-input>
 | 
			
		||||
 | 
			
		||||
                    <ak-form-element-horizontal
 | 
			
		||||
                        label=${msg("Verification Certificate")}
 | 
			
		||||
                        name="verificationKp"
 | 
			
		||||
                    >
 | 
			
		||||
                        <ak-crypto-certificate-search
 | 
			
		||||
                            certificate=${ifDefined(provider?.verificationKp ?? undefined)}
 | 
			
		||||
                            nokey
 | 
			
		||||
                        ></ak-crypto-certificate-search>
 | 
			
		||||
                        <p class="pf-c-form__helper-text">
 | 
			
		||||
                            ${msg(
 | 
			
		||||
                                "When selected, incoming assertion's Signatures will be validated against this certificate. To allow unsigned Requests, leave on default.",
 | 
			
		||||
                            )}
 | 
			
		||||
                        </p>
 | 
			
		||||
                    </ak-form-element-horizontal>
 | 
			
		||||
                        <ak-text-input
 | 
			
		||||
                            name="audience"
 | 
			
		||||
                            value=${ifDefined(provider?.audience)}
 | 
			
		||||
                            label=${msg("Audience")}
 | 
			
		||||
                            .errorMessages=${errors?.audience ?? []}
 | 
			
		||||
                        ></ak-text-input>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </ak-form-group>
 | 
			
		||||
 | 
			
		||||
                    <ak-form-element-horizontal
 | 
			
		||||
                        label=${msg("Property mappings")}
 | 
			
		||||
                        ?required=${true}
 | 
			
		||||
                        name="propertyMappings"
 | 
			
		||||
                    >
 | 
			
		||||
                        <select class="pf-c-form-control" multiple>
 | 
			
		||||
                            ${this.propertyMappings?.results.map((mapping) => {
 | 
			
		||||
                                let selected = false;
 | 
			
		||||
                                if (!provider?.propertyMappings) {
 | 
			
		||||
                                    selected =
 | 
			
		||||
                                        mapping.managed?.startsWith(
 | 
			
		||||
                                            "goauthentik.io/providers/saml",
 | 
			
		||||
                                        ) || false;
 | 
			
		||||
                                } else {
 | 
			
		||||
                                    selected = Array.from(provider?.propertyMappings).some((su) => {
 | 
			
		||||
                                        return su == mapping.pk;
 | 
			
		||||
                                    });
 | 
			
		||||
                                }
 | 
			
		||||
                                return html`<option
 | 
			
		||||
                                    value=${ifDefined(mapping.pk)}
 | 
			
		||||
                                    ?selected=${selected}
 | 
			
		||||
                                >
 | 
			
		||||
                                    ${mapping.name}
 | 
			
		||||
                                </option>`;
 | 
			
		||||
                            })}
 | 
			
		||||
                        </select>
 | 
			
		||||
                        <p class="pf-c-form__helper-text">
 | 
			
		||||
                            ${msg("Hold control/command to select multiple items.")}
 | 
			
		||||
                        </p>
 | 
			
		||||
                    </ak-form-element-horizontal>
 | 
			
		||||
                <ak-form-group>
 | 
			
		||||
                    <span slot="header"> ${msg("Advanced protocol settings")} </span>
 | 
			
		||||
                    <div slot="body" class="pf-c-form">
 | 
			
		||||
                        <ak-form-element-horizontal
 | 
			
		||||
                            label=${msg("Signing Certificate")}
 | 
			
		||||
                            name="signingKp"
 | 
			
		||||
                        >
 | 
			
		||||
                            <ak-crypto-certificate-search
 | 
			
		||||
                                certificate=${ifDefined(provider?.signingKp ?? undefined)}
 | 
			
		||||
                            ></ak-crypto-certificate-search>
 | 
			
		||||
                            <p class="pf-c-form__helper-text">
 | 
			
		||||
                                ${msg(
 | 
			
		||||
                                    "Certificate used to sign outgoing Responses going to the Service Provider.",
 | 
			
		||||
                                )}
 | 
			
		||||
                            </p>
 | 
			
		||||
                        </ak-form-element-horizontal>
 | 
			
		||||
 | 
			
		||||
                    <ak-form-element-horizontal
 | 
			
		||||
                        label=${msg("NameID Property Mapping")}
 | 
			
		||||
                        name="nameIdMapping"
 | 
			
		||||
                    >
 | 
			
		||||
                        <ak-saml-property-mapping-search
 | 
			
		||||
                        <ak-form-element-horizontal
 | 
			
		||||
                            label=${msg("Verification Certificate")}
 | 
			
		||||
                            name="verificationKp"
 | 
			
		||||
                        >
 | 
			
		||||
                            <ak-crypto-certificate-search
 | 
			
		||||
                                certificate=${ifDefined(provider?.verificationKp ?? undefined)}
 | 
			
		||||
                                nokey
 | 
			
		||||
                            ></ak-crypto-certificate-search>
 | 
			
		||||
                            <p class="pf-c-form__helper-text">
 | 
			
		||||
                                ${msg(
 | 
			
		||||
                                    "When selected, incoming assertion's Signatures will be validated against this certificate. To allow unsigned Requests, leave on default.",
 | 
			
		||||
                                )}
 | 
			
		||||
                            </p>
 | 
			
		||||
                        </ak-form-element-horizontal>
 | 
			
		||||
 | 
			
		||||
                        <ak-multi-select
 | 
			
		||||
                            label=${msg("Property Mappings")}
 | 
			
		||||
                            name="propertyMappings"
 | 
			
		||||
                            required
 | 
			
		||||
                            .options=${propertyPairs}
 | 
			
		||||
                            .values=${pmValues}
 | 
			
		||||
                            .richhelp=${html` <p class="pf-c-form__helper-text">
 | 
			
		||||
                                    ${msg("Property mappings used for user mapping.")}
 | 
			
		||||
                                </p>
 | 
			
		||||
                                <p class="pf-c-form__helper-text">
 | 
			
		||||
                                    ${msg("Hold control/command to select multiple items.")}
 | 
			
		||||
                                </p>`}
 | 
			
		||||
                        ></ak-multi-select>
 | 
			
		||||
 | 
			
		||||
                        <ak-form-element-horizontal
 | 
			
		||||
                            label=${msg("NameID Property Mapping")}
 | 
			
		||||
                            name="nameIdMapping"
 | 
			
		||||
                            propertymapping=${ifDefined(provider?.nameIdMapping ?? undefined)}
 | 
			
		||||
                        ></ak-saml-property-mapping-search>
 | 
			
		||||
                        <p class="pf-c-form__helper-text">
 | 
			
		||||
                            ${msg(
 | 
			
		||||
                                "Configure how the NameID value will be created. When left empty, the NameIDPolicy of the incoming request will be respected.",
 | 
			
		||||
                        >
 | 
			
		||||
                            <ak-saml-property-mapping-search
 | 
			
		||||
                                name="nameIdMapping"
 | 
			
		||||
                                propertymapping=${ifDefined(provider?.nameIdMapping ?? undefined)}
 | 
			
		||||
                            ></ak-saml-property-mapping-search>
 | 
			
		||||
                            <p class="pf-c-form__helper-text">
 | 
			
		||||
                                ${msg(
 | 
			
		||||
                                    "Configure how the NameID value will be created. When left empty, the NameIDPolicy of the incoming request will be respected.",
 | 
			
		||||
                                )}
 | 
			
		||||
                            </p>
 | 
			
		||||
                        </ak-form-element-horizontal>
 | 
			
		||||
 | 
			
		||||
                        <ak-text-input
 | 
			
		||||
                            name="assertionValidNotBefore"
 | 
			
		||||
                            value=${provider?.assertionValidNotBefore || "minutes=-5"}
 | 
			
		||||
                            required
 | 
			
		||||
                            label=${msg("Assertion valid not before")}
 | 
			
		||||
                            help=${msg(
 | 
			
		||||
                                "Configure the maximum allowed time drift for an assertion.",
 | 
			
		||||
                            )}
 | 
			
		||||
                        </p>
 | 
			
		||||
                    </ak-form-element-horizontal>
 | 
			
		||||
                            .errorMessages=${errors?.assertionValidNotBefore ?? []}
 | 
			
		||||
                        ></ak-text-input>
 | 
			
		||||
 | 
			
		||||
                    <ak-text-input
 | 
			
		||||
                        name="assertionValidNotBefore"
 | 
			
		||||
                        value=${provider?.assertionValidNotBefore || "minutes=-5"}
 | 
			
		||||
                        required
 | 
			
		||||
                        label=${msg("Assertion valid not before")}
 | 
			
		||||
                        help=${msg("Configure the maximum allowed time drift for an assertion.")}
 | 
			
		||||
                    ></ak-text-input>
 | 
			
		||||
                        <ak-text-input
 | 
			
		||||
                            name="assertionValidNotOnOrAfter"
 | 
			
		||||
                            value=${provider?.assertionValidNotOnOrAfter || "minutes=5"}
 | 
			
		||||
                            required
 | 
			
		||||
                            label=${msg("Assertion valid not on or after")}
 | 
			
		||||
                            help=${msg(
 | 
			
		||||
                                "Assertion not valid on or after current time + this value.",
 | 
			
		||||
                            )}
 | 
			
		||||
                            .errorMessages=${errors?.assertionValidNotOnOrAfter ?? []}
 | 
			
		||||
                        ></ak-text-input>
 | 
			
		||||
 | 
			
		||||
                    <ak-text-input
 | 
			
		||||
                        name="assertionValidNotOnOrAfter"
 | 
			
		||||
                        value=${provider?.assertionValidNotOnOrAfter || "minutes=5"}
 | 
			
		||||
                        required
 | 
			
		||||
                        label=${msg("Assertion valid not on or after")}
 | 
			
		||||
                        help=${msg("Assertion not valid on or after current time + this value.")}
 | 
			
		||||
                    ></ak-text-input>
 | 
			
		||||
                        <ak-text-input
 | 
			
		||||
                            name="sessionValidNotOnOrAfter"
 | 
			
		||||
                            value=${provider?.sessionValidNotOnOrAfter || "minutes=86400"}
 | 
			
		||||
                            required
 | 
			
		||||
                            label=${msg("Session valid not on or after")}
 | 
			
		||||
                            help=${msg("Session not valid on or after current time + this value.")}
 | 
			
		||||
                            .errorMessages=${errors?.sessionValidNotOnOrAfter ?? []}
 | 
			
		||||
                        ></ak-text-input>
 | 
			
		||||
 | 
			
		||||
                    <ak-text-input
 | 
			
		||||
                        name="sessionValidNotOnOrAfter"
 | 
			
		||||
                        value=${provider?.sessionValidNotOnOrAfter || "minutes=86400"}
 | 
			
		||||
                        required
 | 
			
		||||
                        label=${msg("Session valid not on or after")}
 | 
			
		||||
                        help=${msg("Session not valid on or after current time + this value.")}
 | 
			
		||||
                    ></ak-text-input>
 | 
			
		||||
                        <ak-radio-input
 | 
			
		||||
                            name="digestAlgorithm"
 | 
			
		||||
                            label=${msg("Digest algorithm")}
 | 
			
		||||
                            required
 | 
			
		||||
                            .options=${digestAlgorithmOptions}
 | 
			
		||||
                            .value=${provider?.digestAlgorithm}
 | 
			
		||||
                        >
 | 
			
		||||
                        </ak-radio-input>
 | 
			
		||||
 | 
			
		||||
                    <ak-radio-input
 | 
			
		||||
                        name="digestAlgorithm"
 | 
			
		||||
                        label=${msg("Digest algorithm")}
 | 
			
		||||
                        required
 | 
			
		||||
                        .options=${digestAlgorithmOptions}
 | 
			
		||||
                        .value=${provider?.digestAlgorithm}
 | 
			
		||||
                    >
 | 
			
		||||
                    </ak-radio-input>
 | 
			
		||||
 | 
			
		||||
                    <ak-radio-input
 | 
			
		||||
                        name="signatureAlgorithm"
 | 
			
		||||
                        label=${msg("Signature algorithm")}
 | 
			
		||||
                        required
 | 
			
		||||
                        .options=${signatureAlgorithmOptions}
 | 
			
		||||
                        .value=${provider?.signatureAlgorithm}
 | 
			
		||||
                    >
 | 
			
		||||
                    </ak-radio-input>
 | 
			
		||||
                </div>
 | 
			
		||||
            </ak-form-group>
 | 
			
		||||
        </form>`;
 | 
			
		||||
                        <ak-radio-input
 | 
			
		||||
                            name="signatureAlgorithm"
 | 
			
		||||
                            label=${msg("Signature algorithm")}
 | 
			
		||||
                            required
 | 
			
		||||
                            .options=${signatureAlgorithmOptions}
 | 
			
		||||
                            .value=${provider?.signatureAlgorithm}
 | 
			
		||||
                        >
 | 
			
		||||
                        </ak-radio-input>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </ak-form-group>
 | 
			
		||||
            </form>`;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,81 +0,0 @@
 | 
			
		||||
import "@goauthentik/admin/common/ak-flow-search/ak-flow-search-no-default";
 | 
			
		||||
import "@goauthentik/components/ak-file-input";
 | 
			
		||||
import { AkFileInput } from "@goauthentik/components/ak-file-input";
 | 
			
		||||
import "@goauthentik/components/ak-text-input";
 | 
			
		||||
import "@goauthentik/elements/forms/HorizontalFormElement";
 | 
			
		||||
 | 
			
		||||
import { msg } from "@lit/localize";
 | 
			
		||||
import { customElement } from "@lit/reactive-element/decorators/custom-element.js";
 | 
			
		||||
import { html } from "lit";
 | 
			
		||||
import { query } from "lit/decorators.js";
 | 
			
		||||
import { ifDefined } from "lit/directives/if-defined.js";
 | 
			
		||||
 | 
			
		||||
import {
 | 
			
		||||
    FlowsInstancesListDesignationEnum,
 | 
			
		||||
    ProvidersSamlImportMetadataCreateRequest,
 | 
			
		||||
} from "@goauthentik/api";
 | 
			
		||||
 | 
			
		||||
import BaseProviderPanel from "../BaseProviderPanel";
 | 
			
		||||
 | 
			
		||||
@customElement("ak-application-wizard-authentication-by-saml-import")
 | 
			
		||||
export class ApplicationWizardProviderSamlImport extends BaseProviderPanel {
 | 
			
		||||
    @query('ak-file-input[name="metadata"]')
 | 
			
		||||
    fileInput!: AkFileInput;
 | 
			
		||||
 | 
			
		||||
    handleChange(ev: InputEvent) {
 | 
			
		||||
        if (!ev.target) {
 | 
			
		||||
            console.warn(`Received event with no target: ${ev}`);
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        const target = ev.target as HTMLInputElement;
 | 
			
		||||
        if (target.type === "file") {
 | 
			
		||||
            const file = this.fileInput.files?.[0] ?? null;
 | 
			
		||||
            if (file) {
 | 
			
		||||
                this.dispatchWizardUpdate({
 | 
			
		||||
                    update: {
 | 
			
		||||
                        provider: {
 | 
			
		||||
                            file,
 | 
			
		||||
                        },
 | 
			
		||||
                    },
 | 
			
		||||
                    status: this.form.checkValidity() ? "valid" : "invalid",
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        super.handleChange(ev);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    render() {
 | 
			
		||||
        const provider = this.wizard.provider as
 | 
			
		||||
            | ProvidersSamlImportMetadataCreateRequest
 | 
			
		||||
            | undefined;
 | 
			
		||||
 | 
			
		||||
        return html` <form class="pf-c-form pf-m-horizontal" @input=${this.handleChange}>
 | 
			
		||||
            <ak-text-input
 | 
			
		||||
                name="name"
 | 
			
		||||
                value=${ifDefined(provider?.name)}
 | 
			
		||||
                label=${msg("Name")}
 | 
			
		||||
                required
 | 
			
		||||
                help=${msg("Method's display Name.")}
 | 
			
		||||
            ></ak-text-input>
 | 
			
		||||
 | 
			
		||||
            <ak-form-element-horizontal
 | 
			
		||||
                label=${msg("Authorization flow")}
 | 
			
		||||
                ?required=${true}
 | 
			
		||||
                name="authorizationFlow"
 | 
			
		||||
            >
 | 
			
		||||
                <ak-flow-search-no-default
 | 
			
		||||
                    flowType=${FlowsInstancesListDesignationEnum.Authorization}
 | 
			
		||||
                    required
 | 
			
		||||
                ></ak-flow-search-no-default>
 | 
			
		||||
                <p class="pf-c-form__helper-text">
 | 
			
		||||
                    ${msg("Flow used when authorizing this provider.")}
 | 
			
		||||
                </p>
 | 
			
		||||
            </ak-form-element-horizontal>
 | 
			
		||||
 | 
			
		||||
            <ak-file-input name="metadata" label=${msg("Metadata")} required></ak-file-input>
 | 
			
		||||
        </form>`;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default ApplicationWizardProviderSamlImport;
 | 
			
		||||
@ -1,7 +1,10 @@
 | 
			
		||||
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 { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
 | 
			
		||||
import { first } from "@goauthentik/common/utils";
 | 
			
		||||
import "@goauthentik/components/ak-multi-select";
 | 
			
		||||
import "@goauthentik/components/ak-switch-input";
 | 
			
		||||
import "@goauthentik/components/ak-text-input";
 | 
			
		||||
import "@goauthentik/elements/forms/FormGroup";
 | 
			
		||||
@ -12,14 +15,7 @@ import { customElement, state } from "@lit/reactive-element/decorators.js";
 | 
			
		||||
import { html } from "lit";
 | 
			
		||||
import { ifDefined } from "lit/directives/if-defined.js";
 | 
			
		||||
 | 
			
		||||
import {
 | 
			
		||||
    CoreApi,
 | 
			
		||||
    CoreGroupsListRequest,
 | 
			
		||||
    type Group,
 | 
			
		||||
    PaginatedSCIMMappingList,
 | 
			
		||||
    PropertymappingsApi,
 | 
			
		||||
    type SCIMProvider,
 | 
			
		||||
} from "@goauthentik/api";
 | 
			
		||||
import { PaginatedSCIMMappingList, PropertymappingsApi, type SCIMProvider } from "@goauthentik/api";
 | 
			
		||||
 | 
			
		||||
import BaseProviderPanel from "../BaseProviderPanel";
 | 
			
		||||
 | 
			
		||||
@ -31,158 +27,129 @@ export class ApplicationWizardAuthenticationBySCIM extends BaseProviderPanel {
 | 
			
		||||
    constructor() {
 | 
			
		||||
        super();
 | 
			
		||||
        new PropertymappingsApi(DEFAULT_CONFIG)
 | 
			
		||||
            .propertymappingsScopeList({
 | 
			
		||||
                ordering: "scope_name",
 | 
			
		||||
            .propertymappingsScimList({
 | 
			
		||||
                ordering: "managed",
 | 
			
		||||
            })
 | 
			
		||||
            .then((propertyMappings: PaginatedSCIMMappingList) => {
 | 
			
		||||
                this.propertyMappings = propertyMappings;
 | 
			
		||||
            });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    propertyMappingConfiguration(provider?: SCIMProvider) {
 | 
			
		||||
        const propertyMappings = this.propertyMappings?.results ?? [];
 | 
			
		||||
 | 
			
		||||
        const configuredMappings = (providerMappings: string[]) =>
 | 
			
		||||
            propertyMappings.map((pm) => pm.pk).filter((pmpk) => providerMappings.includes(pmpk));
 | 
			
		||||
 | 
			
		||||
        const managedMappings = (key: string) =>
 | 
			
		||||
            propertyMappings
 | 
			
		||||
                .filter((pm) => pm.managed === `goauthentik.io/providers/scim/${key}`)
 | 
			
		||||
                .map((pm) => pm.pk);
 | 
			
		||||
 | 
			
		||||
        const pmUserValues = provider?.propertyMappings
 | 
			
		||||
            ? configuredMappings(provider?.propertyMappings ?? [])
 | 
			
		||||
            : managedMappings("user");
 | 
			
		||||
 | 
			
		||||
        const pmGroupValues = provider?.propertyMappingsGroup
 | 
			
		||||
            ? configuredMappings(provider?.propertyMappingsGroup ?? [])
 | 
			
		||||
            : managedMappings("group");
 | 
			
		||||
 | 
			
		||||
        const propertyPairs = propertyMappings.map((pm) => [pm.pk, pm.name]);
 | 
			
		||||
 | 
			
		||||
        return { pmUserValues, pmGroupValues, propertyPairs };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    render() {
 | 
			
		||||
        const provider = this.wizard.provider as SCIMProvider | undefined;
 | 
			
		||||
        const errors = this.wizard.errors.provider;
 | 
			
		||||
 | 
			
		||||
        return html`<form class="pf-c-form pf-m-horizontal" @input=${this.handleChange}>
 | 
			
		||||
            <ak-text-input
 | 
			
		||||
                name="name"
 | 
			
		||||
                label=${msg("Name")}
 | 
			
		||||
                value=${ifDefined(provider?.name)}
 | 
			
		||||
                required
 | 
			
		||||
            ></ak-text-input>
 | 
			
		||||
            <ak-form-group expanded>
 | 
			
		||||
                <span slot="header"> ${msg("Protocol settings")} </span>
 | 
			
		||||
                <div slot="body" class="pf-c-form">
 | 
			
		||||
                    <ak-text-input
 | 
			
		||||
                        name="url"
 | 
			
		||||
                        label=${msg("URL")}
 | 
			
		||||
                        value="${first(provider?.url, "")}"
 | 
			
		||||
                        required
 | 
			
		||||
                        help=${msg("SCIM base url, usually ends in /v2.")}
 | 
			
		||||
                    >
 | 
			
		||||
                    </ak-text-input>
 | 
			
		||||
                    <ak-text-input
 | 
			
		||||
                        name="token"
 | 
			
		||||
                        label=${msg("Token")}
 | 
			
		||||
                        value="${first(provider?.token, "")}"
 | 
			
		||||
                        required
 | 
			
		||||
                        help=${msg(
 | 
			
		||||
                            "Token to authenticate with. Currently only bearer authentication is supported.",
 | 
			
		||||
                        )}
 | 
			
		||||
                    >
 | 
			
		||||
                    </ak-text-input>
 | 
			
		||||
                </div>
 | 
			
		||||
            </ak-form-group>
 | 
			
		||||
            <ak-form-group expanded>
 | 
			
		||||
                <span slot="header">${msg("User filtering")}</span>
 | 
			
		||||
                <div slot="body" class="pf-c-form">
 | 
			
		||||
                    <ak-switch-input
 | 
			
		||||
                        name="excludeUsersServiceAccount"
 | 
			
		||||
                        ?checked=${first(provider?.excludeUsersServiceAccount, true)}
 | 
			
		||||
                        label=${msg("Exclude service accounts")}
 | 
			
		||||
                    ></ak-switch-input>
 | 
			
		||||
                    <ak-form-element-horizontal label=${msg("Group")} name="filterGroup">
 | 
			
		||||
                        <ak-search-select
 | 
			
		||||
                            .fetchObjects=${async (query?: string): Promise<Group[]> => {
 | 
			
		||||
                                const args: CoreGroupsListRequest = {
 | 
			
		||||
                                    ordering: "name",
 | 
			
		||||
                                };
 | 
			
		||||
                                if (query !== undefined) {
 | 
			
		||||
                                    args.search = query;
 | 
			
		||||
                                }
 | 
			
		||||
                                const groups = await new CoreApi(DEFAULT_CONFIG).coreGroupsList(
 | 
			
		||||
                                    args,
 | 
			
		||||
                                );
 | 
			
		||||
                                return groups.results;
 | 
			
		||||
                            }}
 | 
			
		||||
                            .renderElement=${(group: Group): string => {
 | 
			
		||||
                                return group.name;
 | 
			
		||||
                            }}
 | 
			
		||||
                            .value=${(group: Group | undefined): string | undefined => {
 | 
			
		||||
                                return group ? group.pk : undefined;
 | 
			
		||||
                            }}
 | 
			
		||||
                            .selected=${(group: Group): boolean => {
 | 
			
		||||
                                return group.pk === provider?.filterGroup;
 | 
			
		||||
                            }}
 | 
			
		||||
                            ?blankable=${true}
 | 
			
		||||
        const { pmUserValues, pmGroupValues, propertyPairs } =
 | 
			
		||||
            this.propertyMappingConfiguration(provider);
 | 
			
		||||
 | 
			
		||||
        return html`<ak-wizard-title>${msg("Configure SCIM Provider")}</ak-wizard-title>
 | 
			
		||||
            <form class="pf-c-form pf-m-horizontal" @input=${this.handleChange}>
 | 
			
		||||
                <ak-text-input
 | 
			
		||||
                    name="name"
 | 
			
		||||
                    label=${msg("Name")}
 | 
			
		||||
                    value=${ifDefined(provider?.name)}
 | 
			
		||||
                    .errorMessages=${errors?.name ?? []}
 | 
			
		||||
                    required
 | 
			
		||||
                ></ak-text-input>
 | 
			
		||||
                <ak-form-group expanded>
 | 
			
		||||
                    <span slot="header"> ${msg("Protocol settings")} </span>
 | 
			
		||||
                    <div slot="body" class="pf-c-form">
 | 
			
		||||
                        <ak-text-input
 | 
			
		||||
                            name="url"
 | 
			
		||||
                            label=${msg("URL")}
 | 
			
		||||
                            value="${first(provider?.url, "")}"
 | 
			
		||||
                            required
 | 
			
		||||
                            help=${msg("SCIM base url, usually ends in /v2.")}
 | 
			
		||||
                            .errorMessages=${errors?.url ?? []}
 | 
			
		||||
                        >
 | 
			
		||||
                        </ak-search-select>
 | 
			
		||||
                        <p class="pf-c-form__helper-text">
 | 
			
		||||
                            ${msg("Only sync users within the selected group.")}
 | 
			
		||||
                        </p>
 | 
			
		||||
                    </ak-form-element-horizontal>
 | 
			
		||||
                </div>
 | 
			
		||||
            </ak-form-group>
 | 
			
		||||
            <ak-form-group ?expanded=${true}>
 | 
			
		||||
                <span slot="header"> ${msg("Attribute mapping")} </span>
 | 
			
		||||
                <div slot="body" class="pf-c-form">
 | 
			
		||||
                    <ak-form-element-horizontal
 | 
			
		||||
                        label=${msg("User Property Mappings")}
 | 
			
		||||
                        ?required=${true}
 | 
			
		||||
                        name="propertyMappings"
 | 
			
		||||
                    >
 | 
			
		||||
                        <select class="pf-c-form-control" multiple>
 | 
			
		||||
                            ${this.propertyMappings?.results.map((mapping) => {
 | 
			
		||||
                                let selected = false;
 | 
			
		||||
                                if (!provider?.propertyMappings) {
 | 
			
		||||
                                    selected =
 | 
			
		||||
                                        mapping.managed === "goauthentik.io/providers/scim/user" ||
 | 
			
		||||
                                        false;
 | 
			
		||||
                                } else {
 | 
			
		||||
                                    selected = Array.from(provider?.propertyMappings).some((su) => {
 | 
			
		||||
                                        return su == mapping.pk;
 | 
			
		||||
                                    });
 | 
			
		||||
                                }
 | 
			
		||||
                                return html`<option
 | 
			
		||||
                                    value=${ifDefined(mapping.pk)}
 | 
			
		||||
                                    ?selected=${selected}
 | 
			
		||||
                                >
 | 
			
		||||
                                    ${mapping.name}
 | 
			
		||||
                                </option>`;
 | 
			
		||||
                            })}
 | 
			
		||||
                        </select>
 | 
			
		||||
                        <p class="pf-c-form__helper-text">
 | 
			
		||||
                            ${msg("Property mappings used to user mapping.")}
 | 
			
		||||
                        </p>
 | 
			
		||||
                        <p class="pf-c-form__helper-text">
 | 
			
		||||
                            ${msg("Hold control/command to select multiple items.")}
 | 
			
		||||
                        </p>
 | 
			
		||||
                    </ak-form-element-horizontal>
 | 
			
		||||
                    <ak-form-element-horizontal
 | 
			
		||||
                        label=${msg("Group Property Mappings")}
 | 
			
		||||
                        ?required=${true}
 | 
			
		||||
                        name="propertyMappingsGroup"
 | 
			
		||||
                    >
 | 
			
		||||
                        <select class="pf-c-form-control" multiple>
 | 
			
		||||
                            ${this.propertyMappings?.results.map((mapping) => {
 | 
			
		||||
                                let selected = false;
 | 
			
		||||
                                if (!provider?.propertyMappingsGroup) {
 | 
			
		||||
                                    selected =
 | 
			
		||||
                                        mapping.managed === "goauthentik.io/providers/scim/group";
 | 
			
		||||
                                } else {
 | 
			
		||||
                                    selected = Array.from(provider?.propertyMappingsGroup).some(
 | 
			
		||||
                                        (su) => {
 | 
			
		||||
                                            return su == mapping.pk;
 | 
			
		||||
                                        },
 | 
			
		||||
                                    );
 | 
			
		||||
                                }
 | 
			
		||||
                                return html`<option
 | 
			
		||||
                                    value=${ifDefined(mapping.pk)}
 | 
			
		||||
                                    ?selected=${selected}
 | 
			
		||||
                                >
 | 
			
		||||
                                    ${mapping.name}
 | 
			
		||||
                                </option>`;
 | 
			
		||||
                            })}
 | 
			
		||||
                        </select>
 | 
			
		||||
                        <p class="pf-c-form__helper-text">
 | 
			
		||||
                            ${msg("Property mappings used to group creation.")}
 | 
			
		||||
                        </p>
 | 
			
		||||
                        <p class="pf-c-form__helper-text">
 | 
			
		||||
                            ${msg("Hold control/command to select multiple items.")}
 | 
			
		||||
                        </p>
 | 
			
		||||
                    </ak-form-element-horizontal>
 | 
			
		||||
                </div>
 | 
			
		||||
            </ak-form-group>
 | 
			
		||||
        </form>`;
 | 
			
		||||
                        </ak-text-input>
 | 
			
		||||
                        <ak-text-input
 | 
			
		||||
                            name="token"
 | 
			
		||||
                            label=${msg("Token")}
 | 
			
		||||
                            value="${first(provider?.token, "")}"
 | 
			
		||||
                            .errorMessages=${errors?.token ?? []}
 | 
			
		||||
                            required
 | 
			
		||||
                            help=${msg(
 | 
			
		||||
                                "Token to authenticate with. Currently only bearer authentication is supported.",
 | 
			
		||||
                            )}
 | 
			
		||||
                        >
 | 
			
		||||
                        </ak-text-input>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </ak-form-group>
 | 
			
		||||
                <ak-form-group expanded>
 | 
			
		||||
                    <span slot="header">${msg("User filtering")}</span>
 | 
			
		||||
                    <div slot="body" class="pf-c-form">
 | 
			
		||||
                        <ak-switch-input
 | 
			
		||||
                            name="excludeUsersServiceAccount"
 | 
			
		||||
                            ?checked=${first(provider?.excludeUsersServiceAccount, true)}
 | 
			
		||||
                            label=${msg("Exclude service accounts")}
 | 
			
		||||
                        ></ak-switch-input>
 | 
			
		||||
                        <ak-form-element-horizontal label=${msg("Group")} name="filterGroup">
 | 
			
		||||
                            <ak-core-group-search
 | 
			
		||||
                                .group=${provider?.filterGroup}
 | 
			
		||||
                            ></ak-core-group-search>
 | 
			
		||||
                            <p class="pf-c-form__helper-text">
 | 
			
		||||
                                ${msg("Only sync users within the selected group.")}
 | 
			
		||||
                            </p>
 | 
			
		||||
                        </ak-form-element-horizontal>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </ak-form-group>
 | 
			
		||||
                <ak-form-group ?expanded=${true}>
 | 
			
		||||
                    <span slot="header"> ${msg("Attribute mapping")} </span>
 | 
			
		||||
                    <div slot="body" class="pf-c-form">
 | 
			
		||||
                        <ak-multi-select
 | 
			
		||||
                            label=${msg("User Property Mappings")}
 | 
			
		||||
                            required
 | 
			
		||||
                            name="propertyMappings"
 | 
			
		||||
                            .options=${propertyPairs}
 | 
			
		||||
                            .values=${pmUserValues}
 | 
			
		||||
                            .richhelp=${html` <p class="pf-c-form__helper-text">
 | 
			
		||||
                                    ${msg("Property mappings used for user mapping.")}
 | 
			
		||||
                                </p>
 | 
			
		||||
                                <p class="pf-c-form__helper-text">
 | 
			
		||||
                                    ${msg("Hold control/command to select multiple items.")}
 | 
			
		||||
                                </p>`}
 | 
			
		||||
                        ></ak-multi-select>
 | 
			
		||||
                        <ak-multi-select
 | 
			
		||||
                            label=${msg("Group Property Mappings")}
 | 
			
		||||
                            required
 | 
			
		||||
                            name="propertyMappingsGroup"
 | 
			
		||||
                            .options=${propertyPairs}
 | 
			
		||||
                            .values=${pmGroupValues}
 | 
			
		||||
                            .richhelp=${html` <p class="pf-c-form__helper-text">
 | 
			
		||||
                                    ${msg("Property mappings used for group creation.")}
 | 
			
		||||
                                </p>
 | 
			
		||||
                                <p class="pf-c-form__helper-text">
 | 
			
		||||
                                    ${msg("Hold control/command to select multiple items.")}
 | 
			
		||||
                                </p>`}
 | 
			
		||||
                        ></ak-multi-select>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </ak-form-group>
 | 
			
		||||
            </form>`;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -15,6 +15,12 @@ import "./commit/ak-application-wizard-commit-application";
 | 
			
		||||
import "./methods/ak-application-wizard-authentication-method";
 | 
			
		||||
import { ApplicationStep as ApplicationStepType } from "./types";
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * In the current implementation, all of the child forms have access to the wizard's
 | 
			
		||||
 * global context, into which all data is written, and which is updated by events
 | 
			
		||||
 * flowing into the top-level orchestrator.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
class ApplicationStep implements ApplicationStepType {
 | 
			
		||||
    id = "application";
 | 
			
		||||
    label = "Application Details";
 | 
			
		||||
 | 
			
		||||
@ -3,7 +3,7 @@ import { customElement } from "@lit/reactive-element/decorators/custom-element.j
 | 
			
		||||
import { state } from "@lit/reactive-element/decorators/state.js";
 | 
			
		||||
import { LitElement, html } from "lit";
 | 
			
		||||
 | 
			
		||||
import applicationWizardContext from "../ContextIdentity";
 | 
			
		||||
import { applicationWizardContext } from "../ContextIdentity";
 | 
			
		||||
import type { ApplicationWizardState } from "../types";
 | 
			
		||||
 | 
			
		||||
@customElement("ak-application-context-display-for-test")
 | 
			
		||||
 | 
			
		||||
@ -9,6 +9,7 @@ import {
 | 
			
		||||
    type RadiusProviderRequest,
 | 
			
		||||
    type SAMLProviderRequest,
 | 
			
		||||
    type SCIMProviderRequest,
 | 
			
		||||
    type ValidationError,
 | 
			
		||||
} from "@goauthentik/api";
 | 
			
		||||
 | 
			
		||||
export type OneOfProvider =
 | 
			
		||||
@ -24,12 +25,13 @@ export interface ApplicationWizardState {
 | 
			
		||||
    providerModel: string;
 | 
			
		||||
    app: Partial<ApplicationRequest>;
 | 
			
		||||
    provider: OneOfProvider;
 | 
			
		||||
    errors: ValidationError;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type StatusType = "invalid" | "valid" | "submitted" | "failed";
 | 
			
		||||
 | 
			
		||||
export type ApplicationWizardStateUpdate = {
 | 
			
		||||
    update?: Partial<ApplicationWizardState>;
 | 
			
		||||
    update?: ApplicationWizardState;
 | 
			
		||||
    status?: StatusType;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,3 +1,4 @@
 | 
			
		||||
import "@goauthentik/admin/events/EventVolumeChart";
 | 
			
		||||
import { EventGeo } from "@goauthentik/admin/events/utils";
 | 
			
		||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
 | 
			
		||||
import { EventWithContext } from "@goauthentik/common/events";
 | 
			
		||||
@ -10,7 +11,7 @@ import { TablePage } from "@goauthentik/elements/table/TablePage";
 | 
			
		||||
import "@patternfly/elements/pf-tooltip/pf-tooltip.js";
 | 
			
		||||
 | 
			
		||||
import { msg, str } from "@lit/localize";
 | 
			
		||||
import { TemplateResult, html } from "lit";
 | 
			
		||||
import { CSSResult, TemplateResult, css, html } from "lit";
 | 
			
		||||
import { customElement, property } from "lit/decorators.js";
 | 
			
		||||
 | 
			
		||||
import { Event, EventsApi } from "@goauthentik/api";
 | 
			
		||||
@ -35,6 +36,14 @@ export class EventListPage extends TablePage<Event> {
 | 
			
		||||
    @property()
 | 
			
		||||
    order = "-created";
 | 
			
		||||
 | 
			
		||||
    static get styles(): CSSResult[] {
 | 
			
		||||
        return super.styles.concat(css`
 | 
			
		||||
            .pf-m-no-padding-bottom {
 | 
			
		||||
                padding-bottom: 0;
 | 
			
		||||
            }
 | 
			
		||||
        `);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async apiEndpoint(page: number): Promise<PaginatedResponse<Event>> {
 | 
			
		||||
        return new EventsApi(DEFAULT_CONFIG).eventsEventsList({
 | 
			
		||||
            ordering: this.order,
 | 
			
		||||
@ -55,6 +64,19 @@ export class EventListPage extends TablePage<Event> {
 | 
			
		||||
        ];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    renderSectionBefore(): TemplateResult {
 | 
			
		||||
        return html`
 | 
			
		||||
            <div class="pf-c-page__main-section pf-m-no-padding-bottom">
 | 
			
		||||
                <ak-events-volume-chart
 | 
			
		||||
                    .query=${{
 | 
			
		||||
                        page: this.page,
 | 
			
		||||
                        search: this.search,
 | 
			
		||||
                    }}
 | 
			
		||||
                ></ak-events-volume-chart>
 | 
			
		||||
            </div>
 | 
			
		||||
        `;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    row(item: EventWithContext): TemplateResult[] {
 | 
			
		||||
        return [
 | 
			
		||||
            html`<div>${actionToLabel(item.action)}</div>
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										63
									
								
								web/src/admin/events/EventVolumeChart.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								web/src/admin/events/EventVolumeChart.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,63 @@
 | 
			
		||||
import { DEFAULT_CONFIG } from "@goauthentik/app/common/api/config";
 | 
			
		||||
import { AKChart } from "@goauthentik/app/elements/charts/Chart";
 | 
			
		||||
import { ChartData } from "chart.js";
 | 
			
		||||
 | 
			
		||||
import { msg } from "@lit/localize";
 | 
			
		||||
import { CSSResult, TemplateResult, css, html } from "lit";
 | 
			
		||||
import { customElement, property } from "lit/decorators.js";
 | 
			
		||||
 | 
			
		||||
import PFCard from "@patternfly/patternfly/components/Card/card.css";
 | 
			
		||||
 | 
			
		||||
import { Coordinate, EventsApi, EventsEventsListRequest } from "@goauthentik/api";
 | 
			
		||||
 | 
			
		||||
@customElement("ak-events-volume-chart")
 | 
			
		||||
export class EventVolumeChart extends AKChart<Coordinate[]> {
 | 
			
		||||
    _query?: EventsEventsListRequest;
 | 
			
		||||
 | 
			
		||||
    @property({ attribute: false })
 | 
			
		||||
    set query(value: EventsEventsListRequest | undefined) {
 | 
			
		||||
        this._query = value;
 | 
			
		||||
        this.refreshHandler();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static get styles(): CSSResult[] {
 | 
			
		||||
        return super.styles.concat(
 | 
			
		||||
            PFCard,
 | 
			
		||||
            css`
 | 
			
		||||
                .pf-c-card__body {
 | 
			
		||||
                    height: 12rem;
 | 
			
		||||
                }
 | 
			
		||||
            `,
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    apiRequest(): Promise<Coordinate[]> {
 | 
			
		||||
        return new EventsApi(DEFAULT_CONFIG).eventsEventsVolumeList(this._query);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    getChartData(data: Coordinate[]): ChartData {
 | 
			
		||||
        return {
 | 
			
		||||
            datasets: [
 | 
			
		||||
                {
 | 
			
		||||
                    label: msg("Events"),
 | 
			
		||||
                    backgroundColor: "rgba(189, 229, 184, .5)",
 | 
			
		||||
                    spanGaps: true,
 | 
			
		||||
                    data:
 | 
			
		||||
                        data.map((cord) => {
 | 
			
		||||
                            return {
 | 
			
		||||
                                x: cord.xCord || 0,
 | 
			
		||||
                                y: cord.yCord || 0,
 | 
			
		||||
                            };
 | 
			
		||||
                        }) || [],
 | 
			
		||||
                },
 | 
			
		||||
            ],
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    render(): TemplateResult {
 | 
			
		||||
        return html`<div class="pf-c-card">
 | 
			
		||||
            <div class="pf-c-card__title">${msg("Event volume")}</div>
 | 
			
		||||
            <div class="pf-c-card__body">${super.render()}</div>
 | 
			
		||||
        </div>`;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -1,120 +0,0 @@
 | 
			
		||||
/** Taken from: https://github.com/zellwk/javascript/tree/master
 | 
			
		||||
 *
 | 
			
		||||
 * We have added some typescript annotations, but this is such a rich feature with deep nesting
 | 
			
		||||
 * we'll just have to watch it closely for any issues. So far there don't seem to be any.
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
function objectType<T>(value: T) {
 | 
			
		||||
    return Object.prototype.toString.call(value);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Creates a deep clone for each value
 | 
			
		||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
 | 
			
		||||
function cloneDescriptorValue(value: any) {
 | 
			
		||||
    // Arrays
 | 
			
		||||
    if (objectType(value) === "[object Array]") {
 | 
			
		||||
        const array = [];
 | 
			
		||||
        for (let v of value) {
 | 
			
		||||
            v = cloneDescriptorValue(v);
 | 
			
		||||
            array.push(v);
 | 
			
		||||
        }
 | 
			
		||||
        return array;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Objects
 | 
			
		||||
    if (objectType(value) === "[object Object]") {
 | 
			
		||||
        const obj = {};
 | 
			
		||||
        const props = Object.keys(value);
 | 
			
		||||
        for (const prop of props) {
 | 
			
		||||
            const descriptor = Object.getOwnPropertyDescriptor(value, prop);
 | 
			
		||||
            if (!descriptor) {
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (descriptor.value) {
 | 
			
		||||
                descriptor.value = cloneDescriptorValue(descriptor.value);
 | 
			
		||||
            }
 | 
			
		||||
            Object.defineProperty(obj, prop, descriptor);
 | 
			
		||||
        }
 | 
			
		||||
        return obj;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Other Types of Objects
 | 
			
		||||
    if (objectType(value) === "[object Date]") {
 | 
			
		||||
        return new Date(value.getTime());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (objectType(value) === "[object Map]") {
 | 
			
		||||
        const map = new Map();
 | 
			
		||||
        for (const entry of value) {
 | 
			
		||||
            map.set(entry[0], cloneDescriptorValue(entry[1]));
 | 
			
		||||
        }
 | 
			
		||||
        return map;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (objectType(value) === "[object Set]") {
 | 
			
		||||
        const set = new Set();
 | 
			
		||||
        for (const entry of value.entries()) {
 | 
			
		||||
            set.add(cloneDescriptorValue(entry[0]));
 | 
			
		||||
        }
 | 
			
		||||
        return set;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Types we don't need to clone or cannot clone.
 | 
			
		||||
    // Examples:
 | 
			
		||||
    // - Primitives don't need to clone
 | 
			
		||||
    // - Functions cannot clone
 | 
			
		||||
    return value;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
 | 
			
		||||
function _merge(output: Record<string, any>, input: Record<string, any>) {
 | 
			
		||||
    const props = Object.keys(input);
 | 
			
		||||
 | 
			
		||||
    for (const prop of props) {
 | 
			
		||||
        // Prevents Prototype Pollution
 | 
			
		||||
        if (prop === "__proto__") continue;
 | 
			
		||||
 | 
			
		||||
        const descriptor = Object.getOwnPropertyDescriptor(input, prop);
 | 
			
		||||
        if (!descriptor) {
 | 
			
		||||
            continue;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const value = descriptor.value;
 | 
			
		||||
        if (value) descriptor.value = cloneDescriptorValue(value);
 | 
			
		||||
 | 
			
		||||
        // If don't have prop => Define property
 | 
			
		||||
        // [ken@goauthentik] Using `hasOwn` is preferable over
 | 
			
		||||
        // the basic identity test, according to Typescript.
 | 
			
		||||
        if (!Object.hasOwn(output, prop)) {
 | 
			
		||||
            Object.defineProperty(output, prop, descriptor);
 | 
			
		||||
            continue;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // If have prop, but type is not object => Overwrite by redefining property
 | 
			
		||||
        if (typeof output[prop] !== "object") {
 | 
			
		||||
            Object.defineProperty(output, prop, descriptor);
 | 
			
		||||
            continue;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // If have prop, but type is Object => Concat the arrays together.
 | 
			
		||||
        if (objectType(descriptor.value) === "[object Array]") {
 | 
			
		||||
            output[prop] = output[prop].concat(descriptor.value);
 | 
			
		||||
            continue;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // If have prop, but type is Object => Merge.
 | 
			
		||||
        _merge(output[prop], descriptor.value);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function merge(...sources: Array<object>) {
 | 
			
		||||
    const result = {};
 | 
			
		||||
    for (const source of sources) {
 | 
			
		||||
        _merge(result, source);
 | 
			
		||||
    }
 | 
			
		||||
    return result;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default merge;
 | 
			
		||||
@ -54,6 +54,13 @@ export function camelToSnake(key: string): string {
 | 
			
		||||
    return result.split(" ").join("_").toLowerCase();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const capitalize = (key: string) => (key.length === 0 ? "" : key[0].toUpperCase() + key.slice(1));
 | 
			
		||||
 | 
			
		||||
export function snakeToCamel(key: string) {
 | 
			
		||||
    const [start, ...rest] = key.split("_");
 | 
			
		||||
    return [start, ...rest.map(capitalize)].join("");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function groupBy<T>(objects: T[], callback: (obj: T) => string): Array<[string, T[]]> {
 | 
			
		||||
    const m = new Map<string, T[]>();
 | 
			
		||||
    objects.forEach((obj) => {
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										72
									
								
								web/src/components/HorizontalLightComponent.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								web/src/components/HorizontalLightComponent.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,72 @@
 | 
			
		||||
import { AKElement } from "@goauthentik/elements/Base";
 | 
			
		||||
 | 
			
		||||
import { TemplateResult, html, nothing } from "lit";
 | 
			
		||||
import { property } from "lit/decorators.js";
 | 
			
		||||
 | 
			
		||||
type HelpType = TemplateResult | typeof nothing;
 | 
			
		||||
 | 
			
		||||
export class HorizontalLightComponent extends AKElement {
 | 
			
		||||
    // Render into the lightDOM. This effectively erases the shadowDOM nature of this component, but
 | 
			
		||||
    // we're not actually using that and, for the meantime, we need the form handlers to be able to
 | 
			
		||||
    // find the children of this component.
 | 
			
		||||
    //
 | 
			
		||||
    // TODO: This abstraction is wrong; it's putting *more* layers in as a way of managing the
 | 
			
		||||
    // visual clutter and legibility issues of ak-form-elemental-horizontal and patternfly in
 | 
			
		||||
    // general.
 | 
			
		||||
    protected createRenderRoot() {
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @property({ type: String })
 | 
			
		||||
    name!: string;
 | 
			
		||||
 | 
			
		||||
    @property({ type: String })
 | 
			
		||||
    label = "";
 | 
			
		||||
 | 
			
		||||
    @property({ type: Boolean })
 | 
			
		||||
    required = false;
 | 
			
		||||
 | 
			
		||||
    @property({ type: String })
 | 
			
		||||
    help = "";
 | 
			
		||||
 | 
			
		||||
    @property({ type: Object })
 | 
			
		||||
    bighelp?: TemplateResult | TemplateResult[];
 | 
			
		||||
 | 
			
		||||
    @property({ type: Boolean })
 | 
			
		||||
    hidden = false;
 | 
			
		||||
 | 
			
		||||
    @property({ type: Boolean })
 | 
			
		||||
    invalid = false;
 | 
			
		||||
 | 
			
		||||
    @property({ attribute: false })
 | 
			
		||||
    errorMessages: string[] = [];
 | 
			
		||||
 | 
			
		||||
    renderControl() {
 | 
			
		||||
        throw new Error("Must be implemented in a subclass");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    renderHelp(): HelpType[] {
 | 
			
		||||
        const bigHelp: HelpType[] = Array.isArray(this.bighelp)
 | 
			
		||||
            ? this.bighelp
 | 
			
		||||
            : [this.bighelp ?? nothing];
 | 
			
		||||
        return [
 | 
			
		||||
            this.help ? html`<p class="pf-c-form__helper-text">${this.help}</p>` : nothing,
 | 
			
		||||
            ...bigHelp,
 | 
			
		||||
        ];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    render() {
 | 
			
		||||
        // prettier-ignore
 | 
			
		||||
        return html`<ak-form-element-horizontal
 | 
			
		||||
            label=${this.label}
 | 
			
		||||
            ?required=${this.required}
 | 
			
		||||
            ?hidden=${this.hidden}
 | 
			
		||||
            name=${this.name}
 | 
			
		||||
            .errorMessages=${this.errorMessages}
 | 
			
		||||
            ?invalid=${this.invalid}
 | 
			
		||||
            >
 | 
			
		||||
              ${this.renderControl()} 
 | 
			
		||||
              ${this.renderHelp()}
 | 
			
		||||
        </ak-form-element-horizontal> `;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										150
									
								
								web/src/components/ak-multi-select.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										150
									
								
								web/src/components/ak-multi-select.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,150 @@
 | 
			
		||||
import "@goauthentik/app/elements/forms/HorizontalFormElement";
 | 
			
		||||
import { AKElement } from "@goauthentik/elements/Base";
 | 
			
		||||
 | 
			
		||||
import { TemplateResult, css, html, nothing } from "lit";
 | 
			
		||||
import { customElement, property } from "lit/decorators.js";
 | 
			
		||||
import { ifDefined } from "lit/directives/if-defined.js";
 | 
			
		||||
import { map } from "lit/directives/map.js";
 | 
			
		||||
import { Ref, createRef, ref } from "lit/directives/ref.js";
 | 
			
		||||
 | 
			
		||||
import PFForm from "@patternfly/patternfly/components/Form/form.css";
 | 
			
		||||
import PFFormControl from "@patternfly/patternfly/components/FormControl/form-control.css";
 | 
			
		||||
import PFBase from "@patternfly/patternfly/patternfly-base.css";
 | 
			
		||||
 | 
			
		||||
type Pair = [string, string];
 | 
			
		||||
 | 
			
		||||
const selectStyles = css`
 | 
			
		||||
    select[multiple] {
 | 
			
		||||
        min-height: 15rem;
 | 
			
		||||
    }
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Horizontal layout control with a multi-select.
 | 
			
		||||
 *
 | 
			
		||||
 * @part select - The select itself, to override the height specified above.
 | 
			
		||||
 */
 | 
			
		||||
@customElement("ak-multi-select")
 | 
			
		||||
export class AkMultiSelect extends AKElement {
 | 
			
		||||
    constructor() {
 | 
			
		||||
        super();
 | 
			
		||||
        this.dataset.akControl = "true";
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static get styles() {
 | 
			
		||||
        return [PFBase, PFForm, PFFormControl, selectStyles];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The [name] attribute, which is also distributed to the layout manager and the input control.
 | 
			
		||||
     */
 | 
			
		||||
    @property({ type: String })
 | 
			
		||||
    name!: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The text label to display on the control
 | 
			
		||||
     */
 | 
			
		||||
    @property({ type: String })
 | 
			
		||||
    label = "";
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The values to be displayed in the select. The format is [Value, Label], where the label is
 | 
			
		||||
     * what will be displayed.
 | 
			
		||||
     */
 | 
			
		||||
    @property({ attribute: false })
 | 
			
		||||
    options: Pair[] = [];
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * If true, at least one object must be selected
 | 
			
		||||
     */
 | 
			
		||||
    @property({ type: Boolean })
 | 
			
		||||
    required = false;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Supporting a simple help string
 | 
			
		||||
     */
 | 
			
		||||
    @property({ type: String })
 | 
			
		||||
    help = "";
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * For more complex help instructions, provide a template result.
 | 
			
		||||
     */
 | 
			
		||||
    @property({ type: Object })
 | 
			
		||||
    bighelp!: TemplateResult | TemplateResult[];
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * An array of strings representing the objects currently selected.
 | 
			
		||||
     */
 | 
			
		||||
    @property({ type: Array })
 | 
			
		||||
    values: string[] = [];
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Helper accessor for older code
 | 
			
		||||
     */
 | 
			
		||||
    get value() {
 | 
			
		||||
        return this.values;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * One of two criteria (the other being the data-ak-control flag) that specifies this as a
 | 
			
		||||
     * control that produces values of specific interest to our REST API. This is our modern
 | 
			
		||||
     * accessor name.
 | 
			
		||||
     */
 | 
			
		||||
    json() {
 | 
			
		||||
        return this.values;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    renderHelp() {
 | 
			
		||||
        return [
 | 
			
		||||
            this.help ? html`<p class="pf-c-form__helper-text">${this.help}</p>` : nothing,
 | 
			
		||||
            this.bighelp ? this.bighelp : nothing,
 | 
			
		||||
        ];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    handleChange(ev: Event) {
 | 
			
		||||
        if (ev.type === "change") {
 | 
			
		||||
            this.values = Array.from(this.selectRef.value!.querySelectorAll("option"))
 | 
			
		||||
                .filter((option) => option.selected)
 | 
			
		||||
                .map((option) => option.value);
 | 
			
		||||
            this.dispatchEvent(
 | 
			
		||||
                new CustomEvent("ak-select", {
 | 
			
		||||
                    detail: this.values,
 | 
			
		||||
                    composed: true,
 | 
			
		||||
                    bubbles: true,
 | 
			
		||||
                }),
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    selectRef: Ref<HTMLSelectElement> = createRef();
 | 
			
		||||
 | 
			
		||||
    render() {
 | 
			
		||||
        return html` <div class="pf-c-form">
 | 
			
		||||
            <ak-form-element-horizontal
 | 
			
		||||
                label=${this.label}
 | 
			
		||||
                ?required=${this.required}
 | 
			
		||||
                name=${this.name}
 | 
			
		||||
            >
 | 
			
		||||
                <select
 | 
			
		||||
                    part="select"
 | 
			
		||||
                    class="pf-c-form-control"
 | 
			
		||||
                    name=${ifDefined(this.name)}
 | 
			
		||||
                    multiple
 | 
			
		||||
                    ${ref(this.selectRef)}
 | 
			
		||||
                    @change=${this.handleChange}
 | 
			
		||||
                >
 | 
			
		||||
                    ${map(
 | 
			
		||||
                        this.options,
 | 
			
		||||
                        ([value, label]) =>
 | 
			
		||||
                            html`<option value=${value} ?selected=${this.values.includes(value)}>
 | 
			
		||||
                                ${label}
 | 
			
		||||
                            </option>`,
 | 
			
		||||
                    )}
 | 
			
		||||
                </select>
 | 
			
		||||
                ${this.renderHelp()}
 | 
			
		||||
            </ak-form-element-horizontal>
 | 
			
		||||
        </div>`;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default AkMultiSelect;
 | 
			
		||||
@ -1,51 +1,21 @@
 | 
			
		||||
import { AKElement } from "@goauthentik/elements/Base";
 | 
			
		||||
 | 
			
		||||
import { html, nothing } from "lit";
 | 
			
		||||
import { html } from "lit";
 | 
			
		||||
import { customElement, property } from "lit/decorators.js";
 | 
			
		||||
import { ifDefined } from "lit/directives/if-defined.js";
 | 
			
		||||
 | 
			
		||||
import { HorizontalLightComponent } from "./HorizontalLightComponent";
 | 
			
		||||
 | 
			
		||||
@customElement("ak-number-input")
 | 
			
		||||
export class AkNumberInput extends AKElement {
 | 
			
		||||
    // Render into the lightDOM. This effectively erases the shadowDOM nature of this component, but
 | 
			
		||||
    // we're not actually using that and, for the meantime, we need the form handlers to be able to
 | 
			
		||||
    // find the children of this component.
 | 
			
		||||
    //
 | 
			
		||||
    // TODO: This abstraction is wrong; it's putting *more* layers in as a way of managing the
 | 
			
		||||
    // visual clutter and legibility issues of ak-form-elemental-horizontal and patternfly in
 | 
			
		||||
    // general.
 | 
			
		||||
    protected createRenderRoot() {
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @property({ type: String })
 | 
			
		||||
    name!: string;
 | 
			
		||||
 | 
			
		||||
    @property({ type: String })
 | 
			
		||||
    label = "";
 | 
			
		||||
 | 
			
		||||
export class AkNumberInput extends HorizontalLightComponent {
 | 
			
		||||
    @property({ type: Number, reflect: true })
 | 
			
		||||
    value = 0;
 | 
			
		||||
 | 
			
		||||
    @property({ type: Boolean })
 | 
			
		||||
    required = false;
 | 
			
		||||
 | 
			
		||||
    @property({ type: String })
 | 
			
		||||
    help = "";
 | 
			
		||||
 | 
			
		||||
    render() {
 | 
			
		||||
        return html`<ak-form-element-horizontal
 | 
			
		||||
            label=${this.label}
 | 
			
		||||
    renderControl() {
 | 
			
		||||
        return html`<input
 | 
			
		||||
            type="number"
 | 
			
		||||
            value=${ifDefined(this.value)}
 | 
			
		||||
            class="pf-c-form-control"
 | 
			
		||||
            ?required=${this.required}
 | 
			
		||||
            name=${this.name}
 | 
			
		||||
        >
 | 
			
		||||
            <input
 | 
			
		||||
                type="number"
 | 
			
		||||
                value=${ifDefined(this.value)}
 | 
			
		||||
                class="pf-c-form-control"
 | 
			
		||||
                ?required=${this.required}
 | 
			
		||||
            />
 | 
			
		||||
            ${this.help ? html`<p class="pf-c-form__helper-text">${this.help}</p>` : nothing}
 | 
			
		||||
        </ak-form-element-horizontal> `;
 | 
			
		||||
        />`;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,35 +1,13 @@
 | 
			
		||||
import { AKElement } from "@goauthentik/elements/Base";
 | 
			
		||||
import { RadioOption } from "@goauthentik/elements/forms/Radio";
 | 
			
		||||
import "@goauthentik/elements/forms/Radio";
 | 
			
		||||
 | 
			
		||||
import { html, nothing } from "lit";
 | 
			
		||||
import { customElement, property } from "lit/decorators.js";
 | 
			
		||||
 | 
			
		||||
import { HorizontalLightComponent } from "./HorizontalLightComponent";
 | 
			
		||||
 | 
			
		||||
@customElement("ak-radio-input")
 | 
			
		||||
export class AkRadioInput<T> extends AKElement {
 | 
			
		||||
    // Render into the lightDOM. This effectively erases the shadowDOM nature of this component, but
 | 
			
		||||
    // we're not actually using that and, for the meantime, we need the form handlers to be able to
 | 
			
		||||
    // find the children of this component.
 | 
			
		||||
    //
 | 
			
		||||
    // TODO: This abstraction is wrong; it's putting *more* layers in as a way of managing the
 | 
			
		||||
    // visual clutter and legibility issues of ak-form-elemental-horizontal and patternfly in
 | 
			
		||||
    // general.
 | 
			
		||||
    protected createRenderRoot() {
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @property({ type: String })
 | 
			
		||||
    name!: string;
 | 
			
		||||
 | 
			
		||||
    @property({ type: String })
 | 
			
		||||
    label = "";
 | 
			
		||||
 | 
			
		||||
    @property({ type: String })
 | 
			
		||||
    help = "";
 | 
			
		||||
 | 
			
		||||
    @property({ type: Boolean })
 | 
			
		||||
    required = false;
 | 
			
		||||
 | 
			
		||||
export class AkRadioInput<T> extends HorizontalLightComponent {
 | 
			
		||||
    @property({ type: Object })
 | 
			
		||||
    value!: T;
 | 
			
		||||
 | 
			
		||||
@ -37,24 +15,25 @@ export class AkRadioInput<T> extends AKElement {
 | 
			
		||||
    options: RadioOption<T>[] = [];
 | 
			
		||||
 | 
			
		||||
    handleInput(ev: CustomEvent) {
 | 
			
		||||
        this.value = ev.detail.value;
 | 
			
		||||
        if ("detail" in ev) {
 | 
			
		||||
            this.value = ev.detail.value;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    render() {
 | 
			
		||||
        return html`<ak-form-element-horizontal
 | 
			
		||||
            label=${this.label}
 | 
			
		||||
            ?required=${this.required}
 | 
			
		||||
            name=${this.name}
 | 
			
		||||
        >
 | 
			
		||||
            <ak-radio
 | 
			
		||||
    renderHelp() {
 | 
			
		||||
        // This is weird, but Typescript says it's necessary?
 | 
			
		||||
        return [nothing as typeof nothing];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    renderControl() {
 | 
			
		||||
        return html`<ak-radio
 | 
			
		||||
                .options=${this.options}
 | 
			
		||||
                .value=${this.value}
 | 
			
		||||
                @input=${this.handleInput}
 | 
			
		||||
            ></ak-radio>
 | 
			
		||||
            ${this.help.trim()
 | 
			
		||||
                ? html`<p class="pf-c-form__helper-radio">${this.help}</p>`
 | 
			
		||||
                : nothing}
 | 
			
		||||
        </ak-form-element-horizontal> `;
 | 
			
		||||
                : nothing}`;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,44 +1,16 @@
 | 
			
		||||
import { convertToSlug } from "@goauthentik/common/utils";
 | 
			
		||||
import { AKElement } from "@goauthentik/elements/Base";
 | 
			
		||||
 | 
			
		||||
import { TemplateResult, html, nothing } from "lit";
 | 
			
		||||
import { html } from "lit";
 | 
			
		||||
import { customElement, property, query } from "lit/decorators.js";
 | 
			
		||||
import { ifDefined } from "lit/directives/if-defined.js";
 | 
			
		||||
 | 
			
		||||
import { HorizontalLightComponent } from "./HorizontalLightComponent";
 | 
			
		||||
 | 
			
		||||
@customElement("ak-slug-input")
 | 
			
		||||
export class AkSlugInput extends AKElement {
 | 
			
		||||
    // Render into the lightDOM. This effectively erases the shadowDOM nature of this component, but
 | 
			
		||||
    // we're not actually using that and, for the meantime, we need the form handlers to be able to
 | 
			
		||||
    // find the children of this component.
 | 
			
		||||
    //
 | 
			
		||||
    // TODO: This abstraction is wrong; it's putting *more* layers in as a way of managing the
 | 
			
		||||
    // visual clutter and legibility issues of ak-form-elemental-horizontal and patternfly in
 | 
			
		||||
    // general.
 | 
			
		||||
    protected createRenderRoot() {
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @property({ type: String })
 | 
			
		||||
    name!: string;
 | 
			
		||||
 | 
			
		||||
    @property({ type: String })
 | 
			
		||||
    label = "";
 | 
			
		||||
 | 
			
		||||
export class AkSlugInput extends HorizontalLightComponent {
 | 
			
		||||
    @property({ type: String, reflect: true })
 | 
			
		||||
    value = "";
 | 
			
		||||
 | 
			
		||||
    @property({ type: Boolean })
 | 
			
		||||
    required = false;
 | 
			
		||||
 | 
			
		||||
    @property({ type: String })
 | 
			
		||||
    help = "";
 | 
			
		||||
 | 
			
		||||
    @property({ type: Boolean })
 | 
			
		||||
    hidden = false;
 | 
			
		||||
 | 
			
		||||
    @property({ type: Object })
 | 
			
		||||
    bighelp!: TemplateResult | TemplateResult[];
 | 
			
		||||
 | 
			
		||||
    @property({ type: String })
 | 
			
		||||
    source = "";
 | 
			
		||||
 | 
			
		||||
@ -59,13 +31,6 @@ export class AkSlugInput extends AKElement {
 | 
			
		||||
        this.input.addEventListener("input", this.handleTouch);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    renderHelp() {
 | 
			
		||||
        return [
 | 
			
		||||
            this.help ? html`<p class="pf-c-form__helper-text">${this.help}</p>` : nothing,
 | 
			
		||||
            this.bighelp ? this.bighelp : nothing,
 | 
			
		||||
        ];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Do not stop propagation of this event; it must be sent up the tree so that a parent
 | 
			
		||||
    // component, such as a custom forms manager, may receive it.
 | 
			
		||||
    handleTouch(ev: Event) {
 | 
			
		||||
@ -150,21 +115,13 @@ export class AkSlugInput extends AKElement {
 | 
			
		||||
        super.disconnectedCallback();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    render() {
 | 
			
		||||
        return html`<ak-form-element-horizontal
 | 
			
		||||
            label=${this.label}
 | 
			
		||||
    renderControl() {
 | 
			
		||||
        return html`<input
 | 
			
		||||
            type="text"
 | 
			
		||||
            value=${ifDefined(this.value)}
 | 
			
		||||
            class="pf-c-form-control"
 | 
			
		||||
            ?required=${this.required}
 | 
			
		||||
            ?hidden=${this.hidden}
 | 
			
		||||
            name=${this.name}
 | 
			
		||||
        >
 | 
			
		||||
            <input
 | 
			
		||||
                type="text"
 | 
			
		||||
                value=${ifDefined(this.value)}
 | 
			
		||||
                class="pf-c-form-control"
 | 
			
		||||
                ?required=${this.required}
 | 
			
		||||
            />
 | 
			
		||||
            ${this.renderHelp()}
 | 
			
		||||
        </ak-form-element-horizontal> `;
 | 
			
		||||
        />`;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,65 +1,21 @@
 | 
			
		||||
import { AKElement } from "@goauthentik/elements/Base";
 | 
			
		||||
 | 
			
		||||
import { TemplateResult, html, nothing } from "lit";
 | 
			
		||||
import { html } from "lit";
 | 
			
		||||
import { customElement, property } from "lit/decorators.js";
 | 
			
		||||
import { ifDefined } from "lit/directives/if-defined.js";
 | 
			
		||||
 | 
			
		||||
import { HorizontalLightComponent } from "./HorizontalLightComponent";
 | 
			
		||||
 | 
			
		||||
@customElement("ak-text-input")
 | 
			
		||||
export class AkTextInput extends AKElement {
 | 
			
		||||
    // Render into the lightDOM. This effectively erases the shadowDOM nature of this component, but
 | 
			
		||||
    // we're not actually using that and, for the meantime, we need the form handlers to be able to
 | 
			
		||||
    // find the children of this component.
 | 
			
		||||
    //
 | 
			
		||||
    // TODO: This abstraction is wrong; it's putting *more* layers in as a way of managing the
 | 
			
		||||
    // visual clutter and legibility issues of ak-form-elemental-horizontal and patternfly in
 | 
			
		||||
    // general.
 | 
			
		||||
    protected createRenderRoot() {
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @property({ type: String })
 | 
			
		||||
    name!: string;
 | 
			
		||||
 | 
			
		||||
    @property({ type: String })
 | 
			
		||||
    label = "";
 | 
			
		||||
 | 
			
		||||
export class AkTextInput extends HorizontalLightComponent {
 | 
			
		||||
    @property({ type: String, reflect: true })
 | 
			
		||||
    value = "";
 | 
			
		||||
 | 
			
		||||
    @property({ type: Boolean })
 | 
			
		||||
    required = false;
 | 
			
		||||
 | 
			
		||||
    @property({ type: String })
 | 
			
		||||
    help = "";
 | 
			
		||||
 | 
			
		||||
    @property({ type: Boolean })
 | 
			
		||||
    hidden = false;
 | 
			
		||||
 | 
			
		||||
    @property({ type: Object })
 | 
			
		||||
    bighelp!: TemplateResult | TemplateResult[];
 | 
			
		||||
 | 
			
		||||
    renderHelp() {
 | 
			
		||||
        return [
 | 
			
		||||
            this.help ? html`<p class="pf-c-form__helper-text">${this.help}</p>` : nothing,
 | 
			
		||||
            this.bighelp ? this.bighelp : nothing,
 | 
			
		||||
        ];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    render() {
 | 
			
		||||
        return html`<ak-form-element-horizontal
 | 
			
		||||
            label=${this.label}
 | 
			
		||||
    renderControl() {
 | 
			
		||||
        return html` <input
 | 
			
		||||
            type="text"
 | 
			
		||||
            value=${ifDefined(this.value)}
 | 
			
		||||
            class="pf-c-form-control"
 | 
			
		||||
            ?required=${this.required}
 | 
			
		||||
            ?hidden=${this.hidden}
 | 
			
		||||
            name=${this.name}
 | 
			
		||||
        >
 | 
			
		||||
            <input
 | 
			
		||||
                type="text"
 | 
			
		||||
                value=${ifDefined(this.value)}
 | 
			
		||||
                class="pf-c-form-control"
 | 
			
		||||
                ?required=${this.required}
 | 
			
		||||
            />
 | 
			
		||||
            ${this.renderHelp()}
 | 
			
		||||
        </ak-form-element-horizontal> `;
 | 
			
		||||
        />`;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,57 +1,22 @@
 | 
			
		||||
import { AKElement } from "@goauthentik/elements/Base";
 | 
			
		||||
 | 
			
		||||
import { TemplateResult, html, nothing } from "lit";
 | 
			
		||||
import { html } from "lit";
 | 
			
		||||
import { customElement, property } from "lit/decorators.js";
 | 
			
		||||
 | 
			
		||||
import { HorizontalLightComponent } from "./HorizontalLightComponent";
 | 
			
		||||
 | 
			
		||||
@customElement("ak-textarea-input")
 | 
			
		||||
export class AkTextareaInput extends AKElement {
 | 
			
		||||
    // Render into the lightDOM. This effectively erases the shadowDOM nature of this component, but
 | 
			
		||||
    // we're not actually using that and, for the meantime, we need the form handlers to be able to
 | 
			
		||||
    // find the children of this component.
 | 
			
		||||
    //
 | 
			
		||||
    // TODO: This abstraction is wrong; it's putting *more* layers in as a way of managing the
 | 
			
		||||
    // visual clutter and legibility issues of ak-form-elemental-horizontal and patternfly in
 | 
			
		||||
    // general.
 | 
			
		||||
    protected createRenderRoot() {
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @property({ type: String })
 | 
			
		||||
    name!: string;
 | 
			
		||||
 | 
			
		||||
    @property({ type: String })
 | 
			
		||||
    label = "";
 | 
			
		||||
 | 
			
		||||
    @property({ type: String })
 | 
			
		||||
export class AkTextareaInput extends HorizontalLightComponent {
 | 
			
		||||
    @property({ type: String, reflect: true })
 | 
			
		||||
    value = "";
 | 
			
		||||
 | 
			
		||||
    @property({ type: Boolean })
 | 
			
		||||
    required = false;
 | 
			
		||||
 | 
			
		||||
    @property({ type: String })
 | 
			
		||||
    help = "";
 | 
			
		||||
 | 
			
		||||
    @property({ type: Object })
 | 
			
		||||
    bighelp!: TemplateResult | TemplateResult[];
 | 
			
		||||
 | 
			
		||||
    renderHelp() {
 | 
			
		||||
        return [
 | 
			
		||||
            this.help ? html`<p class="pf-c-form__helper-textarea">${this.help}</p>` : nothing,
 | 
			
		||||
            this.bighelp ? this.bighelp : nothing,
 | 
			
		||||
        ];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    render() {
 | 
			
		||||
        return html`<ak-form-element-horizontal
 | 
			
		||||
            label=${this.label}
 | 
			
		||||
    renderControl() {
 | 
			
		||||
        // Prevent the leading spaces added by Prettier's whitespace algo
 | 
			
		||||
        // prettier-ignore
 | 
			
		||||
        return html`<textarea
 | 
			
		||||
            class="pf-c-form-control"
 | 
			
		||||
            ?required=${this.required}
 | 
			
		||||
            name=${this.name}
 | 
			
		||||
        >
 | 
			
		||||
            <textarea class="pf-c-form-control" ?required=${this.required} name=${this.name}>
 | 
			
		||||
${this.value !== undefined ? this.value : ""}</textarea
 | 
			
		||||
            >
 | 
			
		||||
            ${this.renderHelp()}
 | 
			
		||||
        </ak-form-element-horizontal> `;
 | 
			
		||||
        >${this.value !== undefined ? this.value : ""}</textarea
 | 
			
		||||
        > `;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										79
									
								
								web/src/components/stories/ak-multi-select.stories.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								web/src/components/stories/ak-multi-select.stories.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,79 @@
 | 
			
		||||
import "@goauthentik/elements/messages/MessageContainer";
 | 
			
		||||
import { Meta } from "@storybook/web-components";
 | 
			
		||||
 | 
			
		||||
import { TemplateResult, html, render } from "lit";
 | 
			
		||||
 | 
			
		||||
import "../ak-multi-select";
 | 
			
		||||
import AkMultiSelect from "../ak-multi-select";
 | 
			
		||||
 | 
			
		||||
const metadata: Meta<AkMultiSelect> = {
 | 
			
		||||
    title: "Components / MultiSelect",
 | 
			
		||||
    component: "ak-multi-select",
 | 
			
		||||
    parameters: {
 | 
			
		||||
        docs: {
 | 
			
		||||
            description: {
 | 
			
		||||
                component: "A stylized value control for multi-select displays",
 | 
			
		||||
            },
 | 
			
		||||
        },
 | 
			
		||||
    },
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default metadata;
 | 
			
		||||
 | 
			
		||||
const container = (testItem: TemplateResult) =>
 | 
			
		||||
    html` <div style="background: #fff; padding: 2em">
 | 
			
		||||
        <style>
 | 
			
		||||
            li {
 | 
			
		||||
                display: block;
 | 
			
		||||
            }
 | 
			
		||||
            p {
 | 
			
		||||
                margin-top: 1em;
 | 
			
		||||
            }
 | 
			
		||||
        </style>
 | 
			
		||||
 | 
			
		||||
        ${testItem}
 | 
			
		||||
 | 
			
		||||
        <div id="message-pad" style="margin-top: 1em"></div>
 | 
			
		||||
    </div>`;
 | 
			
		||||
 | 
			
		||||
const testOptions = [
 | 
			
		||||
    ["funky", "Option One: Funky"],
 | 
			
		||||
    ["strange", "Option Two: Strange"],
 | 
			
		||||
    ["weird", "Option Three: Weird"],
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
export const RadioInput = () => {
 | 
			
		||||
    const result = "";
 | 
			
		||||
 | 
			
		||||
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
 | 
			
		||||
    const displayChange = (ev: any) => {
 | 
			
		||||
        const messagePad = document.getElementById("message-pad");
 | 
			
		||||
        const component: AkMultiSelect | null = document.querySelector(
 | 
			
		||||
            'ak-multi-select[name="ak-test-multi-select"]',
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        const results = html`
 | 
			
		||||
            <p>Results from event:</p>
 | 
			
		||||
            <ul style="list-style-type: disc">
 | 
			
		||||
                ${ev.target.value.map((v: string) => html`<li>${v}</li>`)}
 | 
			
		||||
            </ul>
 | 
			
		||||
            <p>Results from component:</p>
 | 
			
		||||
            <ul style="list-style-type: disc">
 | 
			
		||||
                ${component!.json().map((v: string) => html`<li>${v}</li>`)}
 | 
			
		||||
            </ul>
 | 
			
		||||
        `;
 | 
			
		||||
 | 
			
		||||
        render(results, messagePad!);
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    return container(
 | 
			
		||||
        html`<ak-multi-select
 | 
			
		||||
                @ak-select=${displayChange}
 | 
			
		||||
                label="Test Radio Button"
 | 
			
		||||
                name="ak-test-multi-select"
 | 
			
		||||
                help="This is where you would read the help messages"
 | 
			
		||||
                .options=${testOptions}
 | 
			
		||||
            ></ak-multi-select>
 | 
			
		||||
            <div>${result}</div>`,
 | 
			
		||||
    );
 | 
			
		||||
};
 | 
			
		||||
@ -31,10 +31,15 @@ export interface KeyUnknown {
 | 
			
		||||
    [key: string]: unknown;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Literally the only field `assignValue()` cares about.
 | 
			
		||||
type HTMLNamedElement = Pick<HTMLInputElement, "name">;
 | 
			
		||||
 | 
			
		||||
type AkControlElement = HTMLInputElement & { json: () => string | string[] };
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Recursively assign `value` into `json` while interpreting the dot-path of `element.name`
 | 
			
		||||
 */
 | 
			
		||||
function assignValue(element: HTMLInputElement, value: unknown, json: KeyUnknown): void {
 | 
			
		||||
function assignValue(element: HTMLNamedElement, value: unknown, json: KeyUnknown): void {
 | 
			
		||||
    let parent = json;
 | 
			
		||||
    if (!element.name?.includes(".")) {
 | 
			
		||||
        parent[element.name] = value;
 | 
			
		||||
@ -60,6 +65,16 @@ export function serializeForm<T extends KeyUnknown>(
 | 
			
		||||
    const json: { [key: string]: unknown } = {};
 | 
			
		||||
    elements.forEach((element) => {
 | 
			
		||||
        element.requestUpdate();
 | 
			
		||||
        if (element.hidden) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // TODO: Tighten up the typing so that we can handle both.
 | 
			
		||||
        if ("akControl" in element.dataset) {
 | 
			
		||||
            assignValue(element, (element as unknown as AkControlElement).json(), json);
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const inputElement = element.querySelector<HTMLInputElement>("[name]");
 | 
			
		||||
        if (element.hidden || !inputElement) {
 | 
			
		||||
            return;
 | 
			
		||||
 | 
			
		||||
@ -18,7 +18,7 @@ import "@goauthentik/flow/stages/RedirectStage";
 | 
			
		||||
import { StageHost } from "@goauthentik/flow/stages/base";
 | 
			
		||||
 | 
			
		||||
import { msg } from "@lit/localize";
 | 
			
		||||
import { CSSResult, TemplateResult, css, html, render } from "lit";
 | 
			
		||||
import { CSSResult, TemplateResult, css, html, nothing } from "lit";
 | 
			
		||||
import { customElement, property, state } from "lit/decorators.js";
 | 
			
		||||
import { unsafeHTML } from "lit/directives/unsafe-html.js";
 | 
			
		||||
import { until } from "lit/directives/until.js";
 | 
			
		||||
@ -46,7 +46,8 @@ import {
 | 
			
		||||
 | 
			
		||||
@customElement("ak-flow-executor")
 | 
			
		||||
export class FlowExecutor extends Interface implements StageHost {
 | 
			
		||||
    flowSlug?: string;
 | 
			
		||||
    @property()
 | 
			
		||||
    flowSlug: string = window.location.pathname.split("/")[3];
 | 
			
		||||
 | 
			
		||||
    private _challenge?: ChallengeTypes;
 | 
			
		||||
 | 
			
		||||
@ -94,6 +95,9 @@ export class FlowExecutor extends Interface implements StageHost {
 | 
			
		||||
 | 
			
		||||
    static get styles(): CSSResult[] {
 | 
			
		||||
        return [PFBase, PFLogin, PFDrawer, PFButton, PFTitle, PFList, PFBackgroundImage].concat(css`
 | 
			
		||||
            :host {
 | 
			
		||||
                --pf-c-login__main-body--PaddingBottom: var(--pf-global--spacer--2xl);
 | 
			
		||||
            }
 | 
			
		||||
            .pf-c-background-image::before {
 | 
			
		||||
                --pf-c-background-image--BackgroundImage: var(--ak-flow-background);
 | 
			
		||||
                --pf-c-background-image--BackgroundImage-2x: var(--ak-flow-background);
 | 
			
		||||
@ -111,6 +115,9 @@ export class FlowExecutor extends Interface implements StageHost {
 | 
			
		||||
                background-color: transparent;
 | 
			
		||||
            }
 | 
			
		||||
            /* layouts */
 | 
			
		||||
            .pf-c-login.stacked .pf-c-login__main {
 | 
			
		||||
                margin-top: 13rem;
 | 
			
		||||
            }
 | 
			
		||||
            .pf-c-login__container.content-right {
 | 
			
		||||
                grid-template-areas:
 | 
			
		||||
                    "header main"
 | 
			
		||||
@ -146,13 +153,27 @@ export class FlowExecutor extends Interface implements StageHost {
 | 
			
		||||
            :host([theme="dark"]) .pf-c-login.sidebar_right .pf-c-list {
 | 
			
		||||
                color: var(--ak-dark-foreground);
 | 
			
		||||
            }
 | 
			
		||||
            .pf-c-brand {
 | 
			
		||||
                padding-top: calc(
 | 
			
		||||
                    var(--pf-c-login__main-footer-links--PaddingTop) +
 | 
			
		||||
                        var(--pf-c-login__main-footer-links--PaddingBottom) +
 | 
			
		||||
                        var(--pf-c-login__main-body--PaddingBottom)
 | 
			
		||||
                );
 | 
			
		||||
                max-height: 9rem;
 | 
			
		||||
            }
 | 
			
		||||
            .ak-brand {
 | 
			
		||||
                display: flex;
 | 
			
		||||
                justify-content: center;
 | 
			
		||||
            }
 | 
			
		||||
            .ak-brand img {
 | 
			
		||||
                padding: 0 2rem;
 | 
			
		||||
            }
 | 
			
		||||
        `);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    constructor() {
 | 
			
		||||
        super();
 | 
			
		||||
        this.ws = new WebsocketClient();
 | 
			
		||||
        this.flowSlug = window.location.pathname.split("/")[3];
 | 
			
		||||
        if (window.location.search.includes("inspector")) {
 | 
			
		||||
            this.inspectorOpen = !this.inspectorOpen;
 | 
			
		||||
        }
 | 
			
		||||
@ -165,75 +186,68 @@ export class FlowExecutor extends Interface implements StageHost {
 | 
			
		||||
        return globalAK()?.tenant.uiTheme || UiThemeEnum.Automatic;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    submit(payload?: FlowChallengeResponseRequest): Promise<boolean> {
 | 
			
		||||
    async submit(payload?: FlowChallengeResponseRequest): Promise<boolean> {
 | 
			
		||||
        if (!payload) return Promise.reject();
 | 
			
		||||
        if (!this.challenge) return Promise.reject();
 | 
			
		||||
        // @ts-ignore
 | 
			
		||||
        payload.component = this.challenge.component;
 | 
			
		||||
        this.loading = true;
 | 
			
		||||
        return new FlowsApi(DEFAULT_CONFIG)
 | 
			
		||||
            .flowsExecutorSolve({
 | 
			
		||||
                flowSlug: this.flowSlug || "",
 | 
			
		||||
        try {
 | 
			
		||||
            const challenge = await new FlowsApi(DEFAULT_CONFIG).flowsExecutorSolve({
 | 
			
		||||
                flowSlug: this.flowSlug,
 | 
			
		||||
                query: window.location.search.substring(1),
 | 
			
		||||
                flowChallengeResponseRequest: payload,
 | 
			
		||||
            })
 | 
			
		||||
            .then((data) => {
 | 
			
		||||
                if (this.inspectorOpen) {
 | 
			
		||||
                    window.dispatchEvent(
 | 
			
		||||
                        new CustomEvent(EVENT_FLOW_ADVANCE, {
 | 
			
		||||
                            bubbles: true,
 | 
			
		||||
                            composed: true,
 | 
			
		||||
                        }),
 | 
			
		||||
                    );
 | 
			
		||||
                }
 | 
			
		||||
                this.challenge = data;
 | 
			
		||||
                if (this.challenge.flowInfo) {
 | 
			
		||||
                    this.flowInfo = this.challenge.flowInfo;
 | 
			
		||||
                }
 | 
			
		||||
                if (this.challenge.responseErrors) {
 | 
			
		||||
                    return false;
 | 
			
		||||
                }
 | 
			
		||||
                return true;
 | 
			
		||||
            })
 | 
			
		||||
            .catch((e: Error | ResponseError) => {
 | 
			
		||||
                this.errorMessage(e);
 | 
			
		||||
                return false;
 | 
			
		||||
            })
 | 
			
		||||
            .finally(() => {
 | 
			
		||||
                this.loading = false;
 | 
			
		||||
                return false;
 | 
			
		||||
            });
 | 
			
		||||
            if (this.inspectorOpen) {
 | 
			
		||||
                window.dispatchEvent(
 | 
			
		||||
                    new CustomEvent(EVENT_FLOW_ADVANCE, {
 | 
			
		||||
                        bubbles: true,
 | 
			
		||||
                        composed: true,
 | 
			
		||||
                    }),
 | 
			
		||||
                );
 | 
			
		||||
            }
 | 
			
		||||
            this.challenge = challenge;
 | 
			
		||||
            if (this.challenge.flowInfo) {
 | 
			
		||||
                this.flowInfo = this.challenge.flowInfo;
 | 
			
		||||
            }
 | 
			
		||||
            if (this.challenge.responseErrors) {
 | 
			
		||||
                return false;
 | 
			
		||||
            }
 | 
			
		||||
            return true;
 | 
			
		||||
        } catch (exc: unknown) {
 | 
			
		||||
            this.errorMessage(exc as Error | ResponseError);
 | 
			
		||||
            return false;
 | 
			
		||||
        } finally {
 | 
			
		||||
            this.loading = false;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    firstUpdated(): void {
 | 
			
		||||
    async firstUpdated(): Promise<void> {
 | 
			
		||||
        configureSentry();
 | 
			
		||||
        this.loading = true;
 | 
			
		||||
        new FlowsApi(DEFAULT_CONFIG)
 | 
			
		||||
            .flowsExecutorGet({
 | 
			
		||||
                flowSlug: this.flowSlug || "",
 | 
			
		||||
        try {
 | 
			
		||||
            const challenge = await new FlowsApi(DEFAULT_CONFIG).flowsExecutorGet({
 | 
			
		||||
                flowSlug: this.flowSlug,
 | 
			
		||||
                query: window.location.search.substring(1),
 | 
			
		||||
            })
 | 
			
		||||
            .then((challenge) => {
 | 
			
		||||
                if (this.inspectorOpen) {
 | 
			
		||||
                    window.dispatchEvent(
 | 
			
		||||
                        new CustomEvent(EVENT_FLOW_ADVANCE, {
 | 
			
		||||
                            bubbles: true,
 | 
			
		||||
                            composed: true,
 | 
			
		||||
                        }),
 | 
			
		||||
                    );
 | 
			
		||||
                }
 | 
			
		||||
                this.challenge = challenge;
 | 
			
		||||
                if (this.challenge.flowInfo) {
 | 
			
		||||
                    this.flowInfo = this.challenge.flowInfo;
 | 
			
		||||
                }
 | 
			
		||||
            })
 | 
			
		||||
            .catch((e: Error | ResponseError) => {
 | 
			
		||||
                // Catch JSON or Update errors
 | 
			
		||||
                this.errorMessage(e);
 | 
			
		||||
            })
 | 
			
		||||
            .finally(() => {
 | 
			
		||||
                this.loading = false;
 | 
			
		||||
            });
 | 
			
		||||
            if (this.inspectorOpen) {
 | 
			
		||||
                window.dispatchEvent(
 | 
			
		||||
                    new CustomEvent(EVENT_FLOW_ADVANCE, {
 | 
			
		||||
                        bubbles: true,
 | 
			
		||||
                        composed: true,
 | 
			
		||||
                    }),
 | 
			
		||||
                );
 | 
			
		||||
            }
 | 
			
		||||
            this.challenge = challenge;
 | 
			
		||||
            if (this.challenge.flowInfo) {
 | 
			
		||||
                this.flowInfo = this.challenge.flowInfo;
 | 
			
		||||
            }
 | 
			
		||||
        } catch (exc: unknown) {
 | 
			
		||||
            // Catch JSON or Update errors
 | 
			
		||||
            this.errorMessage(exc as Error | ResponseError);
 | 
			
		||||
        } finally {
 | 
			
		||||
            this.loading = false;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async errorMessage(error: Error | ResponseError): Promise<void> {
 | 
			
		||||
@ -412,12 +426,15 @@ 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" />
 | 
			
		||||
        </div>`;
 | 
			
		||||
        if (!this.challenge) {
 | 
			
		||||
            return html`<ak-empty-state ?loading=${true} header=${msg("Loading")}>
 | 
			
		||||
            </ak-empty-state>`;
 | 
			
		||||
            return html`${logo}<ak-empty-state ?loading=${true} header=${msg("Loading")}>
 | 
			
		||||
                </ak-empty-state>`;
 | 
			
		||||
        }
 | 
			
		||||
        return html`
 | 
			
		||||
            ${this.loading ? html`<ak-loading-overlay></ak-loading-overlay>` : html``}
 | 
			
		||||
            ${this.loading ? html`<ak-loading-overlay></ak-loading-overlay>` : nothing} ${logo}
 | 
			
		||||
            ${until(this.renderChallenge())}
 | 
			
		||||
        `;
 | 
			
		||||
    }
 | 
			
		||||
@ -453,43 +470,9 @@ export class FlowExecutor extends Interface implements StageHost {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    renderBackgroundOverlay(): TemplateResult {
 | 
			
		||||
        const overlaySVG = html`<svg
 | 
			
		||||
            xmlns="http://www.w3.org/2000/svg"
 | 
			
		||||
            class="pf-c-background-image__filter"
 | 
			
		||||
            width="0"
 | 
			
		||||
            height="0"
 | 
			
		||||
        >
 | 
			
		||||
            <filter id="image_overlay">
 | 
			
		||||
                <feColorMatrix
 | 
			
		||||
                    in="SourceGraphic"
 | 
			
		||||
                    type="matrix"
 | 
			
		||||
                    values="1.3 0 0 0 0 0 1.3 0 0 0 0 0 1.3 0 0 0 0 0 1 0"
 | 
			
		||||
                />
 | 
			
		||||
                <feComponentTransfer color-interpolation-filters="sRGB" result="duotone">
 | 
			
		||||
                    <feFuncR
 | 
			
		||||
                        type="table"
 | 
			
		||||
                        tableValues="0.086274509803922 0.43921568627451"
 | 
			
		||||
                    ></feFuncR>
 | 
			
		||||
                    <feFuncG
 | 
			
		||||
                        type="table"
 | 
			
		||||
                        tableValues="0.086274509803922 0.43921568627451"
 | 
			
		||||
                    ></feFuncG>
 | 
			
		||||
                    <feFuncB
 | 
			
		||||
                        type="table"
 | 
			
		||||
                        tableValues="0.086274509803922 0.43921568627451"
 | 
			
		||||
                    ></feFuncB>
 | 
			
		||||
                    <feFuncA type="table" tableValues="0 1"></feFuncA>
 | 
			
		||||
                </feComponentTransfer>
 | 
			
		||||
            </filter>
 | 
			
		||||
        </svg>`;
 | 
			
		||||
        render(overlaySVG, document.body);
 | 
			
		||||
        return overlaySVG;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    render(): TemplateResult {
 | 
			
		||||
        return html` <ak-locale-context>
 | 
			
		||||
            <div class="pf-c-background-image">${this.renderBackgroundOverlay()}</div>
 | 
			
		||||
            <div class="pf-c-background-image"></div>
 | 
			
		||||
            <div class="pf-c-page__drawer">
 | 
			
		||||
                <div class="pf-c-drawer ${this.inspectorOpen ? "pf-m-expanded" : "pf-m-collapsed"}">
 | 
			
		||||
                    <div class="pf-c-drawer__main">
 | 
			
		||||
@ -497,14 +480,6 @@ export class FlowExecutor extends Interface implements StageHost {
 | 
			
		||||
                            <div class="pf-c-drawer__body">
 | 
			
		||||
                                <div class="pf-c-login ${this.getLayout()}">
 | 
			
		||||
                                    <div class="${this.getLayoutClass()}">
 | 
			
		||||
                                        <header class="pf-c-login__header">
 | 
			
		||||
                                            <div class="pf-c-brand ak-brand">
 | 
			
		||||
                                                <img
 | 
			
		||||
                                                    src="${first(this.tenant?.brandingLogo, "")}"
 | 
			
		||||
                                                    alt="authentik Logo"
 | 
			
		||||
                                                />
 | 
			
		||||
                                            </div>
 | 
			
		||||
                                        </header>
 | 
			
		||||
                                        <div class="pf-c-login__main">
 | 
			
		||||
                                            ${this.renderChallengeWrapper()}
 | 
			
		||||
                                        </div>
 | 
			
		||||
 | 
			
		||||
@ -96,7 +96,7 @@ export class LibraryApplication extends AKElement {
 | 
			
		||||
            this.application.metaPublisher !== "" ||
 | 
			
		||||
            this.application.metaDescription !== "";
 | 
			
		||||
 | 
			
		||||
        const classes = { "pf-m-selectable pf-m-selected": this.selected };
 | 
			
		||||
        const classes = { "pf-m-selectable": this.selected, "pf-m-selected": this.selected };
 | 
			
		||||
        const styles = this.background ? { background: this.background } : {};
 | 
			
		||||
 | 
			
		||||
        return html` <div
 | 
			
		||||
 | 
			
		||||
@ -38,7 +38,9 @@ export class LibraryPageApplicationList extends AKElement {
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    @property({ attribute: false })
 | 
			
		||||
    apps: Application[] = [];
 | 
			
		||||
    set apps(value: Application[]) {
 | 
			
		||||
        this.fuse.setCollection(value);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @property()
 | 
			
		||||
    query = getURLParam<string | undefined>("search", undefined);
 | 
			
		||||
@ -63,7 +65,7 @@ export class LibraryPageApplicationList extends AKElement {
 | 
			
		||||
            shouldSort: true,
 | 
			
		||||
            ignoreFieldNorm: true,
 | 
			
		||||
            useExtendedSearch: true,
 | 
			
		||||
            threshold: 0.5,
 | 
			
		||||
            threshold: 0.3,
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -77,7 +79,6 @@ export class LibraryPageApplicationList extends AKElement {
 | 
			
		||||
 | 
			
		||||
    connectedCallback() {
 | 
			
		||||
        super.connectedCallback();
 | 
			
		||||
        this.fuse.setCollection(this.apps);
 | 
			
		||||
        if (!this.query) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user