stages/authenticator_webauthn: fix incorrect response being sent

This commit is contained in:
Jens Langhammer 2021-02-22 19:54:05 +01:00
parent 388c8c8bec
commit 451c117ea4
8 changed files with 161 additions and 9 deletions

View file

@ -0,0 +1,86 @@
# Generated by Django 3.1.6 on 2021-02-22 18:21
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_policies_event_matcher", "0009_auto_20210215_2159"),
]
operations = [
migrations.AlterField(
model_name="eventmatcherpolicy",
name="app",
field=models.TextField(
blank=True,
choices=[
("authentik.admin", "authentik Admin"),
("authentik.api", "authentik API"),
("authentik.events", "authentik Events"),
("authentik.crypto", "authentik Crypto"),
("authentik.flows", "authentik Flows"),
("authentik.outposts", "authentik Outpost"),
("authentik.lib", "authentik lib"),
("authentik.policies", "authentik Policies"),
("authentik.policies.dummy", "authentik Policies.Dummy"),
(
"authentik.policies.event_matcher",
"authentik Policies.Event Matcher",
),
("authentik.policies.expiry", "authentik Policies.Expiry"),
("authentik.policies.expression", "authentik Policies.Expression"),
(
"authentik.policies.group_membership",
"authentik Policies.Group Membership",
),
("authentik.policies.hibp", "authentik Policies.HaveIBeenPwned"),
("authentik.policies.password", "authentik Policies.Password"),
("authentik.policies.reputation", "authentik Policies.Reputation"),
("authentik.providers.proxy", "authentik Providers.Proxy"),
("authentik.providers.oauth2", "authentik Providers.OAuth2"),
("authentik.providers.saml", "authentik Providers.SAML"),
("authentik.recovery", "authentik Recovery"),
("authentik.sources.ldap", "authentik Sources.LDAP"),
("authentik.sources.oauth", "authentik Sources.OAuth"),
("authentik.sources.saml", "authentik Sources.SAML"),
(
"authentik.stages.authenticator_static",
"authentik Stages.Authenticator.Static",
),
(
"authentik.stages.authenticator_totp",
"authentik Stages.Authenticator.TOTP",
),
(
"authentik.stages.authenticator_validate",
"authentik Stages.Authenticator.Validate",
),
(
"authentik.stages.authenticator_webauthn",
"authentik Stages.Authenticator.WebAuthn",
),
("authentik.stages.captcha", "authentik Stages.Captcha"),
("authentik.stages.consent", "authentik Stages.Consent"),
("authentik.stages.dummy", "authentik Stages.Dummy"),
("authentik.stages.email", "authentik Stages.Email"),
(
"authentik.stages.identification",
"authentik Stages.Identification",
),
("authentik.stages.invitation", "authentik Stages.User Invitation"),
("authentik.stages.password", "authentik Stages.Password"),
("authentik.stages.prompt", "authentik Stages.Prompt"),
("authentik.stages.user_delete", "authentik Stages.User Delete"),
("authentik.stages.user_login", "authentik Stages.User Login"),
("authentik.stages.user_logout", "authentik Stages.User Logout"),
("authentik.stages.user_write", "authentik Stages.User Write"),
("authentik.managed", "authentik Managed"),
("authentik.core", "authentik Core"),
],
default="",
help_text="Match events created by selected application. When left empty, all applications are matched.",
),
),
]

View file

@ -22,7 +22,7 @@ def create_default_setup_flow(apps: Apps, schema_editor: BaseDatabaseSchemaEdito
designation=FlowDesignation.STAGE_CONFIGURATION, designation=FlowDesignation.STAGE_CONFIGURATION,
defaults={ defaults={
"name": "default-authenticator-webuahtn-setup", "name": "default-authenticator-webuahtn-setup",
"title": "Setup Static OTP Tokens", "title": "Setup WebAuthn",
}, },
) )

View file

@ -0,0 +1,20 @@
# Generated by Django 3.1.6 on 2021-02-22 18:21
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_stages_authenticator_webauthn", "0002_default_setup_flow"),
]
operations = [
migrations.AddField(
model_name="webauthndevice",
name="confirmed",
field=models.BooleanField(
default=True, help_text="Is this device ready for use?"
),
),
]

View file

@ -8,6 +8,7 @@ from django.urls import reverse
from django.utils.timezone import now from django.utils.timezone import now
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django.views import View from django.views import View
from django_otp.models import Device
from rest_framework.serializers import BaseSerializer from rest_framework.serializers import BaseSerializer
from authentik.flows.models import ConfigurableStage, Stage from authentik.flows.models import ConfigurableStage, Stage
@ -56,7 +57,7 @@ class AuthenticateWebAuthnStage(ConfigurableStage, Stage):
verbose_name_plural = _("WebAuthn Authenticator Setup Stages") verbose_name_plural = _("WebAuthn Authenticator Setup Stages")
class WebAuthnDevice(models.Model): class WebAuthnDevice(Device):
"""WebAuthn Device for a single user""" """WebAuthn Device for a single user"""
user = models.ForeignKey(get_user_model(), on_delete=models.CASCADE) user = models.ForeignKey(get_user_model(), on_delete=models.CASCADE)

View file

@ -7,6 +7,7 @@ from rest_framework.serializers import ValidationError
from structlog.stdlib import get_logger from structlog.stdlib import get_logger
from webauthn.webauthn import ( from webauthn.webauthn import (
RegistrationRejectedException, RegistrationRejectedException,
WebAuthnCredential,
WebAuthnMakeCredentialOptions, WebAuthnMakeCredentialOptions,
WebAuthnRegistrationResponse, WebAuthnRegistrationResponse,
) )
@ -87,7 +88,7 @@ class AuthenticatorWebAuthnChallengeResponse(ChallengeResponse):
) )
webauthn_credential.public_key = str(webauthn_credential.public_key, "utf-8") webauthn_credential.public_key = str(webauthn_credential.public_key, "utf-8")
return webauthn_registration_response return webauthn_credential
class AuthenticatorWebAuthnStageView(ChallengeStageView): class AuthenticatorWebAuthnStageView(ChallengeStageView):
@ -145,7 +146,7 @@ class AuthenticatorWebAuthnStageView(ChallengeStageView):
def challenge_valid(self, response: ChallengeResponse) -> HttpResponse: def challenge_valid(self, response: ChallengeResponse) -> HttpResponse:
# Webauthn Challenge has already been validated # Webauthn Challenge has already been validated
webauthn_credential = response.validated_data["response"] webauthn_credential: WebAuthnCredential = response.validated_data["response"]
existing_device = WebAuthnDevice.objects.filter( existing_device = WebAuthnDevice.objects.filter(
credential_id=webauthn_credential.credential_id credential_id=webauthn_credential.credential_id
).first() ).first()

View file

@ -0,0 +1,42 @@
# Generated by Django 3.1.6 on 2021-02-22 18:21
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_stages_prompt", "0002_auto_20200920_1859"),
]
operations = [
migrations.AlterField(
model_name="prompt",
name="type",
field=models.CharField(
choices=[
("text", "Text: Simple Text input"),
(
"username",
"Username: Same as Text input, but checks for and prevents duplicate usernames.",
),
("email", "Email: Text field with Email type."),
(
"password",
"Password: Masked input, password is validated against sources. Policies still have to be applied to this Stage. If two of these are used in the same stage, they are ensured to be identical.",
),
("number", "Number"),
("checkbox", "Checkbox"),
("date", "Date"),
("date-time", "Date Time"),
("separator", "Separator: Static Separator Line"),
(
"hidden",
"Hidden: Hidden field, can be used to insert data into form.",
),
("static", "Static: Static value, displayed as-is."),
],
max_length=100,
),
),
]

View file

@ -57,10 +57,9 @@ export class WebAuthnAuthenticatorRegisterStage extends BaseStage {
// post the transformed credential data to the server for validation // post the transformed credential data to the server for validation
// and storing the public key // and storing the public key
try { try {
const response = <WebAuthnAuthenticatorRegisterChallengeResponse>{ const formData = new FormData();
response: newAssertionForServer formData.set("response", JSON.stringify(newAssertionForServer))
}; await this.host?.submit(formData);
await this.host?.submit(JSON.stringify(response));
} catch (err) { } catch (err) {
throw new Error(gettext(`Server validation of credential failed: ${err}`)); throw new Error(gettext(`Server validation of credential failed: ${err}`));
} }
@ -100,6 +99,9 @@ export class WebAuthnAuthenticatorRegisterStage extends BaseStage {
</div>`: </div>`:
html` html`
<div class="pf-c-form__group pf-m-action"> <div class="pf-c-form__group pf-m-action">
${this.challenge?.response_errors ?
html`<p class="pf-m-block">${this.challenge.response_errors["response"][0].string}</p>`:
html``}
<p class="pf-m-block">${this.registerMessage}</p> <p class="pf-m-block">${this.registerMessage}</p>
<button class="pf-c-button pf-m-primary pf-m-block" @click=${() => { <button class="pf-c-button pf-m-primary pf-m-block" @click=${() => {
this.registerWrapper(); this.registerWrapper();

View file

@ -63,7 +63,7 @@ export class FlowExecutor extends LitElement {
}); });
} }
submit(formData?: string | FormData): Promise<void> { submit(formData?: FormData): Promise<void> {
const csrftoken = getCookie("authentik_csrf"); const csrftoken = getCookie("authentik_csrf");
const request = new Request(DefaultClient.makeUrl(["flows", "executor", this.flowSlug]), { const request = new Request(DefaultClient.makeUrl(["flows", "executor", this.flowSlug]), {
headers: { headers: {