2019-04-25 04:52:08 +07:00
package handlers
import (
"fmt"
2020-05-21 05:03:15 +07:00
"math"
"math/rand"
"sync"
2019-04-25 04:52:08 +07:00
"time"
2019-12-24 09:14:52 +07:00
"github.com/authelia/authelia/internal/authentication"
"github.com/authelia/authelia/internal/middlewares"
"github.com/authelia/authelia/internal/regulation"
"github.com/authelia/authelia/internal/session"
2019-04-25 04:52:08 +07:00
)
2020-05-21 05:03:15 +07:00
func movingAverageIteration ( value time . Duration , successful bool , movingAverageCursor * int , execDurationMovingAverage * [ ] time . Duration , mutex sync . Locker ) float64 {
mutex . Lock ( )
if successful {
( * execDurationMovingAverage ) [ * movingAverageCursor ] = value
* movingAverageCursor = ( * movingAverageCursor + 1 ) % movingAverageWindow
}
var sum int64
for _ , v := range * execDurationMovingAverage {
sum += v . Milliseconds ( )
}
mutex . Unlock ( )
return float64 ( sum / movingAverageWindow )
}
func calculateActualDelay ( ctx * middlewares . AutheliaCtx , execDuration time . Duration , avgExecDurationMs float64 , successful * bool ) float64 {
2020-09-04 10:20:17 +07:00
randomDelayMs := float64 ( rand . Int63n ( msMaximumRandomDelay ) ) //nolint:gosec // TODO: Consider use of crypto/rand, this should be benchmarked and measured first.
2020-05-21 05:03:15 +07:00
totalDelayMs := math . Max ( avgExecDurationMs , msMinimumDelay1FA ) + randomDelayMs
actualDelayMs := math . Max ( totalDelayMs - float64 ( execDuration . Milliseconds ( ) ) , 1.0 )
ctx . Logger . Tracef ( "attempt successful: %t, exec duration: %d, avg execution duration: %d, random delay ms: %d, total delay ms: %d, actual delay ms: %d" , * successful , execDuration . Milliseconds ( ) , int64 ( avgExecDurationMs ) , int64 ( randomDelayMs ) , int64 ( totalDelayMs ) , int64 ( actualDelayMs ) )
return actualDelayMs
}
func delayToPreventTimingAttacks ( ctx * middlewares . AutheliaCtx , requestTime time . Time , successful * bool , movingAverageCursor * int , execDurationMovingAverage * [ ] time . Duration , mutex sync . Locker ) {
execDuration := time . Since ( requestTime )
avgExecDurationMs := movingAverageIteration ( execDuration , * successful , movingAverageCursor , execDurationMovingAverage , mutex )
actualDelayMs := calculateActualDelay ( ctx , execDuration , avgExecDurationMs , successful )
time . Sleep ( time . Duration ( actualDelayMs ) * time . Millisecond )
}
2019-04-25 04:52:08 +07:00
// FirstFactorPost is the handler performing the first factory.
2020-05-05 02:39:25 +07:00
//nolint:gocyclo // TODO: Consider refactoring time permitting.
2020-05-21 05:03:15 +07:00
func FirstFactorPost ( msInitialDelay time . Duration , delayEnabled bool ) middlewares . RequestHandler {
var execDurationMovingAverage = make ( [ ] time . Duration , movingAverageWindow )
2019-04-25 04:52:08 +07:00
2020-05-21 05:03:15 +07:00
var movingAverageCursor = 0
var mutex = & sync . Mutex { }
for i := range execDurationMovingAverage {
execDurationMovingAverage [ i ] = msInitialDelay * time . Millisecond
2019-04-25 04:52:08 +07:00
}
2020-05-21 05:03:15 +07:00
rand . Seed ( time . Now ( ) . UnixNano ( ) )
return func ( ctx * middlewares . AutheliaCtx ) {
var successful bool
2019-04-25 04:52:08 +07:00
2020-05-21 05:03:15 +07:00
requestTime := time . Now ( )
if delayEnabled {
defer delayToPreventTimingAttacks ( ctx , requestTime , & successful , & movingAverageCursor , & execDurationMovingAverage , mutex )
}
bodyJSON := firstFactorRequestBody { }
err := ctx . ParseBody ( & bodyJSON )
if err != nil {
handleAuthenticationUnauthorized ( ctx , err , authenticationFailedMessage )
2019-11-30 23:49:52 +07:00
return
}
2020-05-06 02:35:32 +07:00
2020-05-21 05:03:15 +07:00
bannedUntil , err := ctx . Providers . Regulator . Regulate ( bodyJSON . Username )
2020-05-06 02:35:32 +07:00
2020-05-21 05:03:15 +07:00
if err != nil {
if err == regulation . ErrUserIsBanned {
handleAuthenticationUnauthorized ( ctx , fmt . Errorf ( "User %s is banned until %s" , bodyJSON . Username , bannedUntil ) , userBannedMessage )
return
}
2019-04-25 04:52:08 +07:00
2020-05-21 05:03:15 +07:00
handleAuthenticationUnauthorized ( ctx , fmt . Errorf ( "Unable to regulate authentication: %s" , err . Error ( ) ) , authenticationFailedMessage )
2019-04-25 04:52:08 +07:00
2020-05-21 05:03:15 +07:00
return
}
2019-11-25 03:27:59 +07:00
2020-05-21 05:03:15 +07:00
userPasswordOk , err := ctx . Providers . UserProvider . CheckUserPassword ( bodyJSON . Username , bodyJSON . Password )
2020-05-06 02:35:32 +07:00
2020-05-21 05:03:15 +07:00
if err != nil {
ctx . Logger . Debugf ( "Mark authentication attempt made by user %s" , bodyJSON . Username )
2020-12-16 08:47:31 +07:00
if err := ctx . Providers . Regulator . Mark ( bodyJSON . Username , false ) ; err != nil {
ctx . Logger . Errorf ( "Unable to mark authentication: %s" , err . Error ( ) )
}
2019-04-25 04:52:08 +07:00
2020-05-21 05:03:15 +07:00
handleAuthenticationUnauthorized ( ctx , fmt . Errorf ( "Error while checking password for user %s: %s" , bodyJSON . Username , err . Error ( ) ) , authenticationFailedMessage )
2019-11-30 23:49:52 +07:00
2020-05-21 05:03:15 +07:00
return
}
2020-05-06 04:27:38 +07:00
2020-05-21 05:03:15 +07:00
if ! userPasswordOk {
ctx . Logger . Debugf ( "Mark authentication attempt made by user %s" , bodyJSON . Username )
2020-05-06 02:35:32 +07:00
2020-12-16 08:47:31 +07:00
if err := ctx . Providers . Regulator . Mark ( bodyJSON . Username , false ) ; err != nil {
ctx . Logger . Errorf ( "Unable to mark authentication: %s" , err . Error ( ) )
}
2019-04-25 04:52:08 +07:00
2020-12-16 08:47:31 +07:00
handleAuthenticationUnauthorized ( ctx , fmt . Errorf ( "Credentials are wrong for user %s" , bodyJSON . Username ) , authenticationFailedMessage )
2019-04-25 04:52:08 +07:00
2020-05-21 05:03:15 +07:00
return
}
2019-11-30 21:33:45 +07:00
2020-05-21 05:03:15 +07:00
ctx . Logger . Debugf ( "Mark authentication attempt made by user %s" , bodyJSON . Username )
err = ctx . Providers . Regulator . Mark ( bodyJSON . Username , true )
2019-04-25 04:52:08 +07:00
2020-05-21 05:03:15 +07:00
if err != nil {
handleAuthenticationUnauthorized ( ctx , fmt . Errorf ( "Unable to mark authentication: %s" , err . Error ( ) ) , authenticationFailedMessage )
return
}
2019-04-25 04:52:08 +07:00
2020-12-16 08:47:31 +07:00
ctx . Logger . Debugf ( "Credentials validation of user %s is ok" , bodyJSON . Username )
2020-05-21 05:03:15 +07:00
// Reset all values from previous session before regenerating the cookie.
err = ctx . SaveSession ( session . NewDefaultUserSession ( ) )
2019-04-25 04:52:08 +07:00
2020-05-21 05:03:15 +07:00
if err != nil {
handleAuthenticationUnauthorized ( ctx , fmt . Errorf ( "Unable to reset the session for user %s: %s" , bodyJSON . Username , err . Error ( ) ) , authenticationFailedMessage )
return
}
2019-04-25 04:52:08 +07:00
2020-05-21 05:03:15 +07:00
err = ctx . Providers . SessionProvider . RegenerateSession ( ctx . RequestCtx )
2020-04-04 06:11:33 +07:00
2019-04-25 04:52:08 +07:00
if err != nil {
2020-05-21 05:03:15 +07:00
handleAuthenticationUnauthorized ( ctx , fmt . Errorf ( "Unable to regenerate session for user %s: %s" , bodyJSON . Username , err . Error ( ) ) , authenticationFailedMessage )
2019-04-25 04:52:08 +07:00
return
}
2020-05-21 05:03:15 +07:00
// Check if bodyJSON.KeepMeLoggedIn can be deref'd and derive the value based on the configuration and JSON data
keepMeLoggedIn := ctx . Providers . SessionProvider . RememberMe != 0 && bodyJSON . KeepMeLoggedIn != nil && * bodyJSON . KeepMeLoggedIn
2019-04-25 04:52:08 +07:00
2020-05-21 05:03:15 +07:00
// Set the cookie to expire if remember me is enabled and the user has asked us to
if keepMeLoggedIn {
err = ctx . Providers . SessionProvider . UpdateExpiration ( ctx . RequestCtx , ctx . Providers . SessionProvider . RememberMe )
if err != nil {
handleAuthenticationUnauthorized ( ctx , fmt . Errorf ( "Unable to update expiration timer for user %s: %s" , bodyJSON . Username , err . Error ( ) ) , authenticationFailedMessage )
return
}
}
2019-04-25 04:52:08 +07:00
2020-05-21 05:03:15 +07:00
// Get the details of the given user from the user provider.
userDetails , err := ctx . Providers . UserProvider . GetDetails ( bodyJSON . Username )
2019-04-25 04:52:08 +07:00
2020-05-21 05:03:15 +07:00
if err != nil {
handleAuthenticationUnauthorized ( ctx , fmt . Errorf ( "Error while retrieving details from user %s: %s" , bodyJSON . Username , err . Error ( ) ) , authenticationFailedMessage )
return
}
2020-05-06 02:35:32 +07:00
2020-05-21 05:03:15 +07:00
ctx . Logger . Tracef ( "Details for user %s => groups: %s, emails %s" , bodyJSON . Username , userDetails . Groups , userDetails . Emails )
2020-05-06 02:35:32 +07:00
2020-05-21 05:03:15 +07:00
// And set those information in the new session.
userSession := ctx . GetSession ( )
userSession . Username = userDetails . Username
2020-06-19 17:50:21 +07:00
userSession . DisplayName = userDetails . DisplayName
2020-05-21 05:03:15 +07:00
userSession . Groups = userDetails . Groups
userSession . Emails = userDetails . Emails
userSession . AuthenticationLevel = authentication . OneFactor
userSession . LastActivity = time . Now ( ) . Unix ( )
userSession . KeepMeLoggedIn = keepMeLoggedIn
refresh , refreshInterval := getProfileRefreshSettings ( ctx . Configuration . AuthenticationBackend )
2019-04-25 04:52:08 +07:00
2020-05-21 05:03:15 +07:00
if refresh {
userSession . RefreshTTL = ctx . Clock . Now ( ) . Add ( refreshInterval )
}
2019-04-25 04:52:08 +07:00
2020-05-21 05:03:15 +07:00
err = ctx . SaveSession ( userSession )
if err != nil {
handleAuthenticationUnauthorized ( ctx , fmt . Errorf ( "Unable to save session of user %s" , bodyJSON . Username ) , authenticationFailedMessage )
return
}
successful = true
Handle1FAResponse ( ctx , bodyJSON . TargetURL , userSession . Username , userSession . Groups )
}
2019-04-25 04:52:08 +07:00
}