1
0
mirror of https://github.com/0rangebananaspy/authelia.git synced 2024-09-14 22:47:21 +07:00
authelia/internal/handlers/handler_register_webauthn.go
James Elliott 8f05846e21
feat: webauthn ()
This implements Webauthn. Old devices can be used to authenticate via the appid compatibility layer which should be automatic. New devices will be registered via Webauthn, and devices which do not support FIDO2 will no longer be able to be registered. At this time it does not fully support multiple devices (backend does, frontend doesn't allow registration of additional devices). Does not support passwordless.
2022-03-03 22:20:43 +11:00

159 lines
5.4 KiB
Go

package handlers
import (
"bytes"
"github.com/duo-labs/webauthn/protocol"
"github.com/duo-labs/webauthn/webauthn"
"github.com/valyala/fasthttp"
"github.com/authelia/authelia/v4/internal/middlewares"
"github.com/authelia/authelia/v4/internal/models"
"github.com/authelia/authelia/v4/internal/regulation"
)
// SecondFactorWebauthnIdentityStart the handler for initiating the identity validation.
var SecondFactorWebauthnIdentityStart = middlewares.IdentityVerificationStart(middlewares.IdentityVerificationStartArgs{
MailTitle: "Register your key",
MailButtonContent: "Register",
TargetEndpoint: "/webauthn/register",
ActionClaim: ActionWebauthnRegistration,
IdentityRetrieverFunc: identityRetrieverFromSession,
}, nil)
// SecondFactorWebauthnIdentityFinish the handler for finishing the identity validation.
var SecondFactorWebauthnIdentityFinish = middlewares.IdentityVerificationFinish(
middlewares.IdentityVerificationFinishArgs{
ActionClaim: ActionWebauthnRegistration,
IsTokenUserValidFunc: isTokenUserValidFor2FARegistration,
}, SecondFactorWebauthnAttestationGET)
// SecondFactorWebauthnAttestationGET returns the attestation challenge from the server.
func SecondFactorWebauthnAttestationGET(ctx *middlewares.AutheliaCtx, _ string) {
var (
w *webauthn.WebAuthn
user *models.WebauthnUser
err error
)
userSession := ctx.GetSession()
if w, err = newWebauthn(ctx); err != nil {
ctx.Logger.Errorf("Unable to create %s attestation challenge for user '%s': %+v", regulation.AuthTypeWebauthn, userSession.Username, err)
respondUnauthorized(ctx, messageUnableToRegisterSecurityKey)
return
}
if user, err = getWebAuthnUser(ctx, userSession); err != nil {
ctx.Logger.Errorf("Unable to load %s devices for assertion challenge for user '%s': %+v", regulation.AuthTypeWebauthn, userSession.Username, err)
respondUnauthorized(ctx, messageMFAValidationFailed)
return
}
var credentialCreation *protocol.CredentialCreation
if credentialCreation, userSession.Webauthn, err = w.BeginRegistration(user); err != nil {
ctx.Logger.Errorf("Unable to create %s attestation challenge for user '%s': %+v", regulation.AuthTypeWebauthn, userSession.Username, err)
respondUnauthorized(ctx, messageUnableToRegisterSecurityKey)
return
}
if err = ctx.SaveSession(userSession); err != nil {
ctx.Logger.Errorf(logFmtErrSessionSave, "attestation challenge", regulation.AuthTypeWebauthn, userSession.Username, err)
respondUnauthorized(ctx, messageUnableToRegisterSecurityKey)
return
}
if err = ctx.SetJSONBody(credentialCreation); err != nil {
ctx.Logger.Errorf(logFmtErrWriteResponseBody, regulation.AuthTypeWebauthn, userSession.Username, err)
respondUnauthorized(ctx, messageUnableToRegisterSecurityKey)
return
}
}
// SecondFactorWebauthnAttestationPOST processes the attestation challenge response from the client.
func SecondFactorWebauthnAttestationPOST(ctx *middlewares.AutheliaCtx) {
var (
err error
w *webauthn.WebAuthn
user *models.WebauthnUser
attestationResponse *protocol.ParsedCredentialCreationData
credential *webauthn.Credential
)
userSession := ctx.GetSession()
if userSession.Webauthn == nil {
ctx.Logger.Errorf("Webauthn session data is not present in order to handle attestation for user '%s'. This could indicate a user trying to POST to the wrong endpoint, or the session data is not present for the browser they used.", userSession.Username)
respondUnauthorized(ctx, messageMFAValidationFailed)
return
}
if w, err = newWebauthn(ctx); err != nil {
ctx.Logger.Errorf("Unable to configure %s during assertion challenge for user '%s': %+v", regulation.AuthTypeWebauthn, userSession.Username, err)
respondUnauthorized(ctx, messageUnableToRegisterSecurityKey)
return
}
if attestationResponse, err = protocol.ParseCredentialCreationResponseBody(bytes.NewReader(ctx.PostBody())); err != nil {
ctx.Logger.Errorf("Unable to parse %s assertionfor user '%s': %+v", regulation.AuthTypeWebauthn, userSession.Username, err)
respondUnauthorized(ctx, messageMFAValidationFailed)
return
}
if user, err = getWebAuthnUser(ctx, userSession); err != nil {
ctx.Logger.Errorf("Unable to load %s devices for assertion challenge for user '%s': %+v", regulation.AuthTypeWebauthn, userSession.Username, err)
respondUnauthorized(ctx, messageMFAValidationFailed)
return
}
if credential, err = w.CreateCredential(user, *userSession.Webauthn, attestationResponse); err != nil {
ctx.Logger.Errorf("Unable to load %s devices for assertion challenge for user '%s': %+v", regulation.AuthTypeWebauthn, userSession.Username, err)
respondUnauthorized(ctx, messageMFAValidationFailed)
return
}
device := models.NewWebauthnDeviceFromCredential(w.Config.RPID, userSession.Username, "Primary", 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)
respondUnauthorized(ctx, messageMFAValidationFailed)
return
}
userSession.Webauthn = nil
if err = ctx.SaveSession(userSession); err != nil {
ctx.Logger.Errorf(logFmtErrSessionSave, "removal of the attestation challenge", regulation.AuthTypeWebauthn, userSession.Username, err)
respondUnauthorized(ctx, messageMFAValidationFailed)
return
}
ctx.ReplyOK()
ctx.SetStatusCode(fasthttp.StatusCreated)
}