stages/authenticator_webauthn: migrate to SPA

This commit is contained in:
Jens Langhammer
2021-02-21 20:53:05 +01:00
parent 0904fea109
commit 76c572cf7c
9 changed files with 174 additions and 186 deletions

View File

@ -1,10 +1,23 @@
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 { getCredentialCreateOptionsFromServer, postNewAssertionToServer, transformCredentialCreateOptions, transformNewAssertionForServer } from "./utils";
import { BaseStage } from "../base";
import { Assertion, transformCredentialCreateOptions, transformNewAssertionForServer } from "./utils";
@customElement("ak-stage-webauthn-register")
export class WebAuthnRegister extends LitElement {
export interface WebAuthnAuthenticatorRegisterChallenge extends WithUserInfoChallenge {
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})
registerRunning = false;
@ -17,17 +30,12 @@ export class WebAuthnRegister extends LitElement {
}
async register(): Promise<void> {
// post the data to the server to generate the PublicKeyCredentialCreateOptions
let credentialCreateOptionsFromServer;
try {
credentialCreateOptionsFromServer = await getCredentialCreateOptionsFromServer();
} catch (err) {
throw new Error(gettext(`Failed to generate credential request options: ${err}`));
if (!this.challenge) {
return;
}
// convert certain members of the PublicKeyCredentialCreateOptions into
// 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.
let credential;
@ -49,7 +57,10 @@ export class WebAuthnRegister extends LitElement {
// post the transformed credential data to the server for validation
// and storing the public key
try {
await postNewAssertionToServer(newAssertionForServer);
const response = <WebAuthnAuthenticatorRegisterChallengeResponse>{
response: newAssertionForServer
};
await this.host?.submit(JSON.stringify(response));
} catch (err) {
throw new Error(gettext(`Server validation of credential failed: ${err}`));
}

View File

@ -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
* formData of the registration form

View File

@ -31,9 +31,4 @@ import "./pages/applications/ApplicationViewPage";
import "./pages/tokens/UserTokenList";
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";

View File

@ -10,6 +10,7 @@ import "../../elements/stages/autosubmit/AutosubmitStage";
import "../../elements/stages/prompt/PromptStage";
import "../../elements/stages/authenticator_totp/AuthenticatorTOTPStage";
import "../../elements/stages/authenticator_static/AuthenticatorStaticStage";
import "../../elements/stages/authenticator_webauthn/WebAuthnAuthenticatorRegisterStage";
import { ShellChallenge, Challenge, ChallengeTypes, Flow, RedirectChallenge } from "../../api/Flows";
import { DefaultClient } from "../../api/Client";
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 { AuthenticatorTOTPChallenge } from "../../elements/stages/authenticator_totp/AuthenticatorTOTPStage";
import { AuthenticatorStaticChallenge } from "../../elements/stages/authenticator_static/AuthenticatorStaticStage";
import { WebAuthnAuthenticatorRegisterChallenge } from "../../elements/stages/authenticator_webauthn/WebAuthnAuthenticatorRegisterStage";
@customElement("ak-flow-executor")
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 request = new Request(DefaultClient.makeUrl(["flows", "executor", this.flowSlug]), {
headers: {
"X-CSRFToken": csrftoken,
},
});
fetch(request, {
return fetch(request, {
method: "POST",
mode: "same-origin",
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>`;
case "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:
break;
}