web/flows: clean up loading, syntax and transitions (#10792)

* remove redundant bindings to ${true}

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* better ui for loading during autosubmit

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* default to loading label when setting ?loading

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* remove more html``

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* refactor non_field_errors

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* remove more html``

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* no loading label for overlay

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix py

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* Revert "web: bump the wdio group across 2 directories with 5 updates (#10945)"

This reverts commit ea14c57989.

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
This commit is contained in:
Jens L.
2024-08-16 14:10:08 +02:00
committed by GitHub
parent 4b21588d8b
commit c3d3646645
36 changed files with 2454 additions and 3829 deletions

View File

@ -134,8 +134,7 @@ class LicenseKey:
exp_ts = int(mktime(lic.expiry.timetuple())) exp_ts = int(mktime(lic.expiry.timetuple()))
if total.exp == 0: if total.exp == 0:
total.exp = exp_ts total.exp = exp_ts
if exp_ts <= total.exp: total.exp = min(total.exp, exp_ts)
total.exp = exp_ts
total.license_flags.extend(lic.status.license_flags) total.license_flags.extend(lic.status.license_flags)
return total return total

File diff suppressed because it is too large Load Diff

View File

@ -6,10 +6,10 @@
"@trivago/prettier-plugin-sort-imports": "^4.3.0", "@trivago/prettier-plugin-sort-imports": "^4.3.0",
"@typescript-eslint/eslint-plugin": "^7.17.0", "@typescript-eslint/eslint-plugin": "^7.17.0",
"@typescript-eslint/parser": "^7.17.0", "@typescript-eslint/parser": "^7.17.0",
"@wdio/cli": "^9.0.1", "@wdio/cli": "^8.40.2",
"@wdio/local-runner": "^9.0.1", "@wdio/local-runner": "^8.40.2",
"@wdio/mocha-framework": "^9.0.0", "@wdio/mocha-framework": "^8.40.2",
"@wdio/spec-reporter": "^9.0.0", "@wdio/spec-reporter": "^8.39.0",
"eslint": "^8.57.0", "eslint": "^8.57.0",
"eslint-config-google": "^0.14.0", "eslint-config-google": "^0.14.0",
"eslint-plugin-sonarjs": "^1.0.3", "eslint-plugin-sonarjs": "^1.0.3",

2515
web/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -74,10 +74,10 @@
"@types/showdown": "^2.0.6", "@types/showdown": "^2.0.6",
"@typescript-eslint/eslint-plugin": "^8.0.1", "@typescript-eslint/eslint-plugin": "^8.0.1",
"@typescript-eslint/parser": "^8.0.1", "@typescript-eslint/parser": "^8.0.1",
"@wdio/browser-runner": "^9.0.1", "@wdio/browser-runner": "^8.40.2",
"@wdio/cli": "^9.0.1", "@wdio/cli": "^8.40.2",
"@wdio/mocha-framework": "^9.0.0", "@wdio/mocha-framework": "^8.40.2",
"@wdio/spec-reporter": "^9.0.0", "@wdio/spec-reporter": "^8.36.1",
"babel-plugin-macros": "^3.1.0", "babel-plugin-macros": "^3.1.0",
"babel-plugin-tsconfig-paths": "^1.0.3", "babel-plugin-tsconfig-paths": "^1.0.3",
"chokidar": "^3.6.0", "chokidar": "^3.6.0",

View File

@ -2,6 +2,7 @@ import { PFSize } from "@goauthentik/common/enums.js";
import { AKElement } from "@goauthentik/elements/Base"; import { AKElement } from "@goauthentik/elements/Base";
import "@goauthentik/elements/Spinner"; import "@goauthentik/elements/Spinner";
import { msg } from "@lit/localize";
import { CSSResult, TemplateResult, css, html } from "lit"; import { CSSResult, TemplateResult, css, html } from "lit";
import { customElement, property } from "lit/decorators.js"; import { customElement, property } from "lit/decorators.js";
@ -21,7 +22,7 @@ export class EmptyState extends AKElement {
fullHeight = false; fullHeight = false;
@property() @property()
header = ""; header?: string;
static get styles(): CSSResult[] { static get styles(): CSSResult[] {
return [ return [
@ -49,7 +50,9 @@ export class EmptyState extends AKElement {
"fa-question-circle"} pf-c-empty-state__icon" "fa-question-circle"} pf-c-empty-state__icon"
aria-hidden="true" aria-hidden="true"
></i>`} ></i>`}
<h1 class="pf-c-title pf-m-lg">${this.header}</h1> <h1 class="pf-c-title pf-m-lg">
${this.loading && this.header === undefined ? msg("Loading") : this.header}
</h1>
<div class="pf-c-empty-state__body"> <div class="pf-c-empty-state__body">
<slot name="body"></slot> <slot name="body"></slot>
</div> </div>

View File

@ -33,7 +33,7 @@ export class LoadingOverlay extends AKElement {
} }
render(): TemplateResult { render(): TemplateResult {
return html`<ak-empty-state ?loading="${true}"> return html`<ak-empty-state loading header="">
<slot name="body" slot="body"></slot> <slot name="body" slot="body"></slot>
</ak-empty-state>`; </ak-empty-state>`;
} }

View File

@ -11,6 +11,7 @@ import { WebsocketClient } from "@goauthentik/common/ws";
import { Interface } from "@goauthentik/elements/Interface"; import { Interface } from "@goauthentik/elements/Interface";
import "@goauthentik/elements/LoadingOverlay"; import "@goauthentik/elements/LoadingOverlay";
import "@goauthentik/elements/ak-locale-context"; import "@goauthentik/elements/ak-locale-context";
import { DefaultBrand } from "@goauthentik/elements/sidebar/SidebarBrand";
import { themeImage } from "@goauthentik/elements/utils/images"; import { themeImage } from "@goauthentik/elements/utils/images";
import "@goauthentik/flow/sources/apple/AppleLoginInit"; import "@goauthentik/flow/sources/apple/AppleLoginInit";
import "@goauthentik/flow/sources/plex/PlexLoginInit"; import "@goauthentik/flow/sources/plex/PlexLoginInit";
@ -281,8 +282,7 @@ export class FlowExecutor extends Interface implements StageHost {
async renderChallenge(): Promise<TemplateResult> { async renderChallenge(): Promise<TemplateResult> {
if (!this.challenge) { if (!this.challenge) {
return html`<ak-empty-state ?loading=${true} header=${msg("Loading")}> return html`<ak-empty-state loading> </ak-empty-state>`;
</ak-empty-state>`;
} }
switch (this.challenge?.component) { switch (this.challenge?.component) {
case "ak-stage-access-denied": case "ak-stage-access-denied":
@ -428,28 +428,9 @@ export class FlowExecutor extends Interface implements StageHost {
} }
} }
renderChallengeWrapper(): TemplateResult { async renderInspector() {
const logo = html`<div class="pf-c-login__main-header pf-c-brand ak-brand">
<img
src="${themeImage(
first(this.brand?.brandingLogo, globalAK()?.brand.brandingLogo, ""),
)}"
alt="authentik Logo"
/>
</div>`;
if (!this.challenge) {
return html`${logo}<ak-empty-state ?loading=${true} header=${msg("Loading")}>
</ak-empty-state>`;
}
return html`
${this.loading ? html`<ak-loading-overlay></ak-loading-overlay>` : nothing} ${logo}
${until(this.renderChallenge())}
`;
}
async renderInspector(): Promise<TemplateResult> {
if (!this.inspectorOpen) { if (!this.inspectorOpen) {
return html``; return nothing;
} }
await import("@goauthentik/flow/FlowInspector"); await import("@goauthentik/flow/FlowInspector");
return html`<ak-flow-inspector return html`<ak-flow-inspector
@ -489,7 +470,24 @@ export class FlowExecutor extends Interface implements StageHost {
<div class="pf-c-login ${this.getLayout()}"> <div class="pf-c-login ${this.getLayout()}">
<div class="${this.getLayoutClass()}"> <div class="${this.getLayoutClass()}">
<div class="pf-c-login__main"> <div class="pf-c-login__main">
${this.renderChallengeWrapper()} ${this.loading && this.challenge
? html`<ak-loading-overlay></ak-loading-overlay>`
: nothing}
<div
class="pf-c-login__main-header pf-c-brand ak-brand"
>
<img
src="${themeImage(
first(
this.brand?.brandingLogo,
globalAK()?.brand.brandingLogo,
DefaultBrand.brandingLogo,
),
)}"
alt="authentik Logo"
/>
</div>
${until(this.renderChallenge())}
</div> </div>
<footer class="pf-c-login__footer"> <footer class="pf-c-login__footer">
<ul class="pf-c-list pf-m-inline"> <ul class="pf-c-list pf-m-inline">

View File

@ -4,7 +4,7 @@ import { AKElement } from "@goauthentik/elements/Base";
import "@goauthentik/elements/Expand"; import "@goauthentik/elements/Expand";
import { msg } from "@lit/localize"; import { msg } from "@lit/localize";
import { CSSResult, TemplateResult, css, html } from "lit"; import { CSSResult, TemplateResult, css, html, nothing } from "lit";
import { customElement, property } from "lit/decorators.js"; import { customElement, property } from "lit/decorators.js";
import PFButton from "@patternfly/patternfly/components/Button/button.css"; import PFButton from "@patternfly/patternfly/components/Button/button.css";
@ -116,8 +116,7 @@ export class FlowInspector extends AKElement {
} }
if (!this.state) { if (!this.state) {
this.advanceHandler(); this.advanceHandler();
return html`<ak-empty-state ?loading="${true}" header=${msg("Loading")}> return html`<ak-empty-state loading> </ak-empty-state>`;
</ak-empty-state>`;
} }
return html`<div class="pf-c-drawer__body pf-m-no-padding"> return html`<div class="pf-c-drawer__body pf-m-no-padding">
<div class="pf-c-notification-drawer"> <div class="pf-c-notification-drawer">
@ -269,7 +268,7 @@ ${JSON.stringify(this.getStage(this.state.currentPlan?.nextPlannedStage?.stageOb
</div> </div>
</div> </div>
</li>` </li>`
: html``} : nothing}
${this.state.currentPlan?.nextPlannedStage && ${this.state.currentPlan?.nextPlannedStage &&
!this.state.isCompleted !this.state.isCompleted
? html`<li ? html`<li
@ -297,7 +296,7 @@ ${JSON.stringify(this.getStage(this.state.currentPlan?.nextPlannedStage?.stageOb
</div> </div>
</div> </div>
</li>` </li>`
: html``} : nothing}
</ol> </ol>
</div> </div>
</div> </div>

View File

@ -30,8 +30,7 @@ export class OAuth2DeviceCode extends BaseStage<
render(): TemplateResult { render(): TemplateResult {
if (!this.challenge) { if (!this.challenge) {
return html`<ak-empty-state ?loading="${true}" header=${msg("Loading")}> return html`<ak-empty-state loading> </ak-empty-state>`;
</ak-empty-state>`;
} }
return html`<header class="pf-c-login__main-header"> return html`<header class="pf-c-login__main-header">
<h1 class="pf-c-title pf-m-3xl">${this.challenge.flowInfo?.title}</h1> <h1 class="pf-c-title pf-m-3xl">${this.challenge.flowInfo?.title}</h1>
@ -46,7 +45,7 @@ export class OAuth2DeviceCode extends BaseStage<
<p>${msg("Enter the code shown on your device.")}</p> <p>${msg("Enter the code shown on your device.")}</p>
<ak-form-element <ak-form-element
label="${msg("Code")}" label="${msg("Code")}"
?required="${true}" required
class="pf-c-form__group" class="pf-c-form__group"
.errors=${(this.challenge?.responseErrors || {})["code"]} .errors=${(this.challenge?.responseErrors || {})["code"]}
> >

View File

@ -15,8 +15,7 @@ export class DeviceCodeFinish extends BaseStage<
> { > {
render(): TemplateResult { render(): TemplateResult {
if (!this.challenge) { if (!this.challenge) {
return html`<ak-empty-state ?loading="${true}" header=${msg("Loading")}> return html`<ak-empty-state loading> </ak-empty-state>`;
</ak-empty-state>`;
} }
return html`<ak-empty-state return html`<ak-empty-state
icon="fas fa-check" icon="fas fa-check"

View File

@ -40,11 +40,11 @@ export class AppleLoginInit extends BaseStage<AppleLoginChallenge, AppleChalleng
this.isModalShown = true; this.isModalShown = true;
}; };
document.head.append(appleAuth); document.head.append(appleAuth);
//Listen for authorization success // Listen for authorization success
document.addEventListener("AppleIDSignInOnSuccess", () => { document.addEventListener("AppleIDSignInOnSuccess", () => {
//handle successful response //handle successful response
}); });
//Listen for authorization failures // Listen for authorization failures
document.addEventListener("AppleIDSignInOnFailure", (error) => { document.addEventListener("AppleIDSignInOnFailure", (error) => {
console.warn(error); console.warn(error);
this.isModalShown = false; this.isModalShown = false;
@ -57,7 +57,7 @@ export class AppleLoginInit extends BaseStage<AppleLoginChallenge, AppleChalleng
</header> </header>
<div class="pf-c-login__main-body"> <div class="pf-c-login__main-body">
<form class="pf-c-form"> <form class="pf-c-form">
<ak-empty-state ?loading="${true}"> </ak-empty-state> <ak-empty-state loading></ak-empty-state>
${!this.isModalShown ${!this.isModalShown
? html`<button ? html`<button
class="pf-c-button pf-m-primary pf-m-block" class="pf-c-button pf-m-primary pf-m-block"

View File

@ -72,10 +72,7 @@ export class PlexLoginInit extends BaseStage<
</header> </header>
<div class="pf-c-login__main-body"> <div class="pf-c-login__main-body">
<form class="pf-c-form"> <form class="pf-c-form">
<ak-empty-state <ak-empty-state loading header=${msg("Waiting for authentication...")}>
?loading="${true}"
header=${msg("Waiting for authentication...")}
>
</ak-empty-state> </ak-empty-state>
<hr class="pf-c-divider" /> <hr class="pf-c-divider" />
<p>${msg("If no Plex popup opens, click the button below.")}</p> <p>${msg("If no Plex popup opens, click the button below.")}</p>

View File

@ -3,7 +3,7 @@ import "@goauthentik/flow/FormStatic";
import { BaseStage } from "@goauthentik/flow/stages/base"; import { BaseStage } from "@goauthentik/flow/stages/base";
import { msg } from "@lit/localize"; import { msg } from "@lit/localize";
import { CSSResult, TemplateResult, css, html } from "lit"; import { CSSResult, TemplateResult, css, html, nothing } from "lit";
import { customElement } from "lit/decorators.js"; import { customElement } from "lit/decorators.js";
import PFForm from "@patternfly/patternfly/components/Form/form.css"; import PFForm from "@patternfly/patternfly/components/Form/form.css";
@ -38,8 +38,7 @@ export class FlowErrorStage extends BaseStage<FlowErrorChallenge, FlowChallengeR
render(): TemplateResult { render(): TemplateResult {
if (!this.challenge) { if (!this.challenge) {
return html`<ak-empty-state ?loading="${true}" header=${msg("Loading")}> return html`<ak-empty-state loading> </ak-empty-state>`;
</ak-empty-state>`;
} }
return html`<header class="pf-c-login__main-header"> return html`<header class="pf-c-login__main-header">
<h1 class="pf-c-title pf-m-3xl">${this.challenge.flowInfo?.title}</h1> <h1 class="pf-c-title pf-m-3xl">${this.challenge.flowInfo?.title}</h1>
@ -57,13 +56,13 @@ export class FlowErrorStage extends BaseStage<FlowErrorChallenge, FlowChallengeR
? html`<div class="pf-c-form__group"> ? html`<div class="pf-c-form__group">
<pre class="ak-exception">${this.challenge.traceback}</pre> <pre class="ak-exception">${this.challenge.traceback}</pre>
</div>` </div>`
: html``} : nothing}
${this.challenge?.requestId ${this.challenge?.requestId
? html`<div class="pf-c-form__group"> ? html`<div class="pf-c-form__group">
<p>${msg("Request ID")}</p> <p>${msg("Request ID")}</p>
<code>${this.challenge.requestId}</code> <code>${this.challenge.requestId}</code>
</div>` </div>`
: html``} : nothing}
</div> </div>
</ak-empty-state> </ak-empty-state>
</form> </form>

View File

@ -66,7 +66,7 @@ export class RedirectStage extends BaseStage<RedirectChallenge, FlowChallengeRes
> >
</ak-empty-state>`; </ak-empty-state>`;
} }
return html`<ak-empty-state ?loading=${true} header=${msg("Loading")}> </ak-empty-state>`; return html`<ak-empty-state loading header=${msg("Loading")}> </ak-empty-state>`;
} }
render(): TemplateResult { render(): TemplateResult {

View File

@ -26,8 +26,7 @@ export class AccessDeniedStage extends BaseStage<
render(): TemplateResult { render(): TemplateResult {
if (!this.challenge) { if (!this.challenge) {
return html`<ak-empty-state ?loading="${true}" header=${msg("Loading")}> return html`<ak-empty-state loading> </ak-empty-state>`;
</ak-empty-state>`;
} }
return html`<header class="pf-c-login__main-header"> return html`<header class="pf-c-login__main-header">
<h1 class="pf-c-title pf-m-3xl">${this.challenge.flowInfo?.title}</h1> <h1 class="pf-c-title pf-m-3xl">${this.challenge.flowInfo?.title}</h1>

View File

@ -65,8 +65,7 @@ export class AuthenticatorDuoStage extends BaseStage<
render(): TemplateResult { render(): TemplateResult {
if (!this.challenge) { if (!this.challenge) {
return html`<ak-empty-state ?loading="${true}" header=${msg("Loading")}> return html`<ak-empty-state loading> </ak-empty-state>`;
</ak-empty-state>`;
} }
return html`<header class="pf-c-login__main-header"> return html`<header class="pf-c-login__main-header">
<h1 class="pf-c-title pf-m-3xl">${this.challenge.flowInfo?.title}</h1> <h1 class="pf-c-title pf-m-3xl">${this.challenge.flowInfo?.title}</h1>

View File

@ -54,7 +54,7 @@ export class AuthenticatorSMSStage extends BaseStage<
</ak-form-static> </ak-form-static>
<ak-form-element <ak-form-element
label="${msg("Phone number")}" label="${msg("Phone number")}"
?required="${true}" required
class="pf-c-form__group" class="pf-c-form__group"
.errors=${(this.challenge?.responseErrors || {})["phone_number"]} .errors=${(this.challenge?.responseErrors || {})["phone_number"]}
> >
@ -68,11 +68,7 @@ export class AuthenticatorSMSStage extends BaseStage<
required required
/> />
</ak-form-element> </ak-form-element>
${"non_field_errors" in (this.challenge?.responseErrors || {}) ${this.renderNonFieldErrors()}
? this.renderNonFieldErrors(
this.challenge?.responseErrors?.non_field_errors || [],
)
: html``}
<div class="pf-c-form__group pf-m-action"> <div class="pf-c-form__group pf-m-action">
<button type="submit" class="pf-c-button pf-m-primary pf-m-block"> <button type="submit" class="pf-c-button pf-m-primary pf-m-block">
${msg("Continue")} ${msg("Continue")}
@ -109,11 +105,10 @@ export class AuthenticatorSMSStage extends BaseStage<
</ak-form-static> </ak-form-static>
<ak-form-element <ak-form-element
label="${msg("Code")}" label="${msg("Code")}"
?required="${true}" required
class="pf-c-form__group" class="pf-c-form__group"
.errors=${(this.challenge?.responseErrors || {})["code"]} .errors=${(this.challenge?.responseErrors || {})["code"]}
> >
<!-- @ts-ignore -->
<input <input
type="text" type="text"
name="code" name="code"
@ -126,11 +121,7 @@ export class AuthenticatorSMSStage extends BaseStage<
required required
/> />
</ak-form-element> </ak-form-element>
${"non_field_errors" in (this.challenge?.responseErrors || {}) ${this.renderNonFieldErrors()}
? this.renderNonFieldErrors(
this.challenge?.responseErrors?.non_field_errors || [],
)
: html``}
<div class="pf-c-form__group pf-m-action"> <div class="pf-c-form__group pf-m-action">
<button type="submit" class="pf-c-button pf-m-primary pf-m-block"> <button type="submit" class="pf-c-button pf-m-primary pf-m-block">
${msg("Continue")} ${msg("Continue")}
@ -145,8 +136,7 @@ export class AuthenticatorSMSStage extends BaseStage<
render(): TemplateResult { render(): TemplateResult {
if (!this.challenge) { if (!this.challenge) {
return html`<ak-empty-state ?loading="${true}" header=${msg("Loading")}> return html`<ak-empty-state loading> </ak-empty-state>`;
</ak-empty-state>`;
} }
if (this.challenge.phoneNumberRequired) { if (this.challenge.phoneNumberRequired) {
return this.renderPhoneNumber(); return this.renderPhoneNumber();

View File

@ -54,8 +54,7 @@ export class AuthenticatorStaticStage extends BaseStage<
render(): TemplateResult { render(): TemplateResult {
if (!this.challenge) { if (!this.challenge) {
return html`<ak-empty-state ?loading="${true}" header=${msg("Loading")}> return html`<ak-empty-state loading> </ak-empty-state>`;
</ak-empty-state>`;
} }
return html`<header class="pf-c-login__main-header"> return html`<header class="pf-c-login__main-header">
<h1 class="pf-c-title pf-m-3xl">${this.challenge.flowInfo?.title}</h1> <h1 class="pf-c-title pf-m-3xl">${this.challenge.flowInfo?.title}</h1>

View File

@ -48,8 +48,7 @@ export class AuthenticatorTOTPStage extends BaseStage<
render(): TemplateResult { render(): TemplateResult {
if (!this.challenge) { if (!this.challenge) {
return html`<ak-empty-state ?loading="${true}" header=${msg("Loading")}> return html`<ak-empty-state loading> </ak-empty-state>`;
</ak-empty-state>`;
} }
return html`<header class="pf-c-login__main-header"> return html`<header class="pf-c-login__main-header">
<h1 class="pf-c-title pf-m-3xl">${this.challenge.flowInfo?.title}</h1> <h1 class="pf-c-title pf-m-3xl">${this.challenge.flowInfo?.title}</h1>
@ -113,7 +112,7 @@ export class AuthenticatorTOTPStage extends BaseStage<
</p> </p>
<ak-form-element <ak-form-element
label="${msg("Code")}" label="${msg("Code")}"
?required="${true}" required
class="pf-c-form__group" class="pf-c-form__group"
.errors=${(this.challenge?.responseErrors || {})["code"]} .errors=${(this.challenge?.responseErrors || {})["code"]}
> >

View File

@ -6,7 +6,7 @@ import { BaseStage, StageHost, SubmitOptions } from "@goauthentik/flow/stages/ba
import { PasswordManagerPrefill } from "@goauthentik/flow/stages/identification/IdentificationStage"; import { PasswordManagerPrefill } from "@goauthentik/flow/stages/identification/IdentificationStage";
import { msg } from "@lit/localize"; import { msg } from "@lit/localize";
import { CSSResult, TemplateResult, css, html } from "lit"; import { CSSResult, TemplateResult, css, html, nothing } from "lit";
import { customElement, state } from "lit/decorators.js"; import { customElement, state } from "lit/decorators.js";
import PFButton from "@patternfly/patternfly/components/Button/button.css"; import PFButton from "@patternfly/patternfly/components/Button/button.css";
@ -112,7 +112,7 @@ export class AuthenticatorValidateStage
`); `);
} }
renderDevicePickerSingle(deviceChallenge: DeviceChallenge): TemplateResult { renderDevicePickerSingle(deviceChallenge: DeviceChallenge) {
switch (deviceChallenge.deviceClass) { switch (deviceChallenge.deviceClass) {
case DeviceClassesEnum.Duo: case DeviceClassesEnum.Duo:
return html`<i class="fas fa-mobile-alt"></i> return html`<i class="fas fa-mobile-alt"></i>
@ -147,7 +147,7 @@ export class AuthenticatorValidateStage
default: default:
break; break;
} }
return html``; return nothing;
} }
renderDevicePicker(): TemplateResult { renderDevicePicker(): TemplateResult {
@ -192,9 +192,9 @@ export class AuthenticatorValidateStage
</ul>`; </ul>`;
} }
renderDeviceChallenge(): TemplateResult { renderDeviceChallenge() {
if (!this.selectedDeviceChallenge) { if (!this.selectedDeviceChallenge) {
return html``; return nothing;
} }
switch (this.selectedDeviceChallenge?.deviceClass) { switch (this.selectedDeviceChallenge?.deviceClass) {
case DeviceClassesEnum.Static: case DeviceClassesEnum.Static:
@ -224,13 +224,12 @@ export class AuthenticatorValidateStage
> >
</ak-stage-authenticator-validate-duo>`; </ak-stage-authenticator-validate-duo>`;
} }
return html``; return nothing;
} }
render(): TemplateResult { render(): TemplateResult {
if (!this.challenge) { if (!this.challenge) {
return html`<ak-empty-state ?loading="${true}" header=${msg("Loading")}> return html`<ak-empty-state loading> </ak-empty-state>`;
</ak-empty-state>`;
} }
// User only has a single device class, so we don't show a picker // User only has a single device class, so we don't show a picker
if (this.challenge?.deviceChallenges.length === 1) { if (this.challenge?.deviceChallenges.length === 1) {

View File

@ -33,8 +33,7 @@ export class AuthenticatorValidateStageWebCode extends BaseDeviceStage<
render(): TemplateResult { render(): TemplateResult {
if (!this.challenge) { if (!this.challenge) {
return html`<ak-empty-state ?loading="${true}" header=${msg("Loading")}> return html`<ak-empty-state loading> </ak-empty-state>`;
</ak-empty-state>`;
} }
return html`<div class="pf-c-login__main-body"> return html`<div class="pf-c-login__main-body">
<form <form
@ -63,7 +62,7 @@ export class AuthenticatorValidateStageWebCode extends BaseDeviceStage<
label="${this.deviceChallenge?.deviceClass === DeviceClassesEnum.Static label="${this.deviceChallenge?.deviceClass === DeviceClassesEnum.Static
? msg("Static token") ? msg("Static token")
: msg("Authentication code")}" : msg("Authentication code")}"
?required="${true}" required
class="pf-c-form__group" class="pf-c-form__group"
.errors=${(this.challenge?.responseErrors || {})["code"]} .errors=${(this.challenge?.responseErrors || {})["code"]}
> >

View File

@ -47,8 +47,7 @@ export class AuthenticatorValidateStageWebDuo extends BaseDeviceStage<
render(): TemplateResult { render(): TemplateResult {
if (!this.challenge) { if (!this.challenge) {
return html`<ak-empty-state ?loading="${true}" header=${msg("Loading")}> return html`<ak-empty-state loading> </ak-empty-state>`;
</ak-empty-state>`;
} }
const errors = this.challenge.responseErrors?.duo || []; const errors = this.challenge.responseErrors?.duo || [];
const errorMessage = errors.map((err) => err.string); const errorMessage = errors.map((err) => err.string);

View File

@ -2,7 +2,7 @@ import { AuthenticatorValidateStage } from "@goauthentik/flow/stages/authenticat
import { BaseStage, FlowInfoChallenge, PendingUserChallenge } from "@goauthentik/flow/stages/base"; import { BaseStage, FlowInfoChallenge, PendingUserChallenge } from "@goauthentik/flow/stages/base";
import { msg } from "@lit/localize"; import { msg } from "@lit/localize";
import { CSSResult, TemplateResult, css, html } from "lit"; import { CSSResult, css, html, nothing } from "lit";
import { property } from "lit/decorators.js"; import { property } from "lit/decorators.js";
import PFButton from "@patternfly/patternfly/components/Button/button.css"; import PFButton from "@patternfly/patternfly/components/Button/button.css";
@ -48,9 +48,9 @@ export class BaseDeviceStage<
return this.host?.submit(payload) || Promise.resolve(); return this.host?.submit(payload) || Promise.resolve();
} }
renderReturnToDevicePicker(): TemplateResult { renderReturnToDevicePicker() {
if (!this.showBackButton) { if (!this.showBackButton) {
return html``; return nothing;
} }
return html`<button return html`<button
class="pf-c-button pf-m-secondary pf-m-block" class="pf-c-button pf-m-secondary pf-m-block"

View File

@ -156,7 +156,7 @@ export class WebAuthnAuthenticatorRegisterStage extends BaseStage<
? html`<p class="pf-m-block"> ? html`<p class="pf-m-block">
${this.challenge.responseErrors["response"][0].string} ${this.challenge.responseErrors["response"][0].string}
</p>` </p>`
: html``} : nothing}
<div class="pf-c-form__group pf-m-action"> <div class="pf-c-form__group pf-m-action">
${!this.registerRunning ${!this.registerRunning
? html` <button ? html` <button

View File

@ -3,7 +3,7 @@ import { BaseStage } from "@goauthentik/flow/stages/base";
import { msg } from "@lit/localize"; import { msg } from "@lit/localize";
import { CSSResult, TemplateResult, html } from "lit"; import { CSSResult, TemplateResult, html } from "lit";
import { customElement } from "lit/decorators.js"; import { customElement, query } from "lit/decorators.js";
import PFButton from "@patternfly/patternfly/components/Button/button.css"; import PFButton from "@patternfly/patternfly/components/Button/button.css";
import PFForm from "@patternfly/patternfly/components/Form/form.css"; import PFForm from "@patternfly/patternfly/components/Form/form.css";
@ -19,30 +19,29 @@ export class AutosubmitStage extends BaseStage<
AutosubmitChallenge, AutosubmitChallenge,
AutoSubmitChallengeResponseRequest AutoSubmitChallengeResponseRequest
> { > {
@query("form")
private form?: HTMLFormElement;
static get styles(): CSSResult[] { static get styles(): CSSResult[] {
return [PFBase, PFLogin, PFForm, PFFormControl, PFButton, PFTitle]; return [PFBase, PFLogin, PFForm, PFFormControl, PFButton, PFTitle];
} }
updated(): void { updated(): void {
this.shadowRoot?.querySelectorAll("form").forEach((form) => { this.form?.submit();
form.submit();
});
} }
render(): TemplateResult { render(): TemplateResult {
if (!this.challenge) { if (!this.challenge) {
return html`<ak-empty-state ?loading="${true}" header=${msg("Loading")}> return html`<ak-empty-state loading> </ak-empty-state>`;
</ak-empty-state>`;
} }
let title = this.challenge.flowInfo?.title; let title = this.challenge.flowInfo?.title;
if (this.challenge.title && this.challenge.title !== "") { if (this.challenge.title && this.challenge.title !== "") {
title = this.challenge.title; title = this.challenge.title;
} }
return html`<header class="pf-c-login__main-header"> if (!title) {
<h1 class="pf-c-title pf-m-3xl">${title}</h1> title = msg("Loading");
</header> }
<div class="pf-c-login__main-body"> return html`<form class="pf-c-form" action="${this.challenge.url}" method="POST">
<form class="pf-c-form" action="${this.challenge.url}" method="POST">
${Object.entries(this.challenge.attrs).map(([key, value]) => { ${Object.entries(this.challenge.attrs).map(([key, value]) => {
return html`<input return html`<input
type="hidden" type="hidden"
@ -50,12 +49,8 @@ export class AutosubmitStage extends BaseStage<
value="${value as string}" value="${value as string}"
/>`; />`;
})} })}
<ak-empty-state ?loading="${true}"> </ak-empty-state> <ak-empty-state loading title=${title}> </ak-empty-state>
</form> </form>`;
</div>
<footer class="pf-c-login__main-footer">
<ul class="pf-c-login__main-footer-links"></ul>
</footer>`;
} }
} }

View File

@ -2,7 +2,7 @@ import { AKElement } from "@goauthentik/elements/Base";
import { KeyUnknown } from "@goauthentik/elements/forms/Form"; import { KeyUnknown } from "@goauthentik/elements/forms/Form";
import { msg } from "@lit/localize"; import { msg } from "@lit/localize";
import { TemplateResult, html } from "lit"; import { html, nothing } from "lit";
import { property } from "lit/decorators.js"; import { property } from "lit/decorators.js";
import { ifDefined } from "lit/directives/if-defined.js"; import { ifDefined } from "lit/directives/if-defined.js";
@ -43,8 +43,14 @@ export interface PendingUserChallenge {
pendingUserAvatar?: string; pendingUserAvatar?: string;
} }
export interface ResponseErrorsChallenge {
responseErrors?: {
[key: string]: Array<ErrorDetail>;
};
}
export class BaseStage< export class BaseStage<
Tin extends FlowInfoChallenge & PendingUserChallenge, Tin extends FlowInfoChallenge & PendingUserChallenge & ResponseErrorsChallenge,
Tout, Tout,
> extends AKElement { > extends AKElement {
host!: StageHost; host!: StageHost;
@ -72,12 +78,17 @@ export class BaseStage<
}); });
} }
renderNonFieldErrors(errors: ErrorDetail[]): TemplateResult { renderNonFieldErrors() {
if (!errors) { const errors = this.challenge?.responseErrors || {};
return html``; if (!("non_field_errors" in errors)) {
return nothing;
}
const nonFieldErrors = errors["non_field_errors"];
if (!nonFieldErrors) {
return nothing;
} }
return html`<div class="pf-c-form__alert"> return html`<div class="pf-c-form__alert">
${errors.map((err) => { ${nonFieldErrors.map((err) => {
return html`<div class="pf-c-alert pf-m-inline pf-m-danger"> return html`<div class="pf-c-alert pf-m-inline pf-m-danger">
<div class="pf-c-alert__icon"> <div class="pf-c-alert__icon">
<i class="fas fa-exclamation-circle"></i> <i class="fas fa-exclamation-circle"></i>
@ -88,9 +99,9 @@ export class BaseStage<
</div>`; </div>`;
} }
renderUserInfo(): TemplateResult { renderUserInfo() {
if (!this.challenge.pendingUser || !this.challenge.pendingUserAvatar) { if (!this.challenge.pendingUser || !this.challenge.pendingUserAvatar) {
return html``; return nothing;
} }
return html` return html`
<ak-form-static <ak-form-static

View File

@ -157,16 +157,12 @@ export class CaptchaStage extends BaseStage<CaptchaChallenge, CaptchaChallengeRe
if (this.captchaInteractive) { if (this.captchaInteractive) {
return html`${this.captchaContainer}`; return html`${this.captchaContainer}`;
} }
return html`<ak-empty-state return html`<ak-empty-state loading header=${msg("Verifying...")}></ak-empty-state>`;
?loading=${true}
header=${msg("Verifying...")}
></ak-empty-state>`;
} }
render(): TemplateResult { render(): TemplateResult {
if (!this.challenge) { if (!this.challenge) {
return html`<ak-empty-state ?loading="${true}" header=${msg("Loading")}> return html`<ak-empty-state loading> </ak-empty-state>`;
</ak-empty-state>`;
} }
return html`<header class="pf-c-login__main-header"> return html`<header class="pf-c-login__main-header">
<h1 class="pf-c-title pf-m-3xl">${this.challenge.flowInfo?.title}</h1> <h1 class="pf-c-title pf-m-3xl">${this.challenge.flowInfo?.title}</h1>

View File

@ -3,7 +3,7 @@ import "@goauthentik/flow/FormStatic";
import { BaseStage } from "@goauthentik/flow/stages/base"; import { BaseStage } from "@goauthentik/flow/stages/base";
import { msg } from "@lit/localize"; import { msg } from "@lit/localize";
import { CSSResult, TemplateResult, html } from "lit"; import { CSSResult, TemplateResult, html, nothing } from "lit";
import { customElement } from "lit/decorators.js"; import { customElement } from "lit/decorators.js";
import { ifDefined } from "lit/directives/if-defined.js"; import { ifDefined } from "lit/directives/if-defined.js";
@ -56,7 +56,7 @@ export class ConsentStage extends BaseStage<ConsentChallenge, ConsentChallengeRe
${this.renderPermissions(this.challenge.permissions)} ${this.renderPermissions(this.challenge.permissions)}
</ul> </ul>
` `
: html``} : nothing}
</div> </div>
`; `;
} }
@ -76,7 +76,7 @@ export class ConsentStage extends BaseStage<ConsentChallenge, ConsentChallengeRe
${this.renderPermissions(this.challenge.permissions)} ${this.renderPermissions(this.challenge.permissions)}
</ul> </ul>
` `
: html``} : nothing}
</div> </div>
<div class="pf-c-form__group pf-u-mt-md"> <div class="pf-c-form__group pf-u-mt-md">
${this.challenge.additionalPermissions.length > 0 ${this.challenge.additionalPermissions.length > 0
@ -88,15 +88,14 @@ export class ConsentStage extends BaseStage<ConsentChallenge, ConsentChallengeRe
${this.renderPermissions(this.challenge.additionalPermissions)} ${this.renderPermissions(this.challenge.additionalPermissions)}
</ul> </ul>
` `
: html``} : nothing}
</div> </div>
`; `;
} }
render(): TemplateResult { render(): TemplateResult {
if (!this.challenge) { if (!this.challenge) {
return html`<ak-empty-state ?loading="${true}" header=${msg("Loading")}> return html`<ak-empty-state loading> </ak-empty-state>`;
</ak-empty-state>`;
} }
return html`<header class="pf-c-login__main-header"> return html`<header class="pf-c-login__main-header">
<h1 class="pf-c-title pf-m-3xl">${this.challenge.flowInfo?.title}</h1> <h1 class="pf-c-title pf-m-3xl">${this.challenge.flowInfo?.title}</h1>

View File

@ -23,8 +23,7 @@ export class DummyStage extends BaseStage<DummyChallenge, DummyChallengeResponse
render(): TemplateResult { render(): TemplateResult {
if (!this.challenge) { if (!this.challenge) {
return html`<ak-empty-state ?loading="${true}" header=${msg("Loading")}> return html`<ak-empty-state loading> </ak-empty-state>`;
</ak-empty-state>`;
} }
return html`<header class="pf-c-login__main-header"> return html`<header class="pf-c-login__main-header">
<h1 class="pf-c-title pf-m-3xl">${this.challenge.flowInfo?.title}</h1> <h1 class="pf-c-title pf-m-3xl">${this.challenge.flowInfo?.title}</h1>

View File

@ -22,8 +22,7 @@ export class EmailStage extends BaseStage<EmailChallenge, EmailChallengeResponse
render(): TemplateResult { render(): TemplateResult {
if (!this.challenge) { if (!this.challenge) {
return html`<ak-empty-state ?loading="${true}" header=${msg("Loading")}> return html`<ak-empty-state loading> </ak-empty-state>`;
</ak-empty-state>`;
} }
return html`<header class="pf-c-login__main-header"> return html`<header class="pf-c-login__main-header">
<h1 class="pf-c-title pf-m-3xl">${this.challenge.flowInfo?.title}</h1> <h1 class="pf-c-title pf-m-3xl">${this.challenge.flowInfo?.title}</h1>

View File

@ -198,9 +198,9 @@ export class IdentificationStage extends BaseStage<
</li>`; </li>`;
} }
renderFooter(): TemplateResult { renderFooter() {
if (!this.challenge?.enrollUrl && !this.challenge?.recoveryUrl) { if (!this.challenge?.enrollUrl && !this.challenge?.recoveryUrl) {
return html``; return nothing;
} }
return html`<div class="pf-c-login__main-footer-band"> return html`<div class="pf-c-login__main-footer-band">
${this.challenge.enrollUrl ${this.challenge.enrollUrl
@ -246,7 +246,7 @@ export class IdentificationStage extends BaseStage<
: nothing} : nothing}
<ak-form-element <ak-form-element
label=${label} label=${label}
?required="${true}" required
class="pf-c-form__group" class="pf-c-form__group"
.errors=${(this.challenge.responseErrors || {})["uid_field"]} .errors=${(this.challenge.responseErrors || {})["uid_field"]}
> >
@ -273,9 +273,7 @@ export class IdentificationStage extends BaseStage<
></ak-flow-input-password> ></ak-flow-input-password>
` `
: nothing} : nothing}
${"non_field_errors" in (this.challenge?.responseErrors || {}) ${this.renderNonFieldErrors()}
? this.renderNonFieldErrors(this.challenge?.responseErrors?.non_field_errors || [])
: nothing}
<div class="pf-c-form__group pf-m-action"> <div class="pf-c-form__group pf-m-action">
<button type="submit" class="pf-c-button pf-m-primary pf-m-block"> <button type="submit" class="pf-c-button pf-m-primary pf-m-block">
${this.challenge.primaryAction} ${this.challenge.primaryAction}
@ -288,8 +286,7 @@ export class IdentificationStage extends BaseStage<
render(): TemplateResult { render(): TemplateResult {
if (!this.challenge) { if (!this.challenge) {
return html`<ak-empty-state ?loading="${true}" header=${msg("Loading")}> return html`<ak-empty-state loading> </ak-empty-state>`;
</ak-empty-state>`;
} }
return html`<header class="pf-c-login__main-header"> return html`<header class="pf-c-login__main-header">
<h1 class="pf-c-title pf-m-3xl">${this.challenge.flowInfo?.title}</h1> <h1 class="pf-c-title pf-m-3xl">${this.challenge.flowInfo?.title}</h1>

View File

@ -33,8 +33,7 @@ export class PasswordStage extends BaseStage<PasswordChallenge, PasswordChalleng
render(): TemplateResult { render(): TemplateResult {
if (!this.challenge) { if (!this.challenge) {
return html`<ak-empty-state ?loading="${true}" header=${msg("Loading")}> return html`<ak-empty-state loading> </ak-empty-state>`;
</ak-empty-state>`;
} }
return html`<header class="pf-c-login__main-header"> return html`<header class="pf-c-login__main-header">
<h1 class="pf-c-title pf-m-3xl">${this.challenge.flowInfo?.title}</h1> <h1 class="pf-c-title pf-m-3xl">${this.challenge.flowInfo?.title}</h1>

View File

@ -9,7 +9,7 @@ import "@goauthentik/elements/forms/FormElement";
import { BaseStage } from "@goauthentik/flow/stages/base"; import { BaseStage } from "@goauthentik/flow/stages/base";
import { msg } from "@lit/localize"; import { msg } from "@lit/localize";
import { CSSResult, TemplateResult, css, html } from "lit"; import { CSSResult, TemplateResult, css, html, nothing } from "lit";
import { customElement } from "lit/decorators.js"; import { customElement } from "lit/decorators.js";
import { unsafeHTML } from "lit/directives/unsafe-html.js"; import { unsafeHTML } from "lit/directives/unsafe-html.js";
@ -81,7 +81,7 @@ ${prompt.initialValue}</textarea
name="${prompt.fieldKey}" name="${prompt.fieldKey}"
placeholder="${prompt.placeholder}" placeholder="${prompt.placeholder}"
class="pf-c-form-control" class="pf-c-form-control"
?readonly=${true} readonly
value="${prompt.initialValue}" value="${prompt.initialValue}"
/>`; />`;
case PromptTypeEnum.TextAreaReadOnly: case PromptTypeEnum.TextAreaReadOnly:
@ -222,9 +222,9 @@ ${prompt.initialValue}</textarea
} }
} }
renderPromptHelpText(prompt: StagePrompt): TemplateResult { renderPromptHelpText(prompt: StagePrompt) {
if (prompt.subText === "") { if (prompt.subText === "") {
return html``; return nothing;
} }
return html`<p class="pf-c-form__helper-text">${unsafeHTML(prompt.subText)}</p>`; return html`<p class="pf-c-form__helper-text">${unsafeHTML(prompt.subText)}</p>`;
} }
@ -253,7 +253,7 @@ ${prompt.initialValue}</textarea
<label class="pf-c-check__label" for="${prompt.fieldKey}">${prompt.label}</label> <label class="pf-c-check__label" for="${prompt.fieldKey}">${prompt.label}</label>
${prompt.required ${prompt.required
? html`<p class="pf-c-form__helper-text">${msg("Required.")}</p>` ? html`<p class="pf-c-form__helper-text">${msg("Required.")}</p>`
: html``} : nothing}
<p class="pf-c-form__helper-text">${unsafeHTML(prompt.subText)}</p> <p class="pf-c-form__helper-text">${unsafeHTML(prompt.subText)}</p>
</div>`; </div>`;
} }
@ -280,8 +280,7 @@ ${prompt.initialValue}</textarea
render(): TemplateResult { render(): TemplateResult {
if (!this.challenge) { if (!this.challenge) {
return html`<ak-empty-state ?loading="${true}" header=${msg("Loading")}> return html`<ak-empty-state loading> </ak-empty-state>`;
</ak-empty-state>`;
} }
return html`<header class="pf-c-login__main-header"> return html`<header class="pf-c-login__main-header">
<h1 class="pf-c-title pf-m-3xl">${this.challenge.flowInfo?.title}</h1> <h1 class="pf-c-title pf-m-3xl">${this.challenge.flowInfo?.title}</h1>
@ -296,12 +295,7 @@ ${prompt.initialValue}</textarea
${this.challenge.fields.map((prompt) => { ${this.challenge.fields.map((prompt) => {
return this.renderField(prompt); return this.renderField(prompt);
})} })}
${"non_field_errors" in (this.challenge?.responseErrors || {}) ${this.renderNonFieldErrors()} ${this.renderContinue()}
? this.renderNonFieldErrors(
this.challenge?.responseErrors?.non_field_errors || [],
)
: html``}
${this.renderContinue()}
</form> </form>
</div> </div>
<footer class="pf-c-login__main-footer"> <footer class="pf-c-login__main-footer">

View File

@ -29,8 +29,7 @@ export class PasswordStage extends BaseStage<
render(): TemplateResult { render(): TemplateResult {
if (!this.challenge) { if (!this.challenge) {
return html`<ak-empty-state ?loading="${true}" header=${msg("Loading")}> return html`<ak-empty-state loading> </ak-empty-state>`;
</ak-empty-state>`;
} }
return html`<header class="pf-c-login__main-header"> return html`<header class="pf-c-login__main-header">
<h1 class="pf-c-title pf-m-3xl">${this.challenge.flowInfo?.title}</h1> <h1 class="pf-c-title pf-m-3xl">${this.challenge.flowInfo?.title}</h1>

View File

@ -75,12 +75,7 @@ export class UserSettingsPromptStage extends PromptStage {
${this.challenge.fields.map((prompt) => { ${this.challenge.fields.map((prompt) => {
return this.renderField(prompt); return this.renderField(prompt);
})} })}
${"non_field_errors" in (this.challenge?.responseErrors || {}) ${this.renderNonFieldErrors()} ${this.renderContinue()}
? this.renderNonFieldErrors(
this.challenge?.responseErrors?.non_field_errors || [],
)
: html``}
${this.renderContinue()}
</form> </form>
</div> </div>
<footer class="pf-c-login__main-footer"> <footer class="pf-c-login__main-footer">