From 1814fbfa89647891d14cbba64418b3acb93528bd Mon Sep 17 00:00:00 2001 From: Nikolai Siedov <46809683+Qweder93@users.noreply.github.com> Date: Mon, 21 Oct 2019 15:48:29 +0300 Subject: [PATCH] satellite/console: new passwordChange API endpoint (#3308) --- .../console/consoleweb/consoleapi/auth.go | 185 ++++++++++++++++++ satellite/console/consoleweb/server.go | 97 +-------- 2 files changed, 191 insertions(+), 91 deletions(-) create mode 100644 satellite/console/consoleweb/consoleapi/auth.go diff --git a/satellite/console/consoleweb/consoleapi/auth.go b/satellite/console/consoleweb/consoleapi/auth.go new file mode 100644 index 000000000..c17fe1b01 --- /dev/null +++ b/satellite/console/consoleweb/consoleapi/auth.go @@ -0,0 +1,185 @@ +// Copyright (C) 2019 Storj Labs, Inc. +// See LICENSE for copying information. + +package consoleapi + +import ( + "context" + "encoding/json" + "net/http" + "strings" + + "go.uber.org/zap" + + "storj.io/storj/internal/post" + "storj.io/storj/pkg/auth" + "storj.io/storj/satellite/console" + "storj.io/storj/satellite/console/consoleweb/consoleql" + "storj.io/storj/satellite/mailservice" +) + +// Auth is an api controller that exposes all auth functionality. +type Auth struct { + log *zap.Logger + service *console.Service + mailService *mailservice.Service + + ExternalAddress string +} + +// NewAuth is a constructor for api auth controller. +func NewAuth(log *zap.Logger, service *console.Service, mailService *mailservice.Service, externalAddress string) *Auth { + return &Auth{ + log: log, + service: service, + mailService: mailService, + ExternalAddress: externalAddress, + } +} + +// Token authenticates User by credentials and returns auth token. +func (a *Auth) Token(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + var err error + defer mon.Task()(&ctx)(&err) + + var tokenRequest struct { + Email string `json:"email"` + Password string `json:"password"` + } + + err = json.NewDecoder(r.Body).Decode(&tokenRequest) + if err != nil { + a.serveJSONError(w, http.StatusBadRequest, err) + return + } + + var tokenResponse struct { + Token string `json:"token"` + } + + tokenResponse.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) + if err != nil { + a.log.Error("token handler could not encode token response", zap.Error(err)) + return + } +} + +// Register creates new User, sends activation e-mail. +func (a *Auth) Register(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + var err error + defer mon.Task()(&ctx)(&err) + + var request struct { + UserInfo console.CreateUser `json:"userInfo"` + SecretInput string `json:"secret"` + ReferrerUserID string `json:"referrerUserID"` + } + + err = json.NewDecoder(r.Body).Decode(&request) + if err != nil { + a.serveJSONError(w, http.StatusBadRequest, err) + return + } + + secret, err := console.RegistrationSecretFromBase64(request.SecretInput) + if err != nil { + a.serveJSONError(w, http.StatusBadRequest, err) + return + } + + user, err := a.service.CreateUser(ctx, request.UserInfo, secret, request.ReferrerUserID) + if err != nil { + a.serveJSONError(w, http.StatusInternalServerError, err) + return + } + + token, err := a.service.GenerateActivationToken(ctx, user.ID, user.Email) + if err != nil { + a.serveJSONError(w, http.StatusInternalServerError, err) + return + } + + link := a.ExternalAddress + consoleql.ActivationPath + token + userName := user.ShortName + if user.ShortName == "" { + userName = user.FullName + } + + a.mailService.SendRenderedAsync( + ctx, + []post.Address{{Address: user.Email, Name: userName}}, + &consoleql.AccountActivationEmail{ + ActivationLink: link, + Origin: a.ExternalAddress, + }, + ) + + err = json.NewEncoder(w).Encode(&user.ID) + if err != nil { + a.log.Error("registration handler could not encode error", zap.Error(err)) + return + } +} + +// PasswordChange auth user, changes users password for a new one. +func (a *Auth) PasswordChange(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + var err error + defer mon.Task()(&ctx)(&err) + + var passwordChange struct { + CurrentPassword string `json:"password"` + NewPassword string `json:"newPassword"` + } + + ctx = a.authorize(ctx, r) + + err = json.NewDecoder(r.Body).Decode(&passwordChange) + if err != nil { + a.serveJSONError(w, http.StatusBadRequest, err) + return + } + + err = a.service.ChangePassword(ctx, passwordChange.CurrentPassword, passwordChange.NewPassword) + if err != nil { + a.serveJSONError(w, http.StatusNotFound, err) + return + } +} + +// serveJSONError writes JSON error to response output stream. +func (a *Auth) serveJSONError(w http.ResponseWriter, status int, err error) { + w.WriteHeader(status) + + var response struct { + Error string `json:"error"` + } + + response.Error = err.Error() + + err = json.NewEncoder(w).Encode(response) + if err != nil { + a.log.Error("failed to write json error response", zap.Error(err)) + } +} + +// authorize checks request for authorization token, validates it and updates context with auth data. +func (a *Auth) authorize(ctx context.Context, r *http.Request) context.Context { + authHeaderValue := r.Header.Get("Authorization") + token := strings.TrimPrefix(authHeaderValue, "Bearer ") + + auth, err := a.service.Authorize(auth.WithAPIKey(ctx, []byte(token))) + if err != nil { + return console.WithAuthFailure(ctx, err) + } + + return console.WithAuth(ctx, auth) +} diff --git a/satellite/console/consoleweb/server.go b/satellite/console/consoleweb/server.go index 195687f16..6772b82f8 100644 --- a/satellite/console/consoleweb/server.go +++ b/satellite/console/consoleweb/server.go @@ -24,7 +24,6 @@ import ( "golang.org/x/sync/errgroup" monkit "gopkg.in/spacemonkeygo/monkit.v2" - "storj.io/storj/internal/post" "storj.io/storj/pkg/auth" "storj.io/storj/satellite/console" "storj.io/storj/satellite/console/consoleweb/consoleapi" @@ -118,13 +117,17 @@ func NewServer(logger *zap.Logger, config Config, service *console.Service, mail fs := http.FileServer(http.Dir(server.config.StaticDir)) paymentController := consoleapi.NewPayments(logger, service) + authController := consoleapi.NewAuth(logger, service, mailService, config.ExternalAddress) + router.Handle("/api/v0/payments/cards", http.HandlerFunc(paymentController.AddCreditCard)) router.Handle("/api/v0/payments/account/balance", http.HandlerFunc(paymentController.AccountBalance)) router.Handle("/api/v0/payments/account", http.HandlerFunc(paymentController.SetupAccount)) + router.Handle("/api/v0/register", http.HandlerFunc(authController.Register)) + router.Handle("/api/v0/token", http.HandlerFunc(authController.Token)) + router.Handle("/api/v0/passwordChange", http.HandlerFunc(authController.PasswordChange)) + router.Handle("/api/v0/graphql", http.HandlerFunc(server.grapqlHandler)) - router.Handle("/api/v0/token", http.HandlerFunc(server.tokenHandler)) - router.Handle("/api/v0/register", http.HandlerFunc(server.registerHandler)) router.Handle("/registrationToken/", http.HandlerFunc(server.createRegistrationTokenHandler)) router.Handle("/robots.txt", http.HandlerFunc(server.seoHandler)) @@ -201,94 +204,6 @@ func (server *Server) appHandler(w http.ResponseWriter, r *http.Request) { } } -// tokenRequestHandler authenticates User by credentials and returns auth token. -func (server *Server) tokenHandler(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - var err error - defer mon.Task()(&ctx)(&err) - - var tokenRequest struct { - Email string `json:"email"` - Password string `json:"password"` - } - - err = json.NewDecoder(r.Body).Decode(&tokenRequest) - if err != nil { - server.serveJSONError(w, http.StatusBadRequest, err) - return - } - - var tokenResponse struct { - Token string `json:"token"` - } - - tokenResponse.Token, err = server.service.Token(ctx, tokenRequest.Email, tokenRequest.Password) - if err != nil { - server.serveJSONError(w, http.StatusUnauthorized, err) - return - } - - err = json.NewEncoder(w).Encode(tokenResponse) - if err != nil { - server.log.Error("token handler could not encode token response", zap.Error(err)) - return - } -} - -// registerHandler registers new User. -func (server *Server) registerHandler(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - var err error - defer mon.Task()(&ctx)(&err) - - var request struct { - UserInfo console.CreateUser `json:"userInfo"` - SecretInput string `json:"secret"` - ReferrerUserID string `json:"referrerUserID"` - } - - err = json.NewDecoder(r.Body).Decode(&request) - if err != nil { - server.serveJSONError(w, http.StatusBadRequest, err) - return - } - - secret, err := console.RegistrationSecretFromBase64(request.SecretInput) - if err != nil { - server.serveJSONError(w, http.StatusBadRequest, err) - return - } - - user, err := server.service.CreateUser(ctx, request.UserInfo, secret, request.ReferrerUserID) - if err != nil { - server.serveJSONError(w, http.StatusInternalServerError, err) - return - } - - token, err := server.service.GenerateActivationToken(ctx, user.ID, user.Email) - if err != nil { - server.serveJSONError(w, http.StatusInternalServerError, err) - return - } - - link := server.config.ExternalAddress + consoleql.ActivationPath + token - - server.mailService.SendRenderedAsync( - ctx, - []post.Address{{Address: user.Email, Name: user.FullName}}, - &consoleql.AccountActivationEmail{ - ActivationLink: link, - Origin: server.config.ExternalAddress, - }, - ) - - err = json.NewEncoder(w).Encode(&user.ID) - if err != nil { - server.log.Error("registration handler could not encode error", zap.Error(err)) - return - } -} - // bucketUsageReportHandler generate bucket usage report page for project func (server *Server) bucketUsageReportHandler(w http.ResponseWriter, r *http.Request) { ctx := r.Context()