1
0
mirror of https://github.com/volatiletech/authboss.git synced 2025-04-13 11:50:27 +02:00

Add UpdatePassword.

- Fix #50
This commit is contained in:
Aaron L 2015-03-16 22:58:32 -07:00
parent 04d2716963
commit e83110ee31
3 changed files with 180 additions and 2 deletions

View File

@ -68,6 +68,7 @@ Once you've got this code set up, it's time to implement the use cases you care
<a name="use_cases"></a>Use Cases
=================================
- Get the logged in user ([goto](#current_user))
- Reset a User's password ([goto](#reset_password))
- User authentication via password ([goto](#auth))
- User authentication via OAuth2 ([goto](#oauth2))
- User registration ([goto](#register))
@ -103,6 +104,34 @@ nil, ErrUserNotFound | Session had user ID, but user not found in database.
nil, err | Some horrible error has occurred.
user struct, nil | The user is logged in.
## <a name="reset_password"></a>Reset a User's password
Because on password reset various cleanings need to happen (for example Remember Me tokens
should all be deleted) setting the password yourself is not a good idea.
Authboss has the UpdatePassword method for you to use. Please consult it's documentation
for a thorough explanation of each parameter.
```go
func UpdatePassword(w http.ResponseWriter, r *http.Request, ptPassword string, user interface{}, updater func() error) error {
```
Please read it's documentation as it's quite thorough, and example usage might be:
```go
myUserSave := func() error {
_, err := db.Exec(`update user set name = $1, password = $2 where id = $3`, user.Name, user.Password, user.ID)
return err
}
// HINT: Never pass the form value directly into the database as you see here :D
err := UpdatePassword(w, r, r.FormValue("password"), &user1, myUserSave)
if err != nil {
// Handle error here, in most cases this will be the error from myUserSave
}
```
## <a name="auth"></a>User Authentication via Password
**Requirements:**
- Auth module ([gopkg.in/authboss.v0/auth](https://github.com/go-authboss/authboss/tree/master/auth))

View File

@ -7,9 +7,14 @@ races without having to think about how to store passwords or remember tokens.
package authboss // import "gopkg.in/authboss.v0"
import (
"database/sql"
"errors"
"fmt"
"net/http"
"reflect"
"strings"
"golang.org/x/crypto/bcrypt"
)
// Init authboss and it's loaded modules.
@ -70,3 +75,66 @@ func CurrentUserP(w http.ResponseWriter, r *http.Request) interface{} {
}
return i
}
/*
UpdatePassword should be called to recalculate hashes and do any cleanup
that should occur on password resets. Updater should return an error if the
update to the user failed (for reasons say like validation, duplicate
primary key, etc...). In that case the cleanup will not be performed.
The w and r parameters are for establishing session and cookie storers.
The ptPassword parameter is for the password to update to, if it is empty then
nothing is updated and the cleanup routines are not called.
The user parameter is the user struct which will have it's
Password string/sql.NullString value set to the new bcrypted password. Therefore
it must be passed in as a pointer with the Password field exported or an error
will be returned.
The error returned is returned either from the updater if that produced an error
or from the cleanup routines.
*/
func UpdatePassword(w http.ResponseWriter, r *http.Request,
ptPassword string, user interface{}, updater func() error) error {
updatePwd := len(ptPassword) > 0
if updatePwd {
pass, err := bcrypt.GenerateFromPassword([]byte(ptPassword), Cfg.BCryptCost)
if err != nil {
return err
}
val := reflect.ValueOf(user).Elem()
field := val.FieldByName("Password")
if !field.CanSet() {
return errors.New("authboss: UpdatePassword called without a modifyable user struct")
}
fieldPtr := field.Addr()
if scanner, ok := fieldPtr.Interface().(sql.Scanner); ok {
if err := scanner.Scan(string(pass)); err != nil {
return err
}
} else {
field.SetString(string(pass))
}
}
if err := updater(); err != nil {
return err
}
if !updatePwd {
return nil
}
ctx, err := ContextFromRequest(r)
if err != nil {
return err
}
ctx.SessionStorer = clientStoreWrapper{Cfg.SessionStoreMaker(w, r)}
ctx.CookieStorer = clientStoreWrapper{Cfg.CookieStoreMaker(w, r)}
return Cfg.Callbacks.FireAfter(EventPasswordReset, ctx)
}

View File

@ -1,6 +1,8 @@
package authboss
import (
"database/sql"
"errors"
"net/http"
"net/http/httptest"
"os"
@ -15,7 +17,7 @@ func TestMain(main *testing.M) {
}
func TestAuthBossInit(t *testing.T) {
NewConfig()
Cfg = NewConfig()
err := Init()
if err != nil {
t.Error("Unexpected error:", err)
@ -23,7 +25,7 @@ func TestAuthBossInit(t *testing.T) {
}
func TestAuthBossCurrentUser(t *testing.T) {
NewConfig()
Cfg = NewConfig()
Cfg.Storer = mockStorer{"joe": Attributes{"email": "john@john.com", "password": "lies"}}
Cfg.SessionStoreMaker = func(_ http.ResponseWriter, _ *http.Request) ClientStorer {
return mockClientStore{SessionKey: "joe"}
@ -46,3 +48,82 @@ func TestAuthBossCurrentUser(t *testing.T) {
t.Error("Wrong user found!")
}
}
func TestAuthbossUpdatePassword(t *testing.T) {
Cfg = NewConfig()
session := mockClientStore{}
cookies := mockClientStore{}
Cfg.SessionStoreMaker = func(_ http.ResponseWriter, _ *http.Request) ClientStorer {
return session
}
Cfg.CookieStoreMaker = func(_ http.ResponseWriter, _ *http.Request) ClientStorer {
return cookies
}
called := false
Cfg.Callbacks.After(EventPasswordReset, func(ctx *Context) error {
called = true
return nil
})
user1 := struct {
Password string
}{}
user2 := struct {
Password sql.NullString
}{}
r, _ := http.NewRequest("GET", "http://localhost", nil)
called = false
err := UpdatePassword(nil, r, "newpassword", &user1, func() error { return nil })
if err != nil {
t.Error(err)
}
if len(user1.Password) == 0 {
t.Error("Password not updated")
}
if !called {
t.Error("Callbacks should have been called.")
}
called = false
err = UpdatePassword(nil, r, "newpassword", &user2, func() error { return nil })
if err != nil {
t.Error(err)
}
if !user2.Password.Valid || len(user2.Password.String) == 0 {
t.Error("Password not updated")
}
if !called {
t.Error("Callbacks should have been called.")
}
called = false
oldPassword := user1.Password
err = UpdatePassword(nil, r, "", &user1, func() error { return nil })
if err != nil {
t.Error(err)
}
if user1.Password != oldPassword {
t.Error("Password not updated")
}
if called {
t.Error("Callbacks should not have been called")
}
}
func TestAuthbossUpdatePasswordFail(t *testing.T) {
user1 := struct {
Password string
}{}
anErr := errors.New("AnError")
err := UpdatePassword(nil, nil, "update", &user1, func() error { return anErr })
if err != anErr {
t.Error("Expected an specific error:", err)
}
}