1// Copyright (C) 2020 Storj Labs, Inc.
2// See LICENSE for copying information.
3
4package admin
5
6import (
7	"database/sql"
8	"encoding/json"
9	"errors"
10	"fmt"
11	"io/ioutil"
12	"net/http"
13
14	"github.com/gorilla/mux"
15	"golang.org/x/crypto/bcrypt"
16
17	"storj.io/common/uuid"
18	"storj.io/storj/satellite/console"
19)
20
21func (server *Server) addUser(w http.ResponseWriter, r *http.Request) {
22	ctx := r.Context()
23
24	body, err := ioutil.ReadAll(r.Body)
25	if err != nil {
26		sendJSONError(w, "failed to read body",
27			err.Error(), http.StatusInternalServerError)
28		return
29	}
30
31	var input console.CreateUser
32
33	err = json.Unmarshal(body, &input)
34	if err != nil {
35		sendJSONError(w, "failed to unmarshal request",
36			err.Error(), http.StatusBadRequest)
37		return
38	}
39
40	user := console.CreateUser{
41		Email:    input.Email,
42		FullName: input.FullName,
43		Password: input.Password,
44	}
45
46	err = user.IsValid()
47	if err != nil {
48		sendJSONError(w, "user data is not valid",
49			err.Error(), http.StatusBadRequest)
50		return
51	}
52
53	existingUser, err := server.db.Console().Users().GetByEmail(ctx, input.Email)
54	if err != nil && !errors.Is(sql.ErrNoRows, err) {
55		sendJSONError(w, "failed to check for user email",
56			err.Error(), http.StatusInternalServerError)
57		return
58	}
59	if existingUser != nil {
60		sendJSONError(w, fmt.Sprintf("user with email already exists %s", input.Email),
61			"", http.StatusConflict)
62		return
63	}
64
65	hash, err := bcrypt.GenerateFromPassword([]byte(input.Password), 0)
66	if err != nil {
67		sendJSONError(w, "unable to save password hash",
68			"", http.StatusInternalServerError)
69		return
70	}
71
72	userID, err := uuid.New()
73	if err != nil {
74		sendJSONError(w, "unable to create UUID",
75			"", http.StatusInternalServerError)
76		return
77	}
78
79	newuser, err := server.db.Console().Users().Insert(ctx, &console.User{
80		ID:                    userID,
81		FullName:              user.FullName,
82		ShortName:             user.ShortName,
83		Email:                 user.Email,
84		PasswordHash:          hash,
85		ProjectLimit:          server.config.ConsoleConfig.DefaultProjectLimit,
86		ProjectStorageLimit:   server.config.ConsoleConfig.UsageLimits.Storage.Free.Int64(),
87		ProjectBandwidthLimit: server.config.ConsoleConfig.UsageLimits.Bandwidth.Free.Int64(),
88	})
89	if err != nil {
90		sendJSONError(w, "failed to insert user",
91			err.Error(), http.StatusInternalServerError)
92		return
93	}
94
95	err = server.payments.Setup(ctx, newuser.ID, newuser.Email)
96	if err != nil {
97		sendJSONError(w, "failed to create payment account for user",
98			err.Error(), http.StatusInternalServerError)
99		return
100	}
101
102	// Set User Status to be activated, as we manually created it
103	newuser.Status = console.Active
104	newuser.PasswordHash = nil
105	err = server.db.Console().Users().Update(ctx, newuser)
106	if err != nil {
107		sendJSONError(w, "failed to activate user",
108			err.Error(), http.StatusInternalServerError)
109		return
110	}
111
112	data, err := json.Marshal(newuser)
113	if err != nil {
114		sendJSONError(w, "json encoding failed",
115			err.Error(), http.StatusInternalServerError)
116		return
117	}
118
119	sendJSONData(w, http.StatusOK, data)
120}
121
122func (server *Server) userInfo(w http.ResponseWriter, r *http.Request) {
123	ctx := r.Context()
124
125	vars := mux.Vars(r)
126	userEmail, ok := vars["useremail"]
127	if !ok {
128		sendJSONError(w, "user-email missing",
129			"", http.StatusBadRequest)
130		return
131	}
132
133	user, err := server.db.Console().Users().GetByEmail(ctx, userEmail)
134	if errors.Is(err, sql.ErrNoRows) {
135		sendJSONError(w, fmt.Sprintf("user with email %q does not exist", userEmail),
136			"", http.StatusNotFound)
137		return
138	}
139	if err != nil {
140		sendJSONError(w, "failed to get user",
141			err.Error(), http.StatusInternalServerError)
142		return
143	}
144	user.PasswordHash = nil
145
146	projects, err := server.db.Console().Projects().GetByUserID(ctx, user.ID)
147	if err != nil {
148		sendJSONError(w, "failed to get user projects",
149			err.Error(), http.StatusInternalServerError)
150		return
151	}
152
153	type User struct {
154		ID           uuid.UUID `json:"id"`
155		FullName     string    `json:"fullName"`
156		Email        string    `json:"email"`
157		ProjectLimit int       `json:"projectLimit"`
158	}
159	type Project struct {
160		ID          uuid.UUID `json:"id"`
161		Name        string    `json:"name"`
162		Description string    `json:"description"`
163		OwnerID     uuid.UUID `json:"ownerId"`
164	}
165
166	var output struct {
167		User     User      `json:"user"`
168		Projects []Project `json:"projects"`
169	}
170
171	output.User = User{
172		ID:           user.ID,
173		FullName:     user.FullName,
174		Email:        user.Email,
175		ProjectLimit: user.ProjectLimit,
176	}
177	for _, p := range projects {
178		output.Projects = append(output.Projects, Project{
179			ID:          p.ID,
180			Name:        p.Name,
181			Description: p.Description,
182			OwnerID:     p.OwnerID,
183		})
184	}
185
186	data, err := json.Marshal(output)
187	if err != nil {
188		sendJSONError(w, "json encoding failed",
189			err.Error(), http.StatusInternalServerError)
190		return
191	}
192
193	sendJSONData(w, http.StatusOK, data)
194}
195
196func (server *Server) updateUser(w http.ResponseWriter, r *http.Request) {
197	ctx := r.Context()
198
199	vars := mux.Vars(r)
200	userEmail, ok := vars["useremail"]
201	if !ok {
202		sendJSONError(w, "user-email missing",
203			"", http.StatusBadRequest)
204		return
205	}
206
207	user, err := server.db.Console().Users().GetByEmail(ctx, userEmail)
208	if errors.Is(err, sql.ErrNoRows) {
209		sendJSONError(w, fmt.Sprintf("user with email %q does not exist", userEmail),
210			"", http.StatusNotFound)
211		return
212	}
213	if err != nil {
214		sendJSONError(w, "failed to get user",
215			err.Error(), http.StatusInternalServerError)
216		return
217	}
218
219	body, err := ioutil.ReadAll(r.Body)
220	if err != nil {
221		sendJSONError(w, "failed to read body",
222			err.Error(), http.StatusInternalServerError)
223		return
224	}
225
226	var input console.User
227
228	err = json.Unmarshal(body, &input)
229	if err != nil {
230		sendJSONError(w, "failed to unmarshal request",
231			err.Error(), http.StatusBadRequest)
232		return
233	}
234
235	if input.FullName != "" {
236		user.FullName = input.FullName
237	}
238	if input.ShortName != "" {
239		user.ShortName = input.ShortName
240	}
241	if input.Email != "" {
242		user.Email = input.Email
243	}
244	if !input.PartnerID.IsZero() {
245		user.PartnerID = input.PartnerID
246	}
247	if len(input.PasswordHash) > 0 {
248		user.PasswordHash = input.PasswordHash
249	}
250	if input.ProjectLimit > 0 {
251		user.ProjectLimit = input.ProjectLimit
252	}
253
254	err = server.db.Console().Users().Update(ctx, user)
255	if err != nil {
256		sendJSONError(w, "failed to update user",
257			err.Error(), http.StatusInternalServerError)
258		return
259	}
260}
261
262func (server *Server) deleteUser(w http.ResponseWriter, r *http.Request) {
263	ctx := r.Context()
264
265	vars := mux.Vars(r)
266	userEmail, ok := vars["useremail"]
267	if !ok {
268		sendJSONError(w, "user-email missing", "", http.StatusBadRequest)
269		return
270	}
271
272	user, err := server.db.Console().Users().GetByEmail(ctx, userEmail)
273	if errors.Is(err, sql.ErrNoRows) {
274		sendJSONError(w, fmt.Sprintf("user with email %q does not exist", userEmail),
275			"", http.StatusNotFound)
276		return
277	}
278	if err != nil {
279		sendJSONError(w, "failed to get user details",
280			err.Error(), http.StatusInternalServerError)
281		return
282	}
283
284	// Ensure user has no own projects any longer
285	projects, err := server.db.Console().Projects().GetByUserID(ctx, user.ID)
286	if err != nil {
287		sendJSONError(w, "unable to list projects",
288			err.Error(), http.StatusInternalServerError)
289		return
290	}
291	if len(projects) > 0 {
292		sendJSONError(w, "some projects still exist",
293			fmt.Sprintf("%v", projects), http.StatusConflict)
294		return
295	}
296
297	// Delete memberships in foreign projects
298	members, err := server.db.Console().ProjectMembers().GetByMemberID(ctx, user.ID)
299	if err != nil {
300		sendJSONError(w, "unable to search for user project memberships",
301			err.Error(), http.StatusInternalServerError)
302		return
303	}
304	if len(members) > 0 {
305		for _, project := range members {
306			err := server.db.Console().ProjectMembers().Delete(ctx, user.ID, project.ProjectID)
307			if err != nil {
308				sendJSONError(w, "unable to delete user project membership",
309					err.Error(), http.StatusInternalServerError)
310				return
311			}
312		}
313	}
314
315	// ensure no unpaid invoices exist.
316	invoices, err := server.payments.Invoices().List(ctx, user.ID)
317	if err != nil {
318		sendJSONError(w, "unable to list user invoices",
319			err.Error(), http.StatusInternalServerError)
320		return
321	}
322	if len(invoices) > 0 {
323		for _, invoice := range invoices {
324			if invoice.Status == "draft" || invoice.Status == "open" {
325				sendJSONError(w, "user has unpaid/pending invoices",
326					"", http.StatusConflict)
327				return
328			}
329		}
330	}
331
332	hasItems, err := server.payments.Invoices().CheckPendingItems(ctx, user.ID)
333	if err != nil {
334		sendJSONError(w, "unable to list pending invoice items",
335			err.Error(), http.StatusInternalServerError)
336		return
337	}
338	if hasItems {
339		sendJSONError(w, "user has pending invoice items",
340			"", http.StatusConflict)
341		return
342	}
343
344	userInfo := &console.User{
345		ID:        user.ID,
346		FullName:  "",
347		ShortName: "",
348		Email:     fmt.Sprintf("deactivated+%s@storj.io", user.ID.String()),
349		Status:    console.Deleted,
350	}
351
352	err = server.db.Console().Users().Update(ctx, userInfo)
353	if err != nil {
354		sendJSONError(w, "unable to delete user",
355			err.Error(), http.StatusInternalServerError)
356		return
357	}
358
359	err = server.payments.CreditCards().RemoveAll(ctx, user.ID)
360	if err != nil {
361		sendJSONError(w, "unable to delete credit card(s) from stripe account",
362			err.Error(), http.StatusInternalServerError)
363	}
364}
365