Files
spyda/internal/passwd_handlers.go
2021-01-30 15:42:27 +10:00

195 lines
4.6 KiB
Go

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
}
}
}