4ee647a951
This change adds request IDs to requests, logs them as part of audit logs and sends to the client on error. This is to improve debugging of customer issues. Issue: https://github.com/storj/storj/issues/5898 Change-Id: I801514b547d28d810552d91aa7c8502051e552bf
242 lines
6.1 KiB
Go
242 lines
6.1 KiB
Go
// Copyright (C) 2021 Storj Labs, Inc.
|
|
// See LICENSE for copying information.
|
|
|
|
package consoleapi
|
|
|
|
import (
|
|
"context"
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/gorilla/mux"
|
|
"github.com/zeebo/errs"
|
|
"go.uber.org/zap"
|
|
|
|
"storj.io/common/uuid"
|
|
"storj.io/storj/private/web"
|
|
"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(ctx, w, http.StatusBadRequest, errs.New("missing id route param"))
|
|
return
|
|
}
|
|
|
|
id, err := uuid.FromString(idParam)
|
|
if err != nil {
|
|
p.serveJSONError(ctx, w, http.StatusBadRequest, err)
|
|
}
|
|
|
|
salt, err := p.service.GetSalt(ctx, id)
|
|
if err != nil {
|
|
p.serveJSONError(ctx, w, http.StatusUnauthorized, err)
|
|
return
|
|
}
|
|
|
|
b64SaltString := base64.StdEncoding.EncodeToString(salt)
|
|
|
|
err = json.NewEncoder(w).Encode(b64SaltString)
|
|
if err != nil {
|
|
p.serveJSONError(ctx, w, http.StatusInternalServerError, err)
|
|
}
|
|
}
|
|
|
|
// InviteUsers sends invites to a given project(id) to the given users (emails).
|
|
func (p *Projects) InviteUsers(w http.ResponseWriter, r *http.Request) {
|
|
ctx := r.Context()
|
|
var err error
|
|
defer mon.Task()(&ctx)(&err)
|
|
|
|
idParam, ok := mux.Vars(r)["id"]
|
|
if !ok {
|
|
p.serveJSONError(ctx, w, http.StatusBadRequest, errs.New("missing project id route param"))
|
|
return
|
|
}
|
|
id, err := uuid.FromString(idParam)
|
|
if err != nil {
|
|
p.serveJSONError(ctx, w, http.StatusBadRequest, err)
|
|
}
|
|
|
|
var data struct {
|
|
Emails []string `json:"emails"`
|
|
}
|
|
|
|
err = json.NewDecoder(r.Body).Decode(&data)
|
|
if err != nil {
|
|
p.serveJSONError(ctx, w, http.StatusBadRequest, err)
|
|
return
|
|
}
|
|
|
|
for i, email := range data.Emails {
|
|
data.Emails[i] = strings.TrimSpace(email)
|
|
}
|
|
|
|
_, err = p.service.InviteProjectMembers(ctx, id, data.Emails)
|
|
if err != nil {
|
|
p.serveJSONError(ctx, w, http.StatusInternalServerError, err)
|
|
}
|
|
}
|
|
|
|
// GetInviteLink returns a link to an invitation given project ID and invitee's email.
|
|
func (p *Projects) GetInviteLink(w http.ResponseWriter, r *http.Request) {
|
|
ctx := r.Context()
|
|
var err error
|
|
defer mon.Task()(&ctx)(&err)
|
|
idParam, ok := mux.Vars(r)["id"]
|
|
if !ok {
|
|
p.serveJSONError(ctx, w, http.StatusBadRequest, errs.New("missing project id route param"))
|
|
return
|
|
}
|
|
id, err := uuid.FromString(idParam)
|
|
if err != nil {
|
|
p.serveJSONError(ctx, w, http.StatusBadRequest, err)
|
|
}
|
|
|
|
email := r.URL.Query().Get("email")
|
|
if email == "" {
|
|
p.serveJSONError(ctx, w, http.StatusBadRequest, errs.New("missing email query param"))
|
|
return
|
|
}
|
|
|
|
link, err := p.service.GetInviteLink(ctx, id, email)
|
|
if err != nil {
|
|
p.serveJSONError(ctx, w, http.StatusInternalServerError, err)
|
|
}
|
|
|
|
err = json.NewEncoder(w).Encode(link)
|
|
if err != nil {
|
|
p.serveJSONError(ctx, w, http.StatusInternalServerError, err)
|
|
}
|
|
}
|
|
|
|
// GetUserInvitations returns the user's pending project member invitations.
|
|
func (p *Projects) GetUserInvitations(w http.ResponseWriter, r *http.Request) {
|
|
ctx := r.Context()
|
|
var err error
|
|
defer mon.Task()(&ctx)(&err)
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
|
|
invites, err := p.service.GetUserProjectInvitations(ctx)
|
|
if err != nil {
|
|
p.serveJSONError(ctx, w, http.StatusInternalServerError, err)
|
|
return
|
|
}
|
|
|
|
type jsonInvite struct {
|
|
ProjectID uuid.UUID `json:"projectID"`
|
|
ProjectName string `json:"projectName"`
|
|
ProjectDescription string `json:"projectDescription"`
|
|
InviterEmail string `json:"inviterEmail"`
|
|
CreatedAt time.Time `json:"createdAt"`
|
|
}
|
|
|
|
response := make([]jsonInvite, 0)
|
|
|
|
for _, invite := range invites {
|
|
proj, err := p.service.GetProjectNoAuth(ctx, invite.ProjectID)
|
|
if err != nil {
|
|
p.serveJSONError(ctx, w, http.StatusInternalServerError, err)
|
|
return
|
|
}
|
|
|
|
respInvite := jsonInvite{
|
|
ProjectID: proj.PublicID,
|
|
ProjectName: proj.Name,
|
|
ProjectDescription: proj.Description,
|
|
CreatedAt: invite.CreatedAt,
|
|
}
|
|
|
|
if invite.InviterID != nil {
|
|
inviter, err := p.service.GetUser(ctx, *invite.InviterID)
|
|
if err != nil {
|
|
p.serveJSONError(ctx, w, http.StatusInternalServerError, err)
|
|
return
|
|
}
|
|
respInvite.InviterEmail = inviter.Email
|
|
}
|
|
|
|
response = append(response, respInvite)
|
|
}
|
|
|
|
err = json.NewEncoder(w).Encode(response)
|
|
if err != nil {
|
|
p.serveJSONError(ctx, w, http.StatusInternalServerError, err)
|
|
}
|
|
}
|
|
|
|
// RespondToInvitation handles accepting or declining a user's project member invitation.
|
|
func (p *Projects) RespondToInvitation(w http.ResponseWriter, r *http.Request) {
|
|
ctx := r.Context()
|
|
var err error
|
|
defer mon.Task()(&ctx)(&err)
|
|
|
|
var ok bool
|
|
var idParam string
|
|
|
|
if idParam, ok = mux.Vars(r)["id"]; !ok {
|
|
p.serveJSONError(ctx, w, http.StatusBadRequest, errs.New("missing project id route param"))
|
|
return
|
|
}
|
|
|
|
id, err := uuid.FromString(idParam)
|
|
if err != nil {
|
|
p.serveJSONError(ctx, w, http.StatusBadRequest, err)
|
|
}
|
|
|
|
var payload struct {
|
|
Response console.ProjectInvitationResponse `json:"response"`
|
|
}
|
|
|
|
err = json.NewDecoder(r.Body).Decode(&payload)
|
|
if err != nil {
|
|
p.serveJSONError(ctx, w, http.StatusBadRequest, err)
|
|
return
|
|
}
|
|
|
|
err = p.service.RespondToProjectInvitation(ctx, id, payload.Response)
|
|
if err != nil {
|
|
status := http.StatusInternalServerError
|
|
switch {
|
|
case console.ErrAlreadyMember.Has(err):
|
|
status = http.StatusConflict
|
|
case console.ErrProjectInviteInvalid.Has(err):
|
|
status = http.StatusNotFound
|
|
case console.ErrValidation.Has(err):
|
|
status = http.StatusBadRequest
|
|
}
|
|
p.serveJSONError(ctx, w, status, err)
|
|
}
|
|
}
|
|
|
|
// serveJSONError writes JSON error to response output stream.
|
|
func (p *Projects) serveJSONError(ctx context.Context, w http.ResponseWriter, status int, err error) {
|
|
web.ServeJSONError(ctx, p.log, w, status, err)
|
|
}
|