mirror of
https://github.com/0rangebananaspy/authelia.git
synced 2024-09-14 22:47:21 +07:00
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.
159 lines
5.4 KiB
Go
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)
|
|
}
|