mirror of
https://github.com/0rangebananaspy/authelia.git
synced 2024-09-14 22:47:21 +07:00
ce6bf74c8d
This fixes edge cases where the remote IP was not correctly logged. Generally this is not an issue as most errors do not hit this handler, but in instances where a transport error occurs this is important.
206 lines
6.2 KiB
Go
206 lines
6.2 KiB
Go
package handlers
|
|
|
|
import (
|
|
"bytes"
|
|
|
|
"github.com/go-webauthn/webauthn/protocol"
|
|
"github.com/go-webauthn/webauthn/webauthn"
|
|
|
|
"github.com/authelia/authelia/v4/internal/middlewares"
|
|
"github.com/authelia/authelia/v4/internal/model"
|
|
"github.com/authelia/authelia/v4/internal/regulation"
|
|
)
|
|
|
|
// WebauthnAssertionGET handler starts the assertion ceremony.
|
|
func WebauthnAssertionGET(ctx *middlewares.AutheliaCtx) {
|
|
var (
|
|
w *webauthn.WebAuthn
|
|
user *model.WebauthnUser
|
|
err error
|
|
)
|
|
|
|
userSession := ctx.GetSession()
|
|
|
|
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, messageMFAValidationFailed)
|
|
|
|
return
|
|
}
|
|
|
|
if user, err = getWebAuthnUser(ctx, userSession); err != nil {
|
|
ctx.Logger.Errorf("Unable to create %s assertion challenge for user '%s': %+v", regulation.AuthTypeWebauthn, userSession.Username, err)
|
|
|
|
respondUnauthorized(ctx, messageMFAValidationFailed)
|
|
|
|
return
|
|
}
|
|
|
|
var opts = []webauthn.LoginOption{
|
|
webauthn.WithAllowedCredentials(user.WebAuthnCredentialDescriptors()),
|
|
}
|
|
|
|
extensions := make(map[string]interface{})
|
|
|
|
if user.HasFIDOU2F() {
|
|
extensions["appid"] = w.Config.RPOrigin
|
|
}
|
|
|
|
if len(extensions) != 0 {
|
|
opts = append(opts, webauthn.WithAssertionExtensions(extensions))
|
|
}
|
|
|
|
var assertion *protocol.CredentialAssertion
|
|
|
|
if assertion, userSession.Webauthn, err = w.BeginLogin(user, opts...); err != nil {
|
|
ctx.Logger.Errorf("Unable to create %s assertion challenge for user '%s': %+v", regulation.AuthTypeWebauthn, userSession.Username, err)
|
|
|
|
respondUnauthorized(ctx, messageMFAValidationFailed)
|
|
|
|
return
|
|
}
|
|
|
|
if err = ctx.SaveSession(userSession); err != nil {
|
|
ctx.Logger.Errorf(logFmtErrSessionSave, "assertion challenge", regulation.AuthTypeWebauthn, userSession.Username, err)
|
|
|
|
respondUnauthorized(ctx, messageMFAValidationFailed)
|
|
|
|
return
|
|
}
|
|
|
|
if err = ctx.SetJSONBody(assertion); err != nil {
|
|
ctx.Logger.Errorf(logFmtErrWriteResponseBody, regulation.AuthTypeWebauthn, userSession.Username, err)
|
|
|
|
respondUnauthorized(ctx, messageMFAValidationFailed)
|
|
|
|
return
|
|
}
|
|
}
|
|
|
|
// WebauthnAssertionPOST handler completes the assertion ceremony after verifying the challenge.
|
|
func WebauthnAssertionPOST(ctx *middlewares.AutheliaCtx) {
|
|
var (
|
|
err error
|
|
w *webauthn.WebAuthn
|
|
|
|
requestBody signWebauthnRequestBody
|
|
)
|
|
|
|
if err = ctx.ParseBody(&requestBody); err != nil {
|
|
ctx.Logger.Errorf(logFmtErrParseRequestBody, regulation.AuthTypeWebauthn, err)
|
|
|
|
respondUnauthorized(ctx, messageMFAValidationFailed)
|
|
|
|
return
|
|
}
|
|
|
|
userSession := ctx.GetSession()
|
|
|
|
if userSession.Webauthn == nil {
|
|
ctx.Logger.Errorf("Webauthn session data is not present in order to handle assertion 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, messageMFAValidationFailed)
|
|
|
|
return
|
|
}
|
|
|
|
var (
|
|
assertionResponse *protocol.ParsedCredentialAssertionData
|
|
credential *webauthn.Credential
|
|
user *model.WebauthnUser
|
|
)
|
|
|
|
if assertionResponse, err = protocol.ParseCredentialRequestResponseBody(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.ValidateLogin(user, *userSession.Webauthn, assertionResponse); err != nil {
|
|
_ = markAuthenticationAttempt(ctx, false, nil, userSession.Username, regulation.AuthTypeWebauthn, err)
|
|
|
|
respondUnauthorized(ctx, messageMFAValidationFailed)
|
|
|
|
return
|
|
}
|
|
|
|
var found bool
|
|
|
|
for _, device := range user.Devices {
|
|
if bytes.Equal(device.KID.Bytes(), credential.ID) {
|
|
device.UpdateSignInInfo(w.Config, ctx.Clock.Now(), credential.Authenticator.SignCount)
|
|
|
|
found = true
|
|
|
|
if err = ctx.Providers.StorageProvider.UpdateWebauthnDeviceSignIn(ctx, device.ID, device.RPID, device.LastUsedAt, device.SignCount, device.CloneWarning); err != nil {
|
|
ctx.Logger.Errorf("Unable to save %s device signin count for assertion challenge for user '%s': %+v", regulation.AuthTypeWebauthn, userSession.Username, err)
|
|
|
|
respondUnauthorized(ctx, messageMFAValidationFailed)
|
|
|
|
return
|
|
}
|
|
|
|
break
|
|
}
|
|
}
|
|
|
|
if !found {
|
|
ctx.Logger.Errorf("Unable to save %s device signin count for assertion challenge for user '%s' device '%x' count '%d': unable to find device", regulation.AuthTypeWebauthn, userSession.Username, credential.ID, credential.Authenticator.SignCount)
|
|
|
|
respondUnauthorized(ctx, messageMFAValidationFailed)
|
|
|
|
return
|
|
}
|
|
|
|
if err = ctx.Providers.SessionProvider.RegenerateSession(ctx.RequestCtx); err != nil {
|
|
ctx.Logger.Errorf(logFmtErrSessionRegenerate, regulation.AuthTypeWebauthn, userSession.Username, err)
|
|
|
|
respondUnauthorized(ctx, messageMFAValidationFailed)
|
|
|
|
return
|
|
}
|
|
|
|
if err = markAuthenticationAttempt(ctx, true, nil, userSession.Username, regulation.AuthTypeWebauthn, nil); err != nil {
|
|
respondUnauthorized(ctx, messageMFAValidationFailed)
|
|
|
|
return
|
|
}
|
|
|
|
userSession.SetTwoFactorWebauthn(ctx.Clock.Now(),
|
|
assertionResponse.Response.AuthenticatorData.Flags.UserPresent(),
|
|
assertionResponse.Response.AuthenticatorData.Flags.UserVerified())
|
|
|
|
if err = ctx.SaveSession(userSession); err != nil {
|
|
ctx.Logger.Errorf(logFmtErrSessionSave, "removal of the assertion challenge and authentication time", regulation.AuthTypeWebauthn, userSession.Username, err)
|
|
|
|
respondUnauthorized(ctx, messageMFAValidationFailed)
|
|
|
|
return
|
|
}
|
|
|
|
if userSession.ConsentChallengeID != nil {
|
|
handleOIDCWorkflowResponse(ctx)
|
|
} else {
|
|
Handle2FAResponse(ctx, requestBody.TargetURL)
|
|
}
|
|
}
|