// Copyright (C) 2020 Storj Labs, Inc. // See LICENSE for copying information. package consoleapi_test import ( "bytes" "context" "encoding/json" "errors" "fmt" "io" "math/rand" "net/http" "net/http/httptest" "net/url" "reflect" "strconv" "strings" "testing" "testing/quick" "time" "github.com/stretchr/testify/require" "go.uber.org/zap" "storj.io/common/testcontext" "storj.io/common/testrand" "storj.io/storj/private/post" "storj.io/storj/private/testplanet" "storj.io/storj/satellite" "storj.io/storj/satellite/console" "storj.io/storj/satellite/console/consoleweb/consoleapi" ) func TestAuth_Register(t *testing.T) { testplanet.Run(t, testplanet.Config{ SatelliteCount: 1, StorageNodeCount: 0, UplinkCount: 0, Reconfigure: testplanet.Reconfigure{ Satellite: func(log *zap.Logger, index int, config *satellite.Config) { config.Console.OpenRegistrationEnabled = true config.Console.RateLimit.Burst = 10 config.Mail.AuthType = "nomail" }, }, }, func(t *testing.T, ctx *testcontext.Context, planet *testplanet.Planet) { for i, test := range []struct { Partner string ValidPartner bool }{ {Partner: "minio", ValidPartner: true}, {Partner: "Minio", ValidPartner: true}, {Partner: "Raiden Network", ValidPartner: true}, {Partner: "Raiden nEtwork", ValidPartner: true}, {Partner: "invalid-name", ValidPartner: false}, } { func() { registerData := struct { FullName string `json:"fullName"` ShortName string `json:"shortName"` Email string `json:"email"` Partner string `json:"partner"` UserAgent string `json:"userAgent"` Password string `json:"password"` SecretInput string `json:"secret"` ReferrerUserID string `json:"referrerUserId"` IsProfessional bool `json:"isProfessional"` Position string `json:"Position"` CompanyName string `json:"CompanyName"` EmployeeCount string `json:"EmployeeCount"` SignupPromoCode string `json:"signupPromoCode"` }{ FullName: "testuser" + strconv.Itoa(i), ShortName: "test", Email: "user@test" + strconv.Itoa(i) + ".test", Partner: test.Partner, Password: "abc123", IsProfessional: true, Position: "testposition", CompanyName: "companytestname", EmployeeCount: "0", SignupPromoCode: "STORJ50", } jsonBody, err := json.Marshal(registerData) require.NoError(t, err) url := planet.Satellites[0].ConsoleURL() + "/api/v0/auth/register" req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewBuffer(jsonBody)) require.NoError(t, err) req.Header.Set("Content-Type", "application/json") result, err := http.DefaultClient.Do(req) require.NoError(t, err) defer func() { err = result.Body.Close() require.NoError(t, err) }() require.Equal(t, http.StatusOK, result.StatusCode) require.Len(t, planet.Satellites, 1) // this works only because we configured 'nomail' above. Mail send simulator won't click to activation link. _, users, err := planet.Satellites[0].API.Console.Service.GetUserByEmailWithUnverified(ctx, registerData.Email) require.NoError(t, err) require.Len(t, users, 1) require.Equal(t, []byte(test.Partner), users[0].UserAgent) }() } }) } func TestAuth_Register_CORS(t *testing.T) { testplanet.Run(t, testplanet.Config{ SatelliteCount: 1, StorageNodeCount: 0, UplinkCount: 0, Reconfigure: testplanet.Reconfigure{ Satellite: func(log *zap.Logger, index int, config *satellite.Config) { config.Console.OpenRegistrationEnabled = true config.Console.RateLimit.Burst = 10 config.Mail.AuthType = "nomail" }, }, }, func(t *testing.T, ctx *testcontext.Context, planet *testplanet.Planet) { email := "user@test.com" fullName := "testuser" jsonBody := []byte(fmt.Sprintf(`{"email":"%s","fullName":"%s","password":"abc123","shortName":"test"}`, email, fullName)) url := planet.Satellites[0].ConsoleURL() + "/api/v0/auth/register" req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewBuffer(jsonBody)) require.NoError(t, err) req.Header.Set("Content-Type", "application/json") // 1. OPTIONS request // 1.1 CORS headers should not be set with origin other than storj.io or www.storj.io req.Header.Set("Origin", "https://someexternalorigin.test") req.Method = http.MethodOptions resp, err := http.DefaultClient.Do(req) require.NoError(t, err) require.Equal(t, http.StatusOK, resp.StatusCode) require.Equal(t, "", resp.Header.Get("Access-Control-Allow-Origin")) require.Equal(t, "", resp.Header.Get("Access-Control-Allow-Methods")) require.Equal(t, "", resp.Header.Get("Access-Control-Allow-Headers")) require.NoError(t, resp.Body.Close()) // 1.2 CORS headers should be set with a domain of storj.io req.Header.Set("Origin", "https://storj.io") resp, err = http.DefaultClient.Do(req) require.NoError(t, err) require.Equal(t, http.StatusOK, resp.StatusCode) require.Equal(t, "https://storj.io", resp.Header.Get("Access-Control-Allow-Origin")) require.Equal(t, "POST, OPTIONS", resp.Header.Get("Access-Control-Allow-Methods")) allowedHeaders := strings.Split(resp.Header.Get("Access-Control-Allow-Headers"), ", ") require.ElementsMatch(t, allowedHeaders, []string{ "Content-Type", "Content-Length", "Accept", "Accept-Encoding", "X-CSRF-Token", "Authorization", }) require.NoError(t, resp.Body.Close()) // 1.3 CORS headers should be set with a domain of www.storj.io req.Header.Set("Origin", "https://www.storj.io") resp, err = http.DefaultClient.Do(req) require.NoError(t, err) require.Equal(t, http.StatusOK, resp.StatusCode) require.Equal(t, "https://www.storj.io", resp.Header.Get("Access-Control-Allow-Origin")) require.Equal(t, "POST, OPTIONS", resp.Header.Get("Access-Control-Allow-Methods")) allowedHeaders = strings.Split(resp.Header.Get("Access-Control-Allow-Headers"), ", ") require.ElementsMatch(t, allowedHeaders, []string{ "Content-Type", "Content-Length", "Accept", "Accept-Encoding", "X-CSRF-Token", "Authorization", }) require.NoError(t, resp.Body.Close()) // 2. POST request with origin www.storj.io req.Method = http.MethodPost resp, err = http.DefaultClient.Do(req) require.NoError(t, err) defer func() { err = resp.Body.Close() require.NoError(t, err) }() require.Equal(t, http.StatusOK, resp.StatusCode) require.Equal(t, "https://www.storj.io", resp.Header.Get("Access-Control-Allow-Origin")) require.Equal(t, "POST, OPTIONS", resp.Header.Get("Access-Control-Allow-Methods")) allowedHeaders = strings.Split(resp.Header.Get("Access-Control-Allow-Headers"), ", ") require.ElementsMatch(t, allowedHeaders, []string{ "Content-Type", "Content-Length", "Accept", "Accept-Encoding", "X-CSRF-Token", "Authorization", }) require.Len(t, planet.Satellites, 1) // this works only because we configured 'nomail' above. Mail send simulator won't click to activation link. _, users, err := planet.Satellites[0].API.Console.Service.GetUserByEmailWithUnverified(ctx, email) require.NoError(t, err) require.Len(t, users, 1) require.Equal(t, fullName, users[0].FullName) }) } func TestDeleteAccount(t *testing.T) { ctx := testcontext.New(t) log := testplanet.NewLogger(t) // We do a black box testing because currently we don't allow to delete // accounts through the API hence we must always return an error response. config := &quick.Config{ Values: func(values []reflect.Value, rnd *rand.Rand) { // TODO: use or implement a better and thorough HTTP Request random generator var method string switch rnd.Intn(9) { case 0: method = http.MethodGet case 1: method = http.MethodHead case 2: method = http.MethodPost case 3: method = http.MethodPut case 4: method = http.MethodPatch case 5: method = http.MethodDelete case 6: method = http.MethodConnect case 7: method = http.MethodOptions case 8: method = http.MethodTrace default: t.Fatal("unexpected random value for HTTP method selection") } var path string { val, ok := quick.Value(reflect.TypeOf(""), rnd) require.True(t, ok, "quick.Values generator function couldn't generate a string") path = url.PathEscape(val.String()) } var query string { nparams := rnd.Intn(27) params := make([]string, nparams) for i := 0; i < nparams; i++ { val, ok := quick.Value(reflect.TypeOf(""), rnd) require.True(t, ok, "quick.Values generator function couldn't generate a string") param := val.String() val, ok = quick.Value(reflect.TypeOf(""), rnd) require.True(t, ok, "quick.Values generator function couldn't generate a string") param += "=" + val.String() params[i] = param } query = url.QueryEscape(strings.Join(params, "&")) } var body io.Reader { val, ok := quick.Value(reflect.TypeOf([]byte(nil)), rnd) require.True(t, ok, "quick.Values generator function couldn't generate a byte slice") body = bytes.NewReader(val.Bytes()) } withQuery := "" if len(query) > 0 { withQuery = "?" } reqURL, err := url.Parse("//storj.io/" + path + withQuery + query) require.NoError(t, err, "error when generating a random URL") req, err := http.NewRequestWithContext(ctx, method, reqURL.String(), body) require.NoError(t, err, "error when geneating a random request") values[0] = reflect.ValueOf(req) }, } expectedHandler := func(_ *http.Request) (status int, body []byte) { return http.StatusNotImplemented, []byte("{\"error\":\"The server is incapable of fulfilling the request\"}\n") } actualHandler := func(r *http.Request) (status int, body []byte) { rr := httptest.NewRecorder() authController := consoleapi.NewAuth(log, nil, nil, nil, nil, nil, "", "", "", "", "", "") authController.DeleteAccount(rr, r) //nolint:bodyclose result := rr.Result() defer func() { err := result.Body.Close() require.NoError(t, err) }() body, err := io.ReadAll(result.Body) require.NoError(t, err) return result.StatusCode, body } err := quick.CheckEqual(expectedHandler, actualHandler, config) if err != nil { fmt.Printf("%+v\n", err) var cerr *quick.CheckEqualError require.True(t, errors.As(err, &cerr)) t.Fatalf(`DeleteAccount handler has returned a different response: round: %d input args: %+v expected response: status code: %d response body: %s returned response: status code: %d response body: %s `, cerr.Count, cerr.In, cerr.Out1[0], cerr.Out1[1], cerr.Out2[0], cerr.Out2[1]) } } func TestMFAEndpoints(t *testing.T) { testplanet.Run(t, testplanet.Config{ SatelliteCount: 1, StorageNodeCount: 0, UplinkCount: 0, }, func(t *testing.T, ctx *testcontext.Context, planet *testplanet.Planet) { sat := planet.Satellites[0] user, err := sat.AddUser(ctx, console.CreateUser{ FullName: "MFA Test User", Email: "mfauser@mail.test", }, 1) require.NoError(t, err) tokenInfo, err := sat.API.Console.Service.Token(ctx, console.AuthUser{Email: user.Email, Password: user.FullName}) require.NoError(t, err) require.NotEmpty(t, tokenInfo.Token) type data struct { Passcode string `json:"passcode"` RecoveryCode string `json:"recoveryCode"` } doRequest := func(urlSuffix string, passcode string, recoveryCode string) *http.Response { urlLink := sat.ConsoleURL() + "/api/v0/auth/mfa" + urlSuffix var buf io.Reader body := &data{ Passcode: passcode, RecoveryCode: recoveryCode, } bodyBytes, err := json.Marshal(body) require.NoError(t, err) buf = bytes.NewBuffer(bodyBytes) req, err := http.NewRequestWithContext(ctx, http.MethodPost, urlLink, buf) require.NoError(t, err) req.AddCookie(&http.Cookie{ Name: "_tokenKey", Path: "/", Value: tokenInfo.Token.String(), Expires: time.Now().AddDate(0, 0, 1), }) req.Header.Set("Content-Type", "application/json") result, err := http.DefaultClient.Do(req) require.NoError(t, err) return result } // Expect failure because MFA is not enabled. result := doRequest("/generate-recovery-codes", "", "") require.Equal(t, http.StatusUnauthorized, result.StatusCode) require.NoError(t, result.Body.Close()) // Expect failure due to not having generated a secret key. result = doRequest("/enable", "123456", "") require.Equal(t, http.StatusBadRequest, result.StatusCode) require.NoError(t, result.Body.Close()) // Expect success when generating a secret key. result = doRequest("/generate-secret-key", "", "") require.Equal(t, http.StatusOK, result.StatusCode) var key string err = json.NewDecoder(result.Body).Decode(&key) require.NoError(t, err) require.NoError(t, result.Body.Close()) // Expect failure due to prodiving empty passcode. result = doRequest("/enable", "", "") require.Equal(t, http.StatusBadRequest, result.StatusCode) require.NoError(t, result.Body.Close()) // Expect failure due to providing invalid passcode. badCode, err := console.NewMFAPasscode(key, time.Now().Add(time.Hour)) require.NoError(t, err) result = doRequest("/enable", badCode, "") require.Equal(t, http.StatusBadRequest, result.StatusCode) require.NoError(t, result.Body.Close()) // Expect success when providing valid passcode. goodCode, err := console.NewMFAPasscode(key, time.Now()) require.NoError(t, err) result = doRequest("/enable", goodCode, "") require.Equal(t, http.StatusOK, result.StatusCode) require.NoError(t, result.Body.Close()) // Expect 10 recovery codes to be generated. result = doRequest("/generate-recovery-codes", "", "") require.Equal(t, http.StatusOK, result.StatusCode) var codes []string err = json.NewDecoder(result.Body).Decode(&codes) require.NoError(t, err) require.Len(t, codes, console.MFARecoveryCodeCount) require.NoError(t, result.Body.Close()) // Expect no token due to missing passcode. newToken, err := sat.API.Console.Service.Token(ctx, console.AuthUser{Email: user.Email, Password: user.FullName}) require.True(t, console.ErrMFAMissing.Has(err)) require.Empty(t, newToken) // Expect token when providing valid passcode. newToken, err = sat.API.Console.Service.Token(ctx, console.AuthUser{ Email: user.Email, Password: user.FullName, MFAPasscode: goodCode, }) require.NoError(t, err) require.NotEmpty(t, newToken) // Expect no token when providing invalid recovery code. newToken, err = sat.API.Console.Service.Token(ctx, console.AuthUser{ Email: user.Email, Password: user.FullName, MFARecoveryCode: "BADCODE", }) require.True(t, console.ErrMFARecoveryCode.Has(err)) require.Empty(t, newToken) for _, code := range codes { opts := console.AuthUser{ Email: user.Email, Password: user.FullName, MFARecoveryCode: code, } // Expect token when providing valid recovery code. newToken, err = sat.API.Console.Service.Token(ctx, opts) require.NoError(t, err) require.NotEmpty(t, newToken) // Expect error when providing expired recovery code. newToken, err = sat.API.Console.Service.Token(ctx, opts) require.True(t, console.ErrMFARecoveryCode.Has(err)) require.Empty(t, newToken) } // Expect failure due to disabling MFA with no passcode. result = doRequest("/disable", "", "") require.Equal(t, http.StatusBadRequest, result.StatusCode) require.NoError(t, result.Body.Close()) // Expect failure due to disabling MFA with invalid passcode. result = doRequest("/disable", badCode, "") require.Equal(t, http.StatusBadRequest, result.StatusCode) require.NoError(t, result.Body.Close()) // Expect failure when disabling due to providing both passcode and recovery code. result = doRequest("/generate-recovery-codes", "", "") err = json.NewDecoder(result.Body).Decode(&codes) require.NoError(t, err) require.NoError(t, result.Body.Close()) result = doRequest("/disable", goodCode, codes[0]) require.Equal(t, http.StatusConflict, result.StatusCode) require.NoError(t, result.Body.Close()) // Expect success when disabling MFA with valid passcode. result = doRequest("/disable", goodCode, "") require.Equal(t, http.StatusOK, result.StatusCode) require.NoError(t, result.Body.Close()) // Expect success when disabling MFA with valid recovery code. result = doRequest("/generate-secret-key", "", "") err = json.NewDecoder(result.Body).Decode(&key) require.NoError(t, err) require.NoError(t, result.Body.Close()) goodCode, err = console.NewMFAPasscode(key, time.Now()) require.NoError(t, err) result = doRequest("/enable", goodCode, "") require.NoError(t, result.Body.Close()) result = doRequest("/generate-recovery-codes", "", "") err = json.NewDecoder(result.Body).Decode(&codes) require.NoError(t, err) require.NoError(t, result.Body.Close()) result = doRequest("/disable", "", codes[0]) require.Equal(t, http.StatusOK, result.StatusCode) require.NoError(t, result.Body.Close()) }) } func TestResetPasswordEndpoint(t *testing.T) { testplanet.Run(t, testplanet.Config{ SatelliteCount: 1, StorageNodeCount: 0, UplinkCount: 0, Reconfigure: testplanet.Reconfigure{ Satellite: func(log *zap.Logger, index int, config *satellite.Config) { config.Console.RateLimit.Burst = 10 }, }, }, func(t *testing.T, ctx *testcontext.Context, planet *testplanet.Planet) { sat := planet.Satellites[0] service := sat.API.Console.Service user, err := sat.AddUser(ctx, console.CreateUser{ FullName: "Test User", Email: "test@mail.test", }, 1) require.NoError(t, err) newPass := user.FullName getNewResetToken := func() *console.ResetPasswordToken { token, err := sat.DB.Console().ResetPasswordTokens().Create(ctx, user.ID) require.NoError(t, err) require.NotNil(t, token) return token } tryPasswordReset := func(tokenStr, password, mfaPasscode, mfaRecoveryCode string) (int, bool) { url := sat.ConsoleURL() + "/api/v0/auth/reset-password" bodyBytes, err := json.Marshal(map[string]string{ "password": password, "token": tokenStr, "mfaPasscode": mfaPasscode, "mfaRecoveryCode": mfaRecoveryCode, }) require.NoError(t, err) req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewBuffer(bodyBytes)) require.NoError(t, err) req.Header.Set("Content-Type", "application/json") result, err := http.DefaultClient.Do(req) require.NoError(t, err) var response struct { Code string `json:"code"` } if result.ContentLength > 0 { err = json.NewDecoder(result.Body).Decode(&response) require.NoError(t, err) } require.NoError(t, result.Body.Close()) return result.StatusCode, response.Code == "mfa_required" } token := getNewResetToken() status, mfaError := tryPasswordReset("badToken", newPass, "", "") require.Equal(t, http.StatusUnauthorized, status) require.False(t, mfaError) status, mfaError = tryPasswordReset(token.Secret.String(), "bad", "", "") require.Equal(t, http.StatusBadRequest, status) require.False(t, mfaError) status, mfaError = tryPasswordReset(token.Secret.String(), string(testrand.RandAlphaNumeric(129)), "", "") require.Equal(t, http.StatusBadRequest, status) require.False(t, mfaError) status, mfaError = tryPasswordReset(token.Secret.String(), newPass, "", "") require.Equal(t, http.StatusOK, status) require.False(t, mfaError) token = getNewResetToken() // Enable MFA. userCtx, err := sat.UserContext(ctx, user.ID) require.NoError(t, err) key, err := service.ResetMFASecretKey(userCtx) require.NoError(t, err) userCtx, err = sat.UserContext(ctx, user.ID) require.NoError(t, err) passcode, err := console.NewMFAPasscode(key, token.CreatedAt) require.NoError(t, err) err = service.EnableUserMFA(userCtx, passcode, token.CreatedAt) require.NoError(t, err) status, mfaError = tryPasswordReset(token.Secret.String(), newPass, "", "") require.Equal(t, http.StatusBadRequest, status) require.True(t, mfaError) status, mfaError = tryPasswordReset(token.Secret.String(), newPass, "", "") require.Equal(t, http.StatusBadRequest, status) require.True(t, mfaError) }) } type EmailVerifier struct { Data consoleapi.ContextChannel Context context.Context } func (v *EmailVerifier) SendEmail(ctx context.Context, msg *post.Message) error { body := "" for _, part := range msg.Parts { body += part.Content } return v.Data.Send(v.Context, body) } func (v *EmailVerifier) FromAddress() post.Address { return post.Address{} } func TestRegistrationEmail(t *testing.T) { testplanet.Run(t, testplanet.Config{ SatelliteCount: 1, StorageNodeCount: 0, UplinkCount: 0, }, func(t *testing.T, ctx *testcontext.Context, planet *testplanet.Planet) { sat := planet.Satellites[0] email := "test@mail.test" jsonBody, err := json.Marshal(map[string]interface{}{ "fullName": "Test User", "shortName": "Test", "email": email, "password": "123a123", }) require.NoError(t, err) register := func() { url := planet.Satellites[0].ConsoleURL() + "/api/v0/auth/register" req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewBuffer(jsonBody)) require.NoError(t, err) req.Header.Set("Content-Type", "application/json") result, err := http.DefaultClient.Do(req) require.NoError(t, err) require.Equal(t, http.StatusOK, result.StatusCode) require.NoError(t, result.Body.Close()) } sender := &EmailVerifier{Context: ctx} sat.API.Mail.Service.Sender = sender // Registration attempts using new e-mail address should send activation e-mail. register() body, err := sender.Data.Get(ctx) require.NoError(t, err) require.Contains(t, body, "/activation") // Registration attempts using existing but unverified e-mail address should send activation e-mail. register() body, err = sender.Data.Get(ctx) require.NoError(t, err) require.Contains(t, body, "/activation") // Registration attempts using existing and verified e-mail address should send account already exists e-mail. _, users, err := sat.DB.Console().Users().GetByEmailWithUnverified(ctx, email) require.NoError(t, err) users[0].Status = console.Active require.NoError(t, sat.DB.Console().Users().Update(ctx, users[0].ID, console.UpdateUserRequest{ Status: &users[0].Status, })) register() body, err = sender.Data.Get(ctx) require.NoError(t, err) require.Contains(t, body, "/login") require.Contains(t, body, "/forgot-password") require.Contains(t, body, "/signup") }) } func TestResendActivationEmail(t *testing.T) { testplanet.Run(t, testplanet.Config{ SatelliteCount: 1, StorageNodeCount: 0, UplinkCount: 0, }, func(t *testing.T, ctx *testcontext.Context, planet *testplanet.Planet) { sat := planet.Satellites[0] usersRepo := sat.DB.Console().Users() user, err := sat.AddUser(ctx, console.CreateUser{ FullName: "Test User", Email: "test@mail.test", }, 1) require.NoError(t, err) resendEmail := func() { url := planet.Satellites[0].ConsoleURL() + "/api/v0/auth/resend-email/" + user.Email req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewBufferString(user.Email)) require.NoError(t, err) result, err := http.DefaultClient.Do(req) require.NoError(t, err) require.NoError(t, result.Body.Close()) require.Equal(t, http.StatusOK, result.StatusCode) } sender := &EmailVerifier{Context: ctx} sat.API.Mail.Service.Sender = sender // Expect password reset e-mail to be sent when using verified e-mail address. resendEmail() body, err := sender.Data.Get(ctx) require.NoError(t, err) require.Contains(t, body, "/password-recovery") // Expect activation e-mail to be sent when using unverified e-mail address. user.Status = console.Inactive require.NoError(t, usersRepo.Update(ctx, user.ID, console.UpdateUserRequest{ Status: &user.Status, })) resendEmail() body, err = sender.Data.Get(ctx) require.NoError(t, err) require.Contains(t, body, "/activation") }) } func TestAuth_Register_NameSpecialChars(t *testing.T) { testplanet.Run(t, testplanet.Config{ SatelliteCount: 1, StorageNodeCount: 0, UplinkCount: 0, Reconfigure: testplanet.Reconfigure{ Satellite: func(log *zap.Logger, index int, config *satellite.Config) { config.Mail.AuthType = "nomail" }, }, }, func(t *testing.T, ctx *testcontext.Context, planet *testplanet.Planet) { inputName := "The website has been changed to https://evil.com/login.html<> - Enter Login ' \" Details," filteredName := "The website has been changed to https---evil-com-login-html\\u0026lt;\\u0026gt; - Enter Login \\u0026#39; \\u0026#34; Details," email := "user@mail.test" registerData := struct { FullName string `json:"fullName"` ShortName string `json:"shortName"` Email string `json:"email"` Password string `json:"password"` }{ FullName: inputName, ShortName: inputName, Email: email, Password: "abc123", } jsonBody, err := json.Marshal(registerData) require.NoError(t, err) url := planet.Satellites[0].ConsoleURL() + "/api/v0/auth/register" req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewBuffer(jsonBody)) require.NoError(t, err) req.Header.Set("Content-Type", "application/json") result, err := http.DefaultClient.Do(req) require.NoError(t, err) defer func() { err = result.Body.Close() require.NoError(t, err) }() require.Equal(t, http.StatusOK, result.StatusCode) require.Len(t, planet.Satellites, 1) // this works only because we configured 'nomail' above. Mail send simulator won't click to activation link. _, users, err := planet.Satellites[0].API.Console.Service.GetUserByEmailWithUnverified(ctx, email) require.NoError(t, err) require.Len(t, users, 1) require.Equal(t, filteredName, users[0].FullName) require.Equal(t, filteredName, users[0].ShortName) }) } func TestAuth_Register_ShortPartnerOrPromo(t *testing.T) { testplanet.Run(t, testplanet.Config{ SatelliteCount: 1, StorageNodeCount: 0, UplinkCount: 0, }, func(t *testing.T, ctx *testcontext.Context, planet *testplanet.Planet) { type registerData struct { FullName string `json:"fullName"` Email string `json:"email"` Password string `json:"password"` Partner string `json:"partner"` SignupPromoCode string `json:"signupPromoCode"` } reqURL := planet.Satellites[0].ConsoleURL() + "/api/v0/auth/register" jsonBodyCorrect, err := json.Marshal(®isterData{ FullName: "test", Email: "user@mail.test", Password: "abc123", Partner: string(testrand.RandAlphaNumeric(100)), SignupPromoCode: string(testrand.RandAlphaNumeric(100)), }) require.NoError(t, err) req, err := http.NewRequestWithContext(ctx, http.MethodPost, reqURL, bytes.NewBuffer(jsonBodyCorrect)) require.NoError(t, err) req.Header.Set("Content-Type", "application/json") result, err := http.DefaultClient.Do(req) require.NoError(t, err) require.Equal(t, http.StatusOK, result.StatusCode) err = result.Body.Close() require.NoError(t, err) jsonBodyPartnerInvalid, err := json.Marshal(®isterData{ FullName: "test", Email: "user1@mail.test", Password: "abc123", Partner: string(testrand.RandAlphaNumeric(101)), }) require.NoError(t, err) req, err = http.NewRequestWithContext(ctx, http.MethodPost, reqURL, bytes.NewBuffer(jsonBodyPartnerInvalid)) require.NoError(t, err) result, err = http.DefaultClient.Do(req) require.NoError(t, err) require.Equal(t, http.StatusBadRequest, result.StatusCode) err = result.Body.Close() require.NoError(t, err) jsonBodyPromoInvalid, err := json.Marshal(®isterData{ FullName: "test", Email: "user1@mail.test", Password: "abc123", SignupPromoCode: string(testrand.RandAlphaNumeric(101)), }) require.NoError(t, err) req, err = http.NewRequestWithContext(ctx, http.MethodPost, reqURL, bytes.NewBuffer(jsonBodyPromoInvalid)) require.NoError(t, err) result, err = http.DefaultClient.Do(req) require.NoError(t, err) require.Equal(t, http.StatusBadRequest, result.StatusCode) defer func() { err = result.Body.Close() require.NoError(t, err) }() }) } func TestAuth_Register_PasswordLength(t *testing.T) { testplanet.Run(t, testplanet.Config{ SatelliteCount: 1, StorageNodeCount: 0, UplinkCount: 0, Reconfigure: testplanet.Reconfigure{ Satellite: func(log *zap.Logger, index int, config *satellite.Config) { config.Console.RateLimit.Burst = 10 }, }, }, func(t *testing.T, ctx *testcontext.Context, planet *testplanet.Planet) { for i, tt := range []struct { Name string Length int Ok bool }{ {"Length below minimum must be rejected", 5, false}, {"Length as minimum must be accepted", 6, true}, {"Length as maximum must be accepted", 128, true}, {"Length above maximum must be rejected", 129, false}, } { tt := tt t.Run(tt.Name, func(t *testing.T) { jsonBody, err := json.Marshal(map[string]string{ "fullName": "test", "email": "user" + strconv.Itoa(i) + "@mail.test", "password": string(testrand.RandAlphaNumeric(tt.Length)), }) require.NoError(t, err) url := planet.Satellites[0].ConsoleURL() + "/api/v0/auth/register" req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewBuffer(jsonBody)) require.NoError(t, err) result, err := http.DefaultClient.Do(req) require.NoError(t, err) err = result.Body.Close() require.NoError(t, err) status := http.StatusOK if !tt.Ok { status = http.StatusBadRequest } require.Equal(t, status, result.StatusCode) }) } }) }