feat: provide webauthn device description from frontend on registration (#4363)
parent
bbc9e6422e
commit
92b3a5804b
|
@ -1021,23 +1021,28 @@ components:
|
||||||
type: string
|
type: string
|
||||||
format: byte
|
format: byte
|
||||||
webauthn.CredentialAttestationResponse:
|
webauthn.CredentialAttestationResponse:
|
||||||
allOf:
|
type: object
|
||||||
- $ref: '#/components/schemas/webauthn.PublicKeyCredential'
|
properties:
|
||||||
- type: object
|
credential:
|
||||||
properties:
|
allOf:
|
||||||
clientExtensionResults:
|
- $ref: '#/components/schemas/webauthn.PublicKeyCredential'
|
||||||
type: object
|
- type: object
|
||||||
properties:
|
properties:
|
||||||
appidExclude:
|
clientExtensionResults:
|
||||||
type: boolean
|
type: object
|
||||||
response:
|
|
||||||
allOf:
|
|
||||||
- $ref: '#/components/schemas/webauthn.AuthenticatorResponse'
|
|
||||||
- type: object
|
|
||||||
properties:
|
properties:
|
||||||
attestationObject:
|
appidExclude:
|
||||||
type: string
|
type: boolean
|
||||||
format: byte
|
response:
|
||||||
|
allOf:
|
||||||
|
- $ref: '#/components/schemas/webauthn.AuthenticatorResponse'
|
||||||
|
- type: object
|
||||||
|
properties:
|
||||||
|
attestationObject:
|
||||||
|
type: string
|
||||||
|
format: byte
|
||||||
|
description:
|
||||||
|
type: string
|
||||||
webauthn.CredentialAssertionResponse:
|
webauthn.CredentialAssertionResponse:
|
||||||
allOf:
|
allOf:
|
||||||
- $ref: '#/components/schemas/webauthn.PublicKeyCredential'
|
- $ref: '#/components/schemas/webauthn.PublicKeyCredential'
|
||||||
|
|
|
@ -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,8 +117,17 @@ func WebauthnAttestationPOST(ctx *middlewares.AutheliaCtx) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if attestationResponse, err = protocol.ParseCredentialCreationResponseBody(bytes.NewReader(ctx.PostBody())); err != nil {
|
err = json.Unmarshal(ctx.PostBody(), &postData)
|
||||||
ctx.Logger.Errorf("Unable to parse %s assertionfor user '%s': %+v", regulation.AuthTypeWebauthn, userSession.Username, err)
|
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)
|
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)
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue