diff --git a/satellite/console/consoleweb/consoleapi/projects.go b/satellite/console/consoleweb/consoleapi/projects.go new file mode 100644 index 000000000..871c9593d --- /dev/null +++ b/satellite/console/consoleweb/consoleapi/projects.go @@ -0,0 +1,69 @@ +// Copyright (C) 2021 Storj Labs, Inc. +// See LICENSE for copying information. + +package consoleapi + +import ( + "encoding/base64" + "encoding/json" + "net/http" + + "github.com/gorilla/mux" + "github.com/zeebo/errs" + "go.uber.org/zap" + + "storj.io/common/uuid" + "storj.io/storj/satellite/console" +) + +// Projects is an api controller that exposes projects related functionality. +type Projects struct { + log *zap.Logger + service *console.Service +} + +// NewProjects is a constructor for api analytics controller. +func NewProjects(log *zap.Logger, service *console.Service) *Projects { + return &Projects{ + log: log, + service: service, + } +} + +// GetSalt returns the project's salt. +func (p *Projects) GetSalt(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + var err error + defer mon.Task()(&ctx)(&err) + + w.Header().Set("Content-Type", "application/json") + + idParam, ok := mux.Vars(r)["id"] + if !ok { + p.serveJSONError(w, http.StatusBadRequest, errs.New("missing id route param")) + return + } + + id, err := uuid.FromString(idParam) + if err != nil { + p.serveJSONError(w, http.StatusBadRequest, err) + } + + salt, err := p.service.GetSalt(ctx, id) + if err != nil { + p.serveJSONError(w, http.StatusUnauthorized, err) + return + } + + b64SaltString := base64.StdEncoding.EncodeToString(salt) + + err = json.NewEncoder(w).Encode(b64SaltString) + if err != nil { + p.serveJSONError(w, http.StatusInternalServerError, err) + } +} + +// serveJSONError writes JSON error to response output stream. +func (p *Projects) serveJSONError(w http.ResponseWriter, status int, err error) { + ServeJSONError(p.log, w, status, err) +} diff --git a/satellite/console/consoleweb/endpoints_test.go b/satellite/console/consoleweb/endpoints_test.go index 88e3ff833..f2592e13c 100644 --- a/satellite/console/consoleweb/endpoints_test.go +++ b/satellite/console/consoleweb/endpoints_test.go @@ -4,7 +4,9 @@ package consoleweb_test import ( + "encoding/base64" "encoding/json" + "fmt" "io" "io/ioutil" "net/http" @@ -17,6 +19,7 @@ import ( "github.com/stretchr/testify/require" "storj.io/common/testcontext" + "storj.io/common/uuid" "storj.io/storj/private/testplanet" "storj.io/storj/satellite/payments/storjscan/blockchaintest" ) @@ -362,6 +365,23 @@ func TestProjects(t *testing.T) { require.Equal(t, http.StatusOK, resp.StatusCode) } + { // Get_Salt + projectID := test.defaultProjectID() + id, err := uuid.FromString(projectID) + require.NoError(t, err) + + // get salt from endpoint + var b64Salt string + resp, body := test.request(http.MethodGet, fmt.Sprintf("/projects/%s/salt", test.defaultProjectID()), nil) + require.Equal(t, http.StatusOK, resp.StatusCode) + require.NoError(t, json.Unmarshal([]byte(body), &b64Salt)) + + // get salt from db and base64 encode it + salt, err := planet.Satellites[0].DB.Console().Projects().GetSalt(ctx, id) + require.NoError(t, err) + require.Equal(t, b64Salt, base64.StdEncoding.EncodeToString(salt)) + } + { // Get_ProjectInfo resp, body := test.request(http.MethodPost, "/graphql", test.toJSON(map[string]interface{}{ diff --git a/satellite/console/consoleweb/server.go b/satellite/console/consoleweb/server.go index cacb20b0f..47c007302 100644 --- a/satellite/console/consoleweb/server.go +++ b/satellite/console/consoleweb/server.go @@ -248,6 +248,12 @@ func NewServer(logger *zap.Logger, config Config, service *console.Service, oidc consoleapi.NewUserManagement(logger, mon, server.service, router, &apiAuth{&server}) } + projectsController := consoleapi.NewProjects(logger, service) + router.Handle( + "/api/v0/projects/{id}/salt", + server.withAuth(http.HandlerFunc(projectsController.GetSalt)), + ).Methods(http.MethodGet) + router.HandleFunc("/registrationToken/", server.createRegistrationTokenHandler) router.HandleFunc("/robots.txt", server.seoHandler) diff --git a/satellite/console/service.go b/satellite/console/service.go index 4ef1da4e7..cbf8f4ef6 100644 --- a/satellite/console/service.go +++ b/satellite/console/service.go @@ -1406,6 +1406,21 @@ func (s *Service) GetProject(ctx context.Context, projectID uuid.UUID) (p *Proje return } +// GetSalt is a method for querying project salt by id. +func (s *Service) GetSalt(ctx context.Context, projectID uuid.UUID) (salt []byte, err error) { + defer mon.Task()(&ctx)(&err) + user, err := s.getUserAndAuditLog(ctx, "get project salt", zap.String("projectID", projectID.String())) + if err != nil { + return nil, Error.Wrap(err) + } + + if _, err = s.isProjectMember(ctx, user.ID, projectID); err != nil { + return nil, Error.Wrap(err) + } + + return s.store.Projects().GetSalt(ctx, projectID) +} + // GetUsersProjects is a method for querying all projects. func (s *Service) GetUsersProjects(ctx context.Context) (ps []Project, err error) { defer mon.Task()(&ctx)(&err) diff --git a/satellite/console/service_test.go b/satellite/console/service_test.go index ba4d7f4a9..459a9b62a 100644 --- a/satellite/console/service_test.go +++ b/satellite/console/service_test.go @@ -71,6 +71,18 @@ func TestService(t *testing.T) { require.Nil(t, project) }) + t.Run("GetSalt", func(t *testing.T) { + // Getting project salt as a member should work + salt, err := service.GetSalt(userCtx1, up1Pro1.ID) + require.NoError(t, err) + require.NotNil(t, salt) + + // Getting project salt as a non-member should not work + salt, err = service.GetSalt(userCtx1, up2Pro1.ID) + require.Error(t, err) + require.Nil(t, salt) + }) + t.Run("UpdateProject", func(t *testing.T) { updatedName := "newName" updatedDescription := "newDescription"