console/satellite: track signup captcha scores
This change tracks signup captcha scores in the signup_captcha column in the users table. It slightly modifies the captcha verify method to return both the score and success. see: https://github.com/storj/storj/issues/5067 Change-Id: I7b3993e44958cfcf179806c7df19d6887fe3eda9
This commit is contained in:
parent
cd89e1e557
commit
a4192acabb
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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.
|
||||
|
@ -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 {
|
||||
|
Loading…
Reference in New Issue
Block a user