diff controllers/pwreset.go @ 302:0777aa6de45b

Password reset. Part I
author Sascha L. Teichmann <sascha.teichmann@intevation.de>
date Wed, 01 Aug 2018 12:29:55 +0200
parents
children 69e291f26bbd
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/controllers/pwreset.go	Wed Aug 01 12:29:55 2018 +0200
@@ -0,0 +1,125 @@
+package controllers
+
+import (
+	"database/sql"
+	"encoding/hex"
+	"fmt"
+	"log"
+	"net/http"
+	"strings"
+
+	"gemma.intevation.de/gemma/auth"
+	"gemma.intevation.de/gemma/config"
+	gomail "gopkg.in/gomail.v2"
+)
+
+const (
+	userExistsSQL = `SELECT email_address
+    FROM users.list_users WHERE username = $1
+    LIMIT 1`
+)
+
+const (
+	mailTmpl = `You have requested a password change for your account on
+%s://%s
+
+Please follow this link to get to the page where you can change your password.
+
+%s://%s/#/users/passwordreset/%s
+
+The link is only valid for one hour.
+
+Best regards
+    Your service team`
+)
+
+func messageBody(https bool, hash, serverName string) string {
+
+	var proto string
+
+	if https {
+		proto = "https"
+	} else {
+		proto = "http"
+	}
+
+	return fmt.Sprintf(mailTmpl,
+		proto, serverName,
+		proto, serverName,
+		hash)
+}
+
+const hashLength = 32
+
+func generateHash() string {
+	return hex.EncodeToString(auth.GenerateRandomKey(hashLength))
+}
+
+func passwordReset(
+	input interface{},
+	req *http.Request,
+	db *sql.DB,
+) (jr JSONResult, err error) {
+
+	log.Println("passwordreset")
+
+	user := input.(*PWResetUser)
+
+	if user.User == "" {
+		err = JSONError{http.StatusBadRequest, "Invalid user name"}
+		return
+	}
+
+	cfg := &config.Config
+
+	if db, err = auth.OpenDB(cfg.ServiceUser, cfg.ServicePassword); err != nil {
+		return
+	}
+	defer db.Close()
+
+	var email string
+	err = db.QueryRow(userExistsSQL, user.User).Scan(&email)
+
+	switch {
+	case err == sql.ErrNoRows:
+		err = JSONError{http.StatusNotFound, "User does not exist."}
+		return
+	case err != nil:
+		return
+	}
+
+	hash := generateHash()
+
+	serverName := req.Host
+	useHTTPS := strings.ToLower(req.URL.Scheme) == "https"
+
+	body := messageBody(useHTTPS, hash, serverName)
+
+	m := gomail.NewMessage()
+	m.SetHeader("From", cfg.MailFrom)
+	m.SetHeader("To", email)
+	m.SetHeader("Subject", "Password Reset Link")
+	m.SetBody("text/plain", body)
+
+	d := gomail.Dialer{
+		Host:      cfg.MailHost,
+		Port:      int(cfg.MailPort),
+		Username:  cfg.MailUser,
+		Password:  cfg.MailPassword,
+		LocalName: cfg.MailHelo,
+		SSL:       cfg.MailPort == 465,
+	}
+
+	if err = d.DialAndSend(m); err != nil {
+		return
+	}
+
+	// TODO: Keep hash/user for one hour or till resolved.
+
+	jr.Result = &struct {
+		SendTo string `json:"send-to"`
+	}{
+		SendTo: email,
+	}
+	return
+}