This repository has been archived on 2024-05-31. You can view files and clone it, but cannot push or open issues or pull requests.
authentik/web/src/pages/generic/FlowExecutor.ts

204 lines
6.4 KiB
TypeScript
Raw Normal View History

import { gettext } from "django";
2020-12-01 08:15:41 +00:00
import { LitElement, html, customElement, property, TemplateResult } from "lit-element";
import { unsafeHTML } from "lit-html/directives/unsafe-html";
import { SentryIgnoredError } from "../../common/errors";
import { getCookie } from "../../utils";
import "../../elements/stages/identification/IdentificationStage";
enum ChallengeTypes {
native = "native",
response = "response",
shell = "shell",
redirect = "redirect",
}
interface Challenge {
type: ChallengeTypes;
args: { [key: string]: string };
component?: string;
title?: string;
}
@customElement("ak-flow-executor")
export class FlowExecutor extends LitElement {
@property()
2020-12-01 08:15:41 +00:00
flowBodyUrl = "";
@property({attribute: false})
flowBody?: TemplateResult;
2020-12-01 08:15:41 +00:00
createRenderRoot(): Element | ShadowRoot {
return this;
}
constructor() {
super();
this.addEventListener("ak-flow-submit", () => {
this.submit();
});
}
submit(formData?: FormData): void {
const csrftoken = getCookie("authentik_csrf");
const request = new Request(this.flowBodyUrl, {
headers: {
"X-CSRFToken": csrftoken,
},
});
fetch(request, {
method: "POST",
mode: "same-origin",
body: formData,
})
.then((response) => {
return response.json();
})
.then((data) => {
this.updateCard(data);
})
.catch((e) => {
this.errorMessage(e);
});
}
2020-12-01 08:15:41 +00:00
firstUpdated(): void {
2020-11-21 19:48:49 +00:00
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);
2020-11-21 19:48:49 +00:00
}
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: Challenge): Promise<void> {
switch (data.type) {
case ChallengeTypes.redirect:
console.debug(`authentik/flows: redirecting to ${data.args.to}`);
window.location.assign(data.args.to || "");
2020-12-01 08:15:41 +00:00
break;
case ChallengeTypes.shell:
this.flowBody = html`${unsafeHTML(data.args.body)}`;
2020-12-01 08:15:41 +00:00
await this.requestUpdate();
this.checkAutofocus();
this.loadFormCode();
this.setFormSubmitHandlers();
break;
case ChallengeTypes.native:
switch (data.component) {
case "ak-stage-identification":
this.flowBody = html`<ak-stage-identification .host=${this} .args=${data.args}></ak-stage-identification>`;
break;
default:
break;
}
break;
2020-12-01 08:15:41 +00:00
default:
2020-12-05 21:08:42 +00:00
console.debug(`authentik/flows: unexpected data type ${data.type}`);
2020-12-01 08:15:41 +00:00
break;
}
2020-11-21 19:48:49 +00:00
}
2020-12-01 08:15:41 +00:00
loadFormCode(): void {
2020-11-21 19:48:49 +00:00
this.querySelectorAll("script").forEach((script) => {
2020-12-01 08:15:41 +00:00
const newScript = document.createElement("script");
newScript.src = script.src;
document.head.appendChild(newScript);
});
}
2020-12-01 08:15:41 +00:00
checkAutofocus(): void {
const autofocusElement = <HTMLElement>this.querySelector("[autofocus]");
if (autofocusElement !== null) {
autofocusElement.focus();
}
}
2020-12-01 08:15:41 +00:00
updateFormAction(form: HTMLFormElement): boolean {
for (let index = 0; index < form.elements.length; index++) {
const element = <HTMLInputElement>form.elements[index];
if (element.value === form.action) {
console.debug(
2020-12-05 21:08:42 +00:00
"authentik/flows: Found Form action URL in form elements, not changing form action."
2020-11-21 19:48:49 +00:00
);
return false;
}
}
form.action = this.flowBodyUrl;
2020-12-05 21:08:42 +00:00
console.debug(`authentik/flows: updated form.action ${this.flowBodyUrl}`);
return true;
}
2020-12-01 08:15:41 +00:00
checkAutosubmit(form: HTMLFormElement): void {
if ("autosubmit" in form.attributes) {
return form.submit();
}
}
2020-12-01 08:15:41 +00:00
setFormSubmitHandlers(): void {
2020-11-21 19:48:49 +00:00
this.querySelectorAll("form").forEach((form) => {
2020-12-05 21:08:42 +00:00
console.debug(`authentik/flows: Checking for autosubmit attribute ${form}`);
this.checkAutosubmit(form);
2020-12-05 21:08:42 +00:00
console.debug(`authentik/flows: Setting action for form ${form}`);
this.updateFormAction(form);
2020-12-05 21:08:42 +00:00
console.debug(`authentik/flows: Adding handler for form ${form}`);
2020-11-21 19:48:49 +00:00
form.addEventListener("submit", (e) => {
e.preventDefault();
2020-12-01 08:15:41 +00:00
const formData = new FormData(form);
this.flowBody = undefined;
this.submit(formData);
});
2020-12-05 21:08:42 +00:00
form.classList.add("ak-flow-wrapped");
});
}
2020-12-01 08:15:41 +00:00
errorMessage(error: string): void {
this.flowBody = html`
<style>
2020-12-05 21:08:42 +00:00
.ak-exception {
font-family: monospace;
overflow-x: scroll;
}
</style>
<header class="pf-c-login__main-header">
<h1 class="pf-c-title pf-m-3xl">
${gettext("Whoops!")}
</h1>
</header>
<div class="pf-c-login__main-body">
<h3>${gettext("Something went wrong! Please try again later.")}</h3>
2020-12-05 21:08:42 +00:00
<pre class="ak-exception">${error}</pre>
</div>`;
}
2020-12-01 08:15:41 +00:00
loading(): TemplateResult {
2020-12-05 21:08:42 +00:00
return html` <div class="pf-c-login__main-body ak-loading">
2020-11-26 22:35:59 +00:00
<span class="pf-c-spinner" role="progressbar" aria-valuetext="Loading...">
2020-11-21 19:48:49 +00:00
<span class="pf-c-spinner__clipper"></span>
<span class="pf-c-spinner__lead-ball"></span>
<span class="pf-c-spinner__tail-ball"></span>
</span>
</div>`;
}
2020-12-01 08:15:41 +00:00
render(): TemplateResult {
if (this.flowBody) {
return this.flowBody;
}
return this.loading();
}
}