From 6354b388491dc3e3ca5c90cbcf78af56b387d0ae Mon Sep 17 00:00:00 2001 From: Nikolai Siedov <46809683+Qweder93@users.noreply.github.com> Date: Tue, 29 Oct 2019 16:24:16 +0200 Subject: [PATCH] web/satellite: auth graphql api replaced with REST API (#3396) --- .../console/consoleweb/consoleapi/auth.go | 66 ++++--- .../consoleweb/consoleql/mutation_test.go | 71 ++----- .../consoleweb/consoleql/query_test.go | 50 ++--- .../console/consoleweb/consoleql/user.go | 24 +-- satellite/console/consoleweb/server.go | 17 +- satellite/console/users.go | 23 +-- satellite/console/validation.go | 9 + satellite/mailservice/simulate/linkclicker.go | 3 + web/satellite/src/api/auth.ts | 185 ++++++++++-------- web/satellite/src/api/payments.ts | 2 +- .../account/ChangePasswordPopup.vue | 6 +- .../components/account/DeleteAccountPopup.vue | 4 +- .../common/RegistrationSuccessPopup.vue | 4 +- web/satellite/src/store/index.ts | 6 +- web/satellite/src/types/users.ts | 4 +- web/satellite/src/utils/httpClient.ts | 10 + .../views/forgotPassword/ForgotPassword.vue | 4 +- web/satellite/src/views/login/LoginArea.vue | 5 +- .../src/views/register/RegisterArea.vue | 7 +- 19 files changed, 246 insertions(+), 254 deletions(-) diff --git a/satellite/console/consoleweb/consoleapi/auth.go b/satellite/console/consoleweb/consoleapi/auth.go index 98a1610ea..23106cbdf 100644 --- a/satellite/console/consoleweb/consoleapi/auth.go +++ b/satellite/console/consoleweb/consoleapi/auth.go @@ -62,17 +62,13 @@ func (a *Auth) Token(w http.ResponseWriter, r *http.Request) { return } - var tokenResponse struct { - Token string `json:"token"` - } - - tokenResponse.Token, err = a.service.Token(ctx, tokenRequest.Email, tokenRequest.Password) + token, err := a.service.Token(ctx, tokenRequest.Email, tokenRequest.Password) if err != nil { a.serveJSONError(w, http.StatusUnauthorized, err) return } - err = json.NewEncoder(w).Encode(tokenResponse) + err = json.NewEncoder(w).Encode(token) if err != nil { a.log.Error("token handler could not encode token response", zap.Error(ErrAuthAPI.Wrap(err))) return @@ -85,25 +81,39 @@ func (a *Auth) Register(w http.ResponseWriter, r *http.Request) { var err error defer mon.Task()(&ctx)(&err) - var request struct { - UserInfo console.CreateUser `json:"userInfo"` - SecretInput string `json:"secret"` - ReferrerUserID string `json:"referrerUserID"` + var registerData struct { + FullName string `json:"fullName"` + ShortName string `json:"shortName"` + Email string `json:"email"` + PartnerID string `json:"partnerId"` + Password string `json:"password"` + SecretInput string `json:"secret"` + ReferrerUserID string `json:"referrerUserID"` } - err = json.NewDecoder(r.Body).Decode(&request) + err = json.NewDecoder(r.Body).Decode(®isterData) if err != nil { a.serveJSONError(w, http.StatusBadRequest, err) return } - secret, err := console.RegistrationSecretFromBase64(request.SecretInput) + secret, err := console.RegistrationSecretFromBase64(registerData.SecretInput) if err != nil { a.serveJSONError(w, http.StatusBadRequest, err) return } - user, err := a.service.CreateUser(ctx, request.UserInfo, secret, request.ReferrerUserID) + user, err := a.service.CreateUser(ctx, + console.CreateUser{ + FullName: registerData.FullName, + ShortName: registerData.ShortName, + Email: registerData.Email, + PartnerID: registerData.PartnerID, + Password: registerData.Password, + }, + secret, + registerData.ReferrerUserID, + ) if err != nil { a.serveJSONError(w, http.StatusInternalServerError, err) return @@ -115,7 +125,7 @@ func (a *Auth) Register(w http.ResponseWriter, r *http.Request) { return } - link := a.ExternalAddress + consoleql.ActivationPath + token + link := a.ExternalAddress + "activation/?token=" + token userName := user.ShortName if user.ShortName == "" { userName = user.FullName @@ -137,8 +147,8 @@ func (a *Auth) Register(w http.ResponseWriter, r *http.Request) { } } -// Update updates user's full name and short name. -func (a *Auth) Update(w http.ResponseWriter, r *http.Request) { +// UpdateAccount updates user's full name and short name. +func (a *Auth) UpdateAccount(w http.ResponseWriter, r *http.Request) { ctx := r.Context() var err error defer mon.Task()(&ctx)(&err) @@ -160,8 +170,8 @@ func (a *Auth) Update(w http.ResponseWriter, r *http.Request) { } } -// Get gets authorized user and take it's params. -func (a *Auth) Get(w http.ResponseWriter, r *http.Request) { +// GetAccount gets authorized user and take it's params. +func (a *Auth) GetAccount(w http.ResponseWriter, r *http.Request) { ctx := r.Context() var err error defer mon.Task()(&ctx)(&err) @@ -193,23 +203,23 @@ func (a *Auth) Get(w http.ResponseWriter, r *http.Request) { } } -// Delete - authorizes user and deletes account by password. -func (a *Auth) Delete(w http.ResponseWriter, r *http.Request) { +// DeleteAccount - authorizes user and deletes account by password. +func (a *Auth) DeleteAccount(w http.ResponseWriter, r *http.Request) { ctx := r.Context() var err error defer mon.Task()(&ctx)(&err) - var request struct { + var deleteRequest struct { Password string `json:"password"` } - err = json.NewDecoder(r.Body).Decode(&request) + err = json.NewDecoder(r.Body).Decode(&deleteRequest) if err != nil { a.serveJSONError(w, http.StatusBadRequest, err) return } - err = a.service.DeleteAccount(ctx, request.Password) + err = a.service.DeleteAccount(ctx, deleteRequest.Password) if err != nil { if console.ErrUnauthorized.Has(err) { a.serveJSONError(w, http.StatusUnauthorized, err) @@ -276,8 +286,8 @@ func (a *Auth) ForgotPassword(w http.ResponseWriter, r *http.Request) { return } - passwordRecoveryLink := a.ExternalAddress + consoleql.CancelPasswordRecoveryPath + recoveryToken - cancelPasswordRecoveryLink := a.ExternalAddress + consoleql.CancelPasswordRecoveryPath + recoveryToken + passwordRecoveryLink := a.ExternalAddress + "password-recovery/?token=" + recoveryToken + cancelPasswordRecoveryLink := a.ExternalAddress + "cancel-password-recovery/?token=" + recoveryToken userName := user.ShortName if user.ShortName == "" { userName = user.FullName @@ -309,13 +319,13 @@ func (a *Auth) ResendEmail(w http.ResponseWriter, r *http.Request) { defer mon.Task()(&ctx)(&err) params := mux.Vars(r) - val, ok := params["id"] + id, ok := params["id"] if !ok { a.serveJSONError(w, http.StatusBadRequest, errs.New("id expected")) return } - userID, err := uuid.Parse(val) + userID, err := uuid.Parse(id) if err != nil { a.serveJSONError(w, http.StatusBadRequest, err) return @@ -333,7 +343,7 @@ func (a *Auth) ResendEmail(w http.ResponseWriter, r *http.Request) { return } - link := a.ExternalAddress + consoleql.ActivationPath + token + link := a.ExternalAddress + "activation/?token=" + token userName := user.ShortName if user.ShortName == "" { userName = user.FullName diff --git a/satellite/console/consoleweb/consoleql/mutation_test.go b/satellite/console/consoleweb/consoleql/mutation_test.go index 3c52e59d1..ad06bb9dd 100644 --- a/satellite/console/consoleweb/consoleql/mutation_test.go +++ b/satellite/console/consoleweb/consoleql/mutation_test.go @@ -79,13 +79,11 @@ func TestGrapqhlMutation(t *testing.T) { require.NoError(t, err) createUser := console.CreateUser{ - UserInfo: console.UserInfo{ - FullName: "John Roll", - ShortName: "Roll", - Email: "test@mail.test", - PartnerID: "120bf202-8252-437e-ac12-0e364bee852e", - }, - Password: "123a123", + FullName: "John Roll", + ShortName: "Roll", + Email: "test@mail.test", + PartnerID: "120bf202-8252-437e-ac12-0e364bee852e", + Password: "123a123", } refUserID := "" @@ -126,13 +124,11 @@ func TestGrapqhlMutation(t *testing.T) { t.Run("Create user mutation with partner id", func(t *testing.T) { newUser := console.CreateUser{ - UserInfo: console.UserInfo{ - FullName: "Green Mickey", - ShortName: "Green", - Email: "u1@mail.test", - PartnerID: "120bf202-8252-437e-ac12-0e364bee852e", - }, - Password: "123a123", + FullName: "Green Mickey", + ShortName: "Green", + Email: "u1@mail.test", + PartnerID: "120bf202-8252-437e-ac12-0e364bee852e", + Password: "123a123", } require.NoError(t, err) @@ -178,13 +174,11 @@ func TestGrapqhlMutation(t *testing.T) { t.Run("Create user mutation without partner id", func(t *testing.T) { newUser := console.CreateUser{ - UserInfo: console.UserInfo{ - FullName: "Red Mickey", - ShortName: "Red", - Email: "u2@mail.test", - PartnerID: "", - }, - Password: "123a123", + FullName: "Red Mickey", + ShortName: "Red", + Email: "u2@mail.test", + PartnerID: "", + Password: "123a123", } require.NoError(t, err) @@ -243,24 +237,6 @@ func TestGrapqhlMutation(t *testing.T) { return result.Data } - t.Run("Update account mutation email only", func(t *testing.T) { - email := "new@mail.test" - query := fmt.Sprintf( - "mutation {updateAccount(input:{email:\"%s\"}){id,email,fullName,shortName,createdAt}}", - email, - ) - - result := testQuery(t, query) - - data := result.(map[string]interface{}) - user := data[consoleql.UpdateAccountMutation].(map[string]interface{}) - - assert.Equal(t, rootUser.ID.String(), user[consoleql.FieldID]) - assert.Equal(t, email, user[consoleql.FieldEmail]) - assert.Equal(t, rootUser.FullName, user[consoleql.FieldFullName]) - assert.Equal(t, rootUser.ShortName, user[consoleql.FieldShortName]) - }) - t.Run("Update account mutation fullName only", func(t *testing.T) { fullName := "George" query := fmt.Sprintf( @@ -298,13 +274,11 @@ func TestGrapqhlMutation(t *testing.T) { }) t.Run("Update account mutation all info", func(t *testing.T) { - email := "test@newmail.com" fullName := "Fill Goal" shortName := "Goal" query := fmt.Sprintf( - "mutation {updateAccount(input:{email:\"%s\",fullName:\"%s\",shortName:\"%s\"}){id,email,fullName,shortName,createdAt}}", - email, + "mutation {updateAccount(input:{,fullName:\"%s\",shortName:\"%s\"}){id,fullName,shortName,createdAt}}", fullName, shortName, ) @@ -315,7 +289,6 @@ func TestGrapqhlMutation(t *testing.T) { user := data[consoleql.UpdateAccountMutation].(map[string]interface{}) assert.Equal(t, rootUser.ID.String(), user[consoleql.FieldID]) - assert.Equal(t, email, user[consoleql.FieldEmail]) assert.Equal(t, fullName, user[consoleql.FieldFullName]) assert.Equal(t, shortName, user[consoleql.FieldShortName]) @@ -421,10 +394,8 @@ func TestGrapqhlMutation(t *testing.T) { require.NoError(t, err) user1, err := service.CreateUser(authCtx, console.CreateUser{ - UserInfo: console.UserInfo{ - FullName: "User1", - Email: "u1@mail.test", - }, + FullName: "User1", + Email: "u1@mail.test", Password: "123a123", }, regTokenUser1.Secret, refUserID) require.NoError(t, err) @@ -447,10 +418,8 @@ func TestGrapqhlMutation(t *testing.T) { require.NoError(t, err) user2, err := service.CreateUser(authCtx, console.CreateUser{ - UserInfo: console.UserInfo{ - FullName: "User1", - Email: "u2@mail.test", - }, + FullName: "User1", + Email: "u2@mail.test", Password: "123a123", }, regTokenUser2.Secret, refUserID) require.NoError(t, err) diff --git a/satellite/console/consoleweb/consoleql/query_test.go b/satellite/console/consoleweb/consoleql/query_test.go index 527b620a4..0fffac1ec 100644 --- a/satellite/console/consoleweb/consoleql/query_test.go +++ b/satellite/console/consoleweb/consoleql/query_test.go @@ -66,12 +66,10 @@ func TestGraphqlQuery(t *testing.T) { require.NoError(t, err) createUser := console.CreateUser{ - UserInfo: console.UserInfo{ - FullName: "John", - ShortName: "", - Email: "mtest@mail.test", - }, - Password: "123a123", + FullName: "John", + ShortName: "", + Email: "mtest@mail.test", + Password: "123a123", } refUserID := "" @@ -189,12 +187,10 @@ func TestGraphqlQuery(t *testing.T) { require.NoError(t, err) user1, err := service.CreateUser(authCtx, console.CreateUser{ - UserInfo: console.UserInfo{ - FullName: "Mickey Last", - ShortName: "Last", - Email: "muu1@mail.test", - }, - Password: "123a123", + FullName: "Mickey Last", + ShortName: "Last", + Password: "123a123", + Email: "muu1@mail.test", }, regTokenUser1.Secret, refUserID) require.NoError(t, err) @@ -215,12 +211,10 @@ func TestGraphqlQuery(t *testing.T) { require.NoError(t, err) user2, err := service.CreateUser(authCtx, console.CreateUser{ - UserInfo: console.UserInfo{ - FullName: "Dubas Name", - ShortName: "Name", - Email: "muu2@mail.test", - }, - Password: "123a123", + FullName: "Dubas Name", + ShortName: "Name", + Email: "muu2@mail.test", + Password: "123a123", }, regTokenUser2.Secret, refUserID) require.NoError(t, err) @@ -437,12 +431,10 @@ func TestGraphqlQuery(t *testing.T) { regToken, err := service.CreateRegToken(ctx, 2) require.NoError(t, err) user, err := service.CreateUser(authCtx, console.CreateUser{ - UserInfo: console.UserInfo{ - FullName: "Example User", - ShortName: "Example", - Email: "user@mail.test", - }, - Password: "123a123", + FullName: "Example User", + ShortName: "Example", + Email: "user@mail.test", + Password: "123a123", }, regToken.Secret, refUserID) require.NoError(t, err) @@ -477,12 +469,10 @@ func TestGraphqlQuery(t *testing.T) { regToken, err := service.CreateRegToken(ctx, 2) require.NoError(t, err) user, err := service.CreateUser(authCtx, console.CreateUser{ - UserInfo: console.UserInfo{ - FullName: "Example User", - ShortName: "Example", - Email: "user1@mail.test", - }, - Password: "123a123", + FullName: "Example User", + ShortName: "Example", + Email: "user1@mail.test", + Password: "123a123", }, regToken.Secret, refUserID) require.NoError(t, err) diff --git a/satellite/console/consoleweb/consoleql/user.go b/satellite/console/consoleweb/consoleql/user.go index 73b03121f..6398c4502 100644 --- a/satellite/console/consoleweb/consoleql/user.go +++ b/satellite/console/consoleweb/consoleql/user.go @@ -5,7 +5,6 @@ package consoleql import ( "github.com/graphql-go/graphql" - "github.com/skyrings/skyring-common/tools/uuid" "storj.io/storj/satellite/console" ) @@ -88,31 +87,21 @@ func graphqlUserInput() *graphql.InputObject { }) } -// fromMapUserInfo creates UserInput from input args -func fromMapUserInfo(args map[string]interface{}) (user console.UserInfo) { +func fromMapCreateUser(args map[string]interface{}) (user console.CreateUser) { user.Email, _ = args[FieldEmail].(string) user.FullName, _ = args[FieldFullName].(string) user.ShortName, _ = args[FieldShortName].(string) - user.PartnerID, _ = args[FieldPartnerID].(string) - return -} - -func fromMapCreateUser(args map[string]interface{}) (user console.CreateUser) { - user.UserInfo = fromMapUserInfo(args) user.Password, _ = args[FieldPassword].(string) + user.PartnerID, _ = args[FieldPartnerID].(string) return } // fillUserInfo fills satellite.UserInfo from satellite.User and input args func fillUserInfo(user *console.User, args map[string]interface{}) console.UserInfo { info := console.UserInfo{ - Email: user.Email, FullName: user.FullName, ShortName: user.ShortName, } - if !user.PartnerID.IsZero() { - info.PartnerID = user.PartnerID.String() - } for fieldName, fieldValue := range args { value, ok := fieldValue.(string) @@ -121,21 +110,12 @@ func fillUserInfo(user *console.User, args map[string]interface{}) console.UserI } switch fieldName { - case FieldEmail: - info.Email = value - user.Email = value case FieldFullName: info.FullName = value user.FullName = value case FieldShortName: info.ShortName = value user.ShortName = value - case FieldPartnerID: - info.PartnerID = value - partnerID, err := uuid.Parse(value) - if err == nil { - user.PartnerID = *partnerID - } } } diff --git a/satellite/console/consoleweb/server.go b/satellite/console/consoleweb/server.go index 5aeb127e5..bca279196 100644 --- a/satellite/console/consoleweb/server.go +++ b/satellite/console/consoleweb/server.go @@ -120,19 +120,18 @@ func NewServer(logger *zap.Logger, config Config, service *console.Service, mail router.HandleFunc("/api/v0/graphql", server.grapqlHandler) router.HandleFunc("/registrationToken/", server.createRegistrationTokenHandler) router.HandleFunc("/robots.txt", server.seoHandler) - router.HandleFunc("/satelliteName", server.satelliteNameHandler).Methods(http.MethodGet) + router.HandleFunc("/satellite-name", server.satelliteNameHandler).Methods(http.MethodGet) - authController := consoleapi.NewAuth(logger, service, mailService, config.ExternalAddress, config.LetUsKnowURL, config.TermsAndConditionsURL, config.ContactInfoURL) + authController := consoleapi.NewAuth(logger, service, mailService, server.config.ExternalAddress, config.LetUsKnowURL, config.TermsAndConditionsURL, config.ContactInfoURL) authRouter := router.PathPrefix("/api/v0/auth").Subrouter() + authRouter.Handle("/account", server.withAuth(http.HandlerFunc(authController.GetAccount))).Methods(http.MethodGet) + authRouter.Handle("/account", server.withAuth(http.HandlerFunc(authController.UpdateAccount))).Methods(http.MethodPatch) + authRouter.Handle("/account/change-password", server.withAuth(http.HandlerFunc(authController.ChangePassword))).Methods(http.MethodPost) + authRouter.Handle("/account/delete", server.withAuth(http.HandlerFunc(authController.DeleteAccount))).Methods(http.MethodPost) authRouter.HandleFunc("/token", authController.Token).Methods(http.MethodPost) authRouter.HandleFunc("/register", authController.Register).Methods(http.MethodPost) - authRouter.Handle("/changePassword", server.withAuth(http.HandlerFunc(authController.ChangePassword))).Methods(http.MethodPost) - authRouter.Handle("/delete", server.withAuth(http.HandlerFunc(authController.Delete))).Methods(http.MethodDelete) - authRouter.Handle("/", server.withAuth(http.HandlerFunc(authController.Get))).Methods(http.MethodGet) - authRouter.Handle("/update", server.withAuth(http.HandlerFunc(authController.Update))).Methods(http.MethodPut) - authRouter.HandleFunc("/changePassword", authController.ChangePassword).Methods(http.MethodPost) - authRouter.HandleFunc("/forgotPassword", authController.ForgotPassword).Methods(http.MethodPost) - authRouter.HandleFunc("/resendEmail", authController.ResendEmail).Methods(http.MethodPost) + authRouter.HandleFunc("/forgot-password/{email}", authController.ForgotPassword).Methods(http.MethodPost) + authRouter.HandleFunc("/resend-email/{id}", authController.ResendEmail).Methods(http.MethodPost) paymentController := consoleapi.NewPayments(logger, service) paymentsRouter := router.PathPrefix("/api/v0/payments").Subrouter() diff --git a/satellite/console/users.go b/satellite/console/users.go index 957a067fb..634e6b2f2 100644 --- a/satellite/console/users.go +++ b/satellite/console/users.go @@ -31,21 +31,15 @@ type Users interface { type UserInfo struct { FullName string `json:"fullName"` ShortName string `json:"shortName"` - Email string `json:"email"` - PartnerID string `json:"partnerId"` } // IsValid checks UserInfo validity and returns error describing whats wrong. func (user *UserInfo) IsValid() error { var errs validationErrors - // validate email - _, err := mail.ParseAddress(user.Email) - errs.AddWrap(err) - // validate fullName - if user.FullName == "" { - errs.Add("fullName can't be empty") + if err := validateFullName(user.FullName); err != nil { + errs.AddWrap(err) } return errs.Combine() @@ -53,17 +47,24 @@ func (user *UserInfo) IsValid() error { // CreateUser struct holds info for User creation. type CreateUser struct { - UserInfo - Password string `json:"password"` + FullName string `json:"fullName"` + ShortName string `json:"shortName"` + Email string `json:"email"` + PartnerID string `json:"partnerId"` + Password string `json:"password"` } // IsValid checks CreateUser validity and returns error describing whats wrong. func (user *CreateUser) IsValid() error { var errs validationErrors - errs.AddWrap(user.UserInfo.IsValid()) + errs.AddWrap(validateFullName(user.FullName)) errs.AddWrap(validatePassword(user.Password)) + // validate email + _, err := mail.ParseAddress(user.Email) + errs.AddWrap(err) + if user.PartnerID != "" { _, err := uuid.Parse(user.PartnerID) if err != nil { diff --git a/satellite/console/validation.go b/satellite/console/validation.go index b2f65dcf4..5df08d7de 100644 --- a/satellite/console/validation.go +++ b/satellite/console/validation.go @@ -42,3 +42,12 @@ func validatePassword(pass string) error { return errs.Combine() } + +// validateFullName validates full name. +func validateFullName(name string) error { + if name == "" { + return errs.New("full name can not be empty") + } + + return nil +} diff --git a/satellite/mailservice/simulate/linkclicker.go b/satellite/mailservice/simulate/linkclicker.go index 85345a511..fd7cb5460 100644 --- a/satellite/mailservice/simulate/linkclicker.go +++ b/satellite/mailservice/simulate/linkclicker.go @@ -54,6 +54,9 @@ func (clicker *LinkClicker) SendEmail(ctx context.Context, msg *post.Message) (e var sendError error for _, link := range links { response, err := http.Get(link) + if err != nil { + continue + } sendError = errs.Combine(sendError, err, response.Body.Close()) } diff --git a/web/satellite/src/api/auth.ts b/web/satellite/src/api/auth.ts index c9091ce21..bf6f9d5ce 100644 --- a/web/satellite/src/api/auth.ts +++ b/web/satellite/src/api/auth.ts @@ -1,14 +1,17 @@ // Copyright (C) 2019 Storj Labs, Inc. // See LICENSE for copying information. -import { BaseGql } from '@/api/baseGql'; -import { User } from '@/types/users'; +import { ErrorUnauthorized } from '@/api/errors/ErrorUnauthorized'; +import { UpdatedUser, User } from '@/types/users'; +import { HttpClient } from '@/utils/httpClient'; /** - * AuthApiGql is a graphql implementation of Auth API. + * AuthHttpApi is a console Auth API. * Exposes all auth-related functionality */ -export class AuthApi extends BaseGql { +export class AuthHttpApi { + private readonly http: HttpClient = new HttpClient(); + private readonly ROOT_PATH: string = '/api/v0/auth'; /** * Used to resend an registration confirmation email * @@ -16,16 +19,13 @@ export class AuthApi extends BaseGql { * @throws Error */ public async resendEmail(userId: string): Promise { - const query = - `query ($userId: String!){ - resendAccountActivationEmail(id: $userId) - }`; + const path = `${this.ROOT_PATH}/resend-email/${userId}`; + const response = await this.http.post(path, userId, false); + if (response.ok) { + return; + } - const variables = { - userId, - }; - - await this.query(query, variables); + throw new Error('can not resend Email'); } /** @@ -36,21 +36,17 @@ export class AuthApi extends BaseGql { * @throws Error */ public async token(email: string, password: string): Promise { - const query = - ` query ($email: String!, $password: String!) { - token(email: $email, password: $password) { - token - } - }`; - - const variables = { - email, - password, + const path = `${this.ROOT_PATH}/token`; + const body = { + email: email, + password: password, }; + const response = await this.http.post(path, JSON.stringify(body), false); + if (response.ok) { + return await response.json(); + } - const response = await this.query(query, variables); - - return response.data.token.token; + throw new Error('can not receive authentication token'); } /** @@ -60,16 +56,48 @@ export class AuthApi extends BaseGql { * @throws Error */ public async forgotPassword(email: string): Promise { - const query = - `query($email: String!) { - forgotPassword(email: $email) - }`; + const path = `${this.ROOT_PATH}/forgot-password/${email}`; + const response = await this.http.post(path, email, false); + if (response.ok) { + return; + } - const variables = { - email, + throw new Error('can not resend password'); + } + + /** + * Used to update user full and short name + * + * @param userInfo - full name and short name of the user + * @throws Error + */ + public async update(userInfo: UpdatedUser): Promise { + const path = `${this.ROOT_PATH}/account`; + const body = { + fullName: userInfo.fullName, + shortName: userInfo.shortName, }; + const response = await this.http.patch(path, JSON.stringify(body), true); + if (response.ok) { + return; + } - await this.query(query, variables); + throw new Error('can not update user data'); + } + + /** + * Used to get user data + * + * @throws Error + */ + public async get(): Promise { + const path = `${this.ROOT_PATH}/account`; + const response = await this.http.get(path, true); + if (response.ok) { + return await response.json(); + } + + throw new Error('can not get user data'); } /** @@ -80,22 +108,21 @@ export class AuthApi extends BaseGql { * @throws Error */ public async changePassword(password: string, newPassword: string): Promise { - const query = - `mutation($password: String!, $newPassword: String!) { - changePassword ( - password: $password, - newPassword: $newPassword - ) { - email - } - }`; - - const variables = { - password, - newPassword, + const path = `${this.ROOT_PATH}/account/change-password`; + const body = { + password: password, + newPassword: newPassword, }; + const response = await this.http.post(path, JSON.stringify(body), true); + if (response.ok) { + return; + } - await this.mutate(query, variables); + if (response.status === 401) { + throw new ErrorUnauthorized(); + } + + throw new Error('can not change password'); } /** @@ -105,59 +132,49 @@ export class AuthApi extends BaseGql { * @throws Error */ public async delete(password: string): Promise { - const query = - `mutation ($password: String!){ - deleteAccount(password: $password) { - email - } - }`; - - const variables = { - password, + const path = `${this.ROOT_PATH}/account/delete`; + const body = { + password: password, }; + const response = await this.http.post(path, JSON.stringify(body), true); + if (response.ok) { + return; + } - await this.mutate(query, variables); + if (response.status === 401) { + throw new ErrorUnauthorized(); + } + + throw new Error('can not delete user'); } // TODO: remove secret after Vanguard release /** - * Used to create account + * Used to register account * * @param user - stores user information * @param secret - registration token used in Vanguard release - * @param refUserId - referral id to participate in bonus program + * @param referrerUserId - referral id to participate in bonus program * @returns id of created user * @throws Error */ - public async create(user: User, password: string, secret: string, referrerUserId: string = ''): Promise { - const query = - `mutation($email: String!, $password: String!, $fullName: String!, $shortName: String!, - $partnerID: String!, $referrerUserId: String!, $secret: String!) { - createUser( - input: { - email: $email, - password: $password, - fullName: $fullName, - shortName: $shortName, - partnerId: $partnerID - }, - referrerUserId: $referrerUserId, - secret: $secret, - ) {email, id} - }`; - - const variables = { - email: user.email, + public async register(user: {fullName: string; shortName: string; email: string; partnerId: string; password: string}, secret: string, referrerUserId: string): Promise { + const path = `${this.ROOT_PATH}/register`; + const body = { + secret: secret, + referrerUserId: referrerUserId ? referrerUserId : '', + password: user.password, fullName: user.fullName, shortName: user.shortName, - partnerID: user.partnerId ? user.partnerId : '', - referrerUserId: referrerUserId ? referrerUserId : '', - password, - secret, + email: user.email, + partnerId: user.partnerId ? user.partnerId : '', }; - const response = await this.mutate(query, variables); + const response = await this.http.post(path, JSON.stringify(body), false); + if (!response.ok) { + throw new Error('can not register user'); + } - return response.data.createUser.id; + return await response.json(); } } diff --git a/web/satellite/src/api/payments.ts b/web/satellite/src/api/payments.ts index ce4aea8a3..35cf43340 100644 --- a/web/satellite/src/api/payments.ts +++ b/web/satellite/src/api/payments.ts @@ -63,7 +63,7 @@ export class PaymentsHttpApi implements PaymentsApi { const path = `${this.ROOT_PATH}/cards`; const response = await this.client.post(path, token); - if (!response.ok) { + if (response.ok) { return; } diff --git a/web/satellite/src/components/account/ChangePasswordPopup.vue b/web/satellite/src/components/account/ChangePasswordPopup.vue index 77a0c7719..4954d7497 100644 --- a/web/satellite/src/components/account/ChangePasswordPopup.vue +++ b/web/satellite/src/components/account/ChangePasswordPopup.vue @@ -71,8 +71,8 @@ import VButton from '@/components/common/VButton.vue'; import ChangePasswordIcon from '@/../static/images/account/changePasswordPopup/changePassword.svg'; import CloseCrossIcon from '@/../static/images/common/closeCross.svg'; -import { AuthApi } from '@/api/auth'; -import { APP_STATE_ACTIONS } from '@/utils/constants/actionNames'; +import { AuthHttpApi } from '@/api/auth'; +import { APP_STATE_ACTIONS, NOTIFICATION_ACTIONS } from '@/utils/constants/actionNames'; import { validatePassword } from '@/utils/validation'; @Component({ @@ -91,7 +91,7 @@ export default class ChangePasswordPopup extends Vue { private newPasswordError: string = ''; private confirmationPasswordError: string = ''; - private readonly auth: AuthApi = new AuthApi(); + private readonly auth: AuthHttpApi = new AuthHttpApi(); public setOldPassword(value: string): void { this.oldPassword = value; diff --git a/web/satellite/src/components/account/DeleteAccountPopup.vue b/web/satellite/src/components/account/DeleteAccountPopup.vue index 12e02b905..81f2e7e7b 100644 --- a/web/satellite/src/components/account/DeleteAccountPopup.vue +++ b/web/satellite/src/components/account/DeleteAccountPopup.vue @@ -51,7 +51,7 @@ import VButton from '@/components/common/VButton.vue'; import DeleteAccountIcon from '@/../static/images/account/deleteAccountPopup/deleteAccount.svg'; import CloseCrossIcon from '@/../static/images/common/closeCross.svg'; -import { AuthApi } from '@/api/auth'; +import { AuthHttpApi } from '@/api/auth'; import { RouteConfig } from '@/router'; import { AuthToken } from '@/utils/authToken'; import { APP_STATE_ACTIONS } from '@/utils/constants/actionNames'; @@ -70,7 +70,7 @@ export default class DeleteAccountPopup extends Vue { private password: string = ''; private isLoading: boolean = false; - private readonly auth: AuthApi = new AuthApi(); + private readonly auth: AuthHttpApi = new AuthHttpApi(); public setPassword(value: string): void { this.password = value; diff --git a/web/satellite/src/components/common/RegistrationSuccessPopup.vue b/web/satellite/src/components/common/RegistrationSuccessPopup.vue index f1662a3a7..a6768b0fb 100644 --- a/web/satellite/src/components/common/RegistrationSuccessPopup.vue +++ b/web/satellite/src/components/common/RegistrationSuccessPopup.vue @@ -11,7 +11,7 @@ import VButton from '@/components/common/VButton.vue'; import CloseCrossIcon from '@/../static/images/common/closeCross.svg'; import RegistrationSuccessIcon from '@/../static/images/register/registerSuccess.svg'; -import { AuthApi } from '@/api/auth'; +import { AuthHttpApi } from '@/api/auth'; import { RouteConfig } from '@/router'; import { getUserId } from '@/utils/consoleLocalStorage'; import { APP_STATE_ACTIONS } from '@/utils/constants/actionNames'; @@ -28,7 +28,7 @@ export default class RegistrationSuccessPopup extends Vue { private timeToEnableResendEmailButton: string = '00:30'; private intervalID: any = null; - private readonly auth: AuthApi = new AuthApi(); + private readonly auth: AuthHttpApi = new AuthHttpApi(); public beforeDestroy(): void { if (this.intervalID) { diff --git a/web/satellite/src/store/index.ts b/web/satellite/src/store/index.ts index d523d6264..5fd152235 100644 --- a/web/satellite/src/store/index.ts +++ b/web/satellite/src/store/index.ts @@ -5,13 +5,13 @@ import Vue from 'vue'; import Vuex from 'vuex'; import { ApiKeysApiGql } from '@/api/apiKeys'; +import { AuthHttpApi } from '@/api/auth'; import { BucketsApiGql } from '@/api/buckets'; import { CreditsApiGql } from '@/api/credits'; import { PaymentsHttpApi } from '@/api/payments'; import { ProjectMembersApiGql } from '@/api/projectMembers'; import { ProjectsApiGql } from '@/api/projects'; import { ProjectUsageApiGql } from '@/api/usage'; -import { UsersApiGql } from '@/api/users'; import { makeApiKeysModule } from '@/store/modules/apiKeys'; import { appStateModule } from '@/store/modules/appState'; import { makeBucketsModule } from '@/store/modules/buckets'; @@ -33,7 +33,7 @@ export class StoreModule { } // TODO: remove it after we will use modules as classes and use some DI framework -const usersApi = new UsersApiGql(); +const authApi = new AuthHttpApi(); const apiKeysApi = new ApiKeysApiGql(); const creditsApi = new CreditsApiGql(); const bucketsApi = new BucketsApiGql(); @@ -51,7 +51,7 @@ export const store = new Vuex.Store({ creditsModule: makeCreditsModule(creditsApi), projectMembersModule: makeProjectMembersModule(projectMembersApi), paymentsModule: makePaymentsModule(paymentsApi), - usersModule: makeUsersModule(usersApi), + usersModule: makeUsersModule(authApi), projectsModule: makeProjectsModule(projectsApi), usageModule: makeUsageModule(projectUsageApi), bucketUsageModule: makeBucketsModule(bucketsApi), diff --git a/web/satellite/src/types/users.ts b/web/satellite/src/types/users.ts index 1263936c3..6a21e9b5b 100644 --- a/web/satellite/src/types/users.ts +++ b/web/satellite/src/types/users.ts @@ -30,13 +30,15 @@ export class User { public shortName: string; public email: string; public partnerId: string; + public password: string; - public constructor(id: string = '', fullName: string = '', shortName: string = '', email: string = '', partnerId: string = '') { + public constructor(id: string = '', fullName: string = '', shortName: string = '', email: string = '', partnerId: string = '', password: string = '') { this.id = id; this.fullName = fullName; this.shortName = shortName; this.email = email; this.partnerId = partnerId; + this.password = password; } public getFullName(): string { diff --git a/web/satellite/src/utils/httpClient.ts b/web/satellite/src/utils/httpClient.ts index 75c6c43f9..03aafcaea 100644 --- a/web/satellite/src/utils/httpClient.ts +++ b/web/satellite/src/utils/httpClient.ts @@ -54,6 +54,16 @@ export class HttpClient { return this.sendJSON('PATCH', path, body, auth); } + /** + * Performs PUT http request with JSON body. + * @param path + * @param body serialized JSON + * @param auth indicates if authentication is needed + */ + public async put(path: string, body: string | null, auth: boolean = true): Promise { + return this.sendJSON('PUT', path, body, auth); + } + /** * Performs GET http request. * @param path diff --git a/web/satellite/src/views/forgotPassword/ForgotPassword.vue b/web/satellite/src/views/forgotPassword/ForgotPassword.vue index 9cc733d14..44cc08b7b 100644 --- a/web/satellite/src/views/forgotPassword/ForgotPassword.vue +++ b/web/satellite/src/views/forgotPassword/ForgotPassword.vue @@ -11,7 +11,7 @@ import HeaderlessInput from '@/components/common/HeaderlessInput.vue'; import AuthIcon from '@/../static/images/AuthImage.svg'; import LogoIcon from '@/../static/images/Logo.svg'; -import { AuthApi } from '@/api/auth'; +import { AuthHttpApi } from '@/api/auth'; import { RouteConfig } from '@/router'; import { LOADING_CLASSES } from '@/utils/constants/classConstants'; import { validateEmail } from '@/utils/validation'; @@ -28,7 +28,7 @@ export default class ForgotPassword extends Vue { private email: string = ''; private emailError: string = ''; - private readonly auth: AuthApi = new AuthApi(); + private readonly auth: AuthHttpApi = new AuthHttpApi(); public setEmail(value: string): void { this.email = value; diff --git a/web/satellite/src/views/login/LoginArea.vue b/web/satellite/src/views/login/LoginArea.vue index fb1345c0a..762cbd1b2 100644 --- a/web/satellite/src/views/login/LoginArea.vue +++ b/web/satellite/src/views/login/LoginArea.vue @@ -12,7 +12,7 @@ import AuthIcon from '@/../static/images/AuthImage.svg'; import LogoIcon from '@/../static/images/Logo.svg'; import LoadingLogoIcon from '@/../static/images/LogoWhite.svg'; -import { AuthApi } from '@/api/auth'; +import { AuthHttpApi } from '@/api/auth'; import { RouteConfig } from '@/router'; import { AuthToken } from '@/utils/authToken'; import { APP_STATE_ACTIONS } from '@/utils/constants/actionNames'; @@ -40,7 +40,7 @@ export default class Login extends Vue { private emailError: string = ''; private passwordError: string = ''; - private readonly auth: AuthApi = new AuthApi(); + private readonly auth: AuthHttpApi = new AuthHttpApi(); public onLogoClick(): void { location.reload(); @@ -77,6 +77,7 @@ export default class Login extends Vue { try { this.authToken = await this.auth.token(this.email, this.password); + AuthToken.set(this.authToken); } catch (error) { await this.$notify.error(error.message); this.isLoading = false; diff --git a/web/satellite/src/views/register/RegisterArea.vue b/web/satellite/src/views/register/RegisterArea.vue index 29b727448..54cc3d475 100644 --- a/web/satellite/src/views/register/RegisterArea.vue +++ b/web/satellite/src/views/register/RegisterArea.vue @@ -14,7 +14,7 @@ import AuthIcon from '@/../static/images/AuthImage.svg'; import InfoIcon from '@/../static/images/info.svg'; import LogoIcon from '@/../static/images/Logo.svg'; -import { AuthApi } from '@/api/auth'; +import { AuthHttpApi } from '@/api/auth'; import { RouteConfig } from '@/router'; import { User } from '@/types/users'; import { setUserId } from '@/utils/consoleLocalStorage'; @@ -53,7 +53,7 @@ export default class RegisterArea extends Vue { private loadingClassName: string = LOADING_CLASSES.LOADING_OVERLAY; - private readonly auth: AuthApi = new AuthApi(); + private readonly auth: AuthHttpApi = new AuthHttpApi(); async mounted(): Promise { if (this.$route.query.token) { @@ -116,6 +116,7 @@ export default class RegisterArea extends Vue { this.user.shortName = value.trim(); } public setPassword(value: string): void { + this.user.password = value.trim(); this.password = value; this.passwordError = ''; } @@ -157,7 +158,7 @@ export default class RegisterArea extends Vue { private async createUser(): Promise { try { - this.userId = await this.auth.create(this.user, this.password , this.secret, this.refUserId); + this.userId = await this.auth.register(this.user, this.secret, this.refUserId); setUserId(this.userId);