From 0873b4a446b47d4fca491628273ca79017a3b676 Mon Sep 17 00:00:00 2001 From: Jens Langhammer <jens@goauthentik.io> Date: Sat, 16 Dec 2023 22:39:04 +0100 Subject: [PATCH] add UI for hosted Signed-off-by: Jens Langhammer <jens@goauthentik.io> --- .../stages/authenticator_mobile/stage.py | 4 +- .../AuthenticatorMobileStageForm.ts | 93 ++++++++++++++++--- 2 files changed, 85 insertions(+), 12 deletions(-) diff --git a/authentik/stages/authenticator_mobile/stage.py b/authentik/stages/authenticator_mobile/stage.py index 717746ff5..cfb84db89 100644 --- a/authentik/stages/authenticator_mobile/stage.py +++ b/authentik/stages/authenticator_mobile/stage.py @@ -71,7 +71,9 @@ class AuthenticatorMobileStageView(ChallengeStageView): endpoint = endpoint.replace("https", "http") payload = AuthenticatorMobilePayloadChallenge( data={ - "u": endpoint, + # "u": endpoint, + # For now the app talks back directly to authentik + "u": self.request.build_absolute_uri("/"), "s": str(self.executor.plan.context[FLOW_PLAN_MOBILE_ENROLL_DEVICE].pk), "t": self.executor.plan.context[FLOW_PLAN_MOBILE_ENROLL_TOKEN].token, } diff --git a/web/src/admin/stages/authenticator_mobile/AuthenticatorMobileStageForm.ts b/web/src/admin/stages/authenticator_mobile/AuthenticatorMobileStageForm.ts index 87fe24b2d..3a3a4b084 100644 --- a/web/src/admin/stages/authenticator_mobile/AuthenticatorMobileStageForm.ts +++ b/web/src/admin/stages/authenticator_mobile/AuthenticatorMobileStageForm.ts @@ -1,32 +1,60 @@ import { RenderFlowOption } from "@goauthentik/admin/flows/utils"; +import { KeyUnknown } from "@goauthentik/app/elements/forms/Form"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { first } from "@goauthentik/common/utils"; +import "@goauthentik/elements/Alert"; import "@goauthentik/elements/forms/FormGroup"; import "@goauthentik/elements/forms/HorizontalFormElement"; import { ModelForm } from "@goauthentik/elements/forms/ModelForm"; import "@goauthentik/elements/forms/SearchSelect"; import { msg } from "@lit/localize"; -import { TemplateResult, html } from "lit"; -import { customElement } from "lit/decorators.js"; +import { CSSResult, TemplateResult, html, nothing } from "lit"; +import { customElement, state } from "lit/decorators.js"; + +import PFBanner from "@patternfly/patternfly/components/Banner/banner.css"; import { AuthenticatorMobileStage, AuthenticatorMobileStageRequest, + EnterpriseApi, Flow, FlowsApi, FlowsInstancesListDesignationEnum, FlowsInstancesListRequest, ItemMatchingModeEnum, + LicenseSummary, StagesApi, } from "@goauthentik/api"; +const hostedCGWs: Map<string, string> = new Map([ + ["prod-eu-central-1.cgw.a7k.io", msg("authentik Enterprise eu-central-1")], +]); + @customElement("ak-stage-authenticator-mobile-form") export class AuthenticatorMobileStageForm extends ModelForm<AuthenticatorMobileStage, string> { - loadInstance(pk: string): Promise<AuthenticatorMobileStage> { - return new StagesApi(DEFAULT_CONFIG).stagesAuthenticatorMobileRetrieve({ + @state() + showCustomCGWInput = false; + + @state() + enterpriseStatus?: LicenseSummary; + + static get styles(): CSSResult[] { + return super.styles.concat(PFBanner); + } + + async load(): Promise<void> { + this.enterpriseStatus = await new EnterpriseApi( + DEFAULT_CONFIG, + ).enterpriseLicenseSummaryRetrieve(); + } + + async loadInstance(pk: string): Promise<AuthenticatorMobileStage> { + const instance = await new StagesApi(DEFAULT_CONFIG).stagesAuthenticatorMobileRetrieve({ stageUuid: pk, }); + this.showCustomCGWInput = !hostedCGWs.has(instance.cgwEndpoint); + return instance; } getSuccessMessage(): string { @@ -38,6 +66,9 @@ export class AuthenticatorMobileStageForm extends ModelForm<AuthenticatorMobileS } async send(data: AuthenticatorMobileStage): Promise<AuthenticatorMobileStage> { + if (this.showCustomCGWInput) { + data.cgwEndpoint = (data as unknown as KeyUnknown)["customCgwEndpoint"] as string; + } if (this.instance) { return new StagesApi(DEFAULT_CONFIG).stagesAuthenticatorMobilePartialUpdate({ stageUuid: this.instance.pk || "", @@ -51,7 +82,11 @@ export class AuthenticatorMobileStageForm extends ModelForm<AuthenticatorMobileS } renderForm(): TemplateResult { - return html`<form class="pf-c-form pf-m-horizontal"> + return html` + <div class="pf-c-banner pf-m-info" slot="above-form"> + ${msg("Mobile stage is in preview.")} + <a href="mailto:hello@goauthentik.io">${msg("Send us feedback!")}</a> + </div> <div class="form-help-text"> ${msg( "Stage used to configure a mobile-based authenticator. This stage should be used for configuration flows.", @@ -114,12 +149,48 @@ export class AuthenticatorMobileStageForm extends ModelForm<AuthenticatorMobileS ?required=${false} name="cgwEndpoint" > - <input - type="text" - value="${first(this.instance?.cgwEndpoint, "http://localhost:3415")}" - class="pf-c-form-control" - /> + <ak-radio + @change=${(ev: CustomEvent<{ value: string }>) => { + this.showCustomCGWInput = !hostedCGWs.has(ev.detail.value); + }} + .options=${[ + ...Array.from(hostedCGWs, ([endpoint, label]) => { + return { + label: label, + value: endpoint, + }; + }), + { + label: msg("Custom Endpoint"), + value: hostedCGWs.has(this.instance?.cgwEndpoint || "") + ? false + : this.instance?.cgwEndpoint, + }, + ]} + .value=${this.instance?.cgwEndpoint} + > + </ak-radio> + ${!this.showCustomCGWInput ?? !this.enterpriseStatus?.valid + ? html` + <ak-alert ?inline=${true}> + ${msg("Hosted cloud gateways require authentik Enterprise.")} + </ak-alert> + ` + : html``} </ak-form-element-horizontal> + ${this.showCustomCGWInput + ? html`<ak-form-element-horizontal + label=${msg("Custom Cloud Gateway endpoint")} + ?required=${false} + name="customCgwEndpoint" + > + <input + type="text" + value="${first(this.instance?.cgwEndpoint, "")}" + class="pf-c-form-control" + /> + </ak-form-element-horizontal>` + : nothing} <ak-form-element-horizontal label=${msg("Configuration flow")} name="configureFlow" @@ -162,6 +233,6 @@ export class AuthenticatorMobileStageForm extends ModelForm<AuthenticatorMobileS </ak-form-element-horizontal> </div> </ak-form-group> - </form>`; + `; } }