stages: authenticator_endpoint_gdtc (#10477)
* rework Signed-off-by: Jens Langhammer <jens@goauthentik.io> * add loading overlay for chrome Signed-off-by: Jens Langhammer <jens@goauthentik.io> * start docs Signed-off-by: Jens Langhammer <jens@goauthentik.io> * Apply suggestions from code review Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com> Signed-off-by: Jens L. <jens@beryju.org> * save data Signed-off-by: Jens Langhammer <jens@goauthentik.io> * fix web ui, prevent deletion Signed-off-by: Jens Langhammer <jens@goauthentik.io> * fix Signed-off-by: Jens Langhammer <jens@goauthentik.io> * text fixes Signed-off-by: Jens Langhammer <jens@goauthentik.io> --------- Signed-off-by: Jens Langhammer <jens@goauthentik.io> Signed-off-by: Jens L. <jens@beryju.org> Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>
This commit is contained in:
@ -2,6 +2,7 @@ import "@goauthentik/admin/rbac/ObjectPermissionModal";
|
||||
import "@goauthentik/admin/stages/StageWizard";
|
||||
import "@goauthentik/admin/stages/authenticator_duo/AuthenticatorDuoStageForm";
|
||||
import "@goauthentik/admin/stages/authenticator_duo/DuoDeviceImportForm";
|
||||
import "@goauthentik/admin/stages/authenticator_endpoint_gdtc/AuthenticatorEndpointGDTCStageForm";
|
||||
import "@goauthentik/admin/stages/authenticator_sms/AuthenticatorSMSStageForm";
|
||||
import "@goauthentik/admin/stages/authenticator_static/AuthenticatorStaticStageForm";
|
||||
import "@goauthentik/admin/stages/authenticator_totp/AuthenticatorTOTPStageForm";
|
||||
@ -25,8 +26,7 @@ import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||
import "@goauthentik/elements/forms/DeleteBulkForm";
|
||||
import "@goauthentik/elements/forms/ModalForm";
|
||||
import "@goauthentik/elements/forms/ProxyForm";
|
||||
import { PaginatedResponse } from "@goauthentik/elements/table/Table";
|
||||
import { TableColumn } from "@goauthentik/elements/table/Table";
|
||||
import { PaginatedResponse, TableColumn } from "@goauthentik/elements/table/Table";
|
||||
import { TablePage } from "@goauthentik/elements/table/TablePage";
|
||||
import "@patternfly/elements/pf-tooltip/pf-tooltip.js";
|
||||
|
||||
|
@ -0,0 +1,75 @@
|
||||
import { BaseStageForm } from "@goauthentik/admin/stages/BaseStageForm";
|
||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||
import { first } from "@goauthentik/common/utils";
|
||||
import "@goauthentik/elements/CodeMirror";
|
||||
import { CodeMirrorMode } from "@goauthentik/elements/CodeMirror";
|
||||
import "@goauthentik/elements/forms/FormGroup";
|
||||
import "@goauthentik/elements/forms/HorizontalFormElement";
|
||||
|
||||
import { msg } from "@lit/localize";
|
||||
import { TemplateResult, html } from "lit";
|
||||
import { customElement } from "lit/decorators.js";
|
||||
|
||||
import { AuthenticatorEndpointGDTCStage, StagesApi } from "@goauthentik/api";
|
||||
|
||||
@customElement("ak-stage-authenticator-endpoint-gdtc-form")
|
||||
export class AuthenticatorEndpointGDTCStageForm extends BaseStageForm<AuthenticatorEndpointGDTCStage> {
|
||||
loadInstance(pk: string): Promise<AuthenticatorEndpointGDTCStage> {
|
||||
return new StagesApi(DEFAULT_CONFIG).stagesAuthenticatorEndpointGdtcRetrieve({
|
||||
stageUuid: pk,
|
||||
});
|
||||
}
|
||||
|
||||
async send(data: AuthenticatorEndpointGDTCStage): Promise<AuthenticatorEndpointGDTCStage> {
|
||||
if (this.instance) {
|
||||
return new StagesApi(DEFAULT_CONFIG).stagesAuthenticatorEndpointGdtcPartialUpdate({
|
||||
stageUuid: this.instance.pk || "",
|
||||
patchedAuthenticatorEndpointGDTCStageRequest: data,
|
||||
});
|
||||
} else {
|
||||
return new StagesApi(DEFAULT_CONFIG).stagesAuthenticatorEndpointGdtcCreate({
|
||||
authenticatorEndpointGDTCStageRequest: data,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
renderForm(): TemplateResult {
|
||||
return html` <span>
|
||||
${msg(
|
||||
"Stage used to verify users' browsers using Google Chrome Device Trust. This stage can be used in authentication/authorization flows.",
|
||||
)}
|
||||
</span>
|
||||
<ak-form-element-horizontal label=${msg("Name")} required name="name">
|
||||
<input
|
||||
type="text"
|
||||
value="${first(this.instance?.name, "")}"
|
||||
class="pf-c-form-control"
|
||||
required
|
||||
/>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-group expanded>
|
||||
<span slot="header"> ${msg("Google Verified Access API")} </span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-element-horizontal
|
||||
label=${msg("Credentials")}
|
||||
required
|
||||
name="credentials"
|
||||
>
|
||||
<ak-codemirror
|
||||
mode=${CodeMirrorMode.JavaScript}
|
||||
.value="${first(this.instance?.credentials, {})}"
|
||||
></ak-codemirror>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${msg("Google Cloud credentials file.")}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
</div>
|
||||
</ak-form-group>`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ak-stage-authenticator-endpoint-gdtc-form": AuthenticatorEndpointGDTCStageForm;
|
||||
}
|
||||
}
|
@ -1,11 +1,12 @@
|
||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||
import { SentryIgnoredError } from "@goauthentik/common/errors";
|
||||
import { deviceTypeName } from "@goauthentik/common/labels";
|
||||
import { getRelativeTime } from "@goauthentik/common/utils";
|
||||
import "@goauthentik/elements/forms/DeleteBulkForm";
|
||||
import { PaginatedResponse } from "@goauthentik/elements/table/Table";
|
||||
import { Table, TableColumn } from "@goauthentik/elements/table/Table";
|
||||
|
||||
import { msg } from "@lit/localize";
|
||||
import { msg, str } from "@lit/localize";
|
||||
import { TemplateResult, html } from "lit";
|
||||
import { customElement, property } from "lit/decorators.js";
|
||||
|
||||
@ -54,20 +55,21 @@ export class UserDeviceTable extends Table<Device> {
|
||||
|
||||
async deleteWrapper(device: Device) {
|
||||
const api = new AuthenticatorsApi(DEFAULT_CONFIG);
|
||||
const id = { id: device.pk };
|
||||
switch (device.type) {
|
||||
case "authentik_stages_authenticator_duo.DuoDevice":
|
||||
return api.authenticatorsAdminDuoDestroy(id);
|
||||
return api.authenticatorsAdminDuoDestroy({ id: parseInt(device.pk, 10) });
|
||||
case "authentik_stages_authenticator_sms.SMSDevice":
|
||||
return api.authenticatorsAdminSmsDestroy(id);
|
||||
return api.authenticatorsAdminSmsDestroy({ id: parseInt(device.pk, 10) });
|
||||
case "authentik_stages_authenticator_totp.TOTPDevice":
|
||||
return api.authenticatorsAdminTotpDestroy(id);
|
||||
return api.authenticatorsAdminTotpDestroy({ id: parseInt(device.pk, 10) });
|
||||
case "authentik_stages_authenticator_static.StaticDevice":
|
||||
return api.authenticatorsAdminStaticDestroy(id);
|
||||
return api.authenticatorsAdminStaticDestroy({ id: parseInt(device.pk, 10) });
|
||||
case "authentik_stages_authenticator_webauthn.WebAuthnDevice":
|
||||
return api.authenticatorsAdminWebauthnDestroy(id);
|
||||
return api.authenticatorsAdminWebauthnDestroy({ id: parseInt(device.pk, 10) });
|
||||
default:
|
||||
break;
|
||||
throw new SentryIgnoredError(
|
||||
msg(str`Device type ${device.verboseName} cannot be deleted`),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -16,6 +16,7 @@ import { themeImage } from "@goauthentik/elements/utils/images";
|
||||
import "@goauthentik/flow/sources/apple/AppleLoginInit";
|
||||
import "@goauthentik/flow/sources/plex/PlexLoginInit";
|
||||
import "@goauthentik/flow/stages/FlowErrorStage";
|
||||
import "@goauthentik/flow/stages/FlowFrameStage";
|
||||
import "@goauthentik/flow/stages/RedirectStage";
|
||||
import { StageHost, SubmitOptions } from "@goauthentik/flow/stages/base";
|
||||
|
||||
@ -170,6 +171,19 @@ export class FlowExecutor extends Interface implements StageHost {
|
||||
this.addEventListener(EVENT_FLOW_INSPECTOR_TOGGLE, () => {
|
||||
this.inspectorOpen = !this.inspectorOpen;
|
||||
});
|
||||
window.addEventListener("message", (event) => {
|
||||
const msg: {
|
||||
source?: string;
|
||||
context?: string;
|
||||
message: string;
|
||||
} = event.data;
|
||||
if (msg.source !== "goauthentik.io" || msg.context !== "flow-executor") {
|
||||
return;
|
||||
}
|
||||
if (msg.message === "submit") {
|
||||
this.submit({} as FlowChallengeResponseRequest);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async getTheme(): Promise<UiThemeEnum> {
|
||||
@ -429,6 +443,11 @@ export class FlowExecutor extends Interface implements StageHost {
|
||||
</ak-stage-redirect>`;
|
||||
case "xak-flow-shell":
|
||||
return html`${unsafeHTML((this.challenge as ShellChallenge).body)}`;
|
||||
case "xak-flow-frame":
|
||||
return html`<xak-flow-frame
|
||||
.host=${this as StageHost}
|
||||
.challenge=${this.challenge}
|
||||
></xak-flow-frame>`;
|
||||
default:
|
||||
return html`Invalid native challenge element`;
|
||||
}
|
||||
|
@ -114,7 +114,7 @@ export class InputPassword extends AKElement {
|
||||
this.input.type = "password";
|
||||
this.input.name = this.name;
|
||||
this.input.placeholder = this.placeholder;
|
||||
this.input.autofocus = true;
|
||||
this.input.autofocus = this.grabFocus;
|
||||
this.input.autocomplete = "current-password";
|
||||
this.input.classList.add("pf-c-form-control");
|
||||
this.input.required = true;
|
||||
|
54
web/src/flow/stages/FlowFrameStage.ts
Normal file
54
web/src/flow/stages/FlowFrameStage.ts
Normal file
@ -0,0 +1,54 @@
|
||||
import "@goauthentik/elements/EmptyState";
|
||||
import "@goauthentik/flow/FormStatic";
|
||||
import { BaseStage } from "@goauthentik/flow/stages/base";
|
||||
|
||||
import { CSSResult, TemplateResult, css, html, nothing } from "lit";
|
||||
import { customElement } from "lit/decorators.js";
|
||||
|
||||
import PFForm from "@patternfly/patternfly/components/Form/form.css";
|
||||
import PFFormControl from "@patternfly/patternfly/components/FormControl/form-control.css";
|
||||
import PFLogin from "@patternfly/patternfly/components/Login/login.css";
|
||||
import PFTitle from "@patternfly/patternfly/components/Title/title.css";
|
||||
import PFBase from "@patternfly/patternfly/patternfly-base.css";
|
||||
|
||||
import { FrameChallenge, FrameChallengeResponseRequest } from "@goauthentik/api";
|
||||
|
||||
@customElement("xak-flow-frame")
|
||||
export class FlowFrameStage extends BaseStage<FrameChallenge, FrameChallengeResponseRequest> {
|
||||
static get styles(): CSSResult[] {
|
||||
return [PFBase, PFLogin, PFForm, PFFormControl, PFTitle, css``];
|
||||
}
|
||||
|
||||
render(): TemplateResult {
|
||||
if (!this.challenge) {
|
||||
return html`<ak-empty-state loading> </ak-empty-state>`;
|
||||
}
|
||||
return html` <header class="pf-c-login__main-header">
|
||||
<h1 class="pf-c-title pf-m-3xl">${this.challenge.flowInfo?.title}</h1>
|
||||
</header>
|
||||
<div class="pf-c-login__main-body">
|
||||
${this.challenge.loadingOverlay
|
||||
? html`<ak-empty-state
|
||||
loading
|
||||
header=${this.challenge.loadingText ?? undefined}
|
||||
>
|
||||
</ak-empty-state>`
|
||||
: nothing}
|
||||
<iframe
|
||||
style=${this.challenge.loadingOverlay
|
||||
? "width:0;height:0;position:absolute;"
|
||||
: ""}
|
||||
src=${this.challenge.url}
|
||||
></iframe>
|
||||
</div>
|
||||
<footer class="pf-c-login__main-footer">
|
||||
<ul class="pf-c-login__main-footer-links"></ul>
|
||||
</footer>`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"xak-flow-frame": FlowFrameStage;
|
||||
}
|
||||
}
|
@ -1,8 +1,9 @@
|
||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||
import { SentryIgnoredError } from "@goauthentik/common/errors";
|
||||
import "@goauthentik/elements/forms/HorizontalFormElement";
|
||||
import { ModelForm } from "@goauthentik/elements/forms/ModelForm";
|
||||
|
||||
import { msg } from "@lit/localize";
|
||||
import { msg, str } from "@lit/localize";
|
||||
import { TemplateResult, html } from "lit";
|
||||
import { customElement, property } from "lit/decorators.js";
|
||||
import { ifDefined } from "lit/directives/if-defined.js";
|
||||
@ -10,11 +11,11 @@ import { ifDefined } from "lit/directives/if-defined.js";
|
||||
import { AuthenticatorsApi, Device } from "@goauthentik/api";
|
||||
|
||||
@customElement("ak-user-mfa-form")
|
||||
export class MFADeviceForm extends ModelForm<Device, number> {
|
||||
export class MFADeviceForm extends ModelForm<Device, string> {
|
||||
@property()
|
||||
deviceType!: string;
|
||||
|
||||
async loadInstance(pk: number): Promise<Device> {
|
||||
async loadInstance(pk: string): Promise<Device> {
|
||||
const devices = await new AuthenticatorsApi(DEFAULT_CONFIG).authenticatorsAllList();
|
||||
return devices.filter((device) => {
|
||||
return device.pk === pk && device.type === this.deviceType;
|
||||
@ -29,36 +30,38 @@ export class MFADeviceForm extends ModelForm<Device, number> {
|
||||
switch (this.instance?.type) {
|
||||
case "authentik_stages_authenticator_duo.DuoDevice":
|
||||
await new AuthenticatorsApi(DEFAULT_CONFIG).authenticatorsDuoUpdate({
|
||||
id: this.instance?.pk,
|
||||
id: parseInt(this.instance?.pk, 10),
|
||||
duoDeviceRequest: device,
|
||||
});
|
||||
break;
|
||||
case "authentik_stages_authenticator_sms.SMSDevice":
|
||||
await new AuthenticatorsApi(DEFAULT_CONFIG).authenticatorsSmsUpdate({
|
||||
id: this.instance?.pk,
|
||||
id: parseInt(this.instance?.pk, 10),
|
||||
sMSDeviceRequest: device,
|
||||
});
|
||||
break;
|
||||
case "authentik_stages_authenticator_totp.TOTPDevice":
|
||||
await new AuthenticatorsApi(DEFAULT_CONFIG).authenticatorsTotpUpdate({
|
||||
id: this.instance?.pk,
|
||||
id: parseInt(this.instance?.pk, 10),
|
||||
tOTPDeviceRequest: device,
|
||||
});
|
||||
break;
|
||||
case "authentik_stages_authenticator_static.StaticDevice":
|
||||
await new AuthenticatorsApi(DEFAULT_CONFIG).authenticatorsStaticUpdate({
|
||||
id: this.instance?.pk,
|
||||
id: parseInt(this.instance?.pk, 10),
|
||||
staticDeviceRequest: device,
|
||||
});
|
||||
break;
|
||||
case "authentik_stages_authenticator_webauthn.WebAuthnDevice":
|
||||
await new AuthenticatorsApi(DEFAULT_CONFIG).authenticatorsWebauthnUpdate({
|
||||
id: this.instance?.pk,
|
||||
id: parseInt(this.instance?.pk, 10),
|
||||
webAuthnDeviceRequest: device,
|
||||
});
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
throw new SentryIgnoredError(
|
||||
msg(str`Device type ${device.verboseName} cannot be edited`),
|
||||
);
|
||||
}
|
||||
return device;
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { AndNext, DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||
import { SentryIgnoredError } from "@goauthentik/common/errors";
|
||||
import { deviceTypeName } from "@goauthentik/common/labels";
|
||||
import { getRelativeTime } from "@goauthentik/common/utils";
|
||||
import "@goauthentik/elements/buttons/Dropdown";
|
||||
@ -10,7 +11,7 @@ import { PaginatedResponse, Table, TableColumn } from "@goauthentik/elements/tab
|
||||
import "@goauthentik/user/user-settings/mfa/MFADeviceForm";
|
||||
import "@patternfly/elements/pf-tooltip/pf-tooltip.js";
|
||||
|
||||
import { msg } from "@lit/localize";
|
||||
import { msg, str } from "@lit/localize";
|
||||
import { TemplateResult, html } from "lit";
|
||||
import { customElement, property } from "lit/decorators.js";
|
||||
import { ifDefined } from "lit/directives/if-defined.js";
|
||||
@ -89,7 +90,7 @@ export class MFADevicesPage extends Table<Device> {
|
||||
|
||||
async deleteWrapper(device: Device) {
|
||||
const api = new AuthenticatorsApi(DEFAULT_CONFIG);
|
||||
const id = { id: device.pk };
|
||||
const id = { id: parseInt(device.pk, 10) };
|
||||
switch (device.type) {
|
||||
case "authentik_stages_authenticator_duo.DuoDevice":
|
||||
return api.authenticatorsDuoDestroy(id);
|
||||
@ -102,7 +103,9 @@ export class MFADevicesPage extends Table<Device> {
|
||||
case "authentik_stages_authenticator_webauthn.WebAuthnDevice":
|
||||
return api.authenticatorsWebauthnDestroy(id);
|
||||
default:
|
||||
break;
|
||||
throw new SentryIgnoredError(
|
||||
msg(str`Device type ${device.verboseName} cannot be deleted`),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user