stages/authenticator_webauthn: add MDS support (#9114)

* web: align style to show current user for webauthn enroll

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

* ask for aaguid

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

* initial MDS import

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

* add API

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

* add restriction

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

* fix api, add actual restriction

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

* default authenticator name based on aaguid

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

* connect device with device type

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

* fix typo in webauthn stage name

this typo has been around for 3 years 8708e487ae (diff-bb4aee4a37f4b95c8daa7beb6bf6251d8d2b6deb8c16dce0cd7cb0d6cd71900aR16)

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

* add fido2 dep

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

* add CI pipeline to automate updating blob

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

* fix tests, include device type

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

* add tests

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

* exclude icon for now

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

* add passkeys aaguid

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

* make special unknown device type work, add docs

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

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
This commit is contained in:
Jens L
2024-04-08 12:21:26 +02:00
committed by GitHub
parent 919a190971
commit 9f6dca1170
31 changed files with 1139 additions and 208 deletions

View File

@ -5,7 +5,7 @@ import "@goauthentik/admin/stages/authenticator_sms/AuthenticatorSMSStageForm";
import "@goauthentik/admin/stages/authenticator_static/AuthenticatorStaticStageForm";
import "@goauthentik/admin/stages/authenticator_totp/AuthenticatorTOTPStageForm";
import "@goauthentik/admin/stages/authenticator_validate/AuthenticatorValidateStageForm";
import "@goauthentik/admin/stages/authenticator_webauthn/AuthenticateWebAuthnStageForm";
import "@goauthentik/admin/stages/authenticator_webauthn/AuthenticatorWebAuthnStageForm";
import "@goauthentik/admin/stages/captcha/CaptchaStageForm";
import "@goauthentik/admin/stages/consent/ConsentStageForm";
import "@goauthentik/admin/stages/deny/DenyStageForm";

View File

@ -5,7 +5,7 @@ import "@goauthentik/admin/stages/authenticator_sms/AuthenticatorSMSStageForm";
import "@goauthentik/admin/stages/authenticator_static/AuthenticatorStaticStageForm";
import "@goauthentik/admin/stages/authenticator_totp/AuthenticatorTOTPStageForm";
import "@goauthentik/admin/stages/authenticator_validate/AuthenticatorValidateStageForm";
import "@goauthentik/admin/stages/authenticator_webauthn/AuthenticateWebAuthnStageForm";
import "@goauthentik/admin/stages/authenticator_webauthn/AuthenticatorWebAuthnStageForm";
import "@goauthentik/admin/stages/captcha/CaptchaStageForm";
import "@goauthentik/admin/stages/consent/ConsentStageForm";
import "@goauthentik/admin/stages/deny/DenyStageForm";

View File

@ -1,7 +1,12 @@
import { RenderFlowOption } from "@goauthentik/admin/flows/utils";
import { BaseStageForm } from "@goauthentik/admin/stages/BaseStageForm";
import {
DataProvision,
DualSelectPair,
} from "@goauthentik/authentik/elements/ak-dual-select/types";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { first } from "@goauthentik/common/utils";
import "@goauthentik/elements/ak-dual-select/ak-dual-select-provider";
import "@goauthentik/elements/forms/HorizontalFormElement";
import "@goauthentik/elements/forms/Radio";
import "@goauthentik/elements/forms/SearchSelect";
@ -11,8 +16,8 @@ import { TemplateResult, html } from "lit";
import { customElement } from "lit/decorators.js";
import {
AuthenticateWebAuthnStage,
AuthenticatorAttachmentEnum,
AuthenticatorWebAuthnStage,
Flow,
FlowsApi,
FlowsInstancesListDesignationEnum,
@ -20,28 +25,39 @@ import {
ResidentKeyRequirementEnum,
StagesApi,
UserVerificationEnum,
WebAuthnDeviceType,
} from "@goauthentik/api";
@customElement("ak-stage-authenticator-webauthn-form")
export class AuthenticateWebAuthnStageForm extends BaseStageForm<AuthenticateWebAuthnStage> {
loadInstance(pk: string): Promise<AuthenticateWebAuthnStage> {
export class AuthenticatorWebAuthnStageForm extends BaseStageForm<AuthenticatorWebAuthnStage> {
deviceTypeRestrictionPair(item: WebAuthnDeviceType): DualSelectPair {
const label = item.description ? item.description : item.aaguid;
return [
item.aaguid,
html`<div class="selection-main">${label}</div>
<div class="selection-desc">${item.aaguid}</div>`,
label,
];
}
loadInstance(pk: string): Promise<AuthenticatorWebAuthnStage> {
return new StagesApi(DEFAULT_CONFIG).stagesAuthenticatorWebauthnRetrieve({
stageUuid: pk,
});
}
async send(data: AuthenticateWebAuthnStage): Promise<AuthenticateWebAuthnStage> {
async send(data: AuthenticatorWebAuthnStage): Promise<AuthenticatorWebAuthnStage> {
if (data.authenticatorAttachment?.toString() === "") {
data.authenticatorAttachment = null;
}
if (this.instance) {
return new StagesApi(DEFAULT_CONFIG).stagesAuthenticatorWebauthnUpdate({
stageUuid: this.instance.pk || "",
authenticateWebAuthnStageRequest: data,
authenticatorWebAuthnStageRequest: data,
});
} else {
return new StagesApi(DEFAULT_CONFIG).stagesAuthenticatorWebauthnCreate({
authenticateWebAuthnStageRequest: data,
authenticatorWebAuthnStageRequest: data,
});
}
}
@ -164,6 +180,38 @@ export class AuthenticateWebAuthnStageForm extends BaseStageForm<AuthenticateWeb
>
</ak-radio>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${msg("Device type restrictions")}
name="deviceTypeRestrictions"
>
<ak-dual-select-provider
.provider=${(page: number, search?: string): Promise<DataProvision> => {
return new StagesApi(DEFAULT_CONFIG)
.stagesAuthenticatorWebauthnDeviceTypesList({
page: page,
search: search,
})
.then((results) => {
return {
pagination: results.pagination,
options: results.results.map(
this.deviceTypeRestrictionPair,
),
};
});
}}
.selected=${(this.instance?.deviceTypeRestrictionsObj ?? []).map(
this.deviceTypeRestrictionPair,
)}
available-label="${msg("Available Device types")}"
selected-label="${msg("Selected Device types")}"
></ak-dual-select-provider>
<p class="pf-c-form__helper-text">
${msg(
"Optionally restrict which WebAuthn device types may be used. When no device types are selected, all devices are allowed.",
)}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${msg("Configuration flow")}
name="configureFlow"

View File

@ -10,6 +10,7 @@ import { BaseStage } from "@goauthentik/flow/stages/base";
import { msg, str } from "@lit/localize";
import { CSSResult, TemplateResult, css, html, nothing } from "lit";
import { customElement, property } from "lit/decorators.js";
import { ifDefined } from "lit/directives/if-defined.js";
import PFButton from "@patternfly/patternfly/components/Button/button.css";
import PFForm from "@patternfly/patternfly/components/Form/form.css";
@ -130,6 +131,17 @@ export class WebAuthnAuthenticatorRegisterStage extends BaseStage<
</header>
<div class="pf-c-login__main-body">
<form class="pf-c-form">
<ak-form-static
class="pf-c-form__group"
userAvatar="${this.challenge.pendingUserAvatar}"
user=${this.challenge.pendingUser}
>
<div slot="link">
<a href="${ifDefined(this.challenge.flowInfo?.cancelUrl)}"
>${msg("Not you?")}</a
>
</div>
</ak-form-static>
<ak-empty-state
?loading="${this.registerRunning}"
header=${this.registerRunning