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 type: string
format: byte format: byte
webauthn.CredentialAttestationResponse: webauthn.CredentialAttestationResponse:
type: object
properties:
credential:
allOf: allOf:
- $ref: '#/components/schemas/webauthn.PublicKeyCredential' - $ref: '#/components/schemas/webauthn.PublicKeyCredential'
- type: object - type: object
@ -1038,6 +1041,8 @@ components:
attestationObject: attestationObject:
type: string type: string
format: byte format: byte
description:
type: string
webauthn.CredentialAssertionResponse: webauthn.CredentialAssertionResponse:
allOf: allOf:
- $ref: '#/components/schemas/webauthn.PublicKeyCredential' - $ref: '#/components/schemas/webauthn.PublicKeyCredential'

View File

@ -2,6 +2,7 @@ package handlers
import ( import (
"bytes" "bytes"
"encoding/json"
"github.com/go-webauthn/webauthn/protocol" "github.com/go-webauthn/webauthn/protocol"
"github.com/go-webauthn/webauthn/webauthn" "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. // WebauthnAttestationPOST processes the attestation challenge response from the client.
func WebauthnAttestationPOST(ctx *middlewares.AutheliaCtx) { func WebauthnAttestationPOST(ctx *middlewares.AutheliaCtx) {
type requestPostData struct {
Credential json.RawMessage `json:"credential"`
Description string `json:"description"`
}
var ( var (
err error err error
w *webauthn.WebAuthn w *webauthn.WebAuthn
@ -90,6 +96,7 @@ func WebauthnAttestationPOST(ctx *middlewares.AutheliaCtx) {
attestationResponse *protocol.ParsedCredentialCreationData attestationResponse *protocol.ParsedCredentialCreationData
credential *webauthn.Credential credential *webauthn.Credential
postData *requestPostData
) )
userSession := ctx.GetSession() userSession := ctx.GetSession()
@ -110,7 +117,16 @@ func WebauthnAttestationPOST(ctx *middlewares.AutheliaCtx) {
return 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) ctx.Logger.Errorf("Unable to parse %s assertion for user '%s': %+v", regulation.AuthTypeWebauthn, userSession.Username, err)
respondUnauthorized(ctx, messageMFAValidationFailed) respondUnauthorized(ctx, messageMFAValidationFailed)
@ -134,7 +150,7 @@ func WebauthnAttestationPOST(ctx *middlewares.AutheliaCtx) {
return 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 { 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) 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( async function postAttestationPublicKeyCredentialResult(
credential: AttestationPublicKeyCredential, credential: AttestationPublicKeyCredential,
description: string,
): Promise<AxiosResponse<OptionalDataServiceResponse<any>>> { ): Promise<AxiosResponse<OptionalDataServiceResponse<any>>> {
const credentialJSON = encodeAttestationPublicKeyCredential(credential); const credentialJSON = encodeAttestationPublicKeyCredential(credential);
const postBody = {
return axios.post<OptionalDataServiceResponse<any>>(WebauthnAttestationPath, credentialJSON); credential: credentialJSON,
description: description,
};
return axios.post<OptionalDataServiceResponse<any>>(WebauthnAttestationPath, postBody);
} }
export async function postAssertionPublicKeyCredentialResult( export async function postAssertionPublicKeyCredentialResult(
@ -327,11 +331,10 @@ export async function postAssertionPublicKeyCredentialResult(
workflowID?: string, workflowID?: string,
): Promise<AxiosResponse<ServiceResponse<SignInResponse>>> { ): Promise<AxiosResponse<ServiceResponse<SignInResponse>>> {
const credentialJSON = encodeAssertionPublicKeyCredential(credential, targetURL, workflow, workflowID); const credentialJSON = encodeAssertionPublicKeyCredential(credential, targetURL, workflow, workflowID);
return axios.post<ServiceResponse<SignInResponse>>(WebauthnAssertionPath, credentialJSON); 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); const attestationCreationOpts = await getAttestationCreationOptions(token);
if (attestationCreationOpts.status !== 200 || attestationCreationOpts.options == null) { if (attestationCreationOpts.status !== 200 || attestationCreationOpts.options == null) {
@ -350,7 +353,7 @@ export async function performAttestationCeremony(token: string): Promise<Attesta
return AttestationResult.Failure; 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)) { if (response.data.status === "OK" && (response.status === 200 || response.status === 201)) {
return AttestationResult.Success; return AttestationResult.Success;

View File

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