diff --git a/cmd/otpgateway/handlers.go b/cmd/otpgateway/handlers.go index 20706d4..1238c1c 100644 --- a/cmd/otpgateway/handlers.go +++ b/cmd/otpgateway/handlers.go @@ -307,6 +307,15 @@ func handleVerifyOTP(w http.ResponseWriter, r *http.Request) { return } + if err == store.ErrTooManyAttempts { + code = http.StatusTooManyRequests + errMsg := fmt.Sprintf("Too many attempts. Please retry after %0.f seconds.", + out.TTL.Seconds()) + err := errors.New(errMsg) + sendErrorResponse(w, err.Error(), code, nil) + return + } + if out.Closed { code = http.StatusTooManyRequests } @@ -349,8 +358,17 @@ func handleOTPView(w http.ResponseWriter, r *http.Request) { return } + isOtpLocked := false // Attempts are maxed out and locked. - if isLocked(out) { + if action == actCheck { + if otpErr == store.ErrTooManyAttempts { + isOtpLocked = true + } + } else if isLocked(out) { + isOtpLocked = true + } + + if isOtpLocked { app.tpl.ExecuteTemplate(w, "message", webviewTpl{App: app.constants, Title: "Too many attempts", Description: fmt.Sprintf("Please retry after %d seconds.", int64(out.TTLSeconds)), @@ -522,20 +540,21 @@ func verifyOTP(namespace, id, otp string, deleteOnVerify bool, app *App) (models app.lo.Printf("error checking OTP: %v", err) return out, err } - return out, errors.New("error checking OTP.") + return out, errors.New("error checking OTP") } - errMsg := "" - if isLocked(out) { - errMsg = fmt.Sprintf("Too many attempts. Please retry after %0.f seconds.", - out.TTL.Seconds()) - } else if out.OTP != otp { - errMsg = "Incorrect OTP" + // Attempts exceeded for OTP + if out.Attempts > out.MaxAttempts { + return out, store.ErrTooManyAttempts + } + + // Final attempt with incorrect OTP + if out.Attempts == out.MaxAttempts && out.OTP != otp { + return out, store.ErrTooManyAttempts } - // There was an error. - if errMsg != "" { - return out, errors.New(errMsg) + if out.OTP != otp { + return out, errors.New("incorrect OTP") } // Delete the OTP? diff --git a/internal/store/store.go b/internal/store/store.go index b3953fa..4b9153d 100644 --- a/internal/store/store.go +++ b/internal/store/store.go @@ -6,9 +6,15 @@ import ( "github.com/knadh/otpgateway/v3/internal/models" ) -// ErrNotExist is thrown when an OTP (requested by namespace / ID) -// does not exist. -var ErrNotExist = errors.New("the OTP does not exist") + +var ( + // ErrNotExist is thrown when an OTP (requested by namespace / ID) + // does not exist. + ErrNotExist = errors.New("the OTP does not exist") + // ErrNotExist is thrown when an OTP (requested by namespace / ID) + // strictly exceeds the maximum number of attempts. + ErrTooManyAttempts = errors.New("too many attempts") +) // Store represents a storage backend where OTP data is stored. type Store interface {