web/flows: fix error when using consecutive webauthn validator stages (#9629)

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
This commit is contained in:
Jens L
2024-05-07 19:50:57 +02:00
committed by GitHub
parent a140bad8fb
commit 18b4b2d7b2
5 changed files with 86 additions and 69 deletions

View File

@ -5,7 +5,7 @@ import "@goauthentik/flow/FormStatic";
import { BaseStage } from "@goauthentik/flow/stages/base";
import { msg } from "@lit/localize";
import { CSSResult, TemplateResult, html } from "lit";
import { CSSResult, PropertyValues, TemplateResult, html } from "lit";
import { customElement } from "lit/decorators.js";
import { ifDefined } from "lit/directives/if-defined.js";
@ -32,14 +32,16 @@ export class AuthenticatorDuoStage extends BaseStage<
return [PFBase, PFLogin, PFForm, PFFormControl, PFTitle, PFButton];
}
firstUpdated(): void {
const i = setInterval(() => {
this.checkEnrollStatus().then((shouldStop) => {
if (shouldStop) {
clearInterval(i);
}
});
}, 3000);
updated(changedProperties: PropertyValues<this>) {
if (changedProperties.has("challenge") && this.challenge !== undefined) {
const i = setInterval(() => {
this.checkEnrollStatus().then((shouldStop) => {
if (shouldStop) {
clearInterval(i);
}
});
}, 3000);
}
}
async checkEnrollStatus(): Promise<boolean> {

View File

@ -3,7 +3,7 @@ import "@goauthentik/elements/forms/FormElement";
import { BaseDeviceStage } from "@goauthentik/flow/stages/authenticator_validate/base";
import { msg } from "@lit/localize";
import { TemplateResult, html } from "lit";
import { PropertyValues, TemplateResult, html } from "lit";
import { customElement, property, state } from "lit/decorators.js";
import {
@ -26,21 +26,23 @@ export class AuthenticatorValidateStageWebDuo extends BaseDeviceStage<
@state()
authenticating = false;
firstUpdated(): void {
this.authenticating = true;
this.host
?.submit(
{
duo: this.deviceChallenge?.deviceUid,
},
{ invisible: true },
)
.then(() => {
this.authenticating = false;
})
.catch(() => {
this.authenticating = false;
});
updated(changedProperties: PropertyValues<this>) {
if (changedProperties.has("challenge") && this.challenge !== undefined) {
this.authenticating = true;
this.host
?.submit(
{
duo: this.deviceChallenge?.deviceUid,
},
{ invisible: true },
)
.then(() => {
this.authenticating = false;
})
.catch(() => {
this.authenticating = false;
});
}
}
render(): TemplateResult {

View File

@ -7,7 +7,7 @@ import "@goauthentik/elements/EmptyState";
import { BaseDeviceStage } from "@goauthentik/flow/stages/authenticator_validate/base";
import { msg } from "@lit/localize";
import { TemplateResult, html, nothing } from "lit";
import { PropertyValues, TemplateResult, html, nothing } from "lit";
import { customElement, property, state } from "lit/decorators.js";
import {
@ -72,14 +72,16 @@ export class AuthenticatorValidateStageWebAuthn extends BaseDeviceStage<
}
}
firstUpdated(): void {
// convert certain members of the PublicKeyCredentialRequestOptions into
// byte arrays as expected by the spec.
const credentialRequestOptions = this.deviceChallenge
?.challenge as PublicKeyCredentialRequestOptions;
this.transformedCredentialRequestOptions =
transformCredentialRequestOptions(credentialRequestOptions);
this.authenticateWrapper();
updated(changedProperties: PropertyValues<this>) {
if (changedProperties.has("challenge") && this.challenge !== undefined) {
// convert certain members of the PublicKeyCredentialRequestOptions into
// byte arrays as expected by the spec.
const credentialRequestOptions = this.deviceChallenge
?.challenge as PublicKeyCredentialRequestOptions;
this.transformedCredentialRequestOptions =
transformCredentialRequestOptions(credentialRequestOptions);
this.authenticateWrapper();
}
}
async authenticateWrapper(): Promise<void> {

View File

@ -6,7 +6,7 @@ import { BaseStage } from "@goauthentik/flow/stages/base";
import type { TurnstileObject } from "turnstile-types";
import { msg } from "@lit/localize";
import { CSSResult, TemplateResult, html } from "lit";
import { CSSResult, PropertyValues, TemplateResult, html } from "lit";
import { customElement, state } from "lit/decorators.js";
import { ifDefined } from "lit/directives/if-defined.js";
@ -42,46 +42,55 @@ export class CaptchaStage extends BaseStage<CaptchaChallenge, CaptchaChallengeRe
@state()
captchaContainer: HTMLDivElement;
@state()
scriptElement?: HTMLScriptElement;
constructor() {
super();
this.captchaContainer = document.createElement("div");
this.captchaContainer.id = captchaContainerID;
}
firstUpdated(): void {
const script = document.createElement("script");
script.src = this.challenge.jsUrl;
script.async = true;
script.defer = true;
script.onload = () => {
console.debug("authentik/stages/captcha: script loaded");
let found = false;
let lastError = undefined;
this.handlers.forEach((handler) => {
let handlerFound = false;
try {
console.debug(`authentik/stages/captcha[${handler.name}]: trying handler`);
handlerFound = handler.apply(this);
if (handlerFound) {
updated(changedProperties: PropertyValues<this>) {
if (changedProperties.has("challenge") && this.challenge !== undefined) {
this.scriptElement = document.createElement("script");
this.scriptElement.src = this.challenge.jsUrl;
this.scriptElement.async = true;
this.scriptElement.defer = true;
this.scriptElement.dataset.akCaptchaScript = "true";
this.scriptElement.onload = () => {
console.debug("authentik/stages/captcha: script loaded");
let found = false;
let lastError = undefined;
this.handlers.forEach((handler) => {
let handlerFound = false;
try {
console.debug(`authentik/stages/captcha[${handler.name}]: trying handler`);
handlerFound = handler.apply(this);
if (handlerFound) {
console.debug(
`authentik/stages/captcha[${handler.name}]: handler succeeded`,
);
found = true;
}
} catch (exc) {
console.debug(
`authentik/stages/captcha[${handler.name}]: handler succeeded`,
`authentik/stages/captcha[${handler.name}]: handler failed: ${exc}`,
);
found = true;
}
} catch (exc) {
console.debug(
`authentik/stages/captcha[${handler.name}]: handler failed: ${exc}`,
);
if (handlerFound) {
lastError = exc;
if (handlerFound) {
lastError = exc;
}
}
});
if (!found && lastError) {
this.error = (lastError as Error).toString();
}
});
if (!found && lastError) {
this.error = (lastError as Error).toString();
}
};
document.head.appendChild(script);
};
document.head
.querySelectorAll("[data-ak-captcha-script=true]")
.forEach((el) => el.remove());
document.head.appendChild(this.scriptElement);
}
}
handleGReCaptcha(): boolean {

View File

@ -5,7 +5,7 @@ import "@goauthentik/elements/forms/FormElement";
import { BaseStage } from "@goauthentik/flow/stages/base";
import { msg, str } from "@lit/localize";
import { CSSResult, TemplateResult, css, html, nothing } from "lit";
import { CSSResult, PropertyValues, TemplateResult, css, html, nothing } from "lit";
import { customElement } from "lit/decorators.js";
import PFAlert from "@patternfly/patternfly/components/Alert/alert.css";
@ -63,9 +63,11 @@ export class IdentificationStage extends BaseStage<
`);
}
firstUpdated(): void {
this.autoRedirect();
this.createHelperForm();
updated(changedProperties: PropertyValues<this>) {
if (changedProperties.has("challenge") && this.challenge !== undefined) {
this.autoRedirect();
this.createHelperForm();
}
}
autoRedirect(): void {