github.com/bitcubate/cryptojournal@v1.2.5-0.20171102134152-f578b3d788ab/src/users/actions/password.go (about)

     1  package useractions
     2  
     3  import (
     4  	"fmt"
     5  	"net/http"
     6  	"time"
     7  
     8  	"github.com/fragmenta/auth"
     9  	"github.com/fragmenta/mux"
    10  	"github.com/fragmenta/query"
    11  	"github.com/fragmenta/server"
    12  	"github.com/fragmenta/server/config"
    13  	"github.com/fragmenta/server/log"
    14  	"github.com/fragmenta/view"
    15  
    16  	"github.com/bitcubate/cryptojournal/src/lib/mail"
    17  	"github.com/bitcubate/cryptojournal/src/lib/session"
    18  	"github.com/bitcubate/cryptojournal/src/users"
    19  )
    20  
    21  const (
    22  	// ResetLifetime is the maximum time reset tokens are valid for
    23  	ResetLifetime = time.Hour
    24  )
    25  
    26  // HandlePasswordResetShow responds to GET /users/password/reset
    27  // by showing the password reset page.
    28  func HandlePasswordResetShow(w http.ResponseWriter, r *http.Request) error {
    29  	// No authorisation required, just show the view
    30  	view := view.NewRenderer(w, r)
    31  	view.Template("users/views/password_reset.html.got")
    32  	return view.Render()
    33  }
    34  
    35  // HandlePasswordResetSend responds to POST /users/password/reset
    36  // by sending a password reset email.
    37  func HandlePasswordResetSend(w http.ResponseWriter, r *http.Request) error {
    38  
    39  	// No authorisation required
    40  	// Check the authenticity token
    41  	err := session.CheckAuthenticity(w, r)
    42  	if err != nil {
    43  		return server.NotAuthorizedError(err, "Invalid authenticity token")
    44  	}
    45  
    46  	params, err := mux.Params(r)
    47  	if err != nil {
    48  		return server.InternalError(err)
    49  	}
    50  
    51  	// Find the user by email (if not found let them know)
    52  	// Find the user by hex token in the db
    53  	email := params.Get("email")
    54  	user, err := users.FindFirst("email=?", email)
    55  	if err != nil {
    56  		return server.Redirect(w, r, "/users/password/reset?message=invalid_email")
    57  	}
    58  
    59  	// Generate a random token and url for the email
    60  	token := auth.BytesToHex(auth.RandomToken(32))
    61  
    62  	// Update the user record with with this token
    63  	userParams := map[string]string{
    64  		"password_reset_token": token,
    65  		"password_reset_at":    query.TimeString(time.Now().UTC()),
    66  	}
    67  	// Direct access to the user columns, bypassing validation
    68  	user.Update(userParams)
    69  
    70  	// Generate the url to use in our email
    71  	url := fmt.Sprintf("%s/users/password?token=%s", config.Get("root_url"), token)
    72  
    73  	// Send a password reset email out to this user
    74  	emailContext := map[string]interface{}{
    75  		"url":  url,
    76  		"name": user.Name,
    77  	}
    78  
    79  	log.Info(log.V{"msg": "sending reset email", "user_email": user.Email, "user_id": user.ID})
    80  
    81  	e := mail.New(user.Email)
    82  	e.Subject = "Reset Password"
    83  	e.Template = "users/views/password_reset_mail.html.got"
    84  	err = mail.Send(e, emailContext)
    85  	if err != nil {
    86  		return err
    87  	}
    88  
    89  	// Tell the user what we have done
    90  	return server.Redirect(w, r, "/users/password/sent")
    91  }
    92  
    93  // HandlePasswordResetSentShow responds to GET /users/password/sent
    94  func HandlePasswordResetSentShow(w http.ResponseWriter, r *http.Request) error {
    95  	view := view.NewRenderer(w, r)
    96  	view.Template("users/views/password_sent.html.got")
    97  	return view.Render()
    98  }
    99  
   100  // HandlePasswordReset responds to GET /users/password?token=DEADFISH
   101  // by logging the user in, removing the token
   102  // and allowing them to set their password.
   103  func HandlePasswordReset(w http.ResponseWriter, r *http.Request) error {
   104  
   105  	params, err := mux.Params(r)
   106  	if err != nil {
   107  		return server.InternalError(err)
   108  	}
   109  
   110  	// Note we have no authenticity check, just a random token to check
   111  	token := params.Get("token")
   112  	if len(token) < 10 || len(token) > 64 {
   113  		return server.NotAuthorizedError(fmt.Errorf("Invalid reset token"), "Invalid Token")
   114  	}
   115  
   116  	// Find the user by hex token in the db
   117  	user, err := users.FindFirst("password_reset_token=?", token)
   118  	if err != nil {
   119  		return server.NotAuthorizedError(err)
   120  	}
   121  
   122  	// Make sure the reset at time is less expire time
   123  	if time.Since(user.PasswordResetAt) > ResetLifetime {
   124  		return server.NotAuthorizedError(nil, "Token invalid", "Your password reset token has expired, please request another.")
   125  	}
   126  
   127  	// Remove the reset token from this user
   128  	// using direct access, bypassing validation
   129  	user.Update(map[string]string{"password_reset_token": ""})
   130  
   131  	// Log in the user and store in the session
   132  	// Now save the user details in a secure cookie, so that we remember the next request
   133  	// Build the session from the secure cookie, or create a new one
   134  	session, err := auth.Session(w, r)
   135  	if err != nil {
   136  		return server.NotAuthorizedError(err)
   137  	}
   138  
   139  	// Save login the session cookie
   140  	session.Set(auth.SessionUserKey, fmt.Sprintf("%d", user.ID))
   141  	session.Save(w)
   142  
   143  	// Log action
   144  	log.Info(log.V{"msg": "reset password", "user_email": user.Email, "user_id": user.ID})
   145  
   146  	// Redirect to the user update page so that they can change their password
   147  	return server.Redirect(w, r, fmt.Sprintf("/users/%d/update", user.ID))
   148  }