stages/authenticator_validate: send challenge for each device

This commit is contained in:
Jens Langhammer
2021-02-23 18:24:38 +01:00
parent 3894895d32
commit 8878fac4e7
16 changed files with 230 additions and 149 deletions

View File

@ -42,7 +42,7 @@ export class AuthenticatorStaticStage extends BaseStage {
</h1>
</header>
<div class="pf-c-login__main-body">
<form class="pf-c-form" @submit=${(e: Event) => { this.submit(e); }}>
<form class="pf-c-form" @submit=${(e: Event) => { this.submitForm(e); }}>
<div class="pf-c-form__group">
<div class="form-control-static">
<div class="left">

View File

@ -30,7 +30,7 @@ export class AuthenticatorTOTPStage extends BaseStage {
</h1>
</header>
<div class="pf-c-login__main-body">
<form class="pf-c-form" @submit=${(e: Event) => { this.submit(e); }}>
<form class="pf-c-form" @submit=${(e: Event) => { this.submitForm(e); }}>
<div class="pf-c-form__group">
<div class="form-control-static">
<div class="left">

View File

@ -9,13 +9,18 @@ export enum DeviceClasses {
WEBAUTHN = "webauthn",
}
export interface DeviceChallenge {
device_class: DeviceClasses;
device_uid: string;
challenge: unknown;
}
export interface AuthenticatorValidateStageChallenge extends WithUserInfoChallenge {
users_device_classes: DeviceClasses[];
class_challenges: { [key in DeviceClasses]: unknown };
device_challenges: DeviceChallenge[];
}
export interface AuthenticatorValidateStageChallengeResponse {
device_challenges: { [key in DeviceClasses]: unknown} ;
response: DeviceChallenge;
}
@customElement("ak-stage-authenticator-validate")
@ -24,13 +29,24 @@ export class AuthenticatorValidateStage extends BaseStage implements StageHost {
@property({ attribute: false })
challenge?: AuthenticatorValidateStageChallenge;
renderDeviceClass(deviceClass: DeviceClasses): TemplateResult {
switch (deviceClass) {
@property({attribute: false})
selectedDeviceChallenge?: DeviceChallenge;
renderDeviceChallenge(): TemplateResult {
if (!this.selectedDeviceChallenge) {
return html``;
}
switch (this.selectedDeviceChallenge?.device_class) {
case DeviceClasses.STATIC:
case DeviceClasses.TOTP:
// TODO: Create input for code
return html``;
case DeviceClasses.WEBAUTHN:
return html`<ak-stage-authenticator-validate-webauthn .host=${this} .challenge=${this.challenge}></ak-stage-authenticator-validate-webauthn>`;
return html`<ak-stage-authenticator-validate-webauthn
.host=${this}
.challenge=${this.challenge}
.deviceChallenge=${this.selectedDeviceChallenge}>
</ak-stage-authenticator-validate-webauthn>`;
}
}
@ -40,9 +56,13 @@ export class AuthenticatorValidateStage extends BaseStage implements StageHost {
render(): TemplateResult {
// User only has a single device class, so we don't show a picker
if (this.challenge?.users_device_classes.length === 1) {
return this.renderDeviceClass(this.challenge.users_device_classes[0]);
if (this.challenge?.device_challenges.length === 1) {
this.selectedDeviceChallenge = this.challenge.device_challenges[0];
}
if (this.selectedDeviceChallenge) {
return this.renderDeviceChallenge();
}
// TODO: Create picker between challenges
return html`ak-stage-authenticator-validate`;
}

View File

@ -3,7 +3,7 @@ import { customElement, html, property, TemplateResult } from "lit-element";
import { SpinnerSize } from "../../Spinner";
import { transformAssertionForServer, transformCredentialRequestOptions } from "../authenticator_webauthn/utils";
import { BaseStage } from "../base";
import { AuthenticatorValidateStageChallenge, DeviceClasses } from "./AuthenticatorValidateStage";
import { AuthenticatorValidateStageChallenge, DeviceChallenge } from "./AuthenticatorValidateStage";
@customElement("ak-stage-authenticator-validate-webauthn")
export class AuthenticatorValidateStageWebAuthn extends BaseStage {
@ -11,6 +11,9 @@ export class AuthenticatorValidateStageWebAuthn extends BaseStage {
@property({attribute: false})
challenge?: AuthenticatorValidateStageChallenge;
@property({attribute: false})
deviceChallenge?: DeviceChallenge;
@property({ type: Boolean })
authenticateRunning = false;
@ -20,7 +23,7 @@ export class AuthenticatorValidateStageWebAuthn extends BaseStage {
async authenticate(): Promise<void> {
// convert certain members of the PublicKeyCredentialRequestOptions into
// byte arrays as expected by the spec.
const credentialRequestOptions = <PublicKeyCredentialRequestOptions>this.challenge?.class_challenges[DeviceClasses.WEBAUTHN];
const credentialRequestOptions = <PublicKeyCredentialRequestOptions>this.deviceChallenge?.challenge;
const transformedCredentialRequestOptions = transformCredentialRequestOptions(credentialRequestOptions);
// request the authenticator to create an assertion signature using the
@ -44,7 +47,11 @@ export class AuthenticatorValidateStageWebAuthn extends BaseStage {
// post the assertion to the server for verification.
try {
const formData = new FormData();
formData.set(`response[${DeviceClasses.WEBAUTHN}]`, JSON.stringify(transformedAssertionForServer));
formData.set("response", JSON.stringify(<DeviceChallenge>{
device_class: this.deviceChallenge?.device_class,
device_uid: this.deviceChallenge?.device_uid,
challenge: transformedAssertionForServer,
}));
await this.host?.submit(formData);
} catch (err) {
throw new Error(gettext(`Error when validating assertion on server: ${err}`));

View File

@ -36,7 +36,7 @@ export class ConsentStage extends BaseStage {
</h1>
</header>
<div class="pf-c-login__main-body">
<form class="pf-c-form" @submit=${(e: Event) => { this.submit(e); }}>
<form class="pf-c-form" @submit=${(e: Event) => { this.submitForm(e); }}>
<div class="pf-c-form__group">
<div class="form-control-static">
<div class="left">

View File

@ -26,7 +26,7 @@ export class EmailStage extends BaseStage {
</h1>
</header>
<div class="pf-c-login__main-body">
<form class="pf-c-form" @submit=${(e: Event) => { this.submit(e); }}>
<form class="pf-c-form" @submit=${(e: Event) => { this.submitForm(e); }}>
<div class="pf-c-form__group">
<p>
${gettext("Check your Emails for a password reset link.")}

View File

@ -74,7 +74,7 @@ export class IdentificationStage extends BaseStage {
</h1>
</header>
<div class="pf-c-login__main-body">
<form class="pf-c-form" @submit=${(e: Event) => {this.submit(e);}}>
<form class="pf-c-form" @submit=${(e: Event) => {this.submitForm(e);}}>
${this.challenge.application_pre ?
html`<p>
${gettext(`Login to continue to ${this.challenge.application_pre}.`)}

View File

@ -29,7 +29,7 @@ export class PasswordStage extends BaseStage {
</h1>
</header>
<div class="pf-c-login__main-body">
<form class="pf-c-form" @submit=${(e: Event) => {this.submit(e);}}>
<form class="pf-c-form" @submit=${(e: Event) => {this.submitForm(e);}}>
<div class="pf-c-form__group">
<div class="form-control-static">
<div class="left">

View File

@ -119,7 +119,7 @@ export class PromptStage extends BaseStage {
</h1>
</header>
<div class="pf-c-login__main-body">
<form class="pf-c-form" @submit=${(e: Event) => {this.submit(e);}}>
<form class="pf-c-form" @submit=${(e: Event) => {this.submitForm(e);}}>
${this.challenge.fields.map((prompt) => {
return html`<ak-form-element
label="${prompt.label}"