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