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  }