stages/authenticator_validate: add ability to limit webauthn device types (#9180)
* stages/authenticator_validate: add ability to limit webauthn device types Signed-off-by: Jens Langhammer <jens@goauthentik.io> * reword Signed-off-by: Jens Langhammer <jens@goauthentik.io> * require enterprise attestation when a device restriction is configured as we need the aaguid Signed-off-by: Jens Langhammer <jens@goauthentik.io> * add tests Signed-off-by: Jens Langhammer <jens@goauthentik.io> * improve error message Signed-off-by: Jens Langhammer <jens@goauthentik.io> * add more tests Signed-off-by: Jens Langhammer <jens@goauthentik.io> --------- Signed-off-by: Jens Langhammer <jens@goauthentik.io>
This commit is contained in:
@ -1,5 +1,9 @@
|
||||
import { BaseStageForm } from "@goauthentik/admin/stages/BaseStageForm";
|
||||
import { deviceTypeRestrictionPair } from "@goauthentik/admin/stages/authenticator_webauthn/utils";
|
||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||
import "@goauthentik/elements/Alert";
|
||||
import "@goauthentik/elements/ak-dual-select/ak-dual-select-provider";
|
||||
import { DataProvision } from "@goauthentik/elements/ak-dual-select/types";
|
||||
import "@goauthentik/elements/forms/FormGroup";
|
||||
import "@goauthentik/elements/forms/HorizontalFormElement";
|
||||
import "@goauthentik/elements/forms/Radio";
|
||||
@ -71,7 +75,8 @@ export class AuthenticatorValidateStageForm extends BaseStageForm<AuthenticatorV
|
||||
[DeviceClassesEnum.Sms, msg("SMS-based Authenticators")],
|
||||
];
|
||||
|
||||
return html` <span>
|
||||
return html`
|
||||
<span>
|
||||
${msg(
|
||||
"Stage used to validate any authenticator. This stage should be used during authentication or authorization flows.",
|
||||
)}
|
||||
@ -119,7 +124,7 @@ export class AuthenticatorValidateStageForm extends BaseStageForm<AuthenticatorV
|
||||
/>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${msg(
|
||||
"If any of the devices user of the types selected above have been used within this duration, this stage will be skipped.",
|
||||
"If the user has successfully authenticated with a device in the classes listed above within this configured duration, this stage will be skipped.",
|
||||
)}
|
||||
</p>
|
||||
<ak-utils-time-delta-help></ak-utils-time-delta-help>
|
||||
@ -166,33 +171,6 @@ export class AuthenticatorValidateStageForm extends BaseStageForm<AuthenticatorV
|
||||
</option>
|
||||
</select>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal
|
||||
label=${msg("WebAuthn User verification")}
|
||||
?required=${true}
|
||||
name="webauthnUserVerification"
|
||||
>
|
||||
<ak-radio
|
||||
.options=${[
|
||||
{
|
||||
label: msg("User verification must occur."),
|
||||
value: UserVerificationEnum.Required,
|
||||
default: true,
|
||||
},
|
||||
{
|
||||
label: msg(
|
||||
"User verification is preferred if available, but not required.",
|
||||
),
|
||||
value: UserVerificationEnum.Preferred,
|
||||
},
|
||||
{
|
||||
label: msg("User verification should not occur."),
|
||||
value: UserVerificationEnum.Discouraged,
|
||||
},
|
||||
]}
|
||||
.value=${this.instance?.webauthnUserVerification}
|
||||
>
|
||||
</ak-radio>
|
||||
</ak-form-element-horizontal>
|
||||
${this.showConfigurationStages
|
||||
? html`
|
||||
<ak-form-element-horizontal
|
||||
@ -228,6 +206,77 @@ export class AuthenticatorValidateStageForm extends BaseStageForm<AuthenticatorV
|
||||
`
|
||||
: html``}
|
||||
</div>
|
||||
</ak-form-group>`;
|
||||
</ak-form-group>
|
||||
<ak-form-group .expanded=${true}>
|
||||
<span slot="header"> ${msg("WebAuthn-specific settings")} </span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-element-horizontal
|
||||
label=${msg("WebAuthn User verification")}
|
||||
?required=${true}
|
||||
name="webauthnUserVerification"
|
||||
>
|
||||
<ak-radio
|
||||
.options=${[
|
||||
{
|
||||
label: msg("User verification must occur."),
|
||||
value: UserVerificationEnum.Required,
|
||||
default: true,
|
||||
},
|
||||
{
|
||||
label: msg(
|
||||
"User verification is preferred if available, but not required.",
|
||||
),
|
||||
value: UserVerificationEnum.Preferred,
|
||||
},
|
||||
{
|
||||
label: msg("User verification should not occur."),
|
||||
value: UserVerificationEnum.Discouraged,
|
||||
},
|
||||
]}
|
||||
.value=${this.instance?.webauthnUserVerification}
|
||||
>
|
||||
</ak-radio>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal
|
||||
label=${msg("WebAuthn Device type restrictions")}
|
||||
name="webauthnAllowedDeviceTypes"
|
||||
>
|
||||
<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(deviceTypeRestrictionPair),
|
||||
};
|
||||
});
|
||||
}}
|
||||
.selected=${(this.instance?.webauthnAllowedDeviceTypesObj ?? []).map(
|
||||
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-alert ?inline=${true}>
|
||||
${
|
||||
/* TODO: Remove this after 2024.6..or maybe later? */
|
||||
msg(
|
||||
"This restriction only applies to devices created in authentik 2024.4 or later.",
|
||||
)
|
||||
}
|
||||
</ak-alert>
|
||||
</ak-form-element-horizontal>
|
||||
</div>
|
||||
</ak-form-group>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,9 +1,7 @@
|
||||
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 { deviceTypeRestrictionPair } from "@goauthentik/admin/stages/authenticator_webauthn/utils";
|
||||
import { DataProvision } 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";
|
||||
@ -25,23 +23,12 @@ import {
|
||||
ResidentKeyRequirementEnum,
|
||||
StagesApi,
|
||||
UserVerificationEnum,
|
||||
WebAuthnDeviceType,
|
||||
} from "@goauthentik/api";
|
||||
|
||||
@customElement("ak-stage-authenticator-webauthn-form")
|
||||
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({
|
||||
async loadInstance(pk: string): Promise<AuthenticatorWebAuthnStage> {
|
||||
return await new StagesApi(DEFAULT_CONFIG).stagesAuthenticatorWebauthnRetrieve({
|
||||
stageUuid: pk,
|
||||
});
|
||||
}
|
||||
@ -194,14 +181,12 @@ export class AuthenticatorWebAuthnStageForm extends BaseStageForm<AuthenticatorW
|
||||
.then((results) => {
|
||||
return {
|
||||
pagination: results.pagination,
|
||||
options: results.results.map(
|
||||
this.deviceTypeRestrictionPair,
|
||||
),
|
||||
options: results.results.map(deviceTypeRestrictionPair),
|
||||
};
|
||||
});
|
||||
}}
|
||||
.selected=${(this.instance?.deviceTypeRestrictionsObj ?? []).map(
|
||||
this.deviceTypeRestrictionPair,
|
||||
deviceTypeRestrictionPair,
|
||||
)}
|
||||
available-label="${msg("Available Device types")}"
|
||||
selected-label="${msg("Selected Device types")}"
|
||||
|
||||
15
web/src/admin/stages/authenticator_webauthn/utils.ts
Normal file
15
web/src/admin/stages/authenticator_webauthn/utils.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import { DualSelectPair } from "@goauthentik/elements/ak-dual-select/types";
|
||||
|
||||
import { html } from "lit";
|
||||
|
||||
import { WebAuthnDeviceType } from "@goauthentik/api";
|
||||
|
||||
export function 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,
|
||||
];
|
||||
}
|
||||
Reference in New Issue
Block a user