stages/authenticator_webauthn: migrate to SPA
This commit is contained in:
parent
0904fea109
commit
76c572cf7c
|
@ -1,7 +1,7 @@
|
||||||
"""Static OTP Setup stage"""
|
"""Static OTP Setup stage"""
|
||||||
from django.http import HttpRequest, HttpResponse
|
from django.http import HttpRequest, HttpResponse
|
||||||
from django_otp.plugins.otp_static.models import StaticDevice, StaticToken
|
from django_otp.plugins.otp_static.models import StaticDevice, StaticToken
|
||||||
from rest_framework.fields import CharField, IntegerField, ListField
|
from rest_framework.fields import CharField, ListField
|
||||||
from structlog.stdlib import get_logger
|
from structlog.stdlib import get_logger
|
||||||
|
|
||||||
from authentik.flows.challenge import (
|
from authentik.flows.challenge import (
|
||||||
|
|
|
@ -27,10 +27,10 @@ class AuthenticateWebAuthnStage(ConfigurableStage, Stage):
|
||||||
@property
|
@property
|
||||||
def type(self) -> Type[View]:
|
def type(self) -> Type[View]:
|
||||||
from authentik.stages.authenticator_webauthn.stage import (
|
from authentik.stages.authenticator_webauthn.stage import (
|
||||||
AuthenticateWebAuthnStageView,
|
AuthenticatorWebAuthnStageView,
|
||||||
)
|
)
|
||||||
|
|
||||||
return AuthenticateWebAuthnStageView
|
return AuthenticatorWebAuthnStageView
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def form(self) -> Type[ModelForm]:
|
def form(self) -> Type[ModelForm]:
|
||||||
|
|
|
@ -1,13 +1,27 @@
|
||||||
"""WebAuthn stage"""
|
"""WebAuthn stage"""
|
||||||
|
|
||||||
from django.http import HttpRequest, HttpResponse
|
from django.http import HttpRequest, HttpResponse
|
||||||
from django.shortcuts import render
|
from django.http.request import QueryDict
|
||||||
from django.views.generic import FormView
|
from rest_framework.fields import JSONField
|
||||||
|
from rest_framework.serializers import ValidationError
|
||||||
from structlog.stdlib import get_logger
|
from structlog.stdlib import get_logger
|
||||||
|
from webauthn.webauthn import (
|
||||||
|
RegistrationRejectedException,
|
||||||
|
WebAuthnMakeCredentialOptions,
|
||||||
|
WebAuthnRegistrationResponse,
|
||||||
|
)
|
||||||
|
|
||||||
|
from authentik.core.models import User
|
||||||
|
from authentik.flows.challenge import Challenge, ChallengeResponse, ChallengeTypes
|
||||||
from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER
|
from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER
|
||||||
from authentik.flows.stage import StageView
|
from authentik.flows.stage import ChallengeStageView
|
||||||
|
from authentik.lib.templatetags.authentik_utils import avatar
|
||||||
from authentik.stages.authenticator_webauthn.models import WebAuthnDevice
|
from authentik.stages.authenticator_webauthn.models import WebAuthnDevice
|
||||||
|
from authentik.stages.authenticator_webauthn.utils import generate_challenge
|
||||||
|
|
||||||
|
RP_ID = "localhost"
|
||||||
|
RP_NAME = "authentik"
|
||||||
|
ORIGIN = "http://localhost:8000"
|
||||||
|
|
||||||
LOGGER = get_logger()
|
LOGGER = get_logger()
|
||||||
|
|
||||||
|
@ -16,29 +30,135 @@ SESSION_KEY_WEBAUTHN_AUTHENTICATED = (
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class AuthenticateWebAuthnStageView(FormView, StageView):
|
class AuthenticatorWebAuthnChallenge(Challenge):
|
||||||
|
"""WebAuthn Challenge"""
|
||||||
|
|
||||||
|
registration = JSONField()
|
||||||
|
|
||||||
|
|
||||||
|
class AuthenticatorWebAuthnChallengeResponse(ChallengeResponse):
|
||||||
|
"""WebAuthn Challenge response"""
|
||||||
|
|
||||||
|
response = JSONField()
|
||||||
|
|
||||||
|
request: HttpRequest
|
||||||
|
user: User
|
||||||
|
|
||||||
|
def validate_response(self, response: dict) -> dict:
|
||||||
|
"""Validate webauthn challenge response"""
|
||||||
|
challenge = self.request.session["challenge"]
|
||||||
|
|
||||||
|
trusted_attestation_cert_required = True
|
||||||
|
self_attestation_permitted = True
|
||||||
|
none_attestation_permitted = True
|
||||||
|
|
||||||
|
webauthn_registration_response = WebAuthnRegistrationResponse(
|
||||||
|
RP_ID,
|
||||||
|
ORIGIN,
|
||||||
|
response,
|
||||||
|
challenge,
|
||||||
|
trusted_attestation_cert_required=trusted_attestation_cert_required,
|
||||||
|
self_attestation_permitted=self_attestation_permitted,
|
||||||
|
none_attestation_permitted=none_attestation_permitted,
|
||||||
|
uv_required=False,
|
||||||
|
) # User Verification
|
||||||
|
|
||||||
|
try:
|
||||||
|
webauthn_credential = webauthn_registration_response.verify()
|
||||||
|
except RegistrationRejectedException as exc:
|
||||||
|
LOGGER.warning("registration failed", exc=exc)
|
||||||
|
raise ValidationError("Registration failed. Error: {}".format(exc))
|
||||||
|
|
||||||
|
# Step 17.
|
||||||
|
#
|
||||||
|
# Check that the credentialId is not yet registered to any other user.
|
||||||
|
# If registration is requested for a credential that is already registered
|
||||||
|
# to a different user, the Relying Party SHOULD fail this registration
|
||||||
|
# ceremony, or it MAY decide to accept the registration, e.g. while deleting
|
||||||
|
# the older registration.
|
||||||
|
credential_id_exists = WebAuthnDevice.objects.filter(
|
||||||
|
credential_id=webauthn_credential.credential_id
|
||||||
|
).first()
|
||||||
|
if credential_id_exists:
|
||||||
|
raise ValidationError("Credential ID already exists.")
|
||||||
|
|
||||||
|
webauthn_credential.credential_id = str(
|
||||||
|
webauthn_credential.credential_id, "utf-8"
|
||||||
|
)
|
||||||
|
webauthn_credential.public_key = str(webauthn_credential.public_key, "utf-8")
|
||||||
|
|
||||||
|
return webauthn_registration_response
|
||||||
|
|
||||||
|
|
||||||
|
class AuthenticatorWebAuthnStageView(ChallengeStageView):
|
||||||
"""WebAuthn stage"""
|
"""WebAuthn stage"""
|
||||||
|
|
||||||
|
response_class = AuthenticatorWebAuthnChallengeResponse
|
||||||
|
|
||||||
|
def get_challenge(self, *args, **kwargs) -> Challenge:
|
||||||
|
# clear session variables prior to starting a new registration
|
||||||
|
self.request.session.pop("challenge", None)
|
||||||
|
|
||||||
|
challenge = generate_challenge(32)
|
||||||
|
|
||||||
|
# We strip the saved challenge of padding, so that we can do a byte
|
||||||
|
# comparison on the URL-safe-without-padding challenge we get back
|
||||||
|
# from the browser.
|
||||||
|
# We will still pass the padded version down to the browser so that the JS
|
||||||
|
# can decode the challenge into binary without too much trouble.
|
||||||
|
self.request.session["challenge"] = challenge.rstrip("=")
|
||||||
|
user = self.get_pending_user()
|
||||||
|
make_credential_options = WebAuthnMakeCredentialOptions(
|
||||||
|
challenge,
|
||||||
|
RP_NAME,
|
||||||
|
RP_ID,
|
||||||
|
user.uid,
|
||||||
|
user.username,
|
||||||
|
user.name,
|
||||||
|
avatar(user or User()),
|
||||||
|
)
|
||||||
|
|
||||||
|
return AuthenticatorWebAuthnChallenge(
|
||||||
|
data={
|
||||||
|
"type": ChallengeTypes.native,
|
||||||
|
"component": "ak-stage-authenticator-webauthn-register",
|
||||||
|
"registration": make_credential_options.registration_dict,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
|
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
|
||||||
user = self.executor.plan.context.get(PLAN_CONTEXT_PENDING_USER)
|
user = self.executor.plan.context.get(PLAN_CONTEXT_PENDING_USER)
|
||||||
if not user:
|
if not user:
|
||||||
LOGGER.debug("No pending user, continuing")
|
LOGGER.debug("No pending user, continuing")
|
||||||
return self.executor.stage_ok()
|
return self.executor.stage_ok()
|
||||||
devices = WebAuthnDevice.objects.filter(user=user)
|
return super().get(request, *args, **kwargs)
|
||||||
# If the current user is logged in already, or the pending user
|
|
||||||
# has no devices, show setup
|
|
||||||
if self.request.user == user:
|
|
||||||
# Because the user is already authenticated, skip the later check
|
|
||||||
self.request.session[SESSION_KEY_WEBAUTHN_AUTHENTICATED] = True
|
|
||||||
return render(request, "stages/authenticator_webauthn/setup.html")
|
|
||||||
if not devices.exists():
|
|
||||||
return self.executor.stage_ok()
|
|
||||||
self.request.session[SESSION_KEY_WEBAUTHN_AUTHENTICATED] = False
|
|
||||||
return render(request, "stages/authenticator_webauthn/auth.html")
|
|
||||||
|
|
||||||
def post(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
|
def get_response_instance(
|
||||||
"""Since the client can't directly indicate when a stage is done,
|
self, data: QueryDict
|
||||||
we use the post handler for this"""
|
) -> AuthenticatorWebAuthnChallengeResponse:
|
||||||
if request.session.pop(SESSION_KEY_WEBAUTHN_AUTHENTICATED, False):
|
response: AuthenticatorWebAuthnChallengeResponse = (
|
||||||
return self.executor.stage_ok()
|
super().get_response_instance(data)
|
||||||
return self.executor.stage_invalid()
|
)
|
||||||
|
response.request = self.request
|
||||||
|
response.user = self.get_pending_user()
|
||||||
|
return response
|
||||||
|
|
||||||
|
def challenge_valid(self, response: ChallengeResponse) -> HttpResponse:
|
||||||
|
# Webauthn Challenge has already been validated
|
||||||
|
webauthn_credential = response.validated_data["response"]
|
||||||
|
existing_device = WebAuthnDevice.objects.filter(
|
||||||
|
credential_id=webauthn_credential.credential_id
|
||||||
|
).first()
|
||||||
|
if not existing_device:
|
||||||
|
WebAuthnDevice.objects.create(
|
||||||
|
user=self.get_pending_user(),
|
||||||
|
public_key=webauthn_credential.public_key,
|
||||||
|
credential_id=webauthn_credential.credential_id,
|
||||||
|
sign_count=webauthn_credential.sign_count,
|
||||||
|
rp_id=RP_ID,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
return self.executor.stage_invalid(
|
||||||
|
"Device with Credential ID already exists."
|
||||||
|
)
|
||||||
|
return self.executor.stage_ok()
|
||||||
|
|
|
@ -3,30 +3,17 @@ from django.urls import path
|
||||||
from django.views.decorators.csrf import csrf_exempt
|
from django.views.decorators.csrf import csrf_exempt
|
||||||
|
|
||||||
from authentik.stages.authenticator_webauthn.views import (
|
from authentik.stages.authenticator_webauthn.views import (
|
||||||
BeginActivateView,
|
|
||||||
BeginAssertion,
|
BeginAssertion,
|
||||||
UserSettingsView,
|
UserSettingsView,
|
||||||
VerifyAssertion,
|
VerifyAssertion,
|
||||||
VerifyCredentialInfo,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# TODO: Move to API views so we don't need csrf_exempt
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path(
|
|
||||||
"begin-activate/",
|
|
||||||
csrf_exempt(BeginActivateView.as_view()),
|
|
||||||
name="activate-begin",
|
|
||||||
),
|
|
||||||
path(
|
path(
|
||||||
"begin-assertion/",
|
"begin-assertion/",
|
||||||
csrf_exempt(BeginAssertion.as_view()),
|
csrf_exempt(BeginAssertion.as_view()),
|
||||||
name="assertion-begin",
|
name="assertion-begin",
|
||||||
),
|
),
|
||||||
path(
|
|
||||||
"verify-credential-info/",
|
|
||||||
csrf_exempt(VerifyCredentialInfo.as_view()),
|
|
||||||
name="credential-info-verify",
|
|
||||||
),
|
|
||||||
path(
|
path(
|
||||||
"verify-assertion/",
|
"verify-assertion/",
|
||||||
csrf_exempt(VerifyAssertion.as_view()),
|
csrf_exempt(VerifyAssertion.as_view()),
|
||||||
|
|
|
@ -9,8 +9,6 @@ from structlog.stdlib import get_logger
|
||||||
from webauthn import (
|
from webauthn import (
|
||||||
WebAuthnAssertionOptions,
|
WebAuthnAssertionOptions,
|
||||||
WebAuthnAssertionResponse,
|
WebAuthnAssertionResponse,
|
||||||
WebAuthnMakeCredentialOptions,
|
|
||||||
WebAuthnRegistrationResponse,
|
|
||||||
WebAuthnUser,
|
WebAuthnUser,
|
||||||
)
|
)
|
||||||
from webauthn.webauthn import (
|
from webauthn.webauthn import (
|
||||||
|
@ -53,101 +51,6 @@ class FlowUserRequiredView(View):
|
||||||
return super().dispatch(request, *args, **kwargs)
|
return super().dispatch(request, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class BeginActivateView(FlowUserRequiredView):
|
|
||||||
"""Initial device registration view"""
|
|
||||||
|
|
||||||
def post(self, request: HttpRequest) -> HttpResponse:
|
|
||||||
"""Initial device registration view"""
|
|
||||||
# clear session variables prior to starting a new registration
|
|
||||||
request.session.pop("challenge", None)
|
|
||||||
|
|
||||||
challenge = generate_challenge(32)
|
|
||||||
|
|
||||||
# We strip the saved challenge of padding, so that we can do a byte
|
|
||||||
# comparison on the URL-safe-without-padding challenge we get back
|
|
||||||
# from the browser.
|
|
||||||
# We will still pass the padded version down to the browser so that the JS
|
|
||||||
# can decode the challenge into binary without too much trouble.
|
|
||||||
request.session["challenge"] = challenge.rstrip("=")
|
|
||||||
|
|
||||||
make_credential_options = WebAuthnMakeCredentialOptions(
|
|
||||||
challenge,
|
|
||||||
RP_NAME,
|
|
||||||
RP_ID,
|
|
||||||
self.user.uid,
|
|
||||||
self.user.username,
|
|
||||||
self.user.name,
|
|
||||||
avatar(self.user),
|
|
||||||
)
|
|
||||||
|
|
||||||
return JsonResponse(make_credential_options.registration_dict)
|
|
||||||
|
|
||||||
|
|
||||||
class VerifyCredentialInfo(FlowUserRequiredView):
|
|
||||||
"""Finish device registration"""
|
|
||||||
|
|
||||||
def post(self, request: HttpRequest) -> HttpResponse:
|
|
||||||
"""Finish device registration"""
|
|
||||||
challenge = request.session["challenge"]
|
|
||||||
|
|
||||||
registration_response = request.POST
|
|
||||||
trusted_attestation_cert_required = True
|
|
||||||
self_attestation_permitted = True
|
|
||||||
none_attestation_permitted = True
|
|
||||||
|
|
||||||
webauthn_registration_response = WebAuthnRegistrationResponse(
|
|
||||||
RP_ID,
|
|
||||||
ORIGIN,
|
|
||||||
registration_response,
|
|
||||||
challenge,
|
|
||||||
trusted_attestation_cert_required=trusted_attestation_cert_required,
|
|
||||||
self_attestation_permitted=self_attestation_permitted,
|
|
||||||
none_attestation_permitted=none_attestation_permitted,
|
|
||||||
uv_required=False,
|
|
||||||
) # User Verification
|
|
||||||
|
|
||||||
try:
|
|
||||||
webauthn_credential = webauthn_registration_response.verify()
|
|
||||||
except RegistrationRejectedException as exc:
|
|
||||||
LOGGER.warning("registration failed", exc=exc)
|
|
||||||
return JsonResponse({"fail": "Registration failed. Error: {}".format(exc)})
|
|
||||||
|
|
||||||
# Step 17.
|
|
||||||
#
|
|
||||||
# Check that the credentialId is not yet registered to any other user.
|
|
||||||
# If registration is requested for a credential that is already registered
|
|
||||||
# to a different user, the Relying Party SHOULD fail this registration
|
|
||||||
# ceremony, or it MAY decide to accept the registration, e.g. while deleting
|
|
||||||
# the older registration.
|
|
||||||
credential_id_exists = WebAuthnDevice.objects.filter(
|
|
||||||
credential_id=webauthn_credential.credential_id
|
|
||||||
).first()
|
|
||||||
if credential_id_exists:
|
|
||||||
return JsonResponse({"fail": "Credential ID already exists."}, status=401)
|
|
||||||
|
|
||||||
webauthn_credential.credential_id = str(
|
|
||||||
webauthn_credential.credential_id, "utf-8"
|
|
||||||
)
|
|
||||||
webauthn_credential.public_key = str(webauthn_credential.public_key, "utf-8")
|
|
||||||
existing_device = WebAuthnDevice.objects.filter(
|
|
||||||
credential_id=webauthn_credential.credential_id
|
|
||||||
).first()
|
|
||||||
if not existing_device:
|
|
||||||
user = WebAuthnDevice.objects.create(
|
|
||||||
user=self.user,
|
|
||||||
public_key=webauthn_credential.public_key,
|
|
||||||
credential_id=webauthn_credential.credential_id,
|
|
||||||
sign_count=webauthn_credential.sign_count,
|
|
||||||
rp_id=RP_ID,
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
return JsonResponse({"fail": "User already exists."}, status=401)
|
|
||||||
|
|
||||||
LOGGER.debug("Successfully registered.", user=user)
|
|
||||||
|
|
||||||
return JsonResponse({"success": "User successfully registered."})
|
|
||||||
|
|
||||||
|
|
||||||
class BeginAssertion(FlowUserRequiredView):
|
class BeginAssertion(FlowUserRequiredView):
|
||||||
"""Send the client a challenge that we'll check later"""
|
"""Send the client a challenge that we'll check later"""
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,23 @@
|
||||||
import { gettext } from "django";
|
import { gettext } from "django";
|
||||||
import { customElement, html, LitElement, property, TemplateResult } from "lit-element";
|
import { customElement, html, property, TemplateResult } from "lit-element";
|
||||||
|
import { WithUserInfoChallenge } from "../../../api/Flows";
|
||||||
import { SpinnerSize } from "../../Spinner";
|
import { SpinnerSize } from "../../Spinner";
|
||||||
import { getCredentialCreateOptionsFromServer, postNewAssertionToServer, transformCredentialCreateOptions, transformNewAssertionForServer } from "./utils";
|
import { BaseStage } from "../base";
|
||||||
|
import { Assertion, transformCredentialCreateOptions, transformNewAssertionForServer } from "./utils";
|
||||||
|
|
||||||
@customElement("ak-stage-webauthn-register")
|
export interface WebAuthnAuthenticatorRegisterChallenge extends WithUserInfoChallenge {
|
||||||
export class WebAuthnRegister extends LitElement {
|
registration: PublicKeyCredentialCreationOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface WebAuthnAuthenticatorRegisterChallengeResponse {
|
||||||
|
response: Assertion;
|
||||||
|
}
|
||||||
|
|
||||||
|
@customElement("ak-stage-authenticator-webauthn-register")
|
||||||
|
export class WebAuthnAuthenticatorRegisterStage extends BaseStage {
|
||||||
|
|
||||||
|
@property({ attribute: false })
|
||||||
|
challenge?: WebAuthnAuthenticatorRegisterChallenge;
|
||||||
|
|
||||||
@property({type: Boolean})
|
@property({type: Boolean})
|
||||||
registerRunning = false;
|
registerRunning = false;
|
||||||
|
@ -17,17 +30,12 @@ export class WebAuthnRegister extends LitElement {
|
||||||
}
|
}
|
||||||
|
|
||||||
async register(): Promise<void> {
|
async register(): Promise<void> {
|
||||||
// post the data to the server to generate the PublicKeyCredentialCreateOptions
|
if (!this.challenge) {
|
||||||
let credentialCreateOptionsFromServer;
|
return;
|
||||||
try {
|
|
||||||
credentialCreateOptionsFromServer = await getCredentialCreateOptionsFromServer();
|
|
||||||
} catch (err) {
|
|
||||||
throw new Error(gettext(`Failed to generate credential request options: ${err}`));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// convert certain members of the PublicKeyCredentialCreateOptions into
|
// convert certain members of the PublicKeyCredentialCreateOptions into
|
||||||
// byte arrays as expected by the spec.
|
// byte arrays as expected by the spec.
|
||||||
const publicKeyCredentialCreateOptions = transformCredentialCreateOptions(credentialCreateOptionsFromServer);
|
const publicKeyCredentialCreateOptions = transformCredentialCreateOptions(this.challenge?.registration);
|
||||||
|
|
||||||
// request the authenticator(s) to create a new credential keypair.
|
// request the authenticator(s) to create a new credential keypair.
|
||||||
let credential;
|
let credential;
|
||||||
|
@ -49,7 +57,10 @@ export class WebAuthnRegister extends LitElement {
|
||||||
// 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 {
|
||||||
await postNewAssertionToServer(newAssertionForServer);
|
const response = <WebAuthnAuthenticatorRegisterChallengeResponse>{
|
||||||
|
response: newAssertionForServer
|
||||||
|
};
|
||||||
|
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}`));
|
||||||
}
|
}
|
|
@ -84,38 +84,6 @@ export function transformNewAssertionForServer(newAssertion: PublicKeyCredential
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Post the assertion to the server for validation and logging the user in.
|
|
||||||
* @param {Object} assertionDataForServer
|
|
||||||
*/
|
|
||||||
export async function postNewAssertionToServer(assertionDataForServer: Assertion): Promise<GenericResponse> {
|
|
||||||
const formData = new FormData();
|
|
||||||
Object.entries(assertionDataForServer).forEach(([key, value]) => {
|
|
||||||
formData.set(key, value);
|
|
||||||
});
|
|
||||||
|
|
||||||
return await fetchJSON(
|
|
||||||
"/-/user/authenticator/webauthn/verify-credential-info/", {
|
|
||||||
method: "POST",
|
|
||||||
body: formData
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get PublicKeyCredentialRequestOptions for this user from the server
|
|
||||||
* formData of the registration form
|
|
||||||
* @param {FormData} formData
|
|
||||||
*/
|
|
||||||
export async function getCredentialCreateOptionsFromServer(): Promise<GenericResponse> {
|
|
||||||
return await fetchJSON(
|
|
||||||
"/-/user/authenticator/webauthn/begin-activate/",
|
|
||||||
{
|
|
||||||
method: "POST",
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get PublicKeyCredentialRequestOptions for this user from the server
|
* Get PublicKeyCredentialRequestOptions for this user from the server
|
||||||
* formData of the registration form
|
* formData of the registration form
|
||||||
|
|
|
@ -31,9 +31,4 @@ import "./pages/applications/ApplicationViewPage";
|
||||||
import "./pages/tokens/UserTokenList";
|
import "./pages/tokens/UserTokenList";
|
||||||
import "./pages/LibraryPage";
|
import "./pages/LibraryPage";
|
||||||
|
|
||||||
import "./elements/stages/authenticator_webauthn/WebAuthnRegister";
|
|
||||||
import "./elements/stages/authenticator_webauthn/WebAuthnAuth";
|
|
||||||
import "./elements/stages/authenticator_validate/AuthenticatorValidateStage";
|
|
||||||
import "./elements/stages/identification/IdentificationStage";
|
|
||||||
|
|
||||||
import "./interfaces/AdminInterface";
|
import "./interfaces/AdminInterface";
|
||||||
|
|
|
@ -10,6 +10,7 @@ import "../../elements/stages/autosubmit/AutosubmitStage";
|
||||||
import "../../elements/stages/prompt/PromptStage";
|
import "../../elements/stages/prompt/PromptStage";
|
||||||
import "../../elements/stages/authenticator_totp/AuthenticatorTOTPStage";
|
import "../../elements/stages/authenticator_totp/AuthenticatorTOTPStage";
|
||||||
import "../../elements/stages/authenticator_static/AuthenticatorStaticStage";
|
import "../../elements/stages/authenticator_static/AuthenticatorStaticStage";
|
||||||
|
import "../../elements/stages/authenticator_webauthn/WebAuthnAuthenticatorRegisterStage";
|
||||||
import { ShellChallenge, Challenge, ChallengeTypes, Flow, RedirectChallenge } from "../../api/Flows";
|
import { ShellChallenge, Challenge, ChallengeTypes, Flow, RedirectChallenge } from "../../api/Flows";
|
||||||
import { DefaultClient } from "../../api/Client";
|
import { DefaultClient } from "../../api/Client";
|
||||||
import { IdentificationChallenge } from "../../elements/stages/identification/IdentificationStage";
|
import { IdentificationChallenge } from "../../elements/stages/identification/IdentificationStage";
|
||||||
|
@ -20,6 +21,7 @@ import { AutosubmitChallenge } from "../../elements/stages/autosubmit/Autosubmit
|
||||||
import { PromptChallenge } from "../../elements/stages/prompt/PromptStage";
|
import { PromptChallenge } from "../../elements/stages/prompt/PromptStage";
|
||||||
import { AuthenticatorTOTPChallenge } from "../../elements/stages/authenticator_totp/AuthenticatorTOTPStage";
|
import { AuthenticatorTOTPChallenge } from "../../elements/stages/authenticator_totp/AuthenticatorTOTPStage";
|
||||||
import { AuthenticatorStaticChallenge } from "../../elements/stages/authenticator_static/AuthenticatorStaticStage";
|
import { AuthenticatorStaticChallenge } from "../../elements/stages/authenticator_static/AuthenticatorStaticStage";
|
||||||
|
import { WebAuthnAuthenticatorRegisterChallenge } from "../../elements/stages/authenticator_webauthn/WebAuthnAuthenticatorRegisterStage";
|
||||||
|
|
||||||
@customElement("ak-flow-executor")
|
@customElement("ak-flow-executor")
|
||||||
export class FlowExecutor extends LitElement {
|
export class FlowExecutor extends LitElement {
|
||||||
|
@ -40,14 +42,14 @@ export class FlowExecutor extends LitElement {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
submit(formData?: FormData): void {
|
submit(formData?: string | 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: {
|
||||||
"X-CSRFToken": csrftoken,
|
"X-CSRFToken": csrftoken,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
fetch(request, {
|
return fetch(request, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
mode: "same-origin",
|
mode: "same-origin",
|
||||||
body: formData,
|
body: formData,
|
||||||
|
@ -132,6 +134,8 @@ export class FlowExecutor extends LitElement {
|
||||||
return html`<ak-stage-authenticator-totp .host=${this} .challenge=${this.challenge as AuthenticatorTOTPChallenge}></ak-stage-authenticator-totp>`;
|
return html`<ak-stage-authenticator-totp .host=${this} .challenge=${this.challenge as AuthenticatorTOTPChallenge}></ak-stage-authenticator-totp>`;
|
||||||
case "ak-stage-authenticator-static":
|
case "ak-stage-authenticator-static":
|
||||||
return html`<ak-stage-authenticator-static .host=${this} .challenge=${this.challenge as AuthenticatorStaticChallenge}></ak-stage-authenticator-static>`;
|
return html`<ak-stage-authenticator-static .host=${this} .challenge=${this.challenge as AuthenticatorStaticChallenge}></ak-stage-authenticator-static>`;
|
||||||
|
case "ak-stage-authenticator-webauthn-register":
|
||||||
|
return html`<ak-stage-authenticator-webauthn-register .host=${this} .challenge=${this.challenge as WebAuthnAuthenticatorRegisterChallenge}></ak-stage-authenticator-webauthn-register>`;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
Reference in New Issue