mirror of
https://github.com/volatiletech/authboss.git
synced 2025-07-15 01:24:33 +02:00
29
README.md
29
README.md
@ -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
|
<a name="use_cases"></a>Use Cases
|
||||||
=================================
|
=================================
|
||||||
- Get the logged in user ([goto](#current_user))
|
- 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 password ([goto](#auth))
|
||||||
- User authentication via OAuth2 ([goto](#oauth2))
|
- User authentication via OAuth2 ([goto](#oauth2))
|
||||||
- User registration ([goto](#register))
|
- 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.
|
nil, err | Some horrible error has occurred.
|
||||||
user struct, nil | The user is logged in.
|
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
|
## <a name="auth"></a>User Authentication via Password
|
||||||
**Requirements:**
|
**Requirements:**
|
||||||
- Auth module ([gopkg.in/authboss.v0/auth](https://github.com/go-authboss/authboss/tree/master/auth))
|
- Auth module ([gopkg.in/authboss.v0/auth](https://github.com/go-authboss/authboss/tree/master/auth))
|
||||||
|
68
authboss.go
68
authboss.go
@ -7,9 +7,14 @@ races without having to think about how to store passwords or remember tokens.
|
|||||||
package authboss // import "gopkg.in/authboss.v0"
|
package authboss // import "gopkg.in/authboss.v0"
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/bcrypt"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Init authboss and it's loaded modules.
|
// Init authboss and it's loaded modules.
|
||||||
@ -70,3 +75,66 @@ func CurrentUserP(w http.ResponseWriter, r *http.Request) interface{} {
|
|||||||
}
|
}
|
||||||
return i
|
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)
|
||||||
|
}
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
package authboss
|
package authboss
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"errors"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"os"
|
"os"
|
||||||
@ -15,7 +17,7 @@ func TestMain(main *testing.M) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestAuthBossInit(t *testing.T) {
|
func TestAuthBossInit(t *testing.T) {
|
||||||
NewConfig()
|
Cfg = NewConfig()
|
||||||
err := Init()
|
err := Init()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error("Unexpected error:", err)
|
t.Error("Unexpected error:", err)
|
||||||
@ -23,7 +25,7 @@ func TestAuthBossInit(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestAuthBossCurrentUser(t *testing.T) {
|
func TestAuthBossCurrentUser(t *testing.T) {
|
||||||
NewConfig()
|
Cfg = NewConfig()
|
||||||
Cfg.Storer = mockStorer{"joe": Attributes{"email": "john@john.com", "password": "lies"}}
|
Cfg.Storer = mockStorer{"joe": Attributes{"email": "john@john.com", "password": "lies"}}
|
||||||
Cfg.SessionStoreMaker = func(_ http.ResponseWriter, _ *http.Request) ClientStorer {
|
Cfg.SessionStoreMaker = func(_ http.ResponseWriter, _ *http.Request) ClientStorer {
|
||||||
return mockClientStore{SessionKey: "joe"}
|
return mockClientStore{SessionKey: "joe"}
|
||||||
@ -46,3 +48,82 @@ func TestAuthBossCurrentUser(t *testing.T) {
|
|||||||
t.Error("Wrong user found!")
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user