diff --git a/satellite/console/captcha.go b/satellite/console/captcha.go index 9b439db32..07de004e6 100644 --- a/satellite/console/captcha.go +++ b/satellite/console/captcha.go @@ -21,7 +21,7 @@ const hcaptchaAPIURL = "https://hcaptcha.com/siteverify" // and returning whether the user response characterized by the given // response token and IP is valid. type CaptchaHandler interface { - Verify(ctx context.Context, responseToken string, userIP string) (bool, error) + Verify(ctx context.Context, responseToken string, userIP string) (bool, *float64, error) } // CaptchaType is a type of captcha. @@ -55,12 +55,12 @@ func NewDefaultCaptcha(kind CaptchaType, secretKey string) CaptchaHandler { // Verify contacts the captcha API and returns whether the given response token is valid. // The documentation can be found here for recaptcha: https://developers.google.com/recaptcha/docs/verify // And here for hcaptcha: https://docs.hcaptcha.com/ -func (r captchaHandler) Verify(ctx context.Context, responseToken string, userIP string) (valid bool, err error) { +func (r captchaHandler) Verify(ctx context.Context, responseToken string, userIP string) (valid bool, score *float64, err error) { if responseToken == "" { - return false, errs.New("the response token is empty") + return false, nil, errs.New("the response token is empty") } if userIP == "" { - return false, errs.New("the user's IP address is empty") + return false, nil, errs.New("the user's IP address is empty") } reqBody := url.Values{ @@ -71,13 +71,13 @@ func (r captchaHandler) Verify(ctx context.Context, responseToken string, userIP req, err := http.NewRequestWithContext(ctx, http.MethodPost, r.Endpoint, strings.NewReader(reqBody)) if err != nil { - return false, err + return false, nil, err } req.Header.Set("Content-Type", "application/x-www-form-urlencoded") resp, err := http.DefaultClient.Do(req) if err != nil { - return false, err + return false, nil, err } defer func() { @@ -85,16 +85,17 @@ func (r captchaHandler) Verify(ctx context.Context, responseToken string, userIP }() if resp.StatusCode != http.StatusOK { - return false, errors.New(resp.Status) + return false, nil, errors.New(resp.Status) } var data struct { - Success bool `json:"success"` + Success bool `json:"success"` + Score float64 `json:"score"` } err = json.NewDecoder(resp.Body).Decode(&data) if err != nil { - return false, err + return false, nil, err } - return data.Success, nil + return data.Success, &data.Score, nil } diff --git a/satellite/console/captcha_test.go b/satellite/console/captcha_test.go index 91153d696..46be9f3a1 100644 --- a/satellite/console/captcha_test.go +++ b/satellite/console/captcha_test.go @@ -20,8 +20,9 @@ const validResponseToken = "myResponseToken" type mockRecaptcha struct{} -func (r mockRecaptcha) Verify(ctx context.Context, responseToken string, userIP string) (bool, error) { - return responseToken == validResponseToken, nil +func (r mockRecaptcha) Verify(ctx context.Context, responseToken string, userIP string) (bool, *float64, error) { + score := 1.0 + return responseToken == validResponseToken, &score, nil } // TestRegistrationRecaptcha ensures that registration reCAPTCHA service is working properly. @@ -52,6 +53,8 @@ func TestRegistrationRecaptcha(t *testing.T) { require.NotNil(t, user) require.NoError(t, err) + require.NotNil(t, user.SignupCaptcha) + require.Equal(t, 1.0, *user.SignupCaptcha) regToken2, err := service.CreateRegToken(ctx, 1) require.NoError(t, err) diff --git a/satellite/console/service.go b/satellite/console/service.go index ed7925483..9810e4362 100644 --- a/satellite/console/service.go +++ b/satellite/console/service.go @@ -653,10 +653,12 @@ func (s *Service) checkRegistrationSecret(ctx context.Context, tokenSecret Regis func (s *Service) CreateUser(ctx context.Context, user CreateUser, tokenSecret RegistrationSecret) (u *User, err error) { defer mon.Task()(&ctx)(&err) + var captchaScore *float64 + mon.Counter("create_user_attempt").Inc(1) //mon:locked if s.config.Captcha.Registration.Recaptcha.Enabled || s.config.Captcha.Registration.Hcaptcha.Enabled { - valid, err := s.registrationCaptchaHandler.Verify(ctx, user.CaptchaResponse, user.IP) + valid, score, err := s.registrationCaptchaHandler.Verify(ctx, user.CaptchaResponse, user.IP) if err != nil { mon.Counter("create_user_captcha_error").Inc(1) //mon:locked s.log.Error("captcha authorization failed", zap.Error(err)) @@ -666,6 +668,7 @@ func (s *Service) CreateUser(ctx context.Context, user CreateUser, tokenSecret R mon.Counter("create_user_captcha_unsuccessful").Inc(1) //mon:locked return nil, ErrCaptcha.New("captcha validation unsuccessful") } + captchaScore = score } if err := user.IsValid(); err != nil { @@ -715,6 +718,7 @@ func (s *Service) CreateUser(ctx context.Context, user CreateUser, tokenSecret R EmployeeCount: user.EmployeeCount, HaveSalesContact: user.HaveSalesContact, SignupPromoCode: user.SignupPromoCode, + SignupCaptcha: captchaScore, } if user.UserAgent != nil { @@ -979,7 +983,7 @@ func (s *Service) Token(ctx context.Context, request AuthUser) (token consoleaut mon.Counter("login_attempt").Inc(1) //mon:locked if s.config.Captcha.Login.Recaptcha.Enabled || s.config.Captcha.Login.Hcaptcha.Enabled { - valid, err := s.loginCaptchaHandler.Verify(ctx, request.CaptchaResponse, request.IP) + valid, _, err := s.loginCaptchaHandler.Verify(ctx, request.CaptchaResponse, request.IP) if err != nil { mon.Counter("login_user_captcha_error").Inc(1) //mon:locked return consoleauth.Token{}, ErrCaptcha.Wrap(err) diff --git a/satellite/console/users.go b/satellite/console/users.go index 08209e5d3..6d7dda118 100644 --- a/satellite/console/users.go +++ b/satellite/console/users.go @@ -176,6 +176,7 @@ type User struct { FailedLoginCount int `json:"failedLoginCount"` LoginLockoutExpiration time.Time `json:"loginLockoutExpiration"` + SignupCaptcha *float64 `json:"-"` } // ResponseUser is an entity which describes db User and can be sent in response. diff --git a/satellite/satellitedb/users.go b/satellite/satellitedb/users.go index 165e7e193..af2fec588 100644 --- a/satellite/satellitedb/users.go +++ b/satellite/satellitedb/users.go @@ -155,6 +155,9 @@ func (users *users) Insert(ctx context.Context, user *console.User) (_ *console. optional.EmployeeCount = dbx.User_EmployeeCount(user.EmployeeCount) optional.HaveSalesContact = dbx.User_HaveSalesContact(user.HaveSalesContact) } + if user.SignupCaptcha != nil { + optional.SignupCaptcha = dbx.User_SignupCaptcha(*user.SignupCaptcha) + } createdUser, err := users.db.Create_User(ctx, dbx.User_Id(user.ID[:]), @@ -368,6 +371,7 @@ func userFromDBX(ctx context.Context, user *dbx.User) (_ *console.User, err erro HaveSalesContact: user.HaveSalesContact, MFAEnabled: user.MfaEnabled, VerificationReminders: user.VerificationReminders, + SignupCaptcha: user.SignupCaptcha, } if user.PartnerId != nil {