import { LitElement, html, customElement, property, TemplateResult } from "lit-element"; import { SentryIgnoredError } from "../../common/errors"; enum ResponseType { redirect = "redirect", template = "template", } interface Response { type: ResponseType; to?: string; body?: string; } @customElement("ak-flow-shell-card") export class FlowShellCard extends LitElement { @property() flowBodyUrl = ""; @property() flowBody?: string; createRenderRoot(): Element | ShadowRoot { return this; } firstUpdated(): void { fetch(this.flowBodyUrl) .then((r) => { if (r.status === 404) { // Fallback when the flow does not exist, just redirect to the root window.location.pathname = "/"; } else if (!r.ok) { throw new SentryIgnoredError(r.statusText); } return r; }) .then((r) => { return r.json(); }) .then((r) => { this.updateCard(r); }) .catch((e) => { // Catch JSON or Update errors this.errorMessage(e); }); } async updateCard(data: Response): Promise { switch (data.type) { case ResponseType.redirect: window.location.assign(data.to || ""); break; case ResponseType.template: this.flowBody = data.body; await this.requestUpdate(); this.checkAutofocus(); this.loadFormCode(); this.setFormSubmitHandlers(); break; default: console.debug(`authentik/flows: unexpected data type ${data.type}`); break; } } loadFormCode(): void { this.querySelectorAll("script").forEach((script) => { const newScript = document.createElement("script"); newScript.src = script.src; document.head.appendChild(newScript); }); } checkAutofocus(): void { const autofocusElement = this.querySelector("[autofocus]"); if (autofocusElement !== null) { autofocusElement.focus(); } } updateFormAction(form: HTMLFormElement): boolean { for (let index = 0; index < form.elements.length; index++) { const element = form.elements[index]; if (element.value === form.action) { console.debug( "authentik/flows: Found Form action URL in form elements, not changing form action." ); return false; } } form.action = this.flowBodyUrl; console.debug(`authentik/flows: updated form.action ${this.flowBodyUrl}`); return true; } checkAutosubmit(form: HTMLFormElement): void { if ("autosubmit" in form.attributes) { return form.submit(); } } setFormSubmitHandlers(): void { this.querySelectorAll("form").forEach((form) => { console.debug(`authentik/flows: Checking for autosubmit attribute ${form}`); this.checkAutosubmit(form); console.debug(`authentik/flows: Setting action for form ${form}`); this.updateFormAction(form); console.debug(`authentik/flows: Adding handler for form ${form}`); form.addEventListener("submit", (e) => { e.preventDefault(); const formData = new FormData(form); this.flowBody = undefined; fetch(this.flowBodyUrl, { method: "post", body: formData, }) .then((response) => { return response.json(); }) .then((data) => { this.updateCard(data); }) .catch((e) => { this.errorMessage(e); }); }); form.classList.add("ak-flow-wrapped"); }); } errorMessage(error: string): void { this.flowBody = ` `; } loading(): TemplateResult { return html` `; } render(): TemplateResult { if (this.flowBody) { return html(([this.flowBody])); } return this.loading(); } }