github.com/decred/politeia@v1.4.0/politeiawww/legacy/wwwuser.go (about) 1 // Copyright (c) 2018-2020 The Decred developers 2 // Use of this source code is governed by an ISC 3 // license that can be found in the LICENSE file. 4 5 package legacy 6 7 import ( 8 "encoding/json" 9 "errors" 10 "fmt" 11 "net/http" 12 13 www "github.com/decred/politeia/politeiawww/api/www/v1" 14 "github.com/decred/politeia/politeiawww/legacy/sessions" 15 "github.com/decred/politeia/util" 16 "github.com/google/uuid" 17 "github.com/gorilla/mux" 18 ) 19 20 // handleSecret is a mock handler to test privileged routes. 21 func (p *Politeiawww) handleSecret(w http.ResponseWriter, r *http.Request) { 22 log.Tracef("handleSecret") 23 24 fmt.Fprintf(w, "secret sauce") 25 } 26 27 // handleNewUser handles the incoming new user command. It verifies that the 28 // new user doesn't already exist, and then creates a new user in the db and 29 // generates a random code used for verification. The code is intended to be 30 // sent to the specified email. 31 func (p *Politeiawww) handleNewUser(w http.ResponseWriter, r *http.Request) { 32 log.Tracef("handleNewUser") 33 34 // Get the new user command. 35 var u www.NewUser 36 decoder := json.NewDecoder(r.Body) 37 if err := decoder.Decode(&u); err != nil { 38 RespondWithError(w, r, 0, "handleNewUser: unmarshal", www.UserError{ 39 ErrorCode: www.ErrorStatusInvalidInput, 40 }) 41 return 42 } 43 44 reply, err := p.processNewUser(u) 45 if err != nil { 46 RespondWithError(w, r, 0, "handleNewUser: processNewUser %v", err) 47 return 48 } 49 50 // Reply with the verification token. 51 util.RespondWithJSON(w, http.StatusOK, reply) 52 } 53 54 // handleVerifyNewUser handles the incoming new user verify command. It 55 // verifies that the user with the provided email has a verification token that 56 // matches the provided token and that the verification token has not yet 57 // expired. 58 func (p *Politeiawww) handleVerifyNewUser(w http.ResponseWriter, r *http.Request) { 59 log.Tracef("handleVerifyNewUser") 60 61 // Get the new user verify command. 62 var vnu www.VerifyNewUser 63 err := util.ParseGetParams(r, &vnu) 64 if err != nil { 65 RespondWithError(w, r, 0, "handleVerifyNewUser: ParseGetParams", 66 www.UserError{ 67 ErrorCode: www.ErrorStatusInvalidInput, 68 }) 69 return 70 } 71 72 _, err = p.processVerifyNewUser(vnu) 73 if err != nil { 74 RespondWithError(w, r, 0, "handleVerifyNewUser: "+ 75 "processVerifyNewUser %v", err) 76 return 77 } 78 79 util.RespondWithJSON(w, http.StatusOK, www.VerifyNewUserReply{}) 80 } 81 82 // handleResendVerification sends another verification email for new user 83 // signup, if there is an existing verification token and it is expired. 84 func (p *Politeiawww) handleResendVerification(w http.ResponseWriter, r *http.Request) { 85 log.Tracef("handleResendVerification") 86 87 // Get the resend verification command. 88 var rv www.ResendVerification 89 decoder := json.NewDecoder(r.Body) 90 if err := decoder.Decode(&rv); err != nil { 91 RespondWithError(w, r, 0, "handleResendVerification: unmarshal", 92 www.UserError{ 93 ErrorCode: www.ErrorStatusInvalidInput, 94 }) 95 return 96 } 97 98 rvr, err := p.processResendVerification(&rv) 99 if err != nil { 100 var usrErr www.UserError 101 if errors.As(err, &usrErr) { 102 switch usrErr.ErrorCode { 103 case www.ErrorStatusUserNotFound, www.ErrorStatusEmailAlreadyVerified, 104 www.ErrorStatusVerificationTokenUnexpired: 105 // We do not return these errors because we do not want 106 // the caller to be able to ascertain whether an email 107 // address has an acount. 108 util.RespondWithJSON(w, http.StatusOK, &www.ResendVerificationReply{}) 109 return 110 } 111 } 112 113 RespondWithError(w, r, 0, "handleResendVerification: "+ 114 "processResendVerification %v", err) 115 return 116 } 117 118 // Reply with the verification token. 119 util.RespondWithJSON(w, http.StatusOK, *rvr) 120 } 121 122 // handleLogin handles the incoming login command. It verifies that the user 123 // exists and the accompanying password. On success a cookie is added to the 124 // gorilla sessions that must be returned on subsequent calls. 125 func (p *Politeiawww) handleLogin(w http.ResponseWriter, r *http.Request) { 126 log.Tracef("handleLogin") 127 128 // Get the login command. 129 var l www.Login 130 decoder := json.NewDecoder(r.Body) 131 if err := decoder.Decode(&l); err != nil { 132 RespondWithError(w, r, 0, "handleLogin: failed to decode: %v", 133 www.UserError{ 134 ErrorCode: www.ErrorStatusInvalidInput, 135 }) 136 return 137 } 138 139 reply, err := p.processLogin(l) 140 if err != nil { 141 RespondWithError(w, r, http.StatusUnauthorized, 142 "handleLogin: processLogin: %v", err) 143 return 144 } 145 146 // Initialize a session for the logged in user 147 err = p.sessions.NewSession(w, r, reply.UserID) 148 if err != nil { 149 RespondWithError(w, r, 0, 150 "handleLogin: initSession: %v", err) 151 return 152 } 153 154 // Set session max age 155 reply.SessionMaxAge = sessions.SessionMaxAge 156 157 // Reply with the user information. 158 util.RespondWithJSON(w, http.StatusOK, reply) 159 } 160 161 // handleLogout logs the user out. 162 func (p *Politeiawww) handleLogout(w http.ResponseWriter, r *http.Request) { 163 log.Tracef("handleLogout") 164 165 _, err := p.sessions.GetSessionUser(w, r) 166 if err != nil { 167 RespondWithError(w, r, 0, "handleLogout: getSessionUser", www.UserError{ 168 ErrorCode: www.ErrorStatusNotLoggedIn, 169 }) 170 return 171 } 172 173 err = p.sessions.DelSession(w, r) 174 if err != nil { 175 RespondWithError(w, r, 0, 176 "handleLogout: removeSession %v", err) 177 return 178 } 179 180 // Reply with the user information. 181 var reply www.LogoutReply 182 util.RespondWithJSON(w, http.StatusOK, reply) 183 } 184 185 // handleMe returns logged in user information. 186 func (p *Politeiawww) handleMe(w http.ResponseWriter, r *http.Request) { 187 log.Tracef("handleMe") 188 189 user, err := p.sessions.GetSessionUser(w, r) 190 if err != nil { 191 RespondWithError(w, r, 0, 192 "handleMe: getSessionUser %v", err) 193 return 194 } 195 196 reply, err := p.createLoginReply(user, user.LastLoginTime) 197 if err != nil { 198 RespondWithError(w, r, 0, 199 "handleMe: createLoginReply %v", err) 200 return 201 } 202 203 // Set session max age 204 reply.SessionMaxAge = sessions.SessionMaxAge 205 206 util.RespondWithJSON(w, http.StatusOK, *reply) 207 } 208 209 // handleResetPassword handles the reset password command. 210 func (p *Politeiawww) handleResetPassword(w http.ResponseWriter, r *http.Request) { 211 log.Trace("handleResetPassword") 212 213 // Get the reset password command. 214 var rp www.ResetPassword 215 decoder := json.NewDecoder(r.Body) 216 if err := decoder.Decode(&rp); err != nil { 217 RespondWithError(w, r, 0, "handleResetPassword: unmarshal", 218 www.UserError{ 219 ErrorCode: www.ErrorStatusInvalidInput, 220 }) 221 return 222 } 223 224 rpr, err := p.processResetPassword(rp) 225 if err != nil { 226 RespondWithError(w, r, 0, 227 "handleResetPassword: processResetPassword %v", err) 228 return 229 } 230 231 // Reply with the error code. 232 util.RespondWithJSON(w, http.StatusOK, rpr) 233 } 234 235 // handleVerifyResetPassword handles the verify reset password command. 236 func (p *Politeiawww) handleVerifyResetPassword(w http.ResponseWriter, r *http.Request) { 237 log.Trace("handleVerifyResetPassword") 238 239 var vrp www.VerifyResetPassword 240 decoder := json.NewDecoder(r.Body) 241 if err := decoder.Decode(&vrp); err != nil { 242 RespondWithError(w, r, 0, "handleVerifyResetPassword: unmarshal", 243 www.UserError{ 244 ErrorCode: www.ErrorStatusInvalidInput, 245 }) 246 return 247 } 248 249 reply, err := p.processVerifyResetPassword(vrp) 250 if err != nil { 251 RespondWithError(w, r, 0, 252 "handleVerifyResetPassword: processVerifyResetPassword %v", err) 253 return 254 } 255 256 // Delete all existing sessions for the user. Return a 200 if 257 // either of these calls fail since the password was verified 258 // correctly. 259 user, err := p.db.UserGetByUsername(vrp.Username) 260 if err != nil { 261 log.Errorf("handleVerifyResetPassword: UserGetByUsername(%v): %v", 262 vrp.Username, err) 263 util.RespondWithJSON(w, http.StatusOK, reply) 264 return 265 } 266 err = p.db.SessionsDeleteByUserID(user.ID, []string{}) 267 if err != nil { 268 log.Errorf("handleVerifyResetPassword: SessionsDeleteByUserID(%v, %v): %v", 269 user.ID, []string{}, err) 270 } 271 272 util.RespondWithJSON(w, http.StatusOK, reply) 273 } 274 275 // handleUserDetails handles fetching user details by user id. 276 func (p *Politeiawww) handleUserDetails(w http.ResponseWriter, r *http.Request) { 277 // Add the path param to the struct. 278 log.Tracef("handleUserDetails") 279 pathParams := mux.Vars(r) 280 var ud www.UserDetails 281 ud.UserID = pathParams["userid"] 282 283 userID, err := uuid.Parse(ud.UserID) 284 if err != nil { 285 RespondWithError(w, r, 0, "handleUserDetails: ParseUint", 286 www.UserError{ 287 ErrorCode: www.ErrorStatusInvalidInput, 288 }) 289 return 290 } 291 292 // Get session user. This is a public route so one might not exist. 293 user, err := p.sessions.GetSessionUser(w, r) 294 if err != nil && !errors.Is(err, sessions.ErrSessionNotFound) { 295 RespondWithError(w, r, 0, 296 "handleUserDetails: getSessionUser %v", err) 297 return 298 } 299 300 udr, err := p.processUserDetails(&ud, 301 user != nil && user.ID == userID, 302 user != nil && user.Admin, 303 ) 304 305 if err != nil { 306 RespondWithError(w, r, 0, 307 "handleUserDetails: processUserDetails %v", err) 308 return 309 } 310 311 // Reply with the proposal details. 312 util.RespondWithJSON(w, http.StatusOK, udr) 313 } 314 315 // handleEditUser handles editing a user's preferences. 316 func (p *Politeiawww) handleEditUser(w http.ResponseWriter, r *http.Request) { 317 log.Tracef("handleEditUser") 318 319 var eu www.EditUser 320 decoder := json.NewDecoder(r.Body) 321 if err := decoder.Decode(&eu); err != nil { 322 RespondWithError(w, r, 0, "handleEditUser: unmarshal", 323 www.UserError{ 324 ErrorCode: www.ErrorStatusInvalidInput, 325 }) 326 return 327 } 328 329 adminUser, err := p.sessions.GetSessionUser(w, r) 330 if err != nil { 331 RespondWithError(w, r, 0, "handleEditUser: getSessionUser %v", 332 err) 333 return 334 } 335 336 eur, err := p.processEditUser(&eu, adminUser) 337 if err != nil { 338 RespondWithError(w, r, 0, 339 "handleEditUser: processEditUser %v", err) 340 return 341 } 342 343 util.RespondWithJSON(w, http.StatusOK, eur) 344 } 345 346 // handleUpdateUserKey handles the incoming update user key command. It 347 // generates a random code used for verification. The code is intended to be 348 // sent to the email of the logged in user. 349 func (p *Politeiawww) handleUpdateUserKey(w http.ResponseWriter, r *http.Request) { 350 log.Tracef("handleUpdateUserKey") 351 352 // Get the update user key command. 353 var u www.UpdateUserKey 354 decoder := json.NewDecoder(r.Body) 355 if err := decoder.Decode(&u); err != nil { 356 RespondWithError(w, r, 0, "handleUpdateUserKey: unmarshal", www.UserError{ 357 ErrorCode: www.ErrorStatusInvalidInput, 358 }) 359 return 360 } 361 362 user, err := p.sessions.GetSessionUser(w, r) 363 if err != nil { 364 RespondWithError(w, r, 0, 365 "handleUpdateUserKey: getSessionUser %v", err) 366 return 367 } 368 369 reply, err := p.processUpdateUserKey(user, u) 370 if err != nil { 371 RespondWithError(w, r, 0, "handleUpdateUserKey: processUpdateUserKey %v", err) 372 return 373 } 374 375 // Reply with the verification token. 376 util.RespondWithJSON(w, http.StatusOK, reply) 377 } 378 379 // handleVerifyUpdateUserKey handles the incoming update user key verify 380 // command. It verifies that the user with the provided email has a 381 // verification token that matches the provided token and that the verification 382 // token has not yet expired. 383 func (p *Politeiawww) handleVerifyUpdateUserKey(w http.ResponseWriter, r *http.Request) { 384 log.Tracef("handleVerifyUpdateUserKey") 385 386 // Get the new user verify command. 387 var vuu www.VerifyUpdateUserKey 388 decoder := json.NewDecoder(r.Body) 389 if err := decoder.Decode(&vuu); err != nil { 390 RespondWithError(w, r, 0, "handleVerifyUpdateUserKey: unmarshal", 391 www.UserError{ 392 ErrorCode: www.ErrorStatusInvalidInput, 393 }) 394 return 395 } 396 397 user, err := p.sessions.GetSessionUser(w, r) 398 if err != nil { 399 RespondWithError(w, r, 0, 400 "handleVerifyUpdateUserKey: getSessionUser %v", err) 401 return 402 } 403 404 _, err = p.processVerifyUpdateUserKey(user, vuu) 405 if err != nil { 406 RespondWithError(w, r, 0, "handleVerifyUpdateUserKey: "+ 407 "processVerifyUpdateUserKey %v", err) 408 return 409 } 410 411 util.RespondWithJSON(w, http.StatusOK, www.VerifyUpdateUserKeyReply{}) 412 } 413 414 // handleChangeUsername handles the change user name command. 415 func (p *Politeiawww) handleChangeUsername(w http.ResponseWriter, r *http.Request) { 416 log.Tracef("handleChangeUsername") 417 418 // Get the change username command. 419 var cu www.ChangeUsername 420 decoder := json.NewDecoder(r.Body) 421 if err := decoder.Decode(&cu); err != nil { 422 RespondWithError(w, r, 0, "handleChangeUsername: unmarshal", 423 www.UserError{ 424 ErrorCode: www.ErrorStatusInvalidInput, 425 }) 426 return 427 } 428 429 user, err := p.sessions.GetSessionUser(w, r) 430 if err != nil { 431 RespondWithError(w, r, 0, 432 "handleChangeUsername: getSessionUser %v", err) 433 return 434 } 435 436 reply, err := p.processChangeUsername(user.Email, cu) 437 if err != nil { 438 RespondWithError(w, r, 0, 439 "handleChangeUsername: processChangeUsername %v", err) 440 return 441 } 442 443 // Reply with the error code. 444 util.RespondWithJSON(w, http.StatusOK, reply) 445 } 446 447 // handleChangePassword handles the change password command. 448 func (p *Politeiawww) handleChangePassword(w http.ResponseWriter, r *http.Request) { 449 log.Tracef("handleChangePassword") 450 451 // Get the change password command. 452 var cp www.ChangePassword 453 decoder := json.NewDecoder(r.Body) 454 if err := decoder.Decode(&cp); err != nil { 455 RespondWithError(w, r, 0, "handleChangePassword: unmarshal", 456 www.UserError{ 457 ErrorCode: www.ErrorStatusInvalidInput, 458 }) 459 return 460 } 461 462 session, err := p.sessions.GetSession(r) 463 if err != nil { 464 RespondWithError(w, r, 0, 465 "handleChangePassword: getSession %v", err) 466 return 467 } 468 user, err := p.sessions.GetSessionUser(w, r) 469 if err != nil { 470 RespondWithError(w, r, 0, 471 "handleChangePassword: getSessionUser %v", err) 472 return 473 } 474 475 reply, err := p.processChangePassword(user.Email, cp) 476 if err != nil { 477 RespondWithError(w, r, 0, 478 "handleChangePassword: processChangePassword %v", err) 479 return 480 } 481 482 // Delete all existing sessions for the user except the current. 483 // Return a 200 if this call fails since the password was changed 484 // correctly. 485 err = p.db.SessionsDeleteByUserID(user.ID, []string{session.ID}) 486 if err != nil { 487 log.Errorf("handleChangePassword: SessionsDeleteByUserID(%v, %v): %v", 488 user.ID, []string{session.ID}, err) 489 } 490 491 // Reply with the error code. 492 util.RespondWithJSON(w, http.StatusOK, reply) 493 } 494 495 // handleUsers handles fetching a list of users. 496 func (p *Politeiawww) handleUsers(w http.ResponseWriter, r *http.Request) { 497 log.Tracef("handleUsers") 498 499 var u www.Users 500 err := util.ParseGetParams(r, &u) 501 if err != nil { 502 RespondWithError(w, r, 0, "handleUsers: ParseGetParams", 503 www.UserError{ 504 ErrorCode: www.ErrorStatusInvalidInput, 505 }) 506 return 507 } 508 509 // Get session user. This is a public route so one might not exist. 510 user, err := p.sessions.GetSessionUser(w, r) 511 if err != nil && !errors.Is(err, sessions.ErrSessionNotFound) { 512 RespondWithError(w, r, 0, 513 "handleUsers: getSessionUser %v", err) 514 return 515 } 516 517 isAdmin := (user != nil && user.Admin) 518 ur, err := p.processUsers(&u, isAdmin) 519 520 if err != nil { 521 RespondWithError(w, r, 0, 522 "handleUsers: processUsers %v", err) 523 return 524 } 525 526 util.RespondWithJSON(w, http.StatusOK, ur) 527 } 528 529 // handleManageUser handles editing a user's details. 530 func (p *Politeiawww) handleManageUser(w http.ResponseWriter, r *http.Request) { 531 log.Tracef("handleManageUser") 532 533 var mu www.ManageUser 534 decoder := json.NewDecoder(r.Body) 535 if err := decoder.Decode(&mu); err != nil { 536 RespondWithError(w, r, 0, "handleManageUser: unmarshal", 537 www.UserError{ 538 ErrorCode: www.ErrorStatusInvalidInput, 539 }) 540 return 541 } 542 543 adminUser, err := p.sessions.GetSessionUser(w, r) 544 if err != nil { 545 RespondWithError(w, r, 0, "handleManageUser: getSessionUser %v", 546 err) 547 return 548 } 549 550 mur, err := p.processManageUser(&mu, adminUser) 551 if err != nil { 552 RespondWithError(w, r, 0, 553 "handleManageUser: processManageUser %v", err) 554 return 555 } 556 557 util.RespondWithJSON(w, http.StatusOK, mur) 558 } 559 560 // handleSetTOTP handles the setting of TOTP Key 561 func (p *Politeiawww) handleSetTOTP(w http.ResponseWriter, r *http.Request) { 562 log.Tracef("handleSetTOTP") 563 564 var st www.SetTOTP 565 decoder := json.NewDecoder(r.Body) 566 if err := decoder.Decode(&st); err != nil { 567 RespondWithError(w, r, 0, "handleSetTOTP: unmarshal", 568 www.UserError{ 569 ErrorCode: www.ErrorStatusInvalidInput, 570 }) 571 return 572 } 573 574 u, err := p.sessions.GetSessionUser(w, r) 575 if err != nil { 576 RespondWithError(w, r, 0, 577 "handleSetTOTP: getSessionUser %v", err) 578 return 579 } 580 581 str, err := p.processSetTOTP(st, u) 582 if err != nil { 583 RespondWithError(w, r, 0, 584 "handleSetTOTP: processSetTOTP %v", err) 585 return 586 } 587 588 util.RespondWithJSON(w, http.StatusOK, str) 589 } 590 591 // handleVerifyTOTP handles the request to verify a set TOTP Key. 592 func (p *Politeiawww) handleVerifyTOTP(w http.ResponseWriter, r *http.Request) { 593 log.Tracef("handleVerifyTOTP") 594 595 var vt www.VerifyTOTP 596 decoder := json.NewDecoder(r.Body) 597 if err := decoder.Decode(&vt); err != nil { 598 RespondWithError(w, r, 0, "handleVerifyTOTP: unmarshal", 599 www.UserError{ 600 ErrorCode: www.ErrorStatusInvalidInput, 601 }) 602 return 603 } 604 605 u, err := p.sessions.GetSessionUser(w, r) 606 if err != nil { 607 RespondWithError(w, r, 0, 608 "handleVerifyTOTP: getSessionUser %v", err) 609 return 610 } 611 612 vtr, err := p.processVerifyTOTP(vt, u) 613 if err != nil { 614 RespondWithError(w, r, 0, 615 "handleVerifyTOTP: processVerifyTOTP %v", err) 616 return 617 } 618 619 util.RespondWithJSON(w, http.StatusOK, vtr) 620 }