mirror of https://github.com/authelia/authelia.git
219 lines
7.6 KiB
Go
219 lines
7.6 KiB
Go
package handlers
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
|
|
"github.com/valyala/fasthttp"
|
|
|
|
"github.com/authelia/authelia/v4/internal/middlewares"
|
|
"github.com/authelia/authelia/v4/internal/model"
|
|
"github.com/authelia/authelia/v4/internal/regulation"
|
|
"github.com/authelia/authelia/v4/internal/session"
|
|
"github.com/authelia/authelia/v4/internal/storage"
|
|
)
|
|
|
|
// TimeBasedOneTimePasswordGET returns the users TOTP configuration.
|
|
func TimeBasedOneTimePasswordGET(ctx *middlewares.AutheliaCtx) {
|
|
var (
|
|
userSession session.UserSession
|
|
err error
|
|
)
|
|
|
|
if userSession, err = ctx.GetSession(); err != nil {
|
|
ctx.Logger.WithError(err).Errorf("Error occurred retrieving TOTP configuration: %s", errStrUserSessionData)
|
|
|
|
ctx.SetStatusCode(fasthttp.StatusForbidden)
|
|
ctx.SetJSONError(messageMFAValidationFailed)
|
|
|
|
return
|
|
}
|
|
|
|
if userSession.IsAnonymous() {
|
|
ctx.Logger.WithError(errUserAnonymous).Error("Error occurred retrieving TOTP configuration")
|
|
|
|
ctx.SetStatusCode(fasthttp.StatusForbidden)
|
|
ctx.SetJSONError(messageMFAValidationFailed)
|
|
|
|
return
|
|
}
|
|
|
|
var config *model.TOTPConfiguration
|
|
|
|
if config, err = ctx.Providers.StorageProvider.LoadTOTPConfiguration(ctx, userSession.Username); err != nil {
|
|
ctx.Logger.WithError(err).Errorf("Error occurred retrieving TOTP configuration for user '%s': error occurred retrieving the configuration from the storage backend", userSession.Username)
|
|
|
|
if errors.Is(err, storage.ErrNoTOTPConfiguration) {
|
|
ctx.SetStatusCode(fasthttp.StatusNotFound)
|
|
ctx.SetJSONError("Could not find TOTP Configuration for user.")
|
|
} else {
|
|
ctx.SetStatusCode(fasthttp.StatusInternalServerError)
|
|
ctx.SetJSONError("Could not find TOTP Configuration for user.")
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
if err = ctx.SetJSONBody(config); err != nil {
|
|
ctx.Logger.WithError(err).Errorf("Error occurred retrieving TOTP configuration for user '%s': %s", userSession.Username, errStrRespBody)
|
|
|
|
ctx.SetStatusCode(fasthttp.StatusForbidden)
|
|
ctx.SetJSONError(messageMFAValidationFailed)
|
|
|
|
return
|
|
}
|
|
}
|
|
|
|
// TimeBasedOneTimePasswordPOST validate the TOTP passcode provided by the user.
|
|
//
|
|
//nolint:gocyclo
|
|
func TimeBasedOneTimePasswordPOST(ctx *middlewares.AutheliaCtx) {
|
|
bodyJSON := bodySignTOTPRequest{}
|
|
|
|
var (
|
|
userSession session.UserSession
|
|
config *model.TOTPConfiguration
|
|
valid, exists bool
|
|
step uint64
|
|
err error
|
|
)
|
|
|
|
if userSession, err = ctx.GetSession(); err != nil {
|
|
ctx.Logger.WithError(err).Errorf("Error occurred validating a TOTP authentication: %s", errStrUserSessionData)
|
|
|
|
ctx.SetStatusCode(fasthttp.StatusForbidden)
|
|
ctx.SetJSONError(messageMFAValidationFailed)
|
|
|
|
return
|
|
}
|
|
|
|
if userSession.IsAnonymous() {
|
|
ctx.Logger.WithError(errUserAnonymous).Error("Error occurred validating a TOTP authentication")
|
|
|
|
ctx.SetStatusCode(fasthttp.StatusForbidden)
|
|
ctx.SetJSONError(messageMFAValidationFailed)
|
|
|
|
return
|
|
}
|
|
|
|
if err = ctx.ParseBody(&bodyJSON); err != nil {
|
|
ctx.Logger.WithError(err).Errorf("Error occurred validating a TOTP authentication for user '%s': %s", userSession.Username, errStrReqBodyParse)
|
|
|
|
ctx.SetStatusCode(fasthttp.StatusForbidden)
|
|
ctx.SetJSONError(messageMFAValidationFailed)
|
|
|
|
return
|
|
}
|
|
|
|
if n := len(bodyJSON.Token); n != 6 && n != 8 {
|
|
ctx.Logger.Errorf("Error occurred validating a TOTP authentication for user '%s': expected code length is 6 or 8 but the user provided code was %d characters in length", userSession.Username, n)
|
|
|
|
ctx.SetStatusCode(fasthttp.StatusBadRequest)
|
|
ctx.SetJSONError(messageMFAValidationFailed)
|
|
|
|
return
|
|
}
|
|
|
|
if config, err = ctx.Providers.StorageProvider.LoadTOTPConfiguration(ctx, userSession.Username); err != nil {
|
|
ctx.Logger.WithError(err).Errorf("Error occurred validating a TOTP authentication for user '%s': error occurred retreiving the configuration from the storage backend", userSession.Username)
|
|
|
|
ctx.SetStatusCode(fasthttp.StatusForbidden)
|
|
ctx.SetJSONError(messageMFAValidationFailed)
|
|
|
|
return
|
|
}
|
|
|
|
if valid, step, err = ctx.Providers.TOTP.Validate(ctx, bodyJSON.Token, config); err != nil {
|
|
ctx.Logger.WithError(err).Errorf("Error occurred validating a TOTP authentication for user '%s': error occurred validating the user input", userSession.Username)
|
|
|
|
ctx.SetStatusCode(fasthttp.StatusForbidden)
|
|
ctx.SetJSONError(messageMFAValidationFailed)
|
|
|
|
return
|
|
}
|
|
|
|
if !valid {
|
|
ctx.Logger.WithError(fmt.Errorf("the user input wasn't valid")).Errorf("Error occurred validating a TOTP authentication for user '%s': error occurred validating the user input", userSession.Username)
|
|
|
|
_ = markAuthenticationAttempt(ctx, false, nil, userSession.Username, regulation.AuthTypeTOTP, nil)
|
|
|
|
ctx.SetStatusCode(fasthttp.StatusForbidden)
|
|
ctx.SetJSONError(messageMFAValidationFailed)
|
|
|
|
return
|
|
}
|
|
|
|
if exists, err = ctx.Providers.StorageProvider.ExistsTOTPHistory(ctx, userSession.Username, step*uint64(config.Period)); err != nil {
|
|
ctx.Logger.WithError(err).Errorf("Error occurred validating a TOTP authentication for user '%s': error occurred checking the TOTP history", userSession.Username)
|
|
|
|
ctx.SetStatusCode(fasthttp.StatusForbidden)
|
|
ctx.SetJSONError(messageMFAValidationFailed)
|
|
|
|
return
|
|
}
|
|
|
|
if exists {
|
|
if ctx.Configuration.TOTP.DisableReuseSecurityPolicy {
|
|
ctx.Logger.WithFields(map[string]any{"username": userSession.Username}).Warn("User has reused a Time-based One Time Password with the given step but the policy to disable reuse is disabled")
|
|
} else {
|
|
ctx.Logger.WithError(fmt.Errorf("the user has already used this code recently and will not be permitted to reuse it")).Errorf("Error occurred validating a TOTP authentication for user '%s': error occurred satisfying security policies", userSession.Username)
|
|
|
|
ctx.SetStatusCode(fasthttp.StatusForbidden)
|
|
ctx.SetJSONError(messageMFAValidationFailed)
|
|
|
|
return
|
|
}
|
|
} else if err = ctx.Providers.StorageProvider.SaveTOTPHistory(ctx, userSession.Username, step*uint64(config.Period)); err != nil {
|
|
ctx.Logger.WithError(err).Errorf("Error occurred validating a TOTP authentication for user '%s': error occurred saving the TOTP history to the storage backend", userSession.Username)
|
|
|
|
ctx.SetStatusCode(fasthttp.StatusForbidden)
|
|
ctx.SetJSONError(messageMFAValidationFailed)
|
|
|
|
return
|
|
}
|
|
|
|
if err = markAuthenticationAttempt(ctx, true, nil, userSession.Username, regulation.AuthTypeTOTP, nil); err != nil {
|
|
ctx.SetStatusCode(fasthttp.StatusForbidden)
|
|
ctx.SetJSONError(messageMFAValidationFailed)
|
|
|
|
return
|
|
}
|
|
|
|
if err = ctx.RegenerateSession(); err != nil {
|
|
ctx.Logger.WithError(err).Errorf("Error occurred validating a TOTP authentication for user '%s': error regenerating the user session", userSession.Username)
|
|
|
|
ctx.SetStatusCode(fasthttp.StatusForbidden)
|
|
ctx.SetJSONError(messageMFAValidationFailed)
|
|
|
|
return
|
|
}
|
|
|
|
config.UpdateSignInInfo(ctx.Clock.Now())
|
|
|
|
if err = ctx.Providers.StorageProvider.UpdateTOTPConfigurationSignIn(ctx, config.ID, config.LastUsedAt); err != nil {
|
|
ctx.Logger.WithError(err).Errorf("Error occurred validating a TOTP authentication for user '%s': error occurred saving the credential sign-in information to the storage backend", userSession.Username)
|
|
|
|
ctx.SetStatusCode(fasthttp.StatusForbidden)
|
|
ctx.SetJSONError(messageMFAValidationFailed)
|
|
|
|
return
|
|
}
|
|
|
|
userSession.SetTwoFactorTOTP(ctx.Clock.Now())
|
|
|
|
if err = ctx.SaveSession(userSession); err != nil {
|
|
ctx.Logger.WithError(err).Errorf("Error occurred validating a TOTP authentication for user '%s': %s", userSession.Username, errStrUserSessionDataSave)
|
|
|
|
ctx.SetStatusCode(fasthttp.StatusForbidden)
|
|
ctx.SetJSONError(messageMFAValidationFailed)
|
|
|
|
return
|
|
}
|
|
|
|
if bodyJSON.Workflow == workflowOpenIDConnect {
|
|
handleOIDCWorkflowResponse(ctx, &userSession, bodyJSON.TargetURL, bodyJSON.WorkflowID)
|
|
} else {
|
|
Handle2FAResponse(ctx, bodyJSON.TargetURL)
|
|
}
|
|
}
|