diff --git a/auth/auth.go b/auth/auth.go index 726b404..a49aff2 100644 --- a/auth/auth.go +++ b/auth/auth.go @@ -18,8 +18,7 @@ const ( ) func init() { - a := &Auth{} - authboss.RegisterModule("auth", a) + authboss.RegisterModule("auth", &Auth{}) } type Auth struct { diff --git a/authboss.go b/authboss.go index 27d3c6d..b93de7c 100644 --- a/authboss.go +++ b/authboss.go @@ -27,6 +27,7 @@ func Init() error { // CurrentUser retrieves the current user from the session and the database. func CurrentUser(w http.ResponseWriter, r *http.Request) (interface{}, error) { sessions := Cfg.SessionStoreMaker(w, r) + cookies := Cfg.CookieStoreMaker(w, r) key, ok := sessions.Get(SessionKey) if !ok { return nil, nil @@ -37,6 +38,8 @@ func CurrentUser(w http.ResponseWriter, r *http.Request) (interface{}, error) { return nil, err } + ctx.SessionStorer = clientStoreWrapper{sessions} + ctx.CookieStorer = clientStoreWrapper{cookies} err = ctx.LoadUser(key) if err != nil { return nil, err diff --git a/config.go b/config.go index d7a6de7..5985564 100644 --- a/config.go +++ b/config.go @@ -109,6 +109,8 @@ func NewConfig() *Config { StorePassword, ConfirmPrefix + StorePassword, }, + ExpireAfter: time.Duration(60) * time.Minute, + RecoverOKPath: "/", RecoverTokenDuration: time.Duration(24) * time.Hour, diff --git a/confirm/confirm.go b/confirm/confirm.go index 0f8842b..af2607c 100644 --- a/confirm/confirm.go +++ b/confirm/confirm.go @@ -39,13 +39,8 @@ type ConfirmStorer interface { ConfirmUser(confirmToken string) (interface{}, error) } -// C is the singleton instance of the confirm module which will have been -// configured and ready to use after authboss.Init() -var C *Confirm - func init() { - C = &Confirm{} - authboss.RegisterModule("confirm", C) + authboss.RegisterModule("confirm", &Confirm{}) } type Confirm struct { diff --git a/expire/expire.go b/expire/expire.go index 9bc4504..670aee4 100644 --- a/expire/expire.go +++ b/expire/expire.go @@ -15,13 +15,8 @@ const ( SessionLastAction = "last_action" ) -// E is the singleton instance of the expire module which will have been -// configured and ready to use after authboss.Init() -var E *Expire - func init() { - E = &Expire{} - authboss.RegisterModule("expire", E) + authboss.RegisterModule("expire", &Expire{}) } type Expire struct{} diff --git a/lock/lock.go b/lock/lock.go index 5f5637d..c0d7590 100644 --- a/lock/lock.go +++ b/lock/lock.go @@ -18,13 +18,8 @@ var ( errUserMissing = errors.New("lock: user not loaded in BeforeAuth callback") ) -// L is the singleton instance of the lock module which will have been -// configured and ready to use after authboss.Init() -var L *Lock - func init() { - L = &Lock{} - authboss.RegisterModule("lock", L) + authboss.RegisterModule("lock", &Lock{}) } type Lock struct { diff --git a/lock/lock_test.go b/lock/lock_test.go index f40b9a9..0abcf48 100644 --- a/lock/lock_test.go +++ b/lock/lock_test.go @@ -9,8 +9,9 @@ import ( ) func TestStorage(t *testing.T) { + l := &Lock{} authboss.NewConfig() - storage := L.Storage() + storage := l.Storage() if _, ok := storage[StoreAttemptNumber]; !ok { t.Error("Expected attempt number storage option.") } @@ -20,10 +21,11 @@ func TestStorage(t *testing.T) { } func TestBeforeAuth(t *testing.T) { + l := &Lock{} authboss.NewConfig() ctx := authboss.NewContext() - if interrupt, err := L.BeforeAuth(ctx); err != errUserMissing { + if interrupt, err := l.BeforeAuth(ctx); err != errUserMissing { t.Error("Expected an error because of missing user:", err) } else if interrupt != authboss.InterruptNone { t.Error("Interrupt should not be set:", interrupt) @@ -31,7 +33,7 @@ func TestBeforeAuth(t *testing.T) { ctx.User = authboss.Attributes{"locked": true} - if interrupt, err := L.BeforeAuth(ctx); err != nil { + if interrupt, err := l.BeforeAuth(ctx); err != nil { t.Error(err) } else if interrupt != authboss.InterruptAccountLocked { t.Error("Expected a locked interrupt:", interrupt) diff --git a/register/register.go b/register/register.go index c4a8f66..4da3888 100644 --- a/register/register.go +++ b/register/register.go @@ -2,6 +2,7 @@ package register import ( + "errors" "net/http" "golang.org/x/crypto/bcrypt" @@ -13,13 +14,16 @@ const ( tplRegister = "register.html.tpl" ) -// R is the singleton instance of the register module which will have been -// configured and ready to use after authboss.Init() -var R *Register +// RegisterStorer must be implemented in order to satisfy the register module's +// storage requirments. +type RegisterStorer interface { + authboss.Storer + // Create is the same as put, except it refers to a non-existent key. + Create(key string, attr authboss.Attributes) error +} func init() { - R = &Register{} - authboss.RegisterModule("register", R) + authboss.RegisterModule("register", &Register{}) } // Register module. @@ -29,6 +33,14 @@ type Register struct { // Initialize the module. func (r *Register) Initialize() (err error) { + if authboss.Cfg.Storer == nil { + return errors.New("register: Need a RegisterStorer.") + } + + if _, ok := authboss.Cfg.Storer.(RegisterStorer); !ok { + return errors.New("register: RegisterStorer required for register functionality.") + } + if r.templates, err = render.LoadTemplates(authboss.Cfg.Layout, authboss.Cfg.ViewsPath, tplRegister); err != nil { return err } @@ -96,7 +108,7 @@ func (reg *Register) registerPostHandler(ctx *authboss.Context, w http.ResponseW attr[authboss.StorePassword] = string(pass) ctx.User = attr - if err := authboss.Cfg.Storer.Create(key, attr); err != nil { + if err := authboss.Cfg.Storer.(RegisterStorer).Create(key, attr); err != nil { return err } diff --git a/register/register_test.go b/register/register_test.go index 7c7f47e..c7745c3 100644 --- a/register/register_test.go +++ b/register/register_test.go @@ -33,6 +33,7 @@ func setup() *Register { func TestRegister(t *testing.T) { authboss.Cfg = authboss.NewConfig() + authboss.Cfg.Storer = mocks.NewMockStorer() r := Register{} if err := r.Initialize(); err != nil { diff --git a/remember/remember.go b/remember/remember.go index 0825757..effa93a 100644 --- a/remember/remember.go +++ b/remember/remember.go @@ -10,6 +10,7 @@ import ( "encoding/base64" "errors" "fmt" + "log" "gopkg.in/authboss.v0" ) @@ -40,13 +41,8 @@ type TokenStorer interface { UseToken(givenKey, token string) (key string, err error) } -// R is the singleton instance of the remember module which will have been -// configured and ready to use after authboss.Init() -var R *Remember - func init() { - R = &Remember{} - authboss.RegisterModule("remember", R) + authboss.RegisterModule("remember", &Remember{}) } type Remember struct{} @@ -60,7 +56,8 @@ func (r *Remember) Initialize() error { return errors.New("remember: TokenStorer required for remember me functionality") } - authboss.Cfg.Callbacks.After(authboss.EventAuth, r.AfterAuth) + authboss.Cfg.Callbacks.Before(authboss.EventGet, r.auth) + authboss.Cfg.Callbacks.After(authboss.EventAuth, r.afterAuth) return nil } @@ -73,8 +70,8 @@ func (r *Remember) Storage() authboss.StorageOptions { return nil } -// AfterAuth is called after authentication is successful. -func (r *Remember) AfterAuth(ctx *authboss.Context) error { +// afterAuth is called after authentication is successful. +func (r *Remember) afterAuth(ctx *authboss.Context) error { if val, ok := ctx.FirstPostFormValue(RememberKey); !ok || val != "true" { return nil } @@ -88,17 +85,17 @@ func (r *Remember) AfterAuth(ctx *authboss.Context) error { return err } - if _, err := r.New(ctx.CookieStorer, key); err != nil { + if _, err := r.new(ctx.CookieStorer, key); err != nil { return fmt.Errorf("remember: Failed to create remember token: %v", err) } return nil } -// New generates a new remember token and stores it in the configured TokenStorer. +// new generates a new remember token and stores it in the configured TokenStorer. // The return value is a token that should only be given to a user if the delivery // method is secure which means at least signed if not encrypted. -func (r *Remember) New(cstorer authboss.ClientStorer, storageKey string) (string, error) { +func (r *Remember) new(cstorer authboss.ClientStorer, storageKey string) (string, error) { token := make([]byte, nRandBytes+len(storageKey)+1) copy(token, []byte(storageKey)) token[len(storageKey)] = ';' @@ -122,42 +119,53 @@ func (r *Remember) New(cstorer authboss.ClientStorer, storageKey string) (string return finalToken, nil } -// Auth takes a token that was given to a user and checks to see if something +// auth takes a token that was given to a user and checks to see if something // is matching in the database. If something is found the old token is deleted // and a new one should be generated. The return value is the key of the // record who owned this token. -func (r *Remember) Auth( - cstorer authboss.ClientStorer, - sstorer authboss.ClientStorer, - finalToken string) (string, error) { +func (r *Remember) auth(ctx *authboss.Context) (authboss.Interrupt, error) { + if val, ok := ctx.SessionStorer.Get(authboss.SessionKey); ok || len(val) > 0 { + return authboss.InterruptNone, nil + } + + finalToken, ok := ctx.CookieStorer.Get(RememberKey) + if !ok { + return authboss.InterruptNone, nil + } + + log.Println("finalToken", finalToken) token, err := base64.URLEncoding.DecodeString(finalToken) if err != nil { - return "", err + return authboss.InterruptNone, err } + log.Println("token", token) + index := bytes.IndexByte(token, ';') if index < 0 { - return "", errors.New("remember: Invalid remember me token.") + return authboss.InterruptNone, errors.New("remember: Invalid remember me token.") } // Get the key. givenKey := token[:index] + log.Println("key", givenKey) // Verify the tokens match. sum := md5.Sum(token) key, err := authboss.Cfg.Storer.(TokenStorer).UseToken(string(givenKey), base64.StdEncoding.EncodeToString(sum[:])) + log.Println("lookup", key, err) if err == authboss.ErrTokenNotFound { - return "", nil + return authboss.InterruptNone, nil } else if err != nil { - return "", err + return authboss.InterruptNone, err } // Ensure a half-auth. - sstorer.Put(authboss.SessionHalfAuthKey, "true") + ctx.SessionStorer.Put(authboss.SessionHalfAuthKey, "true") // Log the user in. - sstorer.Put(authboss.SessionKey, key) + ctx.SessionStorer.Put(authboss.SessionKey, string(givenKey)) - return key, nil + return authboss.InterruptNone, nil } diff --git a/remember/remember_test.go b/remember/remember_test.go index c9db7b5..5a7c0db 100644 --- a/remember/remember_test.go +++ b/remember/remember_test.go @@ -32,6 +32,7 @@ func TestInitialize(t *testing.T) { } func TestAfterAuth(t *testing.T) { + r := Remember{} authboss.NewConfig() storer := mocks.NewMockStorer() authboss.Cfg.Storer = storer @@ -54,7 +55,7 @@ func TestAfterAuth(t *testing.T) { ctx.CookieStorer = cookies ctx.User = authboss.Attributes{authboss.Cfg.PrimaryID: "test@email.com"} - if err := R.AfterAuth(ctx); err != nil { + if err := r.afterAuth(ctx); err != nil { t.Error(err) } @@ -64,13 +65,14 @@ func TestAfterAuth(t *testing.T) { } func TestNew(t *testing.T) { + r := &Remember{} authboss.NewConfig() storer := mocks.NewMockStorer() authboss.Cfg.Storer = storer cookies := mocks.NewMockClientStorer() key := "tester" - token, err := R.New(cookies, key) + token, err := r.new(cookies, key) if err != nil { t.Error("Unexpected error:", err) @@ -92,19 +94,24 @@ func TestNew(t *testing.T) { } func TestAuth(t *testing.T) { + r := &Remember{} authboss.NewConfig() storer := mocks.NewMockStorer() authboss.Cfg.Storer = storer + cookies := mocks.NewMockClientStorer() session := mocks.NewMockClientStorer() + ctx := authboss.NewContext() + ctx.CookieStorer = cookies + ctx.SessionStorer = session key := "tester" - token, err := R.New(cookies, key) + _, err := r.new(cookies, key) if err != nil { t.Error("Unexpected error:", err) } - outKey, err := R.Auth(cookies, session, token) + interrupt, err := r.auth(ctx) if err != nil { t.Error("Unexpected error:", err) } @@ -117,7 +124,7 @@ func TestAuth(t *testing.T) { t.Error("The user should have been logged in.") } - if key != outKey { - t.Error("Keys should have matched:", outKey) + if authboss.InterruptNone != interrupt { + t.Error("Keys should have matched:", interrupt) } } diff --git a/storer.go b/storer.go index 6ca6732..b0c2cf6 100644 --- a/storer.go +++ b/storer.go @@ -30,8 +30,6 @@ type StorageOptions map[string]DataType // The type of store is up to the developer implementing it, and all it has to // do is be able to store several simple types. type Storer interface { - // Create is the same as put, except it refers to a non-existent key. - Create(key string, attr Attributes) error // Put is for storing the attributes passed in. The type information can // help serialization without using type assertions. Put(key string, attr Attributes) error