stages/redirect: create redirect stage (#12275)

* create redirect stage

* show "keep context" toggle in Flow mode only

* fix typos

* add docs

Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>

* simplify property pass

* simplify toggle

* remove `print` statements

whoops

* fix typo

* remove default from `RedirectStage.mode`

* remove migration

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

* oops

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

* adjust docs

Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>
Co-authored-by: Jens Langhammer <jens@goauthentik.io>
This commit is contained in:
Simonyi Gergő
2024-12-12 18:00:09 +01:00
committed by GitHub
parent 587f2d74ac
commit ff504a3b80
35 changed files with 1314 additions and 40 deletions

View File

@ -189,21 +189,28 @@ export class FlowForm extends WithCapabilitiesConfig(ModelForm<Flow, string>) {
?selected=${this.instance?.authentication ===
AuthenticationEnum.RequireUnauthenticated}
>
${msg("Require no authentication.")}
${msg("Require no authentication")}
</option>
<option
value=${AuthenticationEnum.RequireSuperuser}
?selected=${this.instance?.authentication ===
AuthenticationEnum.RequireSuperuser}
>
${msg("Require superuser.")}
${msg("Require superuser")}
</option>
<option
value=${AuthenticationEnum.RequireRedirect}
?selected=${this.instance?.authentication ===
AuthenticationEnum.RequireRedirect}
>
${msg("Require being redirected from another flow")}
</option>
<option
value=${AuthenticationEnum.RequireOutpost}
?selected=${this.instance?.authentication ===
AuthenticationEnum.RequireOutpost}
>
${msg("Require Outpost (flow can only be executed from an outpost).")}
${msg("Require Outpost (flow can only be executed from an outpost)")}
</option>
</select>
<p class="pf-c-form__helper-text">

View File

@ -17,6 +17,7 @@ import "@goauthentik/admin/stages/identification/IdentificationStageForm";
import "@goauthentik/admin/stages/invitation/InvitationStageForm";
import "@goauthentik/admin/stages/password/PasswordStageForm";
import "@goauthentik/admin/stages/prompt/PromptStageForm";
import "@goauthentik/admin/stages/redirect/RedirectStageForm";
import "@goauthentik/admin/stages/source/SourceStageForm";
import "@goauthentik/admin/stages/user_delete/UserDeleteStageForm";
import "@goauthentik/admin/stages/user_login/UserLoginStageForm";

View File

@ -15,6 +15,7 @@ import "@goauthentik/admin/stages/identification/IdentificationStageForm";
import "@goauthentik/admin/stages/invitation/InvitationStageForm";
import "@goauthentik/admin/stages/password/PasswordStageForm";
import "@goauthentik/admin/stages/prompt/PromptStageForm";
import "@goauthentik/admin/stages/redirect/RedirectStageForm";
import "@goauthentik/admin/stages/source/SourceStageForm";
import "@goauthentik/admin/stages/user_delete/UserDeleteStageForm";
import "@goauthentik/admin/stages/user_login/UserLoginStageForm";

View File

@ -83,13 +83,13 @@ export class ConsentStageForm extends BaseStageForm<ConsentStage> {
value=${ConsentStageModeEnum.Permanent}
?selected=${this.instance?.mode === ConsentStageModeEnum.Permanent}
>
${msg("Consent given last indefinitely")}
${msg("Consent given lasts indefinitely")}
</option>
<option
value=${ConsentStageModeEnum.Expiring}
?selected=${this.instance?.mode === ConsentStageModeEnum.Expiring}
>
${msg("Consent expires.")}
${msg("Consent expires")}
</option>
</select>
</ak-form-element-horizontal>

View File

@ -0,0 +1,145 @@
import { RenderFlowOption } from "@goauthentik/admin/flows/utils";
import { BaseStageForm } from "@goauthentik/admin/stages/BaseStageForm";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import "@goauthentik/elements/forms/HorizontalFormElement";
import { msg } from "@lit/localize";
import { TemplateResult, html } from "lit";
import { customElement, property } from "lit/decorators.js";
import {
Flow,
FlowsApi,
FlowsInstancesListRequest,
RedirectStage,
RedirectStageModeEnum,
StagesApi,
} from "@goauthentik/api";
@customElement("ak-stage-redirect-form")
export class RedirectStageForm extends BaseStageForm<RedirectStage> {
@property({ type: String })
mode: string = RedirectStageModeEnum.Static;
loadInstance(pk: string): Promise<RedirectStage> {
return new StagesApi(DEFAULT_CONFIG)
.stagesRedirectRetrieve({
stageUuid: pk,
})
.then((stage) => {
this.mode = stage.mode ?? RedirectStageModeEnum.Static;
return stage;
});
}
async send(data: RedirectStage): Promise<RedirectStage> {
if (this.instance) {
return new StagesApi(DEFAULT_CONFIG).stagesRedirectUpdate({
stageUuid: this.instance.pk || "",
redirectStageRequest: data,
});
} else {
return new StagesApi(DEFAULT_CONFIG).stagesRedirectCreate({
redirectStageRequest: data,
});
}
}
renderForm(): TemplateResult {
return html`<span>
${msg("Redirect the user to another flow, potentially with all gathered context")}
</span>
<ak-form-element-horizontal label=${msg("Name")} required name="name">
<input
type="text"
value="${this.instance?.name ?? ""}"
class="pf-c-form-control"
required
/>
</ak-form-element-horizontal>
<ak-form-group expanded>
<span slot="header"> ${msg("Stage-specific settings")} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal label=${msg("Mode")} required name="mode">
<select
class="pf-c-form-control"
@change=${(ev: Event) => {
const target = ev.target as HTMLSelectElement;
this.mode = target.selectedOptions[0].value;
}}
>
<option
value=${RedirectStageModeEnum.Static}
?selected=${this.instance?.mode === RedirectStageModeEnum.Static}
>
${msg("Static")}
</option>
<option
value=${RedirectStageModeEnum.Flow}
?selected=${this.instance?.mode === RedirectStageModeEnum.Flow}
>
${msg("Flow")}
</option>
</select>
</ak-form-element-horizontal>
<ak-form-element-horizontal
?hidden=${this.mode !== RedirectStageModeEnum.Static}
label=${msg("Target URL")}
name="targetStatic"
required
>
<input
type="text"
value="${this.instance?.targetStatic ?? ""}"
class="pf-c-form-control"
/>
<p class="pf-c-form__helper-text">
${msg("Redirect the user to a static URL.")}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
?hidden=${this.mode !== RedirectStageModeEnum.Flow}
label=${msg("Target Flow")}
name="targetFlow"
required
>
<ak-search-select
.fetchObjects=${async (query?: string): Promise<Flow[]> => {
const args: FlowsInstancesListRequest = {
ordering: "slug",
};
if (query !== undefined) {
args.search = query;
}
const flows = await new FlowsApi(DEFAULT_CONFIG).flowsInstancesList(
args,
);
return flows.results;
}}
.renderElement=${(flow: Flow): string => RenderFlowOption(flow)}
.renderDescription=${(flow: Flow): TemplateResult => html`${flow.name}`}
.value=${(flow: Flow | undefined): string | undefined => flow?.pk}
.selected=${(flow: Flow): boolean =>
this.instance?.targetFlow === flow.pk}
blankable
>
</ak-search-select>
<p class="pf-c-form__helper-text">${msg("Redirect the user to a Flow.")}</p>
</ak-form-element-horizontal>
<ak-switch-input
?hidden=${this.mode !== RedirectStageModeEnum.Flow}
name="keepContext"
label=${msg("Keep flow context")}
?checked="${this.instance?.keepContext ?? true}"
>
</ak-switch-input>
</div>
</ak-form-group>`;
}
}
declare global {
interface HTMLElementTagNameMap {
"ak-stage-redirect-form": RedirectStageForm;
}
}