Mercurial > gemma
changeset 302:0777aa6de45b
Password reset. Part I
author | Sascha L. Teichmann <sascha.teichmann@intevation.de> |
---|---|
date | Wed, 01 Aug 2018 12:29:55 +0200 |
parents | 1781e5d7cb5a |
children | 75e32633fb96 69e291f26bbd |
files | 3rdpartylibs.sh auth/opendb.go auth/pool.go cmd/gemma/root.go config/config.go controllers/json.go controllers/pwreset.go controllers/routes.go controllers/types.go |
diffstat | 9 files changed, 199 insertions(+), 24 deletions(-) [+] |
line wrap: on
line diff
--- a/3rdpartylibs.sh Tue Jul 31 22:24:37 2018 +0200 +++ b/3rdpartylibs.sh Wed Aug 01 12:29:55 2018 +0200 @@ -5,3 +5,4 @@ go get -u -v github.com/spf13/cobra go get -u -v github.com/spf13/viper go get -u -v github.com/gorilla/mux +go get -u -v gopkg.in/gomail.v2
--- a/auth/opendb.go Tue Jul 31 22:24:37 2018 +0200 +++ b/auth/opendb.go Wed Aug 01 12:29:55 2018 +0200 @@ -24,7 +24,7 @@ dbQuote(user), dbQuote(password), sslmode) } -func opendb(user, password string) (*sql.DB, error) { +func OpenDB(user, password string) (*sql.DB, error) { dsn := dbDSN( config.Config.DBHost, config.Config.DBPort, config.Config.DBName, @@ -45,7 +45,7 @@ WHERE oid IN (SELECT oid FROM cte) AND rolname <> current_user` func AllOtherRoles(user, password string) ([]string, error) { - db, err := opendb(user, password) + db, err := OpenDB(user, password) if err != nil { return nil, err }
--- a/auth/pool.go Tue Jul 31 22:24:37 2018 +0200 +++ b/auth/pool.go Wed Aug 01 12:29:55 2018 +0200 @@ -256,7 +256,7 @@ } session := con.session - db, err := opendb(session.User, session.Password) + db, err := OpenDB(session.User, session.Password) if err != nil { res <- result{err: err} return
--- a/cmd/gemma/root.go Tue Jul 31 22:24:37 2018 +0200 +++ b/cmd/gemma/root.go Wed Aug 01 12:29:55 2018 +0200 @@ -35,27 +35,52 @@ cobra.OnInitialize(initConfig) fl := rootCmd.PersistentFlags fl().StringVarP(&configFile, "config", "c", "", "config file (default is $HOME/.gemma.toml)") - fl().StringVarP(&config.Config.DBHost, "dbhost", "H", "localhost", "host of the database") - fl().UintVarP(&config.Config.DBPort, "dbport", "P", 5432, "port of the database") - fl().StringVarP(&config.Config.DBName, "dbname", "d", "gemma", "name of the database") - fl().StringVarP(&config.Config.DBSSLMode, "dbssl", "S", "prefer", "SSL mode of the database") + + cfg := &config.Config + + fl().StringVarP(&cfg.DBHost, "dbhost", "H", "localhost", "host of the database") + fl().UintVarP(&cfg.DBPort, "dbport", "P", 5432, "port of the database") + fl().StringVarP(&cfg.DBName, "dbname", "d", "gemma", "name of the database") + fl().StringVarP(&cfg.DBSSLMode, "dbssl", "S", "prefer", "SSL mode of the database") - fl().StringVarP(&config.Config.SessionStore, "sessions", "s", "", "path to the sessions file") + fl().StringVarP(&cfg.SessionStore, "sessions", "s", "", "path to the sessions file") - fl().StringVarP(&config.Config.Web, "web", "w", "", "path to the web files") - fl().StringVarP(&config.Config.WebHost, "host", "o", "localhost", "host of the web app") - fl().UintVarP(&config.Config.WebPort, "port", "p", 8000, "port of the web app") + fl().StringVarP(&cfg.Web, "web", "w", "", "path to the web files") + fl().StringVarP(&cfg.WebHost, "host", "o", "localhost", "host of the web app") + fl().UintVarP(&cfg.WebPort, "port", "p", 8000, "port of the web app") + + fl().StringVar(&cfg.ServiceUser, "service-user", "postgres", "user to do service tasks") + fl().StringVar(&cfg.ServicePassword, "service-password", "", "password of user to do service tasks") - viper.BindPFlag("dbhost", fl().Lookup("dbhost")) - viper.BindPFlag("dbport", fl().Lookup("dbport")) - viper.BindPFlag("dbname", fl().Lookup("dbname")) - viper.BindPFlag("dbssl", fl().Lookup("dbssl")) + fl().StringVar(&cfg.MailHost, "mail-host", "localhost", "server to send mail with") + fl().UintVar(&cfg.MailPort, "mail-port", 464, "port of server to send mail with") + fl().StringVar(&cfg.MailUser, "mail-user", "gemma", "user to send mail with") + fl().StringVar(&cfg.MailPassword, "mail-password", "", "password of user to send mail with") + fl().StringVar(&cfg.MailFrom, "mail-from", "noreplay@localhost", "from line of mails") + fl().StringVar(&cfg.MailHelo, "mail-helo", "localhost", "name of server to send mail from.") + + vbind := func(name string) { viper.BindPFlag(name, fl().Lookup(name)) } + + vbind("dbhost") + vbind("dbport") + vbind("dbname") + vbind("dbssl") - viper.BindPFlag("sessions", fl().Lookup("sessions")) + vbind("sessions") + + vbind("web") + vbind("host") + vbind("port") - viper.BindPFlag("web", fl().Lookup("web")) - viper.BindPFlag("host", fl().Lookup("host")) - viper.BindPFlag("port", fl().Lookup("port")) + vbind("service-user") + vbind("service-password") + + vbind("mail-host") + vbind("mail-port") + vbind("mail-user") + vbind("mail-password") + vbind("mail-from") + vbind("mail-helo") } func initConfig() {
--- a/config/config.go Tue Jul 31 22:24:37 2018 +0200 +++ b/config/config.go Wed Aug 01 12:29:55 2018 +0200 @@ -13,4 +13,14 @@ Web string WebHost string WebPort uint + + ServiceUser string + ServicePassword string + + MailHost string + MailPort uint + MailUser string + MailPassword string + MailFrom string + MailHelo string }
--- a/controllers/json.go Tue Jul 31 22:24:37 2018 +0200 +++ b/controllers/json.go Wed Aug 01 12:29:55 2018 +0200 @@ -43,12 +43,17 @@ } } - token, _ := auth.GetToken(req) var jr JSONResult - err := auth.ConnPool.Do(token, func(db *sql.DB) (err error) { - jr, err = j.Handle(input, req, db) - return err - }) + var err error + + if token, ok := auth.GetToken(req); ok { + err = auth.ConnPool.Do(token, func(db *sql.DB) (err error) { + jr, err = j.Handle(input, req, db) + return err + }) + } else { + jr, err = j.Handle(input, req, nil) + } if err != nil { switch e := err.(type) {
--- /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 +}
--- a/controllers/routes.go Tue Jul 31 22:24:37 2018 +0200 +++ b/controllers/routes.go Wed Aug 01 12:29:55 2018 +0200 @@ -39,6 +39,11 @@ Handle: deleteUser, })).Methods(http.MethodDelete) + api.Handle("/users/passwordreset", &JSONHandler{ + Input: func() interface{} { return new(PWResetUser) }, + Handle: passwordReset, + }).Methods(http.MethodPost) + api.HandleFunc("/login", login). Methods(http.MethodGet, http.MethodPost) api.Handle("/logout", auth.SessionMiddleware(http.HandlerFunc(logout))).