2021-02-17 22:52:49 +00:00
|
|
|
import { gettext } from "django";
|
2020-12-01 08:15:41 +00:00
|
|
|
import { LitElement, html, customElement, property, TemplateResult } from "lit-element";
|
2021-02-17 22:52:49 +00:00
|
|
|
import { unsafeHTML } from "lit-html/directives/unsafe-html";
|
2020-12-12 22:32:55 +00:00
|
|
|
import { SentryIgnoredError } from "../../common/errors";
|
2021-02-17 19:49:58 +00:00
|
|
|
import { getCookie } from "../../utils";
|
2021-02-17 22:52:49 +00:00
|
|
|
import "../../elements/stages/identification/IdentificationStage";
|
2020-10-16 14:36:18 +00:00
|
|
|
|
2021-02-17 22:52:49 +00:00
|
|
|
enum ChallengeTypes {
|
|
|
|
native = "native",
|
|
|
|
response = "response",
|
|
|
|
shell = "shell",
|
2020-11-21 19:43:05 +00:00
|
|
|
redirect = "redirect",
|
2020-11-20 21:08:00 +00:00
|
|
|
}
|
2020-10-16 14:36:18 +00:00
|
|
|
|
2021-02-17 22:52:49 +00:00
|
|
|
interface Challenge {
|
|
|
|
type: ChallengeTypes;
|
2021-02-20 18:59:24 +00:00
|
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
|
|
args: any;
|
2021-02-20 18:41:32 +00:00
|
|
|
component?: string;
|
|
|
|
title?: string;
|
2020-11-20 21:08:00 +00:00
|
|
|
}
|
|
|
|
|
2021-02-17 22:52:49 +00:00
|
|
|
@customElement("ak-flow-executor")
|
|
|
|
export class FlowExecutor extends LitElement {
|
2020-11-20 21:08:00 +00:00
|
|
|
@property()
|
2020-12-01 08:15:41 +00:00
|
|
|
flowBodyUrl = "";
|
2020-11-20 21:08:00 +00:00
|
|
|
|
2021-02-17 22:52:49 +00:00
|
|
|
@property({attribute: false})
|
|
|
|
flowBody?: TemplateResult;
|
2020-10-16 14:36:18 +00:00
|
|
|
|
2020-12-01 08:15:41 +00:00
|
|
|
createRenderRoot(): Element | ShadowRoot {
|
2020-10-16 14:36:18 +00:00
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
2021-02-17 19:49:58 +00:00
|
|
|
constructor() {
|
|
|
|
super();
|
|
|
|
this.addEventListener("ak-flow-submit", () => {
|
2021-02-17 22:52:49 +00:00
|
|
|
this.submit();
|
2021-02-17 19:49:58 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2021-02-17 22:52:49 +00:00
|
|
|
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) => {
|
2020-11-27 17:37:56 +00:00
|
|
|
if (r.status === 404) {
|
|
|
|
// Fallback when the flow does not exist, just redirect to the root
|
|
|
|
window.location.pathname = "/";
|
|
|
|
} else if (!r.ok) {
|
2020-12-12 22:32:55 +00:00
|
|
|
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);
|
|
|
|
});
|
2020-10-16 14:36:18 +00:00
|
|
|
}
|
|
|
|
|
2021-02-17 22:52:49 +00:00
|
|
|
async updateCard(data: Challenge): Promise<void> {
|
2020-10-16 14:36:18 +00:00
|
|
|
switch (data.type) {
|
2021-02-17 22:52:49 +00:00
|
|
|
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;
|
2021-02-17 22:52:49 +00:00
|
|
|
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;
|
2021-02-17 22:52:49 +00:00
|
|
|
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-10-16 14:36:18 +00:00
|
|
|
}
|
2020-11-21 19:48:49 +00:00
|
|
|
}
|
2020-10-16 14:36:18 +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");
|
2020-10-16 14:36:18 +00:00
|
|
|
newScript.src = script.src;
|
|
|
|
document.head.appendChild(newScript);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2020-12-01 08:15:41 +00:00
|
|
|
checkAutofocus(): void {
|
2020-11-20 21:08:00 +00:00
|
|
|
const autofocusElement = <HTMLElement>this.querySelector("[autofocus]");
|
2020-10-16 14:36:18 +00:00
|
|
|
if (autofocusElement !== null) {
|
|
|
|
autofocusElement.focus();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-12-01 08:15:41 +00:00
|
|
|
updateFormAction(form: HTMLFormElement): boolean {
|
2020-10-16 14:36:18 +00:00
|
|
|
for (let index = 0; index < form.elements.length; index++) {
|
2020-11-20 21:08:00 +00:00
|
|
|
const element = <HTMLInputElement>form.elements[index];
|
2020-10-16 14:36:18 +00:00
|
|
|
if (element.value === form.action) {
|
2020-11-26 16:23:29 +00:00
|
|
|
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
|
|
|
);
|
2020-10-16 14:36:18 +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}`);
|
2020-10-16 14:36:18 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2020-12-01 08:15:41 +00:00
|
|
|
checkAutosubmit(form: HTMLFormElement): void {
|
2020-10-16 14:36:18 +00:00
|
|
|
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}`);
|
2020-10-16 14:36:18 +00:00
|
|
|
this.checkAutosubmit(form);
|
2020-12-05 21:08:42 +00:00
|
|
|
console.debug(`authentik/flows: Setting action for form ${form}`);
|
2020-10-16 14:36:18 +00:00
|
|
|
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) => {
|
2020-10-16 14:36:18 +00:00
|
|
|
e.preventDefault();
|
2020-12-01 08:15:41 +00:00
|
|
|
const formData = new FormData(form);
|
2020-10-16 14:36:18 +00:00
|
|
|
this.flowBody = undefined;
|
2021-02-17 22:52:49 +00:00
|
|
|
this.submit(formData);
|
2020-10-16 14:36:18 +00:00
|
|
|
});
|
2020-12-05 21:08:42 +00:00
|
|
|
form.classList.add("ak-flow-wrapped");
|
2020-10-16 14:36:18 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2020-12-01 08:15:41 +00:00
|
|
|
errorMessage(error: string): void {
|
2021-02-17 22:52:49 +00:00
|
|
|
this.flowBody = html`
|
2020-10-26 09:52:13 +00:00
|
|
|
<style>
|
2020-12-05 21:08:42 +00:00
|
|
|
.ak-exception {
|
2020-10-26 09:52:13 +00:00
|
|
|
font-family: monospace;
|
|
|
|
overflow-x: scroll;
|
|
|
|
}
|
|
|
|
</style>
|
|
|
|
<header class="pf-c-login__main-header">
|
|
|
|
<h1 class="pf-c-title pf-m-3xl">
|
2021-02-17 22:52:49 +00:00
|
|
|
${gettext("Whoops!")}
|
2020-10-26 09:52:13 +00:00
|
|
|
</h1>
|
|
|
|
</header>
|
|
|
|
<div class="pf-c-login__main-body">
|
2021-02-17 22:52:49 +00:00
|
|
|
<h3>${gettext("Something went wrong! Please try again later.")}</h3>
|
2020-12-05 21:08:42 +00:00
|
|
|
<pre class="ak-exception">${error}</pre>
|
2020-10-26 09:52:13 +00:00
|
|
|
</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-10-16 14:36:18 +00:00
|
|
|
}
|
|
|
|
|
2020-12-01 08:15:41 +00:00
|
|
|
render(): TemplateResult {
|
2020-11-21 19:43:05 +00:00
|
|
|
if (this.flowBody) {
|
2021-02-17 22:52:49 +00:00
|
|
|
return this.flowBody;
|
2020-10-16 14:36:18 +00:00
|
|
|
}
|
|
|
|
return this.loading();
|
|
|
|
}
|
|
|
|
}
|