Skip to content

Commit

Permalink
implement exponential back off (#44)
Browse files Browse the repository at this point in the history
Signed-off-by: Kathurima Kimathi <kathurimakimathi415@gmail.com>
  • Loading branch information
KathurimaKimathi committed Oct 25, 2021
1 parent 77b60c1 commit 2a1612c
Show file tree
Hide file tree
Showing 6 changed files with 71 additions and 19 deletions.
1 change: 0 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -38,5 +38,4 @@ require (
gorm.io/datatypes v1.0.2
gorm.io/driver/postgres v1.1.2
gorm.io/gorm v1.21.16
moul.io/http2curl v1.0.0 // indirect
)
6 changes: 0 additions & 6 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -340,7 +340,6 @@ github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5m
github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0=
github.com/googleapis/gax-go/v2 v2.1.1 h1:dp3bWCh+PPO1zjRRiCSczJav13sBvG4UhNyVTa1KqdU=
github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/context v0.0.0-20160226214623-1ea25387ff6f/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
Expand Down Expand Up @@ -476,7 +475,6 @@ github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/u
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k=
Expand Down Expand Up @@ -682,9 +680,7 @@ github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMB
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=
Expand Down Expand Up @@ -1329,8 +1325,6 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
moul.io/http2curl v1.0.0 h1:6XwpyZOYsgZJrU8exnG87ncVkU1FVCcTRpwzOkTDUi8=
moul.io/http2curl v1.0.0/go.mod h1:f6cULg+e4Md/oW1cYmwW4IWQOVl2lGbmCNGOHvzX2kE=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
Expand Down
12 changes: 12 additions & 0 deletions pkg/onboarding/application/utils/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package utils
import (
"fmt"
"hash"
"math"
"time"

"github.com/savannahghi/onboarding-service/pkg/onboarding/infrastructure"
Expand All @@ -15,6 +16,8 @@ const (
minPINLength = 4
// Default max length of the date
maxPINLength = 4
//BackOffWaitTime is the default time to wait
BackOffWaitTime = 3
)

// Options is a struct for custom values of salt length, number of iterations, the encoded key's length,
Expand Down Expand Up @@ -73,3 +76,12 @@ func GetHourMinuteSecond(hour, minute, second time.Duration) time.Time {
return time.Now().Add(time.Hour*hour + time.Minute*minute + time.Second*second)

}

// NextAllowedLoginTime calculates the next allowed time to login.
// It depends on the number of user's failed login attempts.
func NextAllowedLoginTime(trials int) time.Time {
baseValue := float64(trials)
result := math.Pow(baseValue, BackOffWaitTime)
nextAllowedLoginTime := GetHourMinuteSecond(0, 0, time.Duration(result))
return nextAllowedLoginTime
}
25 changes: 25 additions & 0 deletions pkg/onboarding/application/utils/helpers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -170,3 +170,28 @@ func TestGetHourMinuteSecond(t *testing.T) {
})
}
}

func TestNextAllowedLoginTime(t *testing.T) {
type args struct {
trials int
}
tests := []struct {
name string
args args
wantError bool
}{
{
name: "Happy case",
args: args{
trials: 3,
},
wantError: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := NextAllowedLoginTime(tt.args.trials)
assert.NotNil(t, got)
})
}
}
28 changes: 23 additions & 5 deletions pkg/onboarding/usecases/user/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,20 @@ func (us *UseCasesUserImpl) Login(ctx context.Context, userID string, pin string
return nil, "", fmt.Errorf("unable to get user profile by userID: %v", err)
}

var nextAllowedLoginTime time.Time

if userProfile.NextAllowedLogin == nil {
nextAllowedLoginTime = time.Now()
} else {
nextAllowedLoginTime = *userProfile.NextAllowedLogin
}

checkCurrentTime := time.Now()
if checkCurrentTime.Before(nextAllowedLoginTime) {
waitTime := nextAllowedLoginTime.Sub(checkCurrentTime).Seconds()
return nil, "", fmt.Errorf("unable to login at the moment. Please try again after %v seconds", waitTime)
}

//Fetch PIN by UserID
userPINData, err := us.Infrastructure.GetUserPINByUserID(ctx, userID)
if err != nil {
Expand All @@ -198,13 +212,13 @@ func (us *UseCasesUserImpl) Login(ctx context.Context, userID string, pin string
if err != nil {
return nil, "", err
}
trials := failedLoginCount + 1
failedLoginAttempts := failedLoginCount + 1
//Convert trials to string
numberOfTrials := strconv.Itoa(trials)
numberOfFailedAttempts := strconv.Itoa(failedLoginAttempts)

// Implement exponential back-off, record the number of trials and last successful login
// 1. Record the user's number of failed login time
if err := us.Infrastructure.UpdateUserFailedLoginCount(ctx, userID, numberOfTrials, flavour); err != nil {
if err := us.Infrastructure.UpdateUserFailedLoginCount(ctx, userID, numberOfFailedAttempts, flavour); err != nil {
return nil, "unable to update number of user failed login counts", fmt.Errorf("unable to update number of user failed login counts: %v", err)
}

Expand All @@ -214,7 +228,11 @@ func (us *UseCasesUserImpl) Login(ctx context.Context, userID string, pin string
return nil, "unable to update number of user last failed login time", fmt.Errorf("unable to update number of user last failed login time: %v", err)
}

// TODO: Implement exponential back-off
// Exponential back-off and Next Allowed Login time
nextAllowedLoginTime := utils.NextAllowedLoginTime(failedLoginAttempts)
if err := us.Infrastructure.UpdateUserNextAllowedLogin(ctx, userID, nextAllowedLoginTime, flavour); err != nil {
return nil, "unable to update user next allowed login time", fmt.Errorf("unable to update user next allowed: %v", err)
}

return nil, "", fmt.Errorf("an error occurred")
}
Expand Down Expand Up @@ -316,7 +334,7 @@ func (us *UseCasesUserImpl) SetUserPIN(ctx context.Context, input *dto.PINInput)
validTo := utils.GetHourMinuteSecond(24, 0, 0)

pinDataInput := &domain.UserPIN{
UserID: "2c301ec0-7ee2-4b65-8e9f-9b99756a1072",
UserID: "57d64e09-4e15-4d38-983c-6d33cb6416ed",
HashedPIN: encryptedPIN,
ValidFrom: time.Now(),
ValidTo: validTo, // Consult for appropriate timings
Expand Down
18 changes: 11 additions & 7 deletions pkg/onboarding/usecases/user/user_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ func TestUseCasesUserImpl_Login_Integration_Test(t *testing.T) {
}

staffInput := &dto.StaffProfileInput{
StaffNumber: "s123",
StaffNumber: ksuid.New().String(),
DefaultFacilityID: facility.ID,
}

Expand Down Expand Up @@ -139,18 +139,18 @@ func TestUseCasesUserImpl_Login_Integration_Test(t *testing.T) {
assert.NotNil(t, userProfile)

//Valid: Fetch PIN by UserID
userPINData, err := m.GetUserPINByUserID(ctx, *staffUserProfile.User.ID)
userPINData, err := m.GetUserPINByUserID(ctx, *userProfile.ID)
assert.Nil(t, err)
assert.NotNil(t, userPINData)

isMatch := extension.ComparePIN("1234", userPINData.Salt, userPINData.HashedPIN, nil)
assert.Equal(t, true, isMatch)

successTime := time.Now()
err = m.UpdateUserLastSuccessfulLogin(ctx, *staffUserProfile.User.ID, successTime, string(flavour))
err = m.UpdateUserLastSuccessfulLogin(ctx, *userProfile.ID, successTime, string(flavour))
assert.Nil(t, err)

err = m.UpdateUserFailedLoginCount(ctx, *staffUserProfile.User.ID, "0", string(flavour))
err = m.UpdateUserFailedLoginCount(ctx, *userProfile.ID, "0", string(flavour))
assert.Nil(t, err)

customToken, err := firebasetools.CreateFirebaseCustomToken(ctx, *userProfile.ID)
Expand All @@ -162,7 +162,7 @@ func TestUseCasesUserImpl_Login_Integration_Test(t *testing.T) {
assert.NotNil(t, userTokens)

//Login
authCred, str, err := i.UserUsecase.Login(ctx, *staffUserProfile.User.ID, pin, flavour.String())
authCred, str, err := i.UserUsecase.Login(ctx, *userProfile.ID, pin, string(userProfile.Flavour))
assert.Nil(t, err)
assert.NotEmpty(t, str)
assert.NotNil(t, str)
Expand Down Expand Up @@ -216,13 +216,17 @@ func TestUseCasesUserImpl_Login_Integration_Test(t *testing.T) {
numberOfTrials := strconv.Itoa(trials)
assert.NotNil(t, numberOfTrials)

err8 := m.UpdateUserFailedLoginCount(ctx, *staffUserProfile.User.ID, numberOfTrials, string(flavour))
err8 := m.UpdateUserFailedLoginCount(ctx, *profile2.ID, numberOfTrials, string(flavour))
assert.Nil(t, err8)

lastFailedLoginTime := time.Now()
err9 := m.UpdateUserLastFailedLogin(ctx, *staffUserProfile.User.ID, lastFailedLoginTime, string(flavour))
err9 := m.UpdateUserLastFailedLogin(ctx, *profile2.ID, lastFailedLoginTime, string(flavour))
assert.Nil(t, err9)

nextAllowedLoginTime := utils.NextAllowedLoginTime(trials)
err10 := m.UpdateUserNextAllowedLogin(ctx, *profile2.ID, nextAllowedLoginTime, string(flavour))
assert.Nil(t, err10)

}

//Cannot Login
Expand Down

0 comments on commit 2a1612c

Please sign in to comment.