mirror of
				https://github.com/volatiletech/authboss.git
				synced 2025-10-30 23:47:59 +02:00 
			
		
		
		
	Reworking recover module
This commit is contained in:
		
							
								
								
									
										39
									
								
								config.go
									
									
									
									
									
								
							
							
						
						
									
										39
									
								
								config.go
									
									
									
									
									
								
							| @@ -5,6 +5,8 @@ import ( | ||||
| 	"io/ioutil" | ||||
| 	"net/smtp" | ||||
| 	"time" | ||||
|  | ||||
| 	"golang.org/x/crypto/bcrypt" | ||||
| ) | ||||
|  | ||||
| // Config holds all the configuration for both authboss and it's modules. | ||||
| @@ -15,12 +17,17 @@ type Config struct { | ||||
| 	ViewsPath string | ||||
| 	// HostName is self explanitory | ||||
| 	HostName string | ||||
| 	// BCryptPasswordCost is self explanitory. | ||||
| 	BCryptCost int | ||||
|  | ||||
| 	AuthLogoutRoute       string | ||||
| 	AuthLoginSuccessRoute string | ||||
|  | ||||
| 	RecoverInitiateRedirect     string | ||||
| 	RecoverRedirect             string | ||||
| 	RecoverInitiateSuccessFlash string | ||||
| 	RecoverTokenDuration        time.Duration | ||||
| 	RecoverTokenExpiredFlash    string | ||||
| 	RecoverFailedErrorFlash     string | ||||
|  | ||||
| 	Policies      []Validator | ||||
| 	ConfirmFields []string | ||||
| @@ -49,14 +56,38 @@ type Config struct { | ||||
| // NewConfig creates a new config full of default values ready to override. | ||||
| func NewConfig() *Config { | ||||
| 	return &Config{ | ||||
| 		MountPath: "/", | ||||
| 		ViewsPath: "/", | ||||
| 		MountPath:  "/", | ||||
| 		ViewsPath:  "/", | ||||
| 		HostName:   "localhost:8080", | ||||
| 		BCryptCost: bcrypt.DefaultCost, | ||||
|  | ||||
| 		AuthLogoutRoute:       "/", | ||||
| 		AuthLoginSuccessRoute: "/", | ||||
|  | ||||
| 		RecoverInitiateRedirect:     "/login", | ||||
| 		Policies: []Validator{ | ||||
| 			Rules{ | ||||
| 				FieldName:       "username", | ||||
| 				Required:        true, | ||||
| 				MinLength:       2, | ||||
| 				MaxLength:       4, | ||||
| 				AllowWhitespace: false, | ||||
| 			}, | ||||
| 			Rules{ | ||||
| 				FieldName: "password", | ||||
| 				Required:  true, | ||||
| 				MinLength: 4, | ||||
| 				MaxLength: 8, | ||||
|  | ||||
| 				AllowWhitespace: false, | ||||
| 			}, | ||||
| 		}, | ||||
| 		ConfirmFields: []string{"username", "confirmUsername", "password", "confirmPassword"}, | ||||
|  | ||||
| 		RecoverRedirect:             "/login", | ||||
| 		RecoverInitiateSuccessFlash: "An email has been sent with further insructions on how to reset your password", | ||||
| 		RecoverTokenDuration:        time.Duration(24) * time.Hour, | ||||
| 		RecoverTokenExpiredFlash:    "Account recovery request has expired.  Please try agian.", | ||||
| 		RecoverFailedErrorFlash:     "Account recovery has failed.  Please contact tech support.", | ||||
|  | ||||
| 		LogWriter: ioutil.Discard, | ||||
| 		Callbacks: NewCallbacks(), | ||||
|   | ||||
							
								
								
									
										11
									
								
								internal/httputil/httputil.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								internal/httputil/httputil.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,11 @@ | ||||
| package httputil | ||||
|  | ||||
| import "gopkg.in/authboss.v0" | ||||
|  | ||||
| // PullFlash is a convenience func to retreive then delete a flash message.  Any ok | ||||
| // checks are ignored as they don't alter the intended use. | ||||
| func PullFlash(storer authboss.ClientStorer, key string) string { | ||||
| 	value, _ := storer.Get(key) | ||||
| 	storer.Del(key) | ||||
| 	return value | ||||
| } | ||||
| @@ -76,7 +76,7 @@ func login_tpl() (*asset, error) { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	info := bindata_file_info{name: "login.tpl", size: 484, mode: os.FileMode(438), modTime: time.Unix(1421030024, 0)} | ||||
| 	info := bindata_file_info{name: "login.tpl", size: 484, mode: os.FileMode(438), modTime: time.Unix(1421625609, 0)} | ||||
| 	a := &asset{bytes: bytes, info:  info} | ||||
| 	return a, nil | ||||
| } | ||||
| @@ -96,27 +96,47 @@ func recover_complete_tpl() (*asset, error) { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	info := bindata_file_info{name: "recover-complete.tpl", size: 0, mode: os.FileMode(438), modTime: time.Unix(1421615708, 0)} | ||||
| 	info := bindata_file_info{name: "recover-complete.tpl", size: 0, mode: os.FileMode(438), modTime: time.Unix(1421625609, 0)} | ||||
| 	a := &asset{bytes: bytes, info:  info} | ||||
| 	return a, nil | ||||
| } | ||||
|  | ||||
| var _recover_init_email = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\xaa\xae\xd6\xf3\xc9\xcc\xcb\xae\xad\x05\x04\x00\x00\xff\xff\x41\xf7\xa1\x3d\x09\x00\x00\x00") | ||||
| var _recover_html_email = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\xb2\x29\x2e\x29\xca\xcf\x4b\xb7\xab\xae\xd6\xf3\xc9\xcc\xcb\xae\xad\xb5\xd1\x87\x8a\x00\x02\x00\x00\xff\xff\xe1\x46\x1b\xff\x1a\x00\x00\x00") | ||||
|  | ||||
| func recover_init_email_bytes() ([]byte, error) { | ||||
| func recover_html_email_bytes() ([]byte, error) { | ||||
| 	return bindata_read( | ||||
| 		_recover_init_email, | ||||
| 		"recover-init.email", | ||||
| 		_recover_html_email, | ||||
| 		"recover-html.email", | ||||
| 	) | ||||
| } | ||||
|  | ||||
| func recover_init_email() (*asset, error) { | ||||
| 	bytes, err := recover_init_email_bytes() | ||||
| func recover_html_email() (*asset, error) { | ||||
| 	bytes, err := recover_html_email_bytes() | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	info := bindata_file_info{name: "recover-init.email", size: 9, mode: os.FileMode(438), modTime: time.Unix(1421607196, 0)} | ||||
| 	info := bindata_file_info{name: "recover-html.email", size: 26, mode: os.FileMode(438), modTime: time.Unix(1422337208, 0)} | ||||
| 	a := &asset{bytes: bytes, info:  info} | ||||
| 	return a, nil | ||||
| } | ||||
|  | ||||
| var _recover_text_email = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\xaa\xae\xd6\xf3\xc9\xcc\xcb\xae\xad\x05\x04\x00\x00\xff\xff\x41\xf7\xa1\x3d\x09\x00\x00\x00") | ||||
|  | ||||
| func recover_text_email_bytes() ([]byte, error) { | ||||
| 	return bindata_read( | ||||
| 		_recover_text_email, | ||||
| 		"recover-text.email", | ||||
| 	) | ||||
| } | ||||
|  | ||||
| func recover_text_email() (*asset, error) { | ||||
| 	bytes, err := recover_text_email_bytes() | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	info := bindata_file_info{name: "recover-text.email", size: 9, mode: os.FileMode(438), modTime: time.Unix(1421625609, 0)} | ||||
| 	a := &asset{bytes: bytes, info:  info} | ||||
| 	return a, nil | ||||
| } | ||||
| @@ -136,7 +156,7 @@ func recover_tpl() (*asset, error) { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	info := bindata_file_info{name: "recover.tpl", size: 18, mode: os.FileMode(438), modTime: time.Unix(1421128431, 0)} | ||||
| 	info := bindata_file_info{name: "recover.tpl", size: 18, mode: os.FileMode(438), modTime: time.Unix(1421625609, 0)} | ||||
| 	a := &asset{bytes: bytes, info:  info} | ||||
| 	return a, nil | ||||
| } | ||||
| @@ -184,7 +204,8 @@ func AssetNames() []string { | ||||
| var _bindata = map[string]func() (*asset, error){ | ||||
| 	"login.tpl": login_tpl, | ||||
| 	"recover-complete.tpl": recover_complete_tpl, | ||||
| 	"recover-init.email": recover_init_email, | ||||
| 	"recover-html.email": recover_html_email, | ||||
| 	"recover-text.email": recover_text_email, | ||||
| 	"recover.tpl": recover_tpl, | ||||
| } | ||||
|  | ||||
| @@ -232,7 +253,9 @@ var _bintree = &_bintree_t{nil, map[string]*_bintree_t{ | ||||
| 	}}, | ||||
| 	"recover-complete.tpl": &_bintree_t{recover_complete_tpl, map[string]*_bintree_t{ | ||||
| 	}}, | ||||
| 	"recover-init.email": &_bintree_t{recover_init_email, map[string]*_bintree_t{ | ||||
| 	"recover-html.email": &_bintree_t{recover_html_email, map[string]*_bintree_t{ | ||||
| 	}}, | ||||
| 	"recover-text.email": &_bintree_t{recover_text_email, map[string]*_bintree_t{ | ||||
| 	}}, | ||||
| 	"recover.tpl": &_bintree_t{recover_tpl, map[string]*_bintree_t{ | ||||
| 	}}, | ||||
|   | ||||
							
								
								
									
										1
									
								
								internal/views/templates/recover-html.email
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								internal/views/templates/recover-html.email
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | ||||
| <strong>{{.Link}}</strong> | ||||
| @@ -9,10 +9,12 @@ import ( | ||||
| 	"fmt" | ||||
| 	"html/template" | ||||
| 	"net/http" | ||||
| 	"time" | ||||
|  | ||||
| 	"io" | ||||
| 	"golang.org/x/crypto/bcrypt" | ||||
|  | ||||
| 	"gopkg.in/authboss.v0" | ||||
| 	"gopkg.in/authboss.v0/internal/httputil" | ||||
| 	"gopkg.in/authboss.v0/internal/views" | ||||
| ) | ||||
|  | ||||
| @@ -23,11 +25,16 @@ const ( | ||||
| 	tplLogin           = "login.tpl" | ||||
| 	tplRecover         = "recover.tpl" | ||||
| 	tplRecoverComplete = "recover-complete.tpl" | ||||
| 	tplInitEmail       = "recover-init.email" | ||||
| 	tplInitHTMLEmail   = "recover-html.email" | ||||
| 	tplInitTextEmail   = "recover-text.email" | ||||
|  | ||||
| 	attrUsername     = "username" | ||||
| 	attrRecoverToken = "recover_token" | ||||
| 	attrEmail        = "email" | ||||
| 	attrUsername           = "username" | ||||
| 	attrRecoverToken       = "recover_token" | ||||
| 	attrRecoverTokenExpiry = "recover_token_expiry" | ||||
| 	attrEmail              = "email" | ||||
| 	attrPassword           = "password" | ||||
|  | ||||
| 	errFormat = "recover [%s]: %s\n" | ||||
| ) | ||||
|  | ||||
| func init() { | ||||
| @@ -35,23 +42,9 @@ func init() { | ||||
| 	authboss.RegisterModule("recover", m) | ||||
| } | ||||
|  | ||||
| type RecoverPage struct { | ||||
| 	Username, ConfirmUsername string | ||||
| 	ErrMap                    map[string][]string | ||||
| } | ||||
|  | ||||
| type RecoverModule struct { | ||||
| 	templates                   *template.Template | ||||
| 	routes                      authboss.RouteTable | ||||
| 	storageOptions              authboss.StorageOptions | ||||
| 	storer                      authboss.RecoverStorer | ||||
| 	logger                      io.Writer | ||||
| 	policies                    []authboss.Validator | ||||
| 	confirmFields               []string | ||||
| 	hostName                    string | ||||
| 	recoverInitiateRedirect     string | ||||
| 	recoverInitiateSuccessFlash string | ||||
| 	fromEmail                   string | ||||
| 	templates *template.Template | ||||
| 	config    *authboss.Config | ||||
| } | ||||
|  | ||||
| func (m *RecoverModule) Initialize(config *authboss.Config) (err error) { | ||||
| @@ -59,148 +52,249 @@ func (m *RecoverModule) Initialize(config *authboss.Config) (err error) { | ||||
| 		return errors.New("recover: Need a RecoverStorer.") | ||||
| 	} | ||||
|  | ||||
| 	if storer, ok := config.Storer.(authboss.RecoverStorer); !ok { | ||||
| 	if _, ok := config.Storer.(authboss.RecoverStorer); !ok { | ||||
| 		return errors.New("recover: RecoverStorer required for recover functionality.") | ||||
| 	} else { | ||||
| 		m.storer = storer | ||||
| 	} | ||||
|  | ||||
| 	if m.templates, err = views.Get(config.ViewsPath, tplRecover, tplRecoverComplete, tplInitEmail); err != nil { | ||||
| 	if m.templates, err = views.Get(config.ViewsPath, tplRecover, tplRecoverComplete, tplInitHTMLEmail, tplInitTextEmail); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	m.routes = authboss.RouteTable{ | ||||
| 		"recover": m.recoverHandlerFunc, | ||||
| 		//"recover/complete": m.recoverCompleteHandlerFunc, | ||||
| 	} | ||||
| 	m.storageOptions = authboss.StorageOptions{ | ||||
| 		attrUsername:     authboss.String, | ||||
| 		attrRecoverToken: authboss.String, | ||||
| 		attrEmail:        authboss.String, | ||||
| 	} | ||||
| 	m.logger = config.LogWriter | ||||
| 	m.fromEmail = config.RecoverFromEmail | ||||
| 	m.hostName = config.HostName | ||||
| 	m.recoverInitiateRedirect = config.RecoverInitiateRedirect | ||||
| 	m.recoverInitiateSuccessFlash = config.RecoverInitiateSuccessFlash | ||||
| 	m.policies = config.Policies | ||||
| 	m.confirmFields = config.ConfirmFields | ||||
| 	m.config = config | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (m *RecoverModule) Routes() authboss.RouteTable      { return m.routes } | ||||
| func (m *RecoverModule) Storage() authboss.StorageOptions { return m.storageOptions } | ||||
| func (m *RecoverModule) Routes() authboss.RouteTable { | ||||
| 	return authboss.RouteTable{ | ||||
| 		"recover":          m.recoverHandlerFunc, | ||||
| 		"recover/complete": m.recoverCompleteHandlerFunc, | ||||
| 	} | ||||
| } | ||||
| func (m *RecoverModule) Storage() authboss.StorageOptions { | ||||
| 	return authboss.StorageOptions{ | ||||
| 		attrUsername:           authboss.String, | ||||
| 		attrRecoverToken:       authboss.String, | ||||
| 		attrEmail:              authboss.String, | ||||
| 		attrRecoverTokenExpiry: authboss.String, | ||||
| 		attrPassword:           authboss.String, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| type pageRecover struct { | ||||
| 	Username, ConfirmUsername string | ||||
| 	ErrMap                    map[string][]string | ||||
| 	FlashError                string | ||||
| } | ||||
|  | ||||
| func (m *RecoverModule) recoverHandlerFunc(ctx *authboss.Context, w http.ResponseWriter, r *http.Request) { | ||||
| 	execTpl := func(data interface{}) { | ||||
| 		if err := m.templates.ExecuteTemplate(w, tplRecover, data); err != nil { | ||||
| 			fmt.Fprintf(m.config.LogWriter, errFormat, "unable to execute template", err) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	switch r.Method { | ||||
| 	case methodGET: | ||||
| 		m.templates.ExecuteTemplate(w, tplRecover, nil) | ||||
| 		execTpl(pageRecover{FlashError: httputil.PullFlash(ctx.SessionStorer, authboss.FlashErrorKey)}) | ||||
| 	case methodPOST: | ||||
| 		// ignore ok checks as we validate these fields anyways | ||||
| 		username, _ := ctx.FirstPostFormValue("username") | ||||
| 		confirmUsername, _ := ctx.FirstPostFormValue("confirmUsername") | ||||
|  | ||||
| 		policies := authboss.FilterValidators(m.policies, "username") | ||||
| 		if validationErrs := ctx.Validate(policies, m.confirmFields...); len(validationErrs) > 0 { | ||||
| 			err := m.templates.ExecuteTemplate(w, tplRecover, RecoverPage{username, confirmUsername, validationErrs.Map()}) | ||||
| 			if err != nil { | ||||
| 				fmt.Fprintln(m.logger, "recover:", err) | ||||
| 			} | ||||
| 		policies := authboss.FilterValidators(m.config.Policies, "username") | ||||
| 		if validationErrs := ctx.Validate(policies, m.config.ConfirmFields...); len(validationErrs) > 0 { | ||||
| 			fmt.Fprintf(m.config.LogWriter, errFormat, "validation failed", validationErrs) | ||||
| 			execTpl(pageRecover{username, confirmUsername, validationErrs.Map(), ""}) | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 		if err := m.initiateRecover(ctx, username, confirmUsername); err != nil { | ||||
| 			fmt.Fprintln(m.logger, fmt.Sprintf("recover: %s", err.Error())) | ||||
| 		if err := m.recover(ctx, username); err != nil { | ||||
| 			// never reveal failed usernames to prevent sniffing | ||||
| 			fmt.Fprintf(m.config.LogWriter, errFormat, "failed to recover", err) | ||||
| 			execTpl(pageRecover{username, confirmUsername, nil, m.config.RecoverFailedErrorFlash}) | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 		ctx.SessionStorer.Put(authboss.FlashSuccessKey, m.recoverInitiateSuccessFlash) | ||||
| 		http.Redirect(w, r, m.recoverInitiateRedirect, http.StatusFound) | ||||
| 		ctx.SessionStorer.Put(authboss.FlashSuccessKey, m.config.RecoverInitiateSuccessFlash) | ||||
| 		http.Redirect(w, r, m.config.RecoverRedirect, http.StatusFound) | ||||
| 	default: | ||||
| 		w.WriteHeader(http.StatusMethodNotAllowed) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (m *RecoverModule) initiateRecover(ctx *authboss.Context, username, confirmUsername string) (err error) { | ||||
| 	if err := ctx.LoadUser(username, m.storer); err != nil { | ||||
| func (m *RecoverModule) recover(ctx *authboss.Context, username string) (err error) { | ||||
| 	if err := ctx.LoadUser(username, m.config.Storer); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	email, ok := ctx.User.String(attrEmail) | ||||
| 	if !ok { | ||||
| 		return fmt.Errorf("missing attr: %s", email) | ||||
| 	} | ||||
|  | ||||
| 	token := make([]byte, 32) | ||||
| 	if _, err := rand.Read(token); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	sum := md5.Sum(token) | ||||
|  | ||||
| 	ctx.User[attrRecoverToken] = base64.StdEncoding.EncodeToString(sum[:]) | ||||
| 	ctx.User[attrRecoverTokenExpiry] = time.Now().Add(m.config.RecoverTokenDuration) | ||||
|  | ||||
| 	if err := ctx.SaveUser(username, m.storer); err != nil { | ||||
| 	if err := ctx.SaveUser(username, m.config.Storer); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	emailBody := &bytes.Buffer{} | ||||
| 	if err := m.templates.ExecuteTemplate(emailBody, tplInitEmail, struct{ Link string }{ | ||||
| 		fmt.Sprintf("%s/recover/complete?token=%s", m.hostName, base64.URLEncoding.EncodeToString(sum[:])), | ||||
| 	}); err != nil { | ||||
| 		return err | ||||
| 	if email, ok := ctx.User.String(attrEmail); !ok { | ||||
| 		return errors.New("email not found; unable to send email") | ||||
| 	} else { | ||||
| 		go m.sendRecoverEmail(email, token) | ||||
| 	} | ||||
|  | ||||
| 	return authboss.SendEmail(email, m.fromEmail, emailBody.Bytes()) | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| /*func (m *RecoverModule) recoverCompleteHandlerFunc(ctx *authboss.Context, w http.ResponseWriter, r *http.Request) { | ||||
| func (m *RecoverModule) sendRecoverEmail(to string, token []byte) { | ||||
| 	data := struct{ Link string }{fmt.Sprintf("%s/recover/complete?token=%s", m.config.HostName, base64.URLEncoding.EncodeToString(token))} | ||||
|  | ||||
| 	htmlEmailBody := &bytes.Buffer{} | ||||
| 	if err := m.templates.ExecuteTemplate(htmlEmailBody, tplInitHTMLEmail, data); err != nil { | ||||
| 		fmt.Fprintf(m.config.LogWriter, errFormat, "failed to build html tpl", err) | ||||
| 	} | ||||
|  | ||||
| 	textEmaiLBody := &bytes.Buffer{} | ||||
| 	if err := m.templates.ExecuteTemplate(textEmaiLBody, tplInitTextEmail, data); err != nil { | ||||
| 		fmt.Fprintf(m.config.LogWriter, errFormat, "failed to build plaintext tpl", err) | ||||
| 	} | ||||
|  | ||||
| 	if err := m.config.Mailer.Send(authboss.Email{ | ||||
| 		To:       []string{to}, | ||||
| 		ToNames:  []string{""}, | ||||
| 		From:     m.config.EmailFrom, | ||||
| 		Subject:  "Password Reset", | ||||
| 		TextBody: textEmaiLBody.String(), | ||||
| 		HTMLBody: htmlEmailBody.String(), | ||||
| 	}); err != nil { | ||||
| 		fmt.Fprintf(m.config.LogWriter, errFormat, "failed to send email", err) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| type pageRecoverComplete struct { | ||||
| 	Token      string | ||||
| 	ErrMap     map[string][]string | ||||
| 	FlashError string | ||||
| } | ||||
|  | ||||
| func (m *RecoverModule) recoverCompleteHandlerFunc(ctx *authboss.Context, w http.ResponseWriter, r *http.Request) { | ||||
| 	switch r.Method { | ||||
| 	case methodGET: | ||||
| 		page := pageRecoverComplete{} | ||||
|  | ||||
| 		if msg, ok := ctx.SessionStorer.Get(authboss.FlashErrorKey); ok { | ||||
| 			page.FlashError = msg | ||||
| 			ctx.SessionStorer.Del(authboss.FlashErrorKey) | ||||
| 		} | ||||
|  | ||||
| 		token, ok := ctx.FirstFormValue("token") | ||||
| 		if !ok { | ||||
| 			fmt.Fprintln(m.logger, "recover: expected value token") | ||||
| 			//http.Redirect(w, r, "/", http.StatusFound) | ||||
| 			fmt.Fprintln(m.config.LogWriter, "recover: expected value token") | ||||
| 			http.Redirect(w, r, "/", http.StatusFound) | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 		userAttrs, err := m.verifyToken(token); | ||||
| 		var err error | ||||
| 		ctx.User, err = m.verifyToken(token) | ||||
| 		if err != nil { | ||||
| 			fmt.Fprintf(m.logger, "recover: %s", err) | ||||
| 			//http.Redirect(w, r, urlStr, code) | ||||
| 			fmt.Fprintln(m.config.LogWriter, "recover:", err) | ||||
| 			http.Redirect(w, r, "/", http.StatusFound) | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 		expiry, ok := ctx.User.DateTime(attrRecoverTokenExpiry) | ||||
| 		if !ok || time.Now().After(expiry) { | ||||
| 			fmt.Fprintln(m.config.LogWriter, "recover: token has expired:", expiry) | ||||
| 			ctx.SessionStorer.Put(authboss.FlashErrorKey, m.config.RecoverTokenExpiredFlash) | ||||
| 			http.Redirect(w, r, "/recover", http.StatusFound) | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
|  | ||||
| 		m.templates.ExecuteTemplate(w, tplRecoverComplete, nil) | ||||
| 		page.Token = token | ||||
| 		if err := m.templates.ExecuteTemplate(w, tplRecoverComplete, pageRecoverComplete{Token: token}); err != nil { | ||||
| 			fmt.Fprintln(m.config.LogWriter, "recover:", err) | ||||
| 		} | ||||
| 	case methodPOST: | ||||
| 		//if err := completeRecover(ctx); err := | ||||
| 		token, ok := ctx.FirstFormValue("token") | ||||
| 		if !ok { | ||||
| 			fmt.Fprintln(m.config.LogWriter, "recover: expected value token") | ||||
| 		} | ||||
|  | ||||
| 		var err error | ||||
| 		ctx.User, err = m.verifyToken(token) | ||||
| 		if err != nil { | ||||
| 			fmt.Fprintln(m.config.LogWriter, "recover:", err) | ||||
| 			http.Redirect(w, r, "/", http.StatusFound) | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 		policies := authboss.FilterValidators(m.config.Policies, "password") | ||||
| 		if validationErrs := ctx.Validate(policies, m.config.ConfirmFields...); len(validationErrs) > 0 { | ||||
| 			err := m.templates.ExecuteTemplate(w, tplRecoverComplete, pageRecoverComplete{Token: token, ErrMap: validationErrs.Map()}) | ||||
| 			if err != nil { | ||||
| 				fmt.Fprintln(m.config.LogWriter, "recover:", err) | ||||
| 			} | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 		password, _ := ctx.FirstFormValue("password") | ||||
|  | ||||
| 		encryptedPassword, err := bcrypt.GenerateFromPassword([]byte(password), m.config.BCryptCost) | ||||
| 		if err != nil { | ||||
| 			fmt.Fprintln(m.config.LogWriter, "recover: failed to encrypt password") | ||||
| 			err := m.templates.ExecuteTemplate(w, tplRecoverComplete, pageRecoverComplete{Token: token, FlashError: m.config.RecoverFailedErrorFlash}) | ||||
| 			if err != nil { | ||||
| 				fmt.Fprintln(m.config.LogWriter, "recover:", err) | ||||
| 			} | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 		ctx.User[attrPassword] = string(encryptedPassword) | ||||
|  | ||||
| 		username, ok := ctx.User.String(attrUsername) | ||||
| 		if !ok { | ||||
| 			fmt.Println(m.config.LogWriter, "reover: expected user attribue missing:", attrUsername) | ||||
| 			err := m.templates.ExecuteTemplate(w, tplRecoverComplete, pageRecoverComplete{Token: token, FlashError: m.config.RecoverFailedErrorFlash}) | ||||
| 			if err != nil { | ||||
| 				fmt.Fprintln(m.config.LogWriter, "recover:", err) | ||||
| 			} | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 		ctx.User[attrRecoverToken] = "" | ||||
| 		ctx.User[attrRecoverTokenExpiry] = time.Now().UTC() | ||||
|  | ||||
| 		if err := ctx.SaveUser(username, m.config.Storer); err != nil { | ||||
| 			fmt.Fprintln(m.config.LogWriter, "recover:", err) | ||||
| 			err := m.templates.ExecuteTemplate(w, tplRecoverComplete, pageRecoverComplete{Token: token, FlashError: m.config.RecoverFailedErrorFlash}) | ||||
| 			if err != nil { | ||||
| 				fmt.Fprintln(m.config.LogWriter, "recover:", err) | ||||
| 			} | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 		ctx.SessionStorer.Put(authboss.SessionKey, username) | ||||
| 		http.Redirect(w, r, m.config.AuthLoginSuccessRoute, http.StatusFound) | ||||
| 	default: | ||||
| 		w.WriteHeader(http.StatusMethodNotAllowed) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (m *RecoverModule) verifyToken(token) (attrs authboss.Attributes, err) { | ||||
| func (m *RecoverModule) verifyToken(token string) (attrs authboss.Attributes, err error) { | ||||
| 	decodedToken, err := base64.URLEncoding.DecodeString(token) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	sum := md5.Sum(decodedToken) | ||||
|  | ||||
| 	userInter, err := m.storer.RecoverUser(base64.StdEncoding.EncodeToString(sum[:])) | ||||
| 	userInter, err := m.config.Storer.(authboss.RecoverStorer).RecoverUser(base64.StdEncoding.EncodeToString(sum[:])) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	return authboss.Unbind(userInter), nil | ||||
| } | ||||
|  | ||||
| func (m *RecoverModule) completeRecover(ctx *authboss.Context, password, confirmPassword string) error { | ||||
| 	if password == confirmPassword { | ||||
| 		return errors.New("Passwords do not match") | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
| */ | ||||
|   | ||||
		Reference in New Issue
	
	Block a user