feat: provide webauthn device description from frontend on registration (#4363)

pull/4372/head
Stephen Kent 2022-11-12 23:59:21 -08:00 committed by GitHub
parent bbc9e6422e
commit 92b3a5804b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 50 additions and 24 deletions

View File

@ -1021,6 +1021,9 @@ components:
type: string
format: byte
webauthn.CredentialAttestationResponse:
type: object
properties:
credential:
allOf:
- $ref: '#/components/schemas/webauthn.PublicKeyCredential'
- type: object
@ -1038,6 +1041,8 @@ components:
attestationObject:
type: string
format: byte
description:
type: string
webauthn.CredentialAssertionResponse:
allOf:
- $ref: '#/components/schemas/webauthn.PublicKeyCredential'

View File

@ -2,6 +2,7 @@ package handlers
import (
"bytes"
"encoding/json"
"github.com/go-webauthn/webauthn/protocol"
"github.com/go-webauthn/webauthn/webauthn"
@ -83,6 +84,11 @@ func SecondFactorWebauthnAttestationGET(ctx *middlewares.AutheliaCtx, _ string)
// WebauthnAttestationPOST processes the attestation challenge response from the client.
func WebauthnAttestationPOST(ctx *middlewares.AutheliaCtx) {
type requestPostData struct {
Credential json.RawMessage `json:"credential"`
Description string `json:"description"`
}
var (
err error
w *webauthn.WebAuthn
@ -90,6 +96,7 @@ func WebauthnAttestationPOST(ctx *middlewares.AutheliaCtx) {
attestationResponse *protocol.ParsedCredentialCreationData
credential *webauthn.Credential
postData *requestPostData
)
userSession := ctx.GetSession()
@ -110,7 +117,16 @@ func WebauthnAttestationPOST(ctx *middlewares.AutheliaCtx) {
return
}
if attestationResponse, err = protocol.ParseCredentialCreationResponseBody(bytes.NewReader(ctx.PostBody())); err != nil {
err = json.Unmarshal(ctx.PostBody(), &postData)
if err != nil {
ctx.Logger.Errorf("Unable to parse %s assertion request data for user '%s': %+v", regulation.AuthTypeWebauthn, userSession.Username, err)
respondUnauthorized(ctx, messageMFAValidationFailed)
return
}
if attestationResponse, err = protocol.ParseCredentialCreationResponseBody(bytes.NewReader(postData.Credential)); err != nil {
ctx.Logger.Errorf("Unable to parse %s assertion for user '%s': %+v", regulation.AuthTypeWebauthn, userSession.Username, err)
respondUnauthorized(ctx, messageMFAValidationFailed)
@ -134,7 +150,7 @@ func WebauthnAttestationPOST(ctx *middlewares.AutheliaCtx) {
return
}
device := model.NewWebauthnDeviceFromCredential(w.Config.RPID, userSession.Username, "Primary", credential)
device := model.NewWebauthnDeviceFromCredential(w.Config.RPID, userSession.Username, postData.Description, credential)
if err = ctx.Providers.StorageProvider.SaveWebauthnDevice(ctx, device); err != nil {
ctx.Logger.Errorf("Unable to load %s devices for assertion challenge for user '%s': %+v", regulation.AuthTypeWebauthn, userSession.Username, err)

View File

@ -314,10 +314,14 @@ export async function getAssertionPublicKeyCredentialResult(
async function postAttestationPublicKeyCredentialResult(
credential: AttestationPublicKeyCredential,
description: string,
): Promise<AxiosResponse<OptionalDataServiceResponse<any>>> {
const credentialJSON = encodeAttestationPublicKeyCredential(credential);
return axios.post<OptionalDataServiceResponse<any>>(WebauthnAttestationPath, credentialJSON);
const postBody = {
credential: credentialJSON,
description: description,
};
return axios.post<OptionalDataServiceResponse<any>>(WebauthnAttestationPath, postBody);
}
export async function postAssertionPublicKeyCredentialResult(
@ -327,11 +331,10 @@ export async function postAssertionPublicKeyCredentialResult(
workflowID?: string,
): Promise<AxiosResponse<ServiceResponse<SignInResponse>>> {
const credentialJSON = encodeAssertionPublicKeyCredential(credential, targetURL, workflow, workflowID);
return axios.post<ServiceResponse<SignInResponse>>(WebauthnAssertionPath, credentialJSON);
}
export async function performAttestationCeremony(token: string): Promise<AttestationResult> {
export async function performAttestationCeremony(token: string, description: string): Promise<AttestationResult> {
const attestationCreationOpts = await getAttestationCreationOptions(token);
if (attestationCreationOpts.status !== 200 || attestationCreationOpts.options == null) {
@ -350,7 +353,7 @@ export async function performAttestationCeremony(token: string): Promise<Attesta
return AttestationResult.Failure;
}
const response = await postAttestationPublicKeyCredentialResult(attestationResult.credential);
const response = await postAttestationPublicKeyCredentialResult(attestationResult.credential, description);
if (response.data.status === "OK" && (response.status === 200 || response.status === 201)) {
return AttestationResult.Success;

View File

@ -12,6 +12,8 @@ import { FirstFactorPath } from "@services/Api";
import { performAttestationCeremony } from "@services/Webauthn";
import { extractIdentityToken } from "@utils/IdentityToken";
const description = "Primary";
const RegisterWebauthn = function () {
const styles = useStyles();
const navigate = useNavigate();
@ -32,7 +34,7 @@ const RegisterWebauthn = function () {
try {
setRegistrationInProgress(true);
const result = await performAttestationCeremony(processToken);
const result = await performAttestationCeremony(processToken, description);
setRegistrationInProgress(false);