package internal import ( "fmt" "net/http" "strings" "time" "github.com/dgrijalva/jwt-go" "github.com/julienschmidt/httprouter" log "github.com/sirupsen/logrus" ) // ResetPasswordHandler ... func (s *Server) ResetPasswordHandler() httprouter.Handle { return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) { ctx := NewContext(s.config, s.db, r) if r.Method == "GET" { ctx.Title = "Reset password" s.render("resetPassword", w, ctx) return } username := NormalizeUsername(r.FormValue("username")) email := strings.TrimSpace(r.FormValue("email")) recovery := fmt.Sprintf("email:%s", FastHash(email)) // Check if user exist if !s.db.HasUser(username) { ctx.Error = true ctx.Message = "User not found!" s.render("error", w, ctx) return } // Get user object from DB user, err := s.db.GetUser(username) if err != nil { ctx.Error = true ctx.Message = "Error loading user" s.render("error", w, ctx) return } if recovery != user.Recovery { ctx.Error = true ctx.Message = "Error! The email address you supplied does not match what you registered with :/" s.render("error", w, ctx) return } // Create magic link expiry time now := time.Now() secs := now.Unix() expiresAfterSeconds := int64(600) // Link expires after 10 minutes expiryTime := secs + expiresAfterSeconds // Create magic link token := jwt.NewWithClaims( jwt.SigningMethodHS256, jwt.MapClaims{"username": username, "expiresAt": expiryTime}, ) tokenString, err := token.SignedString([]byte(s.config.MagicLinkSecret)) if err != nil { ctx.Error = true ctx.Message = err.Error() s.render("error", w, ctx) return } if err := SendPasswordResetEmail(s.config, user, email, tokenString); err != nil { log.WithError(err).Errorf("unable to send reset password email to %s", user.Username) ctx.Error = true ctx.Message = err.Error() s.render("error", w, ctx) return } log.Infof("reset password email sent for %s", user.Username) // Show success msg ctx.Error = false ctx.Message = "Password request request sent! Please check your email and follow the instructions" s.render("error", w, ctx) } } // ResetPasswordMagicLinkHandler ... func (s *Server) ResetPasswordMagicLinkHandler() httprouter.Handle { return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) { ctx := NewContext(s.config, s.db, r) // Get token from query string tokens, ok := r.URL.Query()["token"] // Check if valid token if !ok || len(tokens[0]) < 1 { ctx.Error = true ctx.Message = "Invalid token" s.render("error", w, ctx) return } tokenEmail := tokens[0] ctx.PasswordResetToken = tokenEmail // Show newPassword page s.render("newPassword", w, ctx) } } // NewPasswordHandler ... func (s *Server) NewPasswordHandler() httprouter.Handle { return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) { ctx := NewContext(s.config, s.db, r) if r.Method == "GET" { return } password := r.FormValue("password") tokenEmail := r.FormValue("token") // Check if token is valid token, err := jwt.Parse(tokenEmail, func(token *jwt.Token) (interface{}, error) { if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"]) } return []byte(s.config.MagicLinkSecret), nil }) if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid { var username = fmt.Sprintf("%v", claims["username"]) var expiresAt int = int(claims["expiresAt"].(float64)) now := time.Now() secs := now.Unix() // Check token expiry if secs > int64(expiresAt) { ctx.Error = true ctx.Message = "Token expires" s.render("error", w, ctx) return } user, err := s.db.GetUser(username) if err != nil { ctx.Error = true ctx.Message = "Error loading user" s.render("error", w, ctx) return } // Reset password if password != "" { hash, err := s.pm.CreatePassword(password) if err != nil { ctx.Error = true ctx.Message = "Error loading user" s.render("error", w, ctx) return } user.Password = hash // Save user if err := s.db.SetUser(username, user); err != nil { ctx.Error = true ctx.Message = "Error loading user" s.render("error", w, ctx) return } } log.Infof("password changed: %v", user) // Show success msg ctx.Error = false ctx.Message = "Password reset successfully." s.render("error", w, ctx) } else { ctx.Error = true ctx.Message = err.Error() s.render("error", w, ctx) return } } }