import { t } from "@lingui/macro"; import { CSSResult, LitElement, TemplateResult, html } from "lit"; import { customElement, property } from "lit/decorators.js"; import { unsafeHTML } from "lit/directives/unsafe-html.js"; import AKGlobal from "../../../authentik.css"; import PFButton from "@patternfly/patternfly/components/Button/button.css"; import PFCard from "@patternfly/patternfly/components/Card/card.css"; import PFContent from "@patternfly/patternfly/components/Content/content.css"; import PFPage from "@patternfly/patternfly/components/Page/page.css"; import PFBase from "@patternfly/patternfly/patternfly-base.css"; import { ChallengeChoices, ChallengeTypes, CurrentTenant, FlowChallengeResponseRequest, FlowsApi, RedirectChallenge, ShellChallenge, } from "@goauthentik/api"; import { DEFAULT_CONFIG, tenant } from "../../../api/Config"; import { refreshMe } from "../../../api/Users"; import { EVENT_REFRESH } from "../../../constants"; import "../../../elements/LoadingOverlay"; import { MessageLevel } from "../../../elements/messages/Message"; import { showMessage } from "../../../elements/messages/MessageContainer"; import { StageHost } from "../../../flows/stages/base"; import "./stages/prompt/PromptStage"; @customElement("ak-user-settings-flow-executor") export class UserSettingsFlowExecutor extends LitElement implements StageHost { @property() flowSlug?: string; private _challenge?: ChallengeTypes; @property({ attribute: false }) set challenge(value: ChallengeTypes | undefined) { this._challenge = value; this.requestUpdate(); } get challenge(): ChallengeTypes | undefined { return this._challenge; } @property({ type: Boolean }) loading = false; @property({ attribute: false }) tenant!: CurrentTenant; static get styles(): CSSResult[] { return [PFBase, PFCard, PFPage, PFButton, PFContent, AKGlobal]; } constructor() { super(); tenant().then((tenant) => (this.tenant = tenant)); } submit(payload?: FlowChallengeResponseRequest): Promise { 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 || "", query: window.location.search.substring(1), flowChallengeResponseRequest: payload, }) .then((data) => { showMessage({ level: MessageLevel.success, message: t`Successfully updated details`, }); this.challenge = data; if (this.challenge.responseErrors) { return false; } return true; }) .catch((e: Error | Response) => { this.errorMessage(e); return false; }) .finally(() => { this.loading = false; return false; }); } firstUpdated(): void { tenant().then((tenant) => { this.flowSlug = tenant.flowUserSettings; if (!this.flowSlug) { return; } new FlowsApi(DEFAULT_CONFIG).flowsInstancesExecuteRetrieve({ slug: this.flowSlug || "", }).then(() => { this.nextChallenge(); }) }); } nextChallenge(): void { this.loading = true; new FlowsApi(DEFAULT_CONFIG) .flowsExecutorGet({ flowSlug: this.flowSlug || "", query: window.location.search.substring(1), }) .then((challenge) => { this.challenge = challenge; }) .catch((e: Error | Response) => { // Catch JSON or Update errors this.errorMessage(e); }) .finally(() => { this.loading = false; }); } async errorMessage(error: Error | Response): Promise { let body = ""; if (error instanceof Error) { body = error.message; } this.challenge = { type: ChallengeChoices.Shell, body: ` `, } as ChallengeTypes; } globalRefresh(): void { refreshMe().then(() => { this.dispatchEvent( new CustomEvent(EVENT_REFRESH, { bubbles: true, composed: true, }), ); try { document.querySelectorAll("ak-interface-user").forEach((int) => { (int as LitElement).requestUpdate(); }); } catch { console.debug("authentik/user/flows: failed to find interface to refresh"); } }); } renderChallenge(): TemplateResult { if (!this.challenge) { return html``; } switch (this.challenge.type) { case ChallengeChoices.Redirect: if ((this.challenge as RedirectChallenge).to !== "/") { return html`${"Edit settings"}`; } // Flow has finished, so let's load while in the background we can restart the flow this.loading = true; console.debug("authentik/user/flows: redirect to '/', restarting flow."); this.firstUpdated(); this.globalRefresh(); return html``; case ChallengeChoices.Shell: return html`${unsafeHTML((this.challenge as ShellChallenge).body)}`; case ChallengeChoices.Native: switch (this.challenge.component) { case "ak-stage-prompt": return html``; default: console.log( `authentik/user/flows: unsupported stage type ${this.challenge.component}`, ); return html` ${t`Open settings`} `; } default: console.debug(`authentik/user/flows: unexpected data type ${this.challenge.type}`); break; } return html``; } renderChallengeWrapper(): TemplateResult { if (!this.flowSlug) { return html`

${t`No settings flow configured.`}

`; } if (!this.challenge) { return html` `; } return html` ${this.renderChallenge()} `; } render(): TemplateResult { return html`${this.loading ? html`` : html``}
${t`Update details`}
${this.renderChallengeWrapper()}
`; } }