1
0
mirror of https://github.com/volatiletech/authboss.git synced 2025-02-09 13:47:09 +02:00

Stop reliance on global scope.

- This change was necessary because multi-tenancy sites could not use
  authboss properly.
This commit is contained in:
Aaron 2015-03-31 12:34:03 -07:00
parent bd0d3c5f68
commit f12f10fa43
40 changed files with 556 additions and 495 deletions

View File

@ -29,19 +29,19 @@ type Auth struct {
// Initialize module // Initialize module
func (a *Auth) Initialize() (err error) { func (a *Auth) Initialize() (err error) {
if authboss.Cfg.Storer == nil { if authboss.a.Storer == nil {
return errors.New("auth: Need a Storer") return errors.New("auth: Need a Storer")
} }
if len(authboss.Cfg.XSRFName) == 0 { if len(authboss.a.XSRFName) == 0 {
return errors.New("auth: XSRFName must be set") return errors.New("auth: XSRFName must be set")
} }
if authboss.Cfg.XSRFMaker == nil { if authboss.a.XSRFMaker == nil {
return errors.New("auth: XSRFMaker must be defined") return errors.New("auth: XSRFMaker must be defined")
} }
a.templates, err = response.LoadTemplates(authboss.Cfg.Layout, authboss.Cfg.ViewsPath, tplLogin) a.templates, err = response.LoadTemplates(authboss.a.Layout, authboss.a.ViewsPath, tplLogin)
if err != nil { if err != nil {
return err return err
} }
@ -60,7 +60,7 @@ func (a *Auth) Routes() authboss.RouteTable {
// Storage requirements // Storage requirements
func (a *Auth) Storage() authboss.StorageOptions { func (a *Auth) Storage() authboss.StorageOptions {
return authboss.StorageOptions{ return authboss.StorageOptions{
authboss.Cfg.PrimaryID: authboss.String, authboss.a.PrimaryID: authboss.String,
authboss.StorePassword: authboss.String, authboss.StorePassword: authboss.String,
} }
} }
@ -70,8 +70,8 @@ func (a *Auth) loginHandlerFunc(ctx *authboss.Context, w http.ResponseWriter, r
case methodGET: case methodGET:
if _, ok := ctx.SessionStorer.Get(authboss.SessionKey); ok { if _, ok := ctx.SessionStorer.Get(authboss.SessionKey); ok {
if halfAuthed, ok := ctx.SessionStorer.Get(authboss.SessionHalfAuthKey); !ok || halfAuthed == "false" { if halfAuthed, ok := ctx.SessionStorer.Get(authboss.SessionHalfAuthKey); !ok || halfAuthed == "false" {
//http.Redirect(w, r, authboss.Cfg.AuthLoginOKPath, http.StatusFound, true) //http.Redirect(w, r, authboss.a.AuthLoginOKPath, http.StatusFound, true)
response.Redirect(ctx, w, r, authboss.Cfg.AuthLoginOKPath, "", "", true) response.Redirect(ctx, w, r, authboss.a.AuthLoginOKPath, "", "", true)
return nil return nil
} }
} }
@ -79,23 +79,23 @@ func (a *Auth) loginHandlerFunc(ctx *authboss.Context, w http.ResponseWriter, r
data := authboss.NewHTMLData( data := authboss.NewHTMLData(
"showRemember", authboss.IsLoaded("remember"), "showRemember", authboss.IsLoaded("remember"),
"showRecover", authboss.IsLoaded("recover"), "showRecover", authboss.IsLoaded("recover"),
"primaryID", authboss.Cfg.PrimaryID, "primaryID", authboss.a.PrimaryID,
"primaryIDValue", "", "primaryIDValue", "",
) )
return a.templates.Render(ctx, w, r, tplLogin, data) return a.templates.Render(ctx, w, r, tplLogin, data)
case methodPOST: case methodPOST:
key, _ := ctx.FirstPostFormValue(authboss.Cfg.PrimaryID) key, _ := ctx.FirstPostFormValue(authboss.a.PrimaryID)
password, _ := ctx.FirstPostFormValue("password") password, _ := ctx.FirstPostFormValue("password")
errData := authboss.NewHTMLData( errData := authboss.NewHTMLData(
"error", fmt.Sprintf("invalid %s and/or password", authboss.Cfg.PrimaryID), "error", fmt.Sprintf("invalid %s and/or password", authboss.a.PrimaryID),
"primaryID", authboss.Cfg.PrimaryID, "primaryID", authboss.a.PrimaryID,
"primaryIDValue", key, "primaryIDValue", key,
"showRemember", authboss.IsLoaded("remember"), "showRemember", authboss.IsLoaded("remember"),
"showRecover", authboss.IsLoaded("recover"), "showRecover", authboss.IsLoaded("recover"),
) )
policies := authboss.FilterValidators(authboss.Cfg.Policies, authboss.Cfg.PrimaryID, authboss.StorePassword) policies := authboss.FilterValidators(authboss.a.Policies, authboss.a.PrimaryID, authboss.StorePassword)
if validationErrs := ctx.Validate(policies); len(validationErrs) > 0 { if validationErrs := ctx.Validate(policies); len(validationErrs) > 0 {
return a.templates.Render(ctx, w, r, tplLogin, errData) return a.templates.Render(ctx, w, r, tplLogin, errData)
} }
@ -104,7 +104,7 @@ func (a *Auth) loginHandlerFunc(ctx *authboss.Context, w http.ResponseWriter, r
return a.templates.Render(ctx, w, r, tplLogin, errData) return a.templates.Render(ctx, w, r, tplLogin, errData)
} }
interrupted, err := authboss.Cfg.Callbacks.FireBefore(authboss.EventAuth, ctx) interrupted, err := authboss.a.Callbacks.FireBefore(authboss.EventAuth, ctx)
if err != nil { if err != nil {
return err return err
} else if interrupted != authboss.InterruptNone { } else if interrupted != authboss.InterruptNone {
@ -115,17 +115,17 @@ func (a *Auth) loginHandlerFunc(ctx *authboss.Context, w http.ResponseWriter, r
case authboss.InterruptAccountNotConfirmed: case authboss.InterruptAccountNotConfirmed:
reason = "Your account has not been confirmed." reason = "Your account has not been confirmed."
} }
response.Redirect(ctx, w, r, authboss.Cfg.AuthLoginFailPath, "", reason, false) response.Redirect(ctx, w, r, authboss.a.AuthLoginFailPath, "", reason, false)
return nil return nil
} }
ctx.SessionStorer.Put(authboss.SessionKey, key) ctx.SessionStorer.Put(authboss.SessionKey, key)
ctx.SessionStorer.Del(authboss.SessionHalfAuthKey) ctx.SessionStorer.Del(authboss.SessionHalfAuthKey)
if err := authboss.Cfg.Callbacks.FireAfter(authboss.EventAuth, ctx); err != nil { if err := authboss.a.Callbacks.FireAfter(authboss.EventAuth, ctx); err != nil {
return err return err
} }
response.Redirect(ctx, w, r, authboss.Cfg.AuthLoginOKPath, "", "", true) response.Redirect(ctx, w, r, authboss.a.AuthLoginOKPath, "", "", true)
default: default:
w.WriteHeader(http.StatusMethodNotAllowed) w.WriteHeader(http.StatusMethodNotAllowed)
} }
@ -157,7 +157,7 @@ func (a *Auth) logoutHandlerFunc(ctx *authboss.Context, w http.ResponseWriter, r
ctx.CookieStorer.Del(authboss.CookieRemember) ctx.CookieStorer.Del(authboss.CookieRemember)
ctx.SessionStorer.Del(authboss.SessionLastAction) ctx.SessionStorer.Del(authboss.SessionLastAction)
response.Redirect(ctx, w, r, authboss.Cfg.AuthLogoutOKPath, "You have logged out", "", true) response.Redirect(ctx, w, r, authboss.a.AuthLogoutOKPath, "You have logged out", "", true)
default: default:
w.WriteHeader(http.StatusMethodNotAllowed) w.WriteHeader(http.StatusMethodNotAllowed)
} }

View File

@ -17,14 +17,14 @@ func testSetup() (a *Auth, s *mocks.MockStorer) {
s = mocks.NewMockStorer() s = mocks.NewMockStorer()
authboss.Cfg = authboss.NewConfig() authboss.Cfg = authboss.NewConfig()
authboss.Cfg.LogWriter = ioutil.Discard authboss.a.LogWriter = ioutil.Discard
authboss.Cfg.Layout = template.Must(template.New("").Parse(`{{template "authboss" .}}`)) authboss.a.Layout = template.Must(template.New("").Parse(`{{template "authboss" .}}`))
authboss.Cfg.Storer = s authboss.a.Storer = s
authboss.Cfg.XSRFName = "xsrf" authboss.a.XSRFName = "xsrf"
authboss.Cfg.XSRFMaker = func(_ http.ResponseWriter, _ *http.Request) string { authboss.a.XSRFMaker = func(_ http.ResponseWriter, _ *http.Request) string {
return "xsrfvalue" return "xsrfvalue"
} }
authboss.Cfg.PrimaryID = authboss.StoreUsername authboss.a.PrimaryID = authboss.StoreUsername
a = &Auth{} a = &Auth{}
if err := a.Initialize(); err != nil { if err := a.Initialize(); err != nil {
@ -51,8 +51,8 @@ func TestAuth(t *testing.T) {
a, _ := testSetup() a, _ := testSetup()
storage := a.Storage() storage := a.Storage()
if storage[authboss.Cfg.PrimaryID] != authboss.String { if storage[authboss.a.PrimaryID] != authboss.String {
t.Error("Expected storage KV:", authboss.Cfg.PrimaryID, authboss.String) t.Error("Expected storage KV:", authboss.a.PrimaryID, authboss.String)
} }
if storage[authboss.StorePassword] != authboss.String { if storage[authboss.StorePassword] != authboss.String {
t.Error("Expected storage KV:", authboss.StorePassword, authboss.String) t.Error("Expected storage KV:", authboss.StorePassword, authboss.String)
@ -74,7 +74,7 @@ func TestAuth_loginHandlerFunc_GET_RedirectsWhenHalfAuthed(t *testing.T) {
sessionStore.Put(authboss.SessionKey, "a") sessionStore.Put(authboss.SessionKey, "a")
sessionStore.Put(authboss.SessionHalfAuthKey, "false") sessionStore.Put(authboss.SessionHalfAuthKey, "false")
authboss.Cfg.AuthLoginOKPath = "/dashboard" authboss.a.AuthLoginOKPath = "/dashboard"
if err := a.loginHandlerFunc(ctx, w, r); err != nil { if err := a.loginHandlerFunc(ctx, w, r); err != nil {
t.Error("Unexpeced error:", err) t.Error("Unexpeced error:", err)
@ -85,7 +85,7 @@ func TestAuth_loginHandlerFunc_GET_RedirectsWhenHalfAuthed(t *testing.T) {
} }
loc := w.Header().Get("Location") loc := w.Header().Get("Location")
if loc != authboss.Cfg.AuthLoginOKPath { if loc != authboss.a.AuthLoginOKPath {
t.Error("Unexpected redirect:", loc) t.Error("Unexpected redirect:", loc)
} }
} }
@ -106,7 +106,7 @@ func TestAuth_loginHandlerFunc_GET(t *testing.T) {
if !strings.Contains(body, "<form") { if !strings.Contains(body, "<form") {
t.Error("Should have rendered a form") t.Error("Should have rendered a form")
} }
if !strings.Contains(body, `name="`+authboss.Cfg.PrimaryID) { if !strings.Contains(body, `name="`+authboss.a.PrimaryID) {
t.Error("Form should contain the primary ID field:", body) t.Error("Form should contain the primary ID field:", body)
} }
if !strings.Contains(body, `name="password"`) { if !strings.Contains(body, `name="password"`) {
@ -118,8 +118,8 @@ func TestAuth_loginHandlerFunc_POST_ReturnsErrorOnCallbackFailure(t *testing.T)
a, storer := testSetup() a, storer := testSetup()
storer.Users["john"] = authboss.Attributes{"password": "$2a$10$B7aydtqVF9V8RSNx3lCKB.l09jqLV/aMiVqQHajtL7sWGhCS9jlOu"} storer.Users["john"] = authboss.Attributes{"password": "$2a$10$B7aydtqVF9V8RSNx3lCKB.l09jqLV/aMiVqQHajtL7sWGhCS9jlOu"}
authboss.Cfg.Callbacks = authboss.NewCallbacks() authboss.a.Callbacks = authboss.NewCallbacks()
authboss.Cfg.Callbacks.Before(authboss.EventAuth, func(_ *authboss.Context) (authboss.Interrupt, error) { authboss.a.Callbacks.Before(authboss.EventAuth, func(_ *authboss.Context) (authboss.Interrupt, error) {
return authboss.InterruptNone, errors.New("explode") return authboss.InterruptNone, errors.New("explode")
}) })
@ -134,8 +134,8 @@ func TestAuth_loginHandlerFunc_POST_RedirectsWhenInterrupted(t *testing.T) {
a, storer := testSetup() a, storer := testSetup()
storer.Users["john"] = authboss.Attributes{"password": "$2a$10$B7aydtqVF9V8RSNx3lCKB.l09jqLV/aMiVqQHajtL7sWGhCS9jlOu"} storer.Users["john"] = authboss.Attributes{"password": "$2a$10$B7aydtqVF9V8RSNx3lCKB.l09jqLV/aMiVqQHajtL7sWGhCS9jlOu"}
authboss.Cfg.Callbacks = authboss.NewCallbacks() authboss.a.Callbacks = authboss.NewCallbacks()
authboss.Cfg.Callbacks.Before(authboss.EventAuth, func(_ *authboss.Context) (authboss.Interrupt, error) { authboss.a.Callbacks.Before(authboss.EventAuth, func(_ *authboss.Context) (authboss.Interrupt, error) {
return authboss.InterruptAccountLocked, nil return authboss.InterruptAccountLocked, nil
}) })
@ -150,7 +150,7 @@ func TestAuth_loginHandlerFunc_POST_RedirectsWhenInterrupted(t *testing.T) {
} }
loc := w.Header().Get("Location") loc := w.Header().Get("Location")
if loc != authboss.Cfg.AuthLoginFailPath { if loc != authboss.a.AuthLoginFailPath {
t.Error("Unexpeced location:", loc) t.Error("Unexpeced location:", loc)
} }
@ -159,8 +159,8 @@ func TestAuth_loginHandlerFunc_POST_RedirectsWhenInterrupted(t *testing.T) {
t.Error("Expected error flash message:", expectedMsg) t.Error("Expected error flash message:", expectedMsg)
} }
authboss.Cfg.Callbacks = authboss.NewCallbacks() authboss.a.Callbacks = authboss.NewCallbacks()
authboss.Cfg.Callbacks.Before(authboss.EventAuth, func(_ *authboss.Context) (authboss.Interrupt, error) { authboss.a.Callbacks.Before(authboss.EventAuth, func(_ *authboss.Context) (authboss.Interrupt, error) {
return authboss.InterruptAccountNotConfirmed, nil return authboss.InterruptAccountNotConfirmed, nil
}) })
@ -173,7 +173,7 @@ func TestAuth_loginHandlerFunc_POST_RedirectsWhenInterrupted(t *testing.T) {
} }
loc = w.Header().Get("Location") loc = w.Header().Get("Location")
if loc != authboss.Cfg.AuthLoginFailPath { if loc != authboss.a.AuthLoginFailPath {
t.Error("Unexpeced location:", loc) t.Error("Unexpeced location:", loc)
} }
@ -224,9 +224,9 @@ func TestAuth_loginHandlerFunc_POST(t *testing.T) {
ctx, w, r, _ := testRequest("POST", "username", "john", "password", "1234") ctx, w, r, _ := testRequest("POST", "username", "john", "password", "1234")
cb := mocks.NewMockAfterCallback() cb := mocks.NewMockAfterCallback()
authboss.Cfg.Callbacks = authboss.NewCallbacks() authboss.a.Callbacks = authboss.NewCallbacks()
authboss.Cfg.Callbacks.After(authboss.EventAuth, cb.Fn) authboss.a.Callbacks.After(authboss.EventAuth, cb.Fn)
authboss.Cfg.AuthLoginOKPath = "/dashboard" authboss.a.AuthLoginOKPath = "/dashboard"
sessions := mocks.NewMockClientStorer() sessions := mocks.NewMockClientStorer()
ctx.SessionStorer = sessions ctx.SessionStorer = sessions
@ -244,7 +244,7 @@ func TestAuth_loginHandlerFunc_POST(t *testing.T) {
} }
loc := w.Header().Get("Location") loc := w.Header().Get("Location")
if loc != authboss.Cfg.AuthLoginOKPath { if loc != authboss.a.AuthLoginOKPath {
t.Error("Unexpeced location:", loc) t.Error("Unexpeced location:", loc)
} }
@ -283,7 +283,7 @@ func TestAuth_validateCredentials(t *testing.T) {
storer := mocks.NewMockStorer() storer := mocks.NewMockStorer()
storer.GetErr = "Failed to load user" storer.GetErr = "Failed to load user"
authboss.Cfg.Storer = storer authboss.a.Storer = storer
ctx := authboss.Context{} ctx := authboss.Context{}
@ -305,7 +305,7 @@ func TestAuth_validateCredentials(t *testing.T) {
func TestAuth_logoutHandlerFunc_GET(t *testing.T) { func TestAuth_logoutHandlerFunc_GET(t *testing.T) {
a, _ := testSetup() a, _ := testSetup()
authboss.Cfg.AuthLogoutOKPath = "/dashboard" authboss.a.AuthLogoutOKPath = "/dashboard"
ctx, w, r, sessionStorer := testRequest("GET") ctx, w, r, sessionStorer := testRequest("GET")
sessionStorer.Put(authboss.SessionKey, "asdf") sessionStorer.Put(authboss.SessionKey, "asdf")

View File

@ -17,11 +17,24 @@ import (
"golang.org/x/crypto/bcrypt" "golang.org/x/crypto/bcrypt"
) )
// Authboss contains a configuration and other details for running.
type Authboss struct {
Config
}
// New makes a new instance of authboss with a default
// configuration.
func New() *Authboss {
ab := &Authboss{}
ab.Defaults()
return ab
}
// Init authboss and it's loaded modules. // Init authboss and it's loaded modules.
func Init() error { func (a *Authboss) Init() error {
for name, mod := range modules { for name, mod := range modules {
fmt.Fprintf(Cfg.LogWriter, "%-10s Initializing\n", "["+name+"]") fmt.Fprintf(a.LogWriter, "%-10s Initializing\n", "["+name+"]")
if err := mod.Initialize(); err != nil { if err := mod.Initialize(a); err != nil {
return fmt.Errorf("[%s] Error Initializing: %v", name, err) return fmt.Errorf("[%s] Error Initializing: %v", name, err)
} }
} }
@ -30,16 +43,16 @@ func Init() error {
} }
// CurrentUser retrieves the current user from the session and the database. // CurrentUser retrieves the current user from the session and the database.
func CurrentUser(w http.ResponseWriter, r *http.Request) (interface{}, error) { func (a *Authboss) CurrentUser(w http.ResponseWriter, r *http.Request) (interface{}, error) {
ctx, err := ContextFromRequest(r) ctx, err := a.ContextFromRequest(r)
if err != nil { if err != nil {
return nil, err return nil, err
} }
ctx.SessionStorer = clientStoreWrapper{Cfg.SessionStoreMaker(w, r)} ctx.SessionStorer = clientStoreWrapper{a.SessionStoreMaker(w, r)}
ctx.CookieStorer = clientStoreWrapper{Cfg.CookieStoreMaker(w, r)} ctx.CookieStorer = clientStoreWrapper{a.CookieStoreMaker(w, r)}
_, err = Cfg.Callbacks.FireBefore(EventGetUserSession, ctx) _, err = a.Callbacks.FireBefore(EventGetUserSession, ctx)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -54,22 +67,22 @@ func CurrentUser(w http.ResponseWriter, r *http.Request) (interface{}, error) {
return nil, err return nil, err
} }
_, err = Cfg.Callbacks.FireBefore(EventGet, ctx) _, err = a.Callbacks.FireBefore(EventGet, ctx)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if index := strings.IndexByte(key, ';'); index > 0 { if index := strings.IndexByte(key, ';'); index > 0 {
return Cfg.OAuth2Storer.GetOAuth(key[:index], key[index+1:]) return a.OAuth2Storer.GetOAuth(key[:index], key[index+1:])
} }
return Cfg.Storer.Get(key) return a.Storer.Get(key)
} }
// CurrentUserP retrieves the current user but panics if it's not available for // CurrentUserP retrieves the current user but panics if it's not available for
// any reason. // any reason.
func CurrentUserP(w http.ResponseWriter, r *http.Request) interface{} { func (a *Authboss) CurrentUserP(w http.ResponseWriter, r *http.Request) interface{} {
i, err := CurrentUser(w, r) i, err := a.CurrentUser(w, r)
if err != nil { if err != nil {
panic(err.Error()) panic(err.Error())
} }
@ -96,13 +109,13 @@ will be returned.
The error returned is returned either from the updater if that produced an error The error returned is returned either from the updater if that produced an error
or from the cleanup routines. or from the cleanup routines.
*/ */
func UpdatePassword(w http.ResponseWriter, r *http.Request, func (a *Authboss) UpdatePassword(w http.ResponseWriter, r *http.Request,
ptPassword string, user interface{}, updater func() error) error { ptPassword string, user interface{}, updater func() error) error {
updatePwd := len(ptPassword) > 0 updatePwd := len(ptPassword) > 0
if updatePwd { if updatePwd {
pass, err := bcrypt.GenerateFromPassword([]byte(ptPassword), Cfg.BCryptCost) pass, err := bcrypt.GenerateFromPassword([]byte(ptPassword), a.BCryptCost)
if err != nil { if err != nil {
return err return err
} }
@ -131,11 +144,11 @@ func UpdatePassword(w http.ResponseWriter, r *http.Request,
return nil return nil
} }
ctx, err := ContextFromRequest(r) ctx, err := a.ContextFromRequest(r)
if err != nil { if err != nil {
return err return err
} }
ctx.SessionStorer = clientStoreWrapper{Cfg.SessionStoreMaker(w, r)} ctx.SessionStorer = clientStoreWrapper{a.SessionStoreMaker(w, r)}
ctx.CookieStorer = clientStoreWrapper{Cfg.CookieStoreMaker(w, r)} ctx.CookieStorer = clientStoreWrapper{a.CookieStoreMaker(w, r)}
return Cfg.Callbacks.FireAfter(EventPasswordReset, ctx) return a.Callbacks.FireAfter(EventPasswordReset, ctx)
} }

View File

@ -6,46 +6,41 @@ import (
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"os"
"testing" "testing"
) )
func TestMain(main *testing.M) {
RegisterModule("testmodule", testMod)
Cfg.LogWriter = ioutil.Discard
Init()
code := main.Run()
os.Exit(code)
}
func TestAuthBossInit(t *testing.T) { func TestAuthBossInit(t *testing.T) {
Cfg = NewConfig() t.Parallel()
Cfg.LogWriter = ioutil.Discard
err := Init() ab := New()
ab.LogWriter = ioutil.Discard
err := ab.Init()
if err != nil { if err != nil {
t.Error("Unexpected error:", err) t.Error("Unexpected error:", err)
} }
} }
func TestAuthBossCurrentUser(t *testing.T) { func TestAuthBossCurrentUser(t *testing.T) {
Cfg = NewConfig() t.Parallel()
Cfg.LogWriter = ioutil.Discard
Cfg.Storer = mockStorer{"joe": Attributes{"email": "john@john.com", "password": "lies"}} ab := New()
Cfg.SessionStoreMaker = func(_ http.ResponseWriter, _ *http.Request) ClientStorer { ab.LogWriter = ioutil.Discard
ab.Storer = mockStorer{"joe": Attributes{"email": "john@john.com", "password": "lies"}}
ab.SessionStoreMaker = func(_ http.ResponseWriter, _ *http.Request) ClientStorer {
return mockClientStore{SessionKey: "joe"} return mockClientStore{SessionKey: "joe"}
} }
Cfg.CookieStoreMaker = func(_ http.ResponseWriter, _ *http.Request) ClientStorer { ab.CookieStoreMaker = func(_ http.ResponseWriter, _ *http.Request) ClientStorer {
return mockClientStore{} return mockClientStore{}
} }
if err := Init(); err != nil { if err := ab.Init(); err != nil {
t.Error("Unexpected error:", err) t.Error("Unexpected error:", err)
} }
rec := httptest.NewRecorder() rec := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "localhost", nil) req, _ := http.NewRequest("GET", "localhost", nil)
userStruct := CurrentUserP(rec, req) userStruct := ab.CurrentUserP(rec, req)
us := userStruct.(*mockUser) us := userStruct.(*mockUser)
if us.Email != "john@john.com" || us.Password != "lies" { if us.Email != "john@john.com" || us.Password != "lies" {
@ -54,18 +49,20 @@ func TestAuthBossCurrentUser(t *testing.T) {
} }
func TestAuthbossUpdatePassword(t *testing.T) { func TestAuthbossUpdatePassword(t *testing.T) {
Cfg = NewConfig() t.Parallel()
ab := New()
session := mockClientStore{} session := mockClientStore{}
cookies := mockClientStore{} cookies := mockClientStore{}
Cfg.SessionStoreMaker = func(_ http.ResponseWriter, _ *http.Request) ClientStorer { ab.SessionStoreMaker = func(_ http.ResponseWriter, _ *http.Request) ClientStorer {
return session return session
} }
Cfg.CookieStoreMaker = func(_ http.ResponseWriter, _ *http.Request) ClientStorer { ab.CookieStoreMaker = func(_ http.ResponseWriter, _ *http.Request) ClientStorer {
return cookies return cookies
} }
called := false called := false
Cfg.Callbacks.After(EventPasswordReset, func(ctx *Context) error { ab.Callbacks.After(EventPasswordReset, func(ctx *Context) error {
called = true called = true
return nil return nil
}) })
@ -80,7 +77,7 @@ func TestAuthbossUpdatePassword(t *testing.T) {
r, _ := http.NewRequest("GET", "http://localhost", nil) r, _ := http.NewRequest("GET", "http://localhost", nil)
called = false called = false
err := UpdatePassword(nil, r, "newpassword", &user1, func() error { return nil }) err := ab.UpdatePassword(nil, r, "newpassword", &user1, func() error { return nil })
if err != nil { if err != nil {
t.Error(err) t.Error(err)
} }
@ -93,7 +90,7 @@ func TestAuthbossUpdatePassword(t *testing.T) {
} }
called = false called = false
err = UpdatePassword(nil, r, "newpassword", &user2, func() error { return nil }) err = ab.UpdatePassword(nil, r, "newpassword", &user2, func() error { return nil })
if err != nil { if err != nil {
t.Error(err) t.Error(err)
} }
@ -107,7 +104,7 @@ func TestAuthbossUpdatePassword(t *testing.T) {
called = false called = false
oldPassword := user1.Password oldPassword := user1.Password
err = UpdatePassword(nil, r, "", &user1, func() error { return nil }) err = ab.UpdatePassword(nil, r, "", &user1, func() error { return nil })
if err != nil { if err != nil {
t.Error(err) t.Error(err)
} }
@ -121,12 +118,16 @@ func TestAuthbossUpdatePassword(t *testing.T) {
} }
func TestAuthbossUpdatePasswordFail(t *testing.T) { func TestAuthbossUpdatePasswordFail(t *testing.T) {
t.Parallel()
ab := New()
user1 := struct { user1 := struct {
Password string Password string
}{} }{}
anErr := errors.New("AnError") anErr := errors.New("AnError")
err := UpdatePassword(nil, nil, "update", &user1, func() error { return anErr }) err := ab.UpdatePassword(nil, nil, "update", &user1, func() error { return anErr })
if err != anErr { if err != anErr {
t.Error("Expected an specific error:", err) t.Error("Expected an specific error:", err)
} }

View File

@ -115,7 +115,7 @@ func (c *Callbacks) FireBefore(e Event, ctx *Context) (interrupt Interrupt, err
for _, fn := range callbacks { for _, fn := range callbacks {
interrupt, err = fn(ctx) interrupt, err = fn(ctx)
if err != nil { if err != nil {
fmt.Fprintf(Cfg.LogWriter, "Callback error (%s): %v\n", runtime.FuncForPC(reflect.ValueOf(fn).Pointer()).Name(), err) fmt.Fprintf(ctx.LogWriter, "Callback error (%s): %v\n", runtime.FuncForPC(reflect.ValueOf(fn).Pointer()).Name(), err)
return InterruptNone, err return InterruptNone, err
} }
if interrupt != InterruptNone { if interrupt != InterruptNone {
@ -132,7 +132,7 @@ func (c *Callbacks) FireAfter(e Event, ctx *Context) (err error) {
callbacks := c.after[e] callbacks := c.after[e]
for _, fn := range callbacks { for _, fn := range callbacks {
if err = fn(ctx); err != nil { if err = fn(ctx); err != nil {
fmt.Fprintf(Cfg.LogWriter, "Callback error (%s): %v\n", runtime.FuncForPC(reflect.ValueOf(fn).Pointer()).Name(), err) fmt.Fprintf(ctx.LogWriter, "Callback error (%s): %v\n", runtime.FuncForPC(reflect.ValueOf(fn).Pointer()).Name(), err)
return err return err
} }
} }

View File

@ -8,15 +8,17 @@ import (
) )
func TestCallbacks(t *testing.T) { func TestCallbacks(t *testing.T) {
t.Parallel()
ab := New()
afterCalled := false afterCalled := false
beforeCalled := false beforeCalled := false
c := NewCallbacks()
c.Before(EventRegister, func(ctx *Context) (Interrupt, error) { ab.Callbacks.Before(EventRegister, func(ctx *Context) (Interrupt, error) {
beforeCalled = true beforeCalled = true
return InterruptNone, nil return InterruptNone, nil
}) })
c.After(EventRegister, func(ctx *Context) error { ab.Callbacks.After(EventRegister, func(ctx *Context) error {
afterCalled = true afterCalled = true
return nil return nil
}) })
@ -25,7 +27,7 @@ func TestCallbacks(t *testing.T) {
t.Error("Neither should be called.") t.Error("Neither should be called.")
} }
interrupt, err := c.FireBefore(EventRegister, NewContext()) interrupt, err := ab.Callbacks.FireBefore(EventRegister, ab.NewContext())
if err != nil { if err != nil {
t.Error("Unexpected error:", err) t.Error("Unexpected error:", err)
} }
@ -40,27 +42,29 @@ func TestCallbacks(t *testing.T) {
t.Error("Expected after not to be called.") t.Error("Expected after not to be called.")
} }
c.FireAfter(EventRegister, NewContext()) ab.Callbacks.FireAfter(EventRegister, ab.NewContext())
if !afterCalled { if !afterCalled {
t.Error("Expected after to be called.") t.Error("Expected after to be called.")
} }
} }
func TestCallbacksInterrupt(t *testing.T) { func TestCallbacksInterrupt(t *testing.T) {
t.Parallel()
ab := New()
before1 := false before1 := false
before2 := false before2 := false
c := NewCallbacks()
c.Before(EventRegister, func(ctx *Context) (Interrupt, error) { ab.Callbacks.Before(EventRegister, func(ctx *Context) (Interrupt, error) {
before1 = true before1 = true
return InterruptAccountLocked, nil return InterruptAccountLocked, nil
}) })
c.Before(EventRegister, func(ctx *Context) (Interrupt, error) { ab.Callbacks.Before(EventRegister, func(ctx *Context) (Interrupt, error) {
before2 = true before2 = true
return InterruptNone, nil return InterruptNone, nil
}) })
interrupt, err := c.FireBefore(EventRegister, NewContext()) interrupt, err := ab.Callbacks.FireBefore(EventRegister, ab.NewContext())
if err != nil { if err != nil {
t.Error(err) t.Error(err)
} }
@ -77,26 +81,26 @@ func TestCallbacksInterrupt(t *testing.T) {
} }
func TestCallbacksBeforeErrors(t *testing.T) { func TestCallbacksBeforeErrors(t *testing.T) {
t.Parallel()
ab := New()
log := &bytes.Buffer{} log := &bytes.Buffer{}
Cfg = &Config{ ab.LogWriter = log
LogWriter: log,
}
before1 := false before1 := false
before2 := false before2 := false
c := NewCallbacks()
errValue := errors.New("Problem occured") errValue := errors.New("Problem occured")
c.Before(EventRegister, func(ctx *Context) (Interrupt, error) { ab.Callbacks.Before(EventRegister, func(ctx *Context) (Interrupt, error) {
before1 = true before1 = true
return InterruptNone, errValue return InterruptNone, errValue
}) })
c.Before(EventRegister, func(ctx *Context) (Interrupt, error) { ab.Callbacks.Before(EventRegister, func(ctx *Context) (Interrupt, error) {
before2 = true before2 = true
return InterruptNone, nil return InterruptNone, nil
}) })
interrupt, err := c.FireBefore(EventRegister, NewContext()) interrupt, err := ab.Callbacks.FireBefore(EventRegister, ab.NewContext())
if err != errValue { if err != errValue {
t.Error("Expected an error to come back.") t.Error("Expected an error to come back.")
} }
@ -117,26 +121,26 @@ func TestCallbacksBeforeErrors(t *testing.T) {
} }
func TestCallbacksAfterErrors(t *testing.T) { func TestCallbacksAfterErrors(t *testing.T) {
t.Parallel()
log := &bytes.Buffer{} log := &bytes.Buffer{}
Cfg = &Config{ ab := New()
LogWriter: log, ab.LogWriter = log
}
after1 := false after1 := false
after2 := false after2 := false
c := NewCallbacks()
errValue := errors.New("Problem occured") errValue := errors.New("Problem occured")
c.After(EventRegister, func(ctx *Context) error { ab.Callbacks.After(EventRegister, func(ctx *Context) error {
after1 = true after1 = true
return errValue return errValue
}) })
c.After(EventRegister, func(ctx *Context) error { ab.Callbacks.After(EventRegister, func(ctx *Context) error {
after2 = true after2 = true
return nil return nil
}) })
err := c.FireAfter(EventRegister, NewContext()) err := ab.Callbacks.FireAfter(EventRegister, ab.NewContext())
if err != errValue { if err != errValue {
t.Error("Expected an error to come back.") t.Error("Expected an error to come back.")
} }
@ -154,6 +158,8 @@ func TestCallbacksAfterErrors(t *testing.T) {
} }
func TestEventString(t *testing.T) { func TestEventString(t *testing.T) {
t.Parallel()
tests := []struct { tests := []struct {
ev Event ev Event
str string str string
@ -178,6 +184,8 @@ func TestEventString(t *testing.T) {
} }
func TestInterruptString(t *testing.T) { func TestInterruptString(t *testing.T) {
t.Parallel()
tests := []struct { tests := []struct {
in Interrupt in Interrupt
str string str string

View File

@ -64,8 +64,8 @@ type CookieStoreMaker func(http.ResponseWriter, *http.Request) ClientStorer
type SessionStoreMaker func(http.ResponseWriter, *http.Request) ClientStorer type SessionStoreMaker func(http.ResponseWriter, *http.Request) ClientStorer
// FlashSuccess returns FlashSuccessKey from the session and removes it. // FlashSuccess returns FlashSuccessKey from the session and removes it.
func FlashSuccess(w http.ResponseWriter, r *http.Request) string { func (a *Authboss) FlashSuccess(w http.ResponseWriter, r *http.Request) string {
storer := Cfg.SessionStoreMaker(w, r) storer := a.SessionStoreMaker(w, r)
msg, ok := storer.Get(FlashSuccessKey) msg, ok := storer.Get(FlashSuccessKey)
if ok { if ok {
storer.Del(FlashSuccessKey) storer.Del(FlashSuccessKey)
@ -75,8 +75,8 @@ func FlashSuccess(w http.ResponseWriter, r *http.Request) string {
} }
// FlashError returns FlashError from the session and removes it. // FlashError returns FlashError from the session and removes it.
func FlashError(w http.ResponseWriter, r *http.Request) string { func (a *Authboss) FlashError(w http.ResponseWriter, r *http.Request) string {
storer := Cfg.SessionStoreMaker(w, r) storer := a.SessionStoreMaker(w, r)
msg, ok := storer.Get(FlashErrorKey) msg, ok := storer.Get(FlashErrorKey)
if ok { if ok {
storer.Del(FlashErrorKey) storer.Del(FlashErrorKey)

View File

@ -14,6 +14,8 @@ func (t testClientStorerErr) Get(key string) (string, bool) {
func (t testClientStorerErr) Del(key string) {} func (t testClientStorerErr) Del(key string) {}
func TestClientStorerErr(t *testing.T) { func TestClientStorerErr(t *testing.T) {
t.Parallel()
var cs testClientStorerErr var cs testClientStorerErr
csw := clientStoreWrapper{&cs} csw := clientStoreWrapper{&cs}
@ -30,19 +32,22 @@ func TestClientStorerErr(t *testing.T) {
} }
func TestFlashClearer(t *testing.T) { func TestFlashClearer(t *testing.T) {
t.Parallel()
session := mockClientStore{FlashSuccessKey: "success", FlashErrorKey: "error"} session := mockClientStore{FlashSuccessKey: "success", FlashErrorKey: "error"}
Cfg.SessionStoreMaker = func(w http.ResponseWriter, r *http.Request) ClientStorer { ab := New()
ab.SessionStoreMaker = func(w http.ResponseWriter, r *http.Request) ClientStorer {
return session return session
} }
if msg := FlashSuccess(nil, nil); msg != "success" { if msg := ab.FlashSuccess(nil, nil); msg != "success" {
t.Error("Unexpected flash success:", msg) t.Error("Unexpected flash success:", msg)
} }
if msg, ok := session.Get(FlashSuccessKey); ok { if msg, ok := session.Get(FlashSuccessKey); ok {
t.Error("Unexpected success flash:", msg) t.Error("Unexpected success flash:", msg)
} }
if msg := FlashError(nil, nil); msg != "error" { if msg := ab.FlashError(nil, nil); msg != "error" {
t.Error("Unexpected flash error:", msg) t.Error("Unexpected flash error:", msg)
} }
if msg, ok := session.Get(FlashErrorKey); ok { if msg, ok := session.Get(FlashErrorKey); ok {

View File

@ -10,9 +10,6 @@ import (
"golang.org/x/crypto/bcrypt" "golang.org/x/crypto/bcrypt"
) )
// Cfg is the singleton instance of Config
var Cfg = NewConfig()
// Config holds all the configuration for both authboss and it's modules. // Config holds all the configuration for both authboss and it's modules.
type Config struct { type Config struct {
// MountPath is the path to mount authboss's routes at (eg /auth). // MountPath is the path to mount authboss's routes at (eg /auth).
@ -117,61 +114,55 @@ type Config struct {
Mailer Mailer Mailer Mailer
} }
// NewConfig creates a config full of healthy default values. // Defaults sets the configuration's default values.
// Notable exceptions to default values are the Storers. func (c *Config) Defaults() {
// This method is called automatically on startup and is set to authboss.Cfg c.MountPath = "/"
// so implementers need not call it. Primarily exported for testing. c.ViewsPath = "./"
func NewConfig() *Config { c.RootURL = "http://localhost:8080"
return &Config{ c.BCryptCost = bcrypt.DefaultCost
MountPath: "/",
ViewsPath: "./",
RootURL: "http://localhost:8080",
BCryptCost: bcrypt.DefaultCost,
PrimaryID: StoreEmail, c.PrimaryID = StoreEmail
Layout: template.Must(template.New("").Parse(`<!DOCTYPE html><html><body>{{template "authboss" .}}</body></html>`)), c.Layout = template.Must(template.New("").Parse(`<!DOCTYPE html><html><body>{{template "authboss" .}}</body></html>`))
LayoutHTMLEmail: template.Must(template.New("").Parse(`<!DOCTYPE html><html><body>{{template "authboss" .}}</body></html>`)), c.LayoutHTMLEmail = template.Must(template.New("").Parse(`<!DOCTYPE html><html><body>{{template "authboss" .}}</body></html>`))
LayoutTextEmail: template.Must(template.New("").Parse(`{{template "authboss" .}}`)), c.LayoutTextEmail = template.Must(template.New("").Parse(`{{template "authboss" .}}`))
AuthLoginOKPath: "/", c.AuthLoginOKPath = "/"
AuthLoginFailPath: "/", c.AuthLoginFailPath = "/"
AuthLogoutOKPath: "/", c.AuthLogoutOKPath = "/"
RecoverOKPath: "/", c.RecoverOKPath = "/"
RecoverTokenDuration: time.Duration(24) * time.Hour, c.RecoverTokenDuration = time.Duration(24) * time.Hour
RegisterOKPath: "/", c.RegisterOKPath = "/"
Policies: []Validator{ c.Policies = []Validator{
Rules{ Rules{
FieldName: "username", FieldName: "username",
Required: true, Required: true,
MinLength: 2, MinLength: 2,
MaxLength: 4, MaxLength: 4,
AllowWhitespace: false, AllowWhitespace: false,
},
Rules{
FieldName: "password",
Required: true,
MinLength: 4,
MaxLength: 8,
AllowWhitespace: false,
},
}, },
ConfirmFields: []string{ Rules{
StorePassword, ConfirmPrefix + StorePassword, FieldName: "password",
Required: true,
MinLength: 4,
MaxLength: 8,
AllowWhitespace: false,
}, },
ExpireAfter: 60 * time.Minute,
LockAfter: 3,
LockWindow: 5 * time.Minute,
LockDuration: 5 * time.Hour,
LogWriter: NewDefaultLogger(),
Callbacks: NewCallbacks(),
Mailer: LogMailer(ioutil.Discard),
} }
c.ConfirmFields = []string{
StorePassword, ConfirmPrefix + StorePassword,
}
c.ExpireAfter = 60 * time.Minute
c.LockAfter = 3
c.LockWindow = 5 * time.Minute
c.LockDuration = 5 * time.Hour
c.LogWriter = NewDefaultLogger()
c.Callbacks = NewCallbacks()
c.Mailer = LogMailer(ioutil.Discard)
} }

View File

@ -53,23 +53,23 @@ type Confirm struct {
// Initialize the module // Initialize the module
func (c *Confirm) Initialize() (err error) { func (c *Confirm) Initialize() (err error) {
var ok bool var ok bool
storer, ok := authboss.Cfg.Storer.(ConfirmStorer) storer, ok := authboss.a.Storer.(ConfirmStorer)
if storer == nil || !ok { if storer == nil || !ok {
return errors.New("confirm: Need a ConfirmStorer") return errors.New("confirm: Need a ConfirmStorer")
} }
c.emailHTMLTemplates, err = response.LoadTemplates(authboss.Cfg.LayoutHTMLEmail, authboss.Cfg.ViewsPath, tplConfirmHTML) c.emailHTMLTemplates, err = response.LoadTemplates(authboss.a.LayoutHTMLEmail, authboss.a.ViewsPath, tplConfirmHTML)
if err != nil { if err != nil {
return err return err
} }
c.emailTextTemplates, err = response.LoadTemplates(authboss.Cfg.LayoutTextEmail, authboss.Cfg.ViewsPath, tplConfirmText) c.emailTextTemplates, err = response.LoadTemplates(authboss.a.LayoutTextEmail, authboss.a.ViewsPath, tplConfirmText)
if err != nil { if err != nil {
return err return err
} }
authboss.Cfg.Callbacks.Before(authboss.EventGet, c.beforeGet) authboss.a.Callbacks.Before(authboss.EventGet, c.beforeGet)
authboss.Cfg.Callbacks.Before(authboss.EventAuth, c.beforeGet) authboss.a.Callbacks.Before(authboss.EventAuth, c.beforeGet)
authboss.Cfg.Callbacks.After(authboss.EventRegister, c.afterRegister) authboss.a.Callbacks.After(authboss.EventRegister, c.afterRegister)
return nil return nil
} }
@ -84,10 +84,10 @@ func (c *Confirm) Routes() authboss.RouteTable {
// Storage requirements // Storage requirements
func (c *Confirm) Storage() authboss.StorageOptions { func (c *Confirm) Storage() authboss.StorageOptions {
return authboss.StorageOptions{ return authboss.StorageOptions{
authboss.Cfg.PrimaryID: authboss.String, authboss.a.PrimaryID: authboss.String,
authboss.StoreEmail: authboss.String, authboss.StoreEmail: authboss.String,
StoreConfirmToken: authboss.String, StoreConfirmToken: authboss.String,
StoreConfirmed: authboss.Bool, StoreConfirmed: authboss.Bool,
} }
} }
@ -135,18 +135,18 @@ var goConfirmEmail = func(c *Confirm, to, token string) {
// confirmEmail sends a confirmation e-mail. // confirmEmail sends a confirmation e-mail.
func (c *Confirm) confirmEmail(to, token string) { func (c *Confirm) confirmEmail(to, token string) {
p := path.Join(authboss.Cfg.MountPath, "confirm") p := path.Join(authboss.a.MountPath, "confirm")
url := fmt.Sprintf("%s%s?%s=%s", authboss.Cfg.RootURL, p, url.QueryEscape(FormValueConfirm), url.QueryEscape(token)) url := fmt.Sprintf("%s%s?%s=%s", authboss.a.RootURL, p, url.QueryEscape(FormValueConfirm), url.QueryEscape(token))
email := authboss.Email{ email := authboss.Email{
To: []string{to}, To: []string{to},
From: authboss.Cfg.EmailFrom, From: authboss.a.EmailFrom,
Subject: authboss.Cfg.EmailSubjectPrefix + "Confirm New Account", Subject: authboss.a.EmailSubjectPrefix + "Confirm New Account",
} }
err := response.Email(email, c.emailHTMLTemplates, tplConfirmHTML, c.emailTextTemplates, tplConfirmText, url) err := response.Email(email, c.emailHTMLTemplates, tplConfirmHTML, c.emailTextTemplates, tplConfirmText, url)
if err != nil { if err != nil {
fmt.Fprintf(authboss.Cfg.LogWriter, "confirm: Failed to send e-mail: %v", err) fmt.Fprintf(authboss.a.LogWriter, "confirm: Failed to send e-mail: %v", err)
} }
} }
@ -166,7 +166,7 @@ func (c *Confirm) confirmHandler(ctx *authboss.Context, w http.ResponseWriter, r
sum := md5.Sum(toHash) sum := md5.Sum(toHash)
dbTok := base64.StdEncoding.EncodeToString(sum[:]) dbTok := base64.StdEncoding.EncodeToString(sum[:])
user, err := authboss.Cfg.Storer.(ConfirmStorer).ConfirmUser(dbTok) user, err := authboss.a.Storer.(ConfirmStorer).ConfirmUser(dbTok)
if err == authboss.ErrUserNotFound { if err == authboss.ErrUserNotFound {
return authboss.ErrAndRedirect{Location: "/", Err: errors.New("confirm: token not found")} return authboss.ErrAndRedirect{Location: "/", Err: errors.New("confirm: token not found")}
} else if err != nil { } else if err != nil {
@ -178,7 +178,7 @@ func (c *Confirm) confirmHandler(ctx *authboss.Context, w http.ResponseWriter, r
ctx.User[StoreConfirmToken] = "" ctx.User[StoreConfirmToken] = ""
ctx.User[StoreConfirmed] = true ctx.User[StoreConfirmed] = true
key, err := ctx.User.StringErr(authboss.Cfg.PrimaryID) key, err := ctx.User.StringErr(authboss.a.PrimaryID)
if err != nil { if err != nil {
return err return err
} }
@ -188,7 +188,7 @@ func (c *Confirm) confirmHandler(ctx *authboss.Context, w http.ResponseWriter, r
} }
ctx.SessionStorer.Put(authboss.SessionKey, key) ctx.SessionStorer.Put(authboss.SessionKey, key)
response.Redirect(ctx, w, r, authboss.Cfg.RegisterOKPath, "You have successfully confirmed your account.", "", true) response.Redirect(ctx, w, r, authboss.a.RegisterOKPath, "You have successfully confirmed your account.", "", true)
return nil return nil
} }

View File

@ -18,9 +18,9 @@ import (
func setup() *Confirm { func setup() *Confirm {
authboss.NewConfig() authboss.NewConfig()
authboss.Cfg.Storer = mocks.NewMockStorer() authboss.a.Storer = mocks.NewMockStorer()
authboss.Cfg.LayoutHTMLEmail = template.Must(template.New("").Parse(`email ^_^`)) authboss.a.LayoutHTMLEmail = template.Must(template.New("").Parse(`email ^_^`))
authboss.Cfg.LayoutTextEmail = template.Must(template.New("").Parse(`email`)) authboss.a.LayoutTextEmail = template.Must(template.New("").Parse(`email`))
c := &Confirm{} c := &Confirm{}
if err := c.Initialize(); err != nil { if err := c.Initialize(); err != nil {
@ -100,9 +100,9 @@ func TestConfirm_AfterRegister(t *testing.T) {
c := setup() c := setup()
ctx := authboss.NewContext() ctx := authboss.NewContext()
log := &bytes.Buffer{} log := &bytes.Buffer{}
authboss.Cfg.LogWriter = log authboss.a.LogWriter = log
authboss.Cfg.Mailer = authboss.LogMailer(log) authboss.a.Mailer = authboss.LogMailer(log)
authboss.Cfg.PrimaryID = authboss.StoreUsername authboss.a.PrimaryID = authboss.StoreUsername
sentEmail := false sentEmail := false
@ -115,7 +115,7 @@ func TestConfirm_AfterRegister(t *testing.T) {
t.Error("Expected it to die with user error:", err) t.Error("Expected it to die with user error:", err)
} }
ctx.User = authboss.Attributes{authboss.Cfg.PrimaryID: "username"} ctx.User = authboss.Attributes{authboss.a.PrimaryID: "username"}
if err := c.afterRegister(ctx); err == nil || err.(authboss.AttributeErr).Name != "email" { if err := c.afterRegister(ctx); err == nil || err.(authboss.AttributeErr).Name != "email" {
t.Error("Expected it to die with e-mail address error:", err) t.Error("Expected it to die with e-mail address error:", err)
} }
@ -135,8 +135,8 @@ func TestConfirm_AfterRegister(t *testing.T) {
func TestConfirm_ConfirmHandlerErrors(t *testing.T) { func TestConfirm_ConfirmHandlerErrors(t *testing.T) {
c := setup() c := setup()
log := &bytes.Buffer{} log := &bytes.Buffer{}
authboss.Cfg.LogWriter = log authboss.a.LogWriter = log
authboss.Cfg.Mailer = authboss.LogMailer(log) authboss.a.Mailer = authboss.LogMailer(log)
tests := []struct { tests := []struct {
URL string URL string
@ -177,8 +177,8 @@ func TestConfirm_Confirm(t *testing.T) {
c := setup() c := setup()
ctx := authboss.NewContext() ctx := authboss.NewContext()
log := &bytes.Buffer{} log := &bytes.Buffer{}
authboss.Cfg.LogWriter = log authboss.a.LogWriter = log
authboss.Cfg.Mailer = authboss.LogMailer(log) authboss.a.Mailer = authboss.LogMailer(log)
// Create a token // Create a token
token := []byte("hi") token := []byte("hi")
@ -186,7 +186,7 @@ func TestConfirm_Confirm(t *testing.T) {
// Create the "database" // Create the "database"
storer := mocks.NewMockStorer() storer := mocks.NewMockStorer()
authboss.Cfg.Storer = storer authboss.a.Storer = storer
user := authboss.Attributes{ user := authboss.Attributes{
authboss.StoreUsername: "usern", authboss.StoreUsername: "usern",
StoreConfirmToken: base64.StdEncoding.EncodeToString(sum[:]), StoreConfirmToken: base64.StdEncoding.EncodeToString(sum[:]),

View File

@ -19,6 +19,8 @@ var (
// need for context is a request's session store. It is not safe for use by // need for context is a request's session store. It is not safe for use by
// multiple goroutines. // multiple goroutines.
type Context struct { type Context struct {
*Authboss
SessionStorer ClientStorerErr SessionStorer ClientStorerErr
CookieStorer ClientStorerErr CookieStorer ClientStorerErr
User Attributes User Attributes
@ -28,17 +30,19 @@ type Context struct {
} }
// NewContext is exported for testing modules. // NewContext is exported for testing modules.
func NewContext() *Context { func (a *Authboss) NewContext() *Context {
return &Context{} return &Context{
Authboss: a,
}
} }
// ContextFromRequest creates a context from an http request. // ContextFromRequest creates a context from an http request.
func ContextFromRequest(r *http.Request) (*Context, error) { func (a *Authboss) ContextFromRequest(r *http.Request) (*Context, error) {
if err := r.ParseForm(); err != nil { if err := r.ParseForm(); err != nil {
return nil, err return nil, err
} }
c := NewContext() c := a.NewContext()
c.formValues = map[string][]string(r.Form) c.formValues = map[string][]string(r.Form)
c.postFormValues = map[string][]string(r.PostForm) c.postFormValues = map[string][]string(r.PostForm)
return c, nil return c, nil
@ -111,9 +115,9 @@ func (c *Context) LoadUser(key string) error {
var err error var err error
if index := strings.IndexByte(key, ';'); index > 0 { if index := strings.IndexByte(key, ';'); index > 0 {
user, err = Cfg.OAuth2Storer.GetOAuth(key[:index], key[index+1:]) user, err = c.OAuth2Storer.GetOAuth(key[:index], key[index+1:])
} else { } else {
user, err = Cfg.Storer.Get(key) user, err = c.Storer.Get(key)
} }
if err != nil { if err != nil {
return err return err
@ -144,12 +148,12 @@ func (c *Context) SaveUser() error {
return errors.New("User not initialized.") return errors.New("User not initialized.")
} }
key, ok := c.User.String(Cfg.PrimaryID) key, ok := c.User.String(c.PrimaryID)
if !ok { if !ok {
return errors.New("User improperly initialized, primary ID missing") return errors.New("User improperly initialized, primary ID missing")
} }
return Cfg.Storer.Put(key, c.User) return c.Storer.Put(key, c.User)
} }
// Attributes converts the post form values into an attributes map. // Attributes converts the post form values into an attributes map.

View File

@ -8,13 +8,17 @@ import (
) )
func TestContext_Request(t *testing.T) { func TestContext_Request(t *testing.T) {
t.Parallel()
ab := New()
req, err := http.NewRequest("POST", "http://localhost?query=string", bytes.NewBufferString("post=form")) req, err := http.NewRequest("POST", "http://localhost?query=string", bytes.NewBufferString("post=form"))
if err != nil { if err != nil {
t.Error("Unexpected Error:", err) t.Error("Unexpected Error:", err)
} }
req.Header.Set("Content-Type", "application/x-www-form-urlencoded") req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
ctx, err := ContextFromRequest(req) ctx, err := ab.ContextFromRequest(req)
if err != nil { if err != nil {
t.Error("Unexpected Error:", err) t.Error("Unexpected Error:", err)
} }
@ -69,10 +73,12 @@ func TestContext_Request(t *testing.T) {
} }
func TestContext_SaveUser(t *testing.T) { func TestContext_SaveUser(t *testing.T) {
Cfg = NewConfig() t.Parallel()
ctx := NewContext()
ab := New()
ctx := ab.NewContext()
storer := mockStorer{} storer := mockStorer{}
Cfg.Storer = storer ab.Storer = storer
ctx.User = Attributes{StoreUsername: "joe", StoreEmail: "hello@joe.com", StorePassword: "mysticalhash"} ctx.User = Attributes{StoreUsername: "joe", StoreEmail: "hello@joe.com", StorePassword: "mysticalhash"}
err := ctx.SaveUser() err := ctx.SaveUser()
@ -93,8 +99,10 @@ func TestContext_SaveUser(t *testing.T) {
} }
func TestContext_LoadUser(t *testing.T) { func TestContext_LoadUser(t *testing.T) {
Cfg = NewConfig() t.Parallel()
ctx := NewContext()
ab := New()
ctx := ab.NewContext()
attr := Attributes{ attr := Attributes{
"email": "hello@joe.com", "email": "hello@joe.com",
@ -107,8 +115,8 @@ func TestContext_LoadUser(t *testing.T) {
"joe": attr, "joe": attr,
"whatgoogle": attr, "whatgoogle": attr,
} }
Cfg.Storer = storer ab.Storer = storer
Cfg.OAuth2Storer = storer ab.OAuth2Storer = storer
ctx.User = nil ctx.User = nil
if err := ctx.LoadUser("joe"); err != nil { if err := ctx.LoadUser("joe"); err != nil {
@ -144,12 +152,14 @@ func TestContext_LoadUser(t *testing.T) {
} }
func TestContext_LoadSessionUser(t *testing.T) { func TestContext_LoadSessionUser(t *testing.T) {
Cfg = NewConfig() t.Parallel()
ctx := NewContext()
ab := New()
ctx := ab.NewContext()
storer := mockStorer{ storer := mockStorer{
"joe": Attributes{"email": "hello@joe.com", "password": "mysticalhash"}, "joe": Attributes{"email": "hello@joe.com", "password": "mysticalhash"},
} }
Cfg.Storer = storer ab.Storer = storer
ctx.SessionStorer = mockClientStore{ ctx.SessionStorer = mockClientStore{
SessionKey: "joe", SessionKey: "joe",
} }
@ -169,9 +179,12 @@ func TestContext_LoadSessionUser(t *testing.T) {
} }
func TestContext_Attributes(t *testing.T) { func TestContext_Attributes(t *testing.T) {
t.Parallel()
now := time.Now().UTC() now := time.Now().UTC()
ctx := NewContext() ab := New()
ctx := ab.NewContext()
ctx.postFormValues = map[string][]string{ ctx.postFormValues = map[string][]string{
"a": []string{"a", "1"}, "a": []string{"a", "1"},
"b_int": []string{"5", "hello"}, "b_int": []string{"5", "hello"},

View File

@ -6,6 +6,8 @@ import (
) )
func TestAttributeErr(t *testing.T) { func TestAttributeErr(t *testing.T) {
t.Parallel()
estr := "Failed to retrieve database attribute, type was wrong: lol (want: String, got: int)" estr := "Failed to retrieve database attribute, type was wrong: lol (want: String, got: int)"
if str := NewAttributeErr("lol", String, 5).Error(); str != estr { if str := NewAttributeErr("lol", String, 5).Error(); str != estr {
t.Error("Error was wrong:", str) t.Error("Error was wrong:", str)
@ -19,6 +21,8 @@ func TestAttributeErr(t *testing.T) {
} }
func TestClientDataErr(t *testing.T) { func TestClientDataErr(t *testing.T) {
t.Parallel()
estr := "Failed to retrieve client attribute: lol" estr := "Failed to retrieve client attribute: lol"
err := ClientDataErr{"lol"} err := ClientDataErr{"lol"}
if str := err.Error(); str != estr { if str := err.Error(); str != estr {
@ -27,6 +31,8 @@ func TestClientDataErr(t *testing.T) {
} }
func TestErrAndRedirect(t *testing.T) { func TestErrAndRedirect(t *testing.T) {
t.Parallel()
estr := "Error: cause, Redirecting to: /" estr := "Error: cause, Redirecting to: /"
err := ErrAndRedirect{errors.New("cause"), "/", "success", "failure"} err := ErrAndRedirect{errors.New("cause"), "/", "success", "failure"}
if str := err.Error(); str != estr { if str := err.Error(); str != estr {
@ -35,6 +41,8 @@ func TestErrAndRedirect(t *testing.T) {
} }
func TestRenderErr(t *testing.T) { func TestRenderErr(t *testing.T) {
t.Parallel()
estr := `Error rendering template "lol": cause, data: authboss.HTMLData{"a":5}` estr := `Error rendering template "lol": cause, data: authboss.HTMLData{"a":5}`
err := RenderErr{"lol", NewHTMLData("a", 5), errors.New("cause")} err := RenderErr{"lol", NewHTMLData("a", 5), errors.New("cause")}
if str := err.Error(); str != estr { if str := err.Error(); str != estr {

View File

@ -8,14 +8,14 @@ import (
var nowTime = time.Now var nowTime = time.Now
// TimeToExpiry returns zero if the user session is expired else the time until expiry. // TimeToExpiry returns zero if the user session is expired else the time until expiry.
func TimeToExpiry(w http.ResponseWriter, r *http.Request) time.Duration { func (a *Authboss) TimeToExpiry(w http.ResponseWriter, r *http.Request) time.Duration {
return timeToExpiry(Cfg.SessionStoreMaker(w, r)) return a.timeToExpiry(a.SessionStoreMaker(w, r))
} }
func timeToExpiry(session ClientStorer) time.Duration { func (a *Authboss) timeToExpiry(session ClientStorer) time.Duration {
dateStr, ok := session.Get(SessionLastAction) dateStr, ok := session.Get(SessionLastAction)
if !ok { if !ok {
return Cfg.ExpireAfter return a.ExpireAfter
} }
date, err := time.Parse(time.RFC3339, dateStr) date, err := time.Parse(time.RFC3339, dateStr)
@ -23,7 +23,7 @@ func timeToExpiry(session ClientStorer) time.Duration {
panic("last_action is not a valid RFC3339 date") panic("last_action is not a valid RFC3339 date")
} }
remaining := date.Add(Cfg.ExpireAfter).Sub(nowTime().UTC()) remaining := date.Add(a.ExpireAfter).Sub(nowTime().UTC())
if remaining > 0 { if remaining > 0 {
return remaining return remaining
} }
@ -32,35 +32,36 @@ func timeToExpiry(session ClientStorer) time.Duration {
} }
// RefreshExpiry updates the last action for the user, so he doesn't become expired. // RefreshExpiry updates the last action for the user, so he doesn't become expired.
func RefreshExpiry(w http.ResponseWriter, r *http.Request) { func (a *Authboss) RefreshExpiry(w http.ResponseWriter, r *http.Request) {
session := Cfg.SessionStoreMaker(w, r) session := a.SessionStoreMaker(w, r)
refreshExpiry(session) a.refreshExpiry(session)
} }
func refreshExpiry(session ClientStorer) { func (a *Authboss) refreshExpiry(session ClientStorer) {
session.Put(SessionLastAction, nowTime().UTC().Format(time.RFC3339)) session.Put(SessionLastAction, nowTime().UTC().Format(time.RFC3339))
} }
type expireMiddleware struct { type expireMiddleware struct {
ab *Authboss
next http.Handler next http.Handler
} }
// ExpireMiddleware ensures that the user's expiry information is kept up-to-date // ExpireMiddleware ensures that the user's expiry information is kept up-to-date
// on each request. Deletes the SessionKey from the session if the user is // on each request. Deletes the SessionKey from the session if the user is
// expired (Cfg.ExpireAfter duration since SessionLastAction). // expired (a.ExpireAfter duration since SessionLastAction).
func ExpireMiddleware(next http.Handler) http.Handler { func (a *Authboss) ExpireMiddleware(next http.Handler) http.Handler {
return expireMiddleware{next} return expireMiddleware{a, next}
} }
func (m expireMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Request) { func (m expireMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Request) {
session := Cfg.SessionStoreMaker(w, r) session := m.ab.SessionStoreMaker(w, r)
if _, ok := session.Get(SessionKey); ok { if _, ok := session.Get(SessionKey); ok {
ttl := timeToExpiry(session) ttl := m.ab.timeToExpiry(session)
if ttl == 0 { if ttl == 0 {
session.Del(SessionKey) session.Del(SessionKey)
session.Del(SessionLastAction) session.Del(SessionLastAction)
} else { } else {
refreshExpiry(session) m.ab.refreshExpiry(session)
} }
} }

View File

@ -7,15 +7,17 @@ import (
"time" "time"
) )
// These tests use the global variable nowTime so cannot be parallelized
func TestDudeIsExpired(t *testing.T) { func TestDudeIsExpired(t *testing.T) {
Cfg = NewConfig() ab := New()
session := mockClientStore{SessionKey: "username"} session := mockClientStore{SessionKey: "username"}
refreshExpiry(session) ab.refreshExpiry(session)
nowTime = func() time.Time { nowTime = func() time.Time {
return time.Now().UTC().Add(Cfg.ExpireAfter * 2) return time.Now().UTC().Add(ab.ExpireAfter * 2)
} }
Cfg.SessionStoreMaker = func(_ http.ResponseWriter, _ *http.Request) ClientStorer { ab.SessionStoreMaker = func(_ http.ResponseWriter, _ *http.Request) ClientStorer {
return session return session
} }
@ -23,7 +25,7 @@ func TestDudeIsExpired(t *testing.T) {
w := httptest.NewRecorder() w := httptest.NewRecorder()
called := false called := false
m := ExpireMiddleware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { m := ab.ExpireMiddleware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
called = true called = true
})) }))
@ -43,14 +45,14 @@ func TestDudeIsExpired(t *testing.T) {
} }
func TestDudeIsNotExpired(t *testing.T) { func TestDudeIsNotExpired(t *testing.T) {
Cfg = NewConfig() ab := New()
session := mockClientStore{SessionKey: "username"} session := mockClientStore{SessionKey: "username"}
refreshExpiry(session) ab.refreshExpiry(session)
nowTime = func() time.Time { nowTime = func() time.Time {
return time.Now().UTC().Add(Cfg.ExpireAfter / 2) return time.Now().UTC().Add(ab.ExpireAfter / 2)
} }
Cfg.SessionStoreMaker = func(_ http.ResponseWriter, _ *http.Request) ClientStorer { ab.SessionStoreMaker = func(_ http.ResponseWriter, _ *http.Request) ClientStorer {
return session return session
} }
@ -58,7 +60,7 @@ func TestDudeIsNotExpired(t *testing.T) {
w := httptest.NewRecorder() w := httptest.NewRecorder()
called := false called := false
m := ExpireMiddleware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { m := ab.ExpireMiddleware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
called = true called = true
})) }))

View File

@ -25,10 +25,10 @@ var (
funcMap = template.FuncMap{ funcMap = template.FuncMap{
"title": strings.Title, "title": strings.Title,
"mountpathed": func(location string) string { "mountpathed": func(location string) string {
if authboss.Cfg.MountPath == "/" { if authboss.a.MountPath == "/" {
return location return location
} }
return path.Join(authboss.Cfg.MountPath, location) return path.Join(authboss.a.MountPath, location)
}, },
} }
) )
@ -77,10 +77,10 @@ func (t Templates) Render(ctx *authboss.Context, w http.ResponseWriter, r *http.
return authboss.RenderErr{tpl.Name(), data, ErrTemplateNotFound} return authboss.RenderErr{tpl.Name(), data, ErrTemplateNotFound}
} }
data.MergeKV("xsrfName", template.HTML(authboss.Cfg.XSRFName), "xsrfToken", template.HTML(authboss.Cfg.XSRFMaker(w, r))) data.MergeKV("xsrfName", template.HTML(authboss.a.XSRFName), "xsrfToken", template.HTML(authboss.a.XSRFMaker(w, r)))
if authboss.Cfg.LayoutDataMaker != nil { if authboss.a.LayoutDataMaker != nil {
data.Merge(authboss.Cfg.LayoutDataMaker(w, r)) data.Merge(authboss.a.LayoutDataMaker(w, r))
} }
if flash, ok := ctx.SessionStorer.Get(authboss.FlashSuccessKey); ok { if flash, ok := ctx.SessionStorer.Get(authboss.FlashSuccessKey); ok {
@ -130,7 +130,7 @@ func Email(email authboss.Email, htmlTpls Templates, nameHTML string, textTpls T
} }
email.TextBody = plainBuffer.String() email.TextBody = plainBuffer.String()
if err := authboss.Cfg.Mailer.Send(email); err != nil { if err := authboss.a.Mailer.Send(email); err != nil {
return err return err
} }

View File

@ -86,7 +86,7 @@ func TestTemplates_Render(t *testing.T) {
func Test_Email(t *testing.T) { func Test_Email(t *testing.T) {
mockMailer := &mocks.MockMailer{} mockMailer := &mocks.MockMailer{}
authboss.Cfg.Mailer = mockMailer authboss.a.Mailer = mockMailer
htmlTpls := Templates{"html": testEmailHTMLTempalte} htmlTpls := Templates{"html": testEmailHTMLTempalte}
textTpls := Templates{"plain": testEmailPlainTempalte} textTpls := Templates{"plain": testEmailPlainTempalte}

View File

@ -29,15 +29,15 @@ type Lock struct {
// Initialize the module // Initialize the module
func (l *Lock) Initialize() error { func (l *Lock) Initialize() error {
if authboss.Cfg.Storer == nil { if authboss.a.Storer == nil {
return errors.New("lock: Need a Storer") return errors.New("lock: Need a Storer")
} }
// Events // Events
authboss.Cfg.Callbacks.Before(authboss.EventGet, l.beforeAuth) authboss.a.Callbacks.Before(authboss.EventGet, l.beforeAuth)
authboss.Cfg.Callbacks.Before(authboss.EventAuth, l.beforeAuth) authboss.a.Callbacks.Before(authboss.EventAuth, l.beforeAuth)
authboss.Cfg.Callbacks.After(authboss.EventAuth, l.afterAuth) authboss.a.Callbacks.After(authboss.EventAuth, l.afterAuth)
authboss.Cfg.Callbacks.After(authboss.EventAuthFail, l.afterAuthFail) authboss.a.Callbacks.After(authboss.EventAuthFail, l.afterAuthFail)
return nil return nil
} }
@ -50,10 +50,10 @@ func (l *Lock) Routes() authboss.RouteTable {
// Storage requirements // Storage requirements
func (l *Lock) Storage() authboss.StorageOptions { func (l *Lock) Storage() authboss.StorageOptions {
return authboss.StorageOptions{ return authboss.StorageOptions{
authboss.Cfg.PrimaryID: authboss.String, authboss.a.PrimaryID: authboss.String,
StoreAttemptNumber: authboss.Integer, StoreAttemptNumber: authboss.Integer,
StoreAttemptTime: authboss.DateTime, StoreAttemptTime: authboss.DateTime,
StoreLocked: authboss.DateTime, StoreLocked: authboss.DateTime,
} }
} }
@ -104,9 +104,9 @@ func (l *Lock) afterAuthFail(ctx *authboss.Context) error {
nAttempts++ nAttempts++
if time.Now().UTC().Sub(lastAttempt) <= authboss.Cfg.LockWindow { if time.Now().UTC().Sub(lastAttempt) <= authboss.a.LockWindow {
if nAttempts >= int64(authboss.Cfg.LockAfter) { if nAttempts >= int64(authboss.a.LockAfter) {
ctx.User[StoreLocked] = time.Now().UTC().Add(authboss.Cfg.LockDuration) ctx.User[StoreLocked] = time.Now().UTC().Add(authboss.a.LockDuration)
} }
ctx.User[StoreAttemptNumber] = nAttempts ctx.User[StoreAttemptNumber] = nAttempts
@ -124,7 +124,7 @@ func (l *Lock) afterAuthFail(ctx *authboss.Context) error {
// Lock a user manually. // Lock a user manually.
func (l *Lock) Lock(key string) error { func (l *Lock) Lock(key string) error {
user, err := authboss.Cfg.Storer.Get(key) user, err := authboss.a.Storer.Get(key)
if err != nil { if err != nil {
return err return err
} }
@ -134,14 +134,14 @@ func (l *Lock) Lock(key string) error {
return err return err
} }
attr[StoreLocked] = time.Now().UTC().Add(authboss.Cfg.LockDuration) attr[StoreLocked] = time.Now().UTC().Add(authboss.a.LockDuration)
return authboss.Cfg.Storer.Put(key, attr) return authboss.a.Storer.Put(key, attr)
} }
// Unlock a user that was locked by this module. // Unlock a user that was locked by this module.
func (l *Lock) Unlock(key string) error { func (l *Lock) Unlock(key string) error {
user, err := authboss.Cfg.Storer.Get(key) user, err := authboss.a.Storer.Get(key)
if err != nil { if err != nil {
return err return err
} }
@ -153,9 +153,9 @@ func (l *Lock) Unlock(key string) error {
// Set the last attempt to be -window*2 to avoid immediately // Set the last attempt to be -window*2 to avoid immediately
// giving another login failure. // giving another login failure.
attr[StoreAttemptTime] = time.Now().UTC().Add(-authboss.Cfg.LockWindow * 2) attr[StoreAttemptTime] = time.Now().UTC().Add(-authboss.a.LockWindow * 2)
attr[StoreAttemptNumber] = int64(0) attr[StoreAttemptNumber] = int64(0)
attr[StoreLocked] = time.Now().UTC().Add(-authboss.Cfg.LockDuration) attr[StoreLocked] = time.Now().UTC().Add(-authboss.a.LockDuration)
return authboss.Cfg.Storer.Put(key, attr) return authboss.a.Storer.Put(key, attr)
} }

View File

@ -53,8 +53,8 @@ func TestAfterAuth(t *testing.T) {
} }
storer := mocks.NewMockStorer() storer := mocks.NewMockStorer()
authboss.Cfg.Storer = storer authboss.a.Storer = storer
ctx.User = authboss.Attributes{authboss.Cfg.PrimaryID: "john@john.com"} ctx.User = authboss.Attributes{authboss.a.PrimaryID: "john@john.com"}
if err := lock.afterAuth(ctx); err != nil { if err := lock.afterAuth(ctx); err != nil {
t.Error(err) t.Error(err)
@ -74,15 +74,15 @@ func TestAfterAuthFail_Lock(t *testing.T) {
ctx := authboss.NewContext() ctx := authboss.NewContext()
storer := mocks.NewMockStorer() storer := mocks.NewMockStorer()
authboss.Cfg.Storer = storer authboss.a.Storer = storer
lock := Lock{} lock := Lock{}
authboss.Cfg.LockWindow = 30 * time.Minute authboss.a.LockWindow = 30 * time.Minute
authboss.Cfg.LockDuration = 30 * time.Minute authboss.a.LockDuration = 30 * time.Minute
authboss.Cfg.LockAfter = 3 authboss.a.LockAfter = 3
email := "john@john.com" email := "john@john.com"
ctx.User = map[string]interface{}{authboss.Cfg.PrimaryID: email} ctx.User = map[string]interface{}{authboss.a.PrimaryID: email}
old = time.Now().UTC().Add(-1 * time.Hour) old = time.Now().UTC().Add(-1 * time.Hour)
@ -123,17 +123,17 @@ func TestAfterAuthFail_Reset(t *testing.T) {
ctx := authboss.NewContext() ctx := authboss.NewContext()
storer := mocks.NewMockStorer() storer := mocks.NewMockStorer()
lock := Lock{} lock := Lock{}
authboss.Cfg.LockWindow = 30 * time.Minute authboss.a.LockWindow = 30 * time.Minute
authboss.Cfg.Storer = storer authboss.a.Storer = storer
old = time.Now().UTC().Add(-time.Hour) old = time.Now().UTC().Add(-time.Hour)
email := "john@john.com" email := "john@john.com"
ctx.User = map[string]interface{}{ ctx.User = map[string]interface{}{
authboss.Cfg.PrimaryID: email, authboss.a.PrimaryID: email,
StoreAttemptNumber: int64(2), StoreAttemptNumber: int64(2),
StoreAttemptTime: old, StoreAttemptTime: old,
StoreLocked: old, StoreLocked: old,
} }
lock.afterAuthFail(ctx) lock.afterAuthFail(ctx)
@ -162,13 +162,13 @@ func TestAfterAuthFail_Errors(t *testing.T) {
func TestLock(t *testing.T) { func TestLock(t *testing.T) {
authboss.NewConfig() authboss.NewConfig()
storer := mocks.NewMockStorer() storer := mocks.NewMockStorer()
authboss.Cfg.Storer = storer authboss.a.Storer = storer
lock := Lock{} lock := Lock{}
email := "john@john.com" email := "john@john.com"
storer.Users[email] = map[string]interface{}{ storer.Users[email] = map[string]interface{}{
authboss.Cfg.PrimaryID: email, authboss.a.PrimaryID: email,
"password": "password", "password": "password",
} }
err := lock.Lock(email) err := lock.Lock(email)
@ -184,15 +184,15 @@ func TestLock(t *testing.T) {
func TestUnlock(t *testing.T) { func TestUnlock(t *testing.T) {
authboss.NewConfig() authboss.NewConfig()
storer := mocks.NewMockStorer() storer := mocks.NewMockStorer()
authboss.Cfg.Storer = storer authboss.a.Storer = storer
lock := Lock{} lock := Lock{}
authboss.Cfg.LockWindow = 1 * time.Hour authboss.a.LockWindow = 1 * time.Hour
email := "john@john.com" email := "john@john.com"
storer.Users[email] = map[string]interface{}{ storer.Users[email] = map[string]interface{}{
authboss.Cfg.PrimaryID: email, authboss.a.PrimaryID: email,
"password": "password", "password": "password",
"locked": true, "locked": true,
} }
err := lock.Unlock(email) err := lock.Unlock(email)
@ -201,7 +201,7 @@ func TestUnlock(t *testing.T) {
} }
attemptTime := storer.Users[email][StoreAttemptTime].(time.Time) attemptTime := storer.Users[email][StoreAttemptTime].(time.Time)
if attemptTime.After(time.Now().UTC().Add(-authboss.Cfg.LockWindow)) { if attemptTime.After(time.Now().UTC().Add(-authboss.a.LockWindow)) {
t.Error("StoreLocked not set correctly:", attemptTime) t.Error("StoreLocked not set correctly:", attemptTime)
} }
if number := storer.Users[email][StoreAttemptNumber].(int64); number != int64(0) { if number := storer.Users[email][StoreAttemptNumber].(int64); number != int64(0) {

View File

@ -9,6 +9,8 @@ import (
) )
func TestDefaultLogger(t *testing.T) { func TestDefaultLogger(t *testing.T) {
t.Parallel()
logger := NewDefaultLogger() logger := NewDefaultLogger()
if logger == nil { if logger == nil {
t.Error("Logger was not created.") t.Error("Logger was not created.")
@ -16,6 +18,8 @@ func TestDefaultLogger(t *testing.T) {
} }
func TestDefaultLoggerOutput(t *testing.T) { func TestDefaultLoggerOutput(t *testing.T) {
t.Parallel()
buffer := &bytes.Buffer{} buffer := &bytes.Buffer{}
logger := (*DefaultLogger)(log.New(buffer, "", log.LstdFlags)) logger := (*DefaultLogger)(log.New(buffer, "", log.LstdFlags))
io.WriteString(logger, "hello world") io.WriteString(logger, "hello world")

View File

@ -10,8 +10,8 @@ import (
) )
// SendMail uses the currently configured mailer to deliver e-mails. // SendMail uses the currently configured mailer to deliver e-mails.
func SendMail(data Email) error { func (a *Authboss) SendMail(data Email) error {
return Cfg.Mailer.Send(data) return a.Mailer.Send(data)
} }
// Mailer is a type that is capable of sending an e-mail. // Mailer is a type that is capable of sending an e-mail.

View File

@ -8,15 +8,16 @@ import (
) )
func TestMailer(t *testing.T) { func TestMailer(t *testing.T) {
Cfg = NewConfig() t.Parallel()
ab := New()
mailServer := &bytes.Buffer{} mailServer := &bytes.Buffer{}
Cfg.Mailer = LogMailer(mailServer) ab.Mailer = LogMailer(mailServer)
Cfg.Storer = mockStorer{} ab.Storer = mockStorer{}
Cfg.LogWriter = ioutil.Discard ab.LogWriter = ioutil.Discard
Init()
err := SendMail(Email{ err := ab.SendMail(Email{
To: []string{"some@email.com", "a@a.com"}, To: []string{"some@email.com", "a@a.com"},
ToNames: []string{"Jake", "Noname"}, ToNames: []string{"Jake", "Noname"},
From: "some@guy.com", From: "some@guy.com",
@ -53,6 +54,8 @@ func TestMailer(t *testing.T) {
} }
func TestSMTPMailer(t *testing.T) { func TestSMTPMailer(t *testing.T) {
t.Parallel()
var _ Mailer = SMTPMailer("server", nil) var _ Mailer = SMTPMailer("server", nil)
recovered := false recovered := false

View File

@ -56,7 +56,7 @@ func (m mockClientStore) GetErr(key string) (string, error) {
func (m mockClientStore) Put(key, val string) { m[key] = val } func (m mockClientStore) Put(key, val string) { m[key] = val }
func (m mockClientStore) Del(key string) { delete(m, key) } func (m mockClientStore) Del(key string) { delete(m, key) }
func mockRequestContext(postKeyValues ...string) *Context { func mockRequestContext(ab *Authboss, postKeyValues ...string) *Context {
keyValues := &bytes.Buffer{} keyValues := &bytes.Buffer{}
for i := 0; i < len(postKeyValues); i += 2 { for i := 0; i < len(postKeyValues); i += 2 {
if i != 0 { if i != 0 {
@ -71,7 +71,7 @@ func mockRequestContext(postKeyValues ...string) *Context {
} }
req.Header.Set("Content-Type", "application/x-www-form-urlencoded") req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
ctx, err := ContextFromRequest(req) ctx, err := ab.ContextFromRequest(req)
if err != nil { if err != nil {
panic(err) panic(err)
} }

View File

@ -9,7 +9,7 @@ var ModuleAttributes = make(AttributeMeta)
// Modularizer should be implemented by all the authboss modules. // Modularizer should be implemented by all the authboss modules.
type Modularizer interface { type Modularizer interface {
Initialize() error Initialize(*Authboss) error
Routes() RouteTable Routes() RouteTable
Storage() StorageOptions Storage() StorageOptions
} }

View File

@ -23,12 +23,13 @@ func testHandler(ctx *Context, w http.ResponseWriter, r *http.Request) error {
return nil return nil
} }
func (t *testModule) Initialize() error { return nil } func (t *testModule) Initialize(a *Authboss) error { return nil }
func (t *testModule) Routes() RouteTable { return t.r } func (t *testModule) Routes() RouteTable { return t.r }
func (t *testModule) Storage() StorageOptions { return t.s } func (t *testModule) Storage() StorageOptions { return t.s }
func TestRegister(t *testing.T) { func TestRegister(t *testing.T) {
// RegisterModule called by TestMain. modules = make(map[string]Modularizer)
RegisterModule("testmodule", testMod)
if _, ok := modules["testmodule"]; !ok { if _, ok := modules["testmodule"]; !ok {
t.Error("Expected module to be saved.") t.Error("Expected module to be saved.")
@ -40,7 +41,8 @@ func TestRegister(t *testing.T) {
} }
func TestLoadedModules(t *testing.T) { func TestLoadedModules(t *testing.T) {
// RegisterModule called by TestMain. modules = make(map[string]Modularizer)
RegisterModule("testmodule", testMod)
loadedMods := LoadedModules() loadedMods := LoadedModules()
if len(loadedMods) != 1 { if len(loadedMods) != 1 {

View File

@ -30,7 +30,7 @@ func init() {
// Initialize module // Initialize module
func (o *OAuth2) Initialize() error { func (o *OAuth2) Initialize() error {
if authboss.Cfg.OAuth2Storer == nil { if authboss.a.OAuth2Storer == nil {
return errors.New("oauth2: need an OAuth2Storer") return errors.New("oauth2: need an OAuth2Storer")
} }
return nil return nil
@ -40,7 +40,7 @@ func (o *OAuth2) Initialize() error {
func (o *OAuth2) Routes() authboss.RouteTable { func (o *OAuth2) Routes() authboss.RouteTable {
routes := make(authboss.RouteTable) routes := make(authboss.RouteTable)
for prov, cfg := range authboss.Cfg.OAuth2Providers { for prov, cfg := range authboss.a.OAuth2Providers {
prov = strings.ToLower(prov) prov = strings.ToLower(prov)
init := fmt.Sprintf("/oauth2/%s", prov) init := fmt.Sprintf("/oauth2/%s", prov)
@ -49,11 +49,11 @@ func (o *OAuth2) Routes() authboss.RouteTable {
routes[init] = oauthInit routes[init] = oauthInit
routes[callback] = oauthCallback routes[callback] = oauthCallback
if len(authboss.Cfg.MountPath) > 0 { if len(authboss.a.MountPath) > 0 {
callback = path.Join(authboss.Cfg.MountPath, callback) callback = path.Join(authboss.a.MountPath, callback)
} }
cfg.OAuth2Config.RedirectURL = authboss.Cfg.RootURL + callback a.OAuth2Config.RedirectURL = authboss.a.RootURL + callback
} }
routes["/oauth2/logout"] = logout routes["/oauth2/logout"] = logout
@ -75,7 +75,7 @@ func (o *OAuth2) Storage() authboss.StorageOptions {
func oauthInit(ctx *authboss.Context, w http.ResponseWriter, r *http.Request) error { func oauthInit(ctx *authboss.Context, w http.ResponseWriter, r *http.Request) error {
provider := strings.ToLower(filepath.Base(r.URL.Path)) provider := strings.ToLower(filepath.Base(r.URL.Path))
cfg, ok := authboss.Cfg.OAuth2Providers[provider] cfg, ok := authboss.a.OAuth2Providers[provider]
if !ok { if !ok {
return fmt.Errorf("OAuth2 provider %q not found", provider) return fmt.Errorf("OAuth2 provider %q not found", provider)
} }
@ -106,9 +106,9 @@ func oauthInit(ctx *authboss.Context, w http.ResponseWriter, r *http.Request) er
ctx.SessionStorer.Del(authboss.SessionOAuth2Params) ctx.SessionStorer.Del(authboss.SessionOAuth2Params)
} }
url := cfg.OAuth2Config.AuthCodeURL(state) url := a.OAuth2Config.AuthCodeURL(state)
extraParams := cfg.AdditionalParams.Encode() extraParams := a.AdditionalParams.Encode()
if len(extraParams) > 0 { if len(extraParams) > 0 {
url = fmt.Sprintf("%s&%s", url, extraParams) url = fmt.Sprintf("%s&%s", url, extraParams)
} }
@ -140,18 +140,18 @@ func oauthCallback(ctx *authboss.Context, w http.ResponseWriter, r *http.Request
hasErr := r.FormValue("error") hasErr := r.FormValue("error")
if len(hasErr) > 0 { if len(hasErr) > 0 {
if err := authboss.Cfg.Callbacks.FireAfter(authboss.EventOAuthFail, ctx); err != nil { if err := authboss.a.Callbacks.FireAfter(authboss.EventOAuthFail, ctx); err != nil {
return err return err
} }
return authboss.ErrAndRedirect{ return authboss.ErrAndRedirect{
Err: errors.New(r.FormValue("error_reason")), Err: errors.New(r.FormValue("error_reason")),
Location: authboss.Cfg.AuthLoginFailPath, Location: authboss.a.AuthLoginFailPath,
FlashError: fmt.Sprintf("%s login cancelled or failed.", strings.Title(provider)), FlashError: fmt.Sprintf("%s login cancelled or failed.", strings.Title(provider)),
} }
} }
cfg, ok := authboss.Cfg.OAuth2Providers[provider] cfg, ok := authboss.a.OAuth2Providers[provider]
if !ok { if !ok {
return fmt.Errorf("OAuth2 provider %q not found", provider) return fmt.Errorf("OAuth2 provider %q not found", provider)
} }
@ -165,12 +165,12 @@ func oauthCallback(ctx *authboss.Context, w http.ResponseWriter, r *http.Request
// Get the code // Get the code
code := r.FormValue("code") code := r.FormValue("code")
token, err := exchanger(cfg.OAuth2Config, oauth2.NoContext, code) token, err := exchanger(a.OAuth2Config, oauth2.NoContext, code)
if err != nil { if err != nil {
return fmt.Errorf("Could not validate oauth2 code: %v", err) return fmt.Errorf("Could not validate oauth2 code: %v", err)
} }
user, err := cfg.Callback(*cfg.OAuth2Config, token) user, err := a.Callback(*cfg.OAuth2Config, token)
if err != nil { if err != nil {
return err return err
} }
@ -189,7 +189,7 @@ func oauthCallback(ctx *authboss.Context, w http.ResponseWriter, r *http.Request
user[authboss.StoreOAuth2Refresh] = token.RefreshToken user[authboss.StoreOAuth2Refresh] = token.RefreshToken
} }
if err = authboss.Cfg.OAuth2Storer.PutOAuth(uid, provider, user); err != nil { if err = authboss.a.OAuth2Storer.PutOAuth(uid, provider, user); err != nil {
return err return err
} }
@ -197,13 +197,13 @@ func oauthCallback(ctx *authboss.Context, w http.ResponseWriter, r *http.Request
ctx.SessionStorer.Put(authboss.SessionKey, fmt.Sprintf("%s;%s", uid, provider)) ctx.SessionStorer.Put(authboss.SessionKey, fmt.Sprintf("%s;%s", uid, provider))
ctx.SessionStorer.Del(authboss.SessionHalfAuthKey) ctx.SessionStorer.Del(authboss.SessionHalfAuthKey)
if err = authboss.Cfg.Callbacks.FireAfter(authboss.EventOAuth, ctx); err != nil { if err = authboss.a.Callbacks.FireAfter(authboss.EventOAuth, ctx); err != nil {
return nil return nil
} }
ctx.SessionStorer.Del(authboss.SessionOAuth2Params) ctx.SessionStorer.Del(authboss.SessionOAuth2Params)
redirect := authboss.Cfg.AuthLoginOKPath redirect := authboss.a.AuthLoginOKPath
query := make(url.Values) query := make(url.Values)
for k, v := range values { for k, v := range values {
switch k { switch k {
@ -231,7 +231,7 @@ func logout(ctx *authboss.Context, w http.ResponseWriter, r *http.Request) error
ctx.CookieStorer.Del(authboss.CookieRemember) ctx.CookieStorer.Del(authboss.CookieRemember)
ctx.SessionStorer.Del(authboss.SessionLastAction) ctx.SessionStorer.Del(authboss.SessionLastAction)
response.Redirect(ctx, w, r, authboss.Cfg.AuthLogoutOKPath, "You have logged out", "", true) response.Redirect(ctx, w, r, authboss.a.AuthLogoutOKPath, "You have logged out", "", true)
default: default:
w.WriteHeader(http.StatusMethodNotAllowed) w.WriteHeader(http.StatusMethodNotAllowed)
} }

View File

@ -31,7 +31,7 @@ var testProviders = map[string]authboss.OAuth2Provider{
func TestInitialize(t *testing.T) { func TestInitialize(t *testing.T) {
authboss.Cfg = authboss.NewConfig() authboss.Cfg = authboss.NewConfig()
authboss.Cfg.OAuth2Storer = mocks.NewMockStorer() authboss.a.OAuth2Storer = mocks.NewMockStorer()
o := OAuth2{} o := OAuth2{}
if err := o.Initialize(); err != nil { if err := o.Initialize(); err != nil {
t.Error(err) t.Error(err)
@ -43,11 +43,11 @@ func TestRoutes(t *testing.T) {
mount := "/auth" mount := "/auth"
authboss.Cfg = authboss.NewConfig() authboss.Cfg = authboss.NewConfig()
authboss.Cfg.RootURL = root authboss.a.RootURL = root
authboss.Cfg.MountPath = mount authboss.a.MountPath = mount
authboss.Cfg.OAuth2Providers = testProviders authboss.a.OAuth2Providers = testProviders
googleCfg := authboss.Cfg.OAuth2Providers["google"].OAuth2Config googleCfg := authboss.a.OAuth2Providers["google"].OAuth2Config
if 0 != len(googleCfg.RedirectURL) { if 0 != len(googleCfg.RedirectURL) {
t.Error("RedirectURL should not be set") t.Error("RedirectURL should not be set")
} }
@ -74,7 +74,7 @@ func TestOAuth2Init(t *testing.T) {
cfg := authboss.NewConfig() cfg := authboss.NewConfig()
session := mocks.NewMockClientStorer() session := mocks.NewMockClientStorer()
cfg.OAuth2Providers = testProviders a.OAuth2Providers = testProviders
authboss.Cfg = cfg authboss.Cfg = cfg
r, _ := http.NewRequest("GET", "/oauth2/google?redir=/my/redirect%23lol&rm=true", nil) r, _ := http.NewRequest("GET", "/oauth2/google?redir=/my/redirect%23lol&rm=true", nil)
@ -137,7 +137,7 @@ func TestOAuthSuccess(t *testing.T) {
return fakeToken, nil return fakeToken, nil
} }
cfg.OAuth2Providers = map[string]authboss.OAuth2Provider{ a.OAuth2Providers = map[string]authboss.OAuth2Provider{
"fake": authboss.OAuth2Provider{ "fake": authboss.OAuth2Provider{
OAuth2Config: &oauth2.Config{ OAuth2Config: &oauth2.Config{
ClientID: `jazz`, ClientID: `jazz`,
@ -168,8 +168,8 @@ func TestOAuthSuccess(t *testing.T) {
storer := mocks.NewMockStorer() storer := mocks.NewMockStorer()
ctx.SessionStorer = session ctx.SessionStorer = session
cfg.OAuth2Storer = storer a.OAuth2Storer = storer
cfg.AuthLoginOKPath = "/fakeloginok" a.AuthLoginOKPath = "/fakeloginok"
if err := oauthCallback(ctx, w, r); err != nil { if err := oauthCallback(ctx, w, r); err != nil {
t.Error(err) t.Error(err)
@ -214,7 +214,7 @@ func TestOAuthXSRFFailure(t *testing.T) {
session := mocks.NewMockClientStorer() session := mocks.NewMockClientStorer()
session.Put(authboss.SessionOAuth2State, authboss.FormValueOAuth2State) session.Put(authboss.SessionOAuth2State, authboss.FormValueOAuth2State)
cfg.OAuth2Providers = testProviders a.OAuth2Providers = testProviders
authboss.Cfg = cfg authboss.Cfg = cfg
values := url.Values{} values := url.Values{}
@ -234,7 +234,7 @@ func TestOAuthXSRFFailure(t *testing.T) {
func TestOAuthFailure(t *testing.T) { func TestOAuthFailure(t *testing.T) {
cfg := authboss.NewConfig() cfg := authboss.NewConfig()
cfg.OAuth2Providers = testProviders a.OAuth2Providers = testProviders
authboss.Cfg = cfg authboss.Cfg = cfg
values := url.Values{} values := url.Values{}
@ -260,7 +260,7 @@ func TestOAuthFailure(t *testing.T) {
func TestLogout(t *testing.T) { func TestLogout(t *testing.T) {
authboss.Cfg = authboss.NewConfig() authboss.Cfg = authboss.NewConfig()
authboss.Cfg.AuthLogoutOKPath = "/dashboard" authboss.a.AuthLogoutOKPath = "/dashboard"
r, _ := http.NewRequest("GET", "/oauth2/google?", nil) r, _ := http.NewRequest("GET", "/oauth2/google?", nil)
w := httptest.NewRecorder() w := httptest.NewRecorder()
@ -292,7 +292,7 @@ func TestLogout(t *testing.T) {
} }
location := w.Header().Get("Location") location := w.Header().Get("Location")
if location != authboss.Cfg.AuthLogoutOKPath { if location != authboss.a.AuthLogoutOKPath {
t.Error("Redirect wrong:", location) t.Error("Redirect wrong:", location)
} }
} }

View File

@ -26,8 +26,8 @@ type googleMeResponse struct {
var clientGet = (*http.Client).Get var clientGet = (*http.Client).Get
// Google is a callback appropriate for use with Google's OAuth2 configuration. // Google is a callback appropriate for use with Google's OAuth2 configuration.
func Google(cfg oauth2.Config, token *oauth2.Token) (authboss.Attributes, error) { func Google(a.oauth2.Config, token *oauth2.Token) (authboss.Attributes, error) {
client := cfg.Client(oauth2.NoContext, token) client := a.Client(oauth2.NoContext, token)
resp, err := clientGet(client, googleInfoEndpoint) resp, err := clientGet(client, googleInfoEndpoint)
if err != nil { if err != nil {
return nil, err return nil, err

View File

@ -63,32 +63,32 @@ type Recover struct {
// Initialize module // Initialize module
func (r *Recover) Initialize() (err error) { func (r *Recover) Initialize() (err error) {
if authboss.Cfg.Storer == nil { if authboss.a.Storer == nil {
return errors.New("recover: Need a RecoverStorer") return errors.New("recover: Need a RecoverStorer")
} }
if _, ok := authboss.Cfg.Storer.(RecoverStorer); !ok { if _, ok := authboss.a.Storer.(RecoverStorer); !ok {
return errors.New("recover: RecoverStorer required for recover functionality") return errors.New("recover: RecoverStorer required for recover functionality")
} }
if len(authboss.Cfg.XSRFName) == 0 { if len(authboss.a.XSRFName) == 0 {
return errors.New("auth: XSRFName must be set") return errors.New("auth: XSRFName must be set")
} }
if authboss.Cfg.XSRFMaker == nil { if authboss.a.XSRFMaker == nil {
return errors.New("auth: XSRFMaker must be defined") return errors.New("auth: XSRFMaker must be defined")
} }
r.templates, err = response.LoadTemplates(authboss.Cfg.Layout, authboss.Cfg.ViewsPath, tplRecover, tplRecoverComplete) r.templates, err = response.LoadTemplates(authboss.a.Layout, authboss.a.ViewsPath, tplRecover, tplRecoverComplete)
if err != nil { if err != nil {
return err return err
} }
r.emailHTMLTemplates, err = response.LoadTemplates(authboss.Cfg.LayoutHTMLEmail, authboss.Cfg.ViewsPath, tplInitHTMLEmail) r.emailHTMLTemplates, err = response.LoadTemplates(authboss.a.LayoutHTMLEmail, authboss.a.ViewsPath, tplInitHTMLEmail)
if err != nil { if err != nil {
return err return err
} }
r.emailTextTemplates, err = response.LoadTemplates(authboss.Cfg.LayoutTextEmail, authboss.Cfg.ViewsPath, tplInitTextEmail) r.emailTextTemplates, err = response.LoadTemplates(authboss.a.LayoutTextEmail, authboss.a.ViewsPath, tplInitTextEmail)
if err != nil { if err != nil {
return err return err
} }
@ -107,7 +107,7 @@ func (r *Recover) Routes() authboss.RouteTable {
// Storage requirements // Storage requirements
func (r *Recover) Storage() authboss.StorageOptions { func (r *Recover) Storage() authboss.StorageOptions {
return authboss.StorageOptions{ return authboss.StorageOptions{
authboss.Cfg.PrimaryID: authboss.String, authboss.a.PrimaryID: authboss.String,
authboss.StoreEmail: authboss.String, authboss.StoreEmail: authboss.String,
authboss.StorePassword: authboss.String, authboss.StorePassword: authboss.String,
StoreRecoverToken: authboss.String, StoreRecoverToken: authboss.String,
@ -119,31 +119,31 @@ func (rec *Recover) startHandlerFunc(ctx *authboss.Context, w http.ResponseWrite
switch r.Method { switch r.Method {
case methodGET: case methodGET:
data := authboss.NewHTMLData( data := authboss.NewHTMLData(
"primaryID", authboss.Cfg.PrimaryID, "primaryID", authboss.a.PrimaryID,
"primaryIDValue", "", "primaryIDValue", "",
"confirmPrimaryIDValue", "", "confirmPrimaryIDValue", "",
) )
return rec.templates.Render(ctx, w, r, tplRecover, data) return rec.templates.Render(ctx, w, r, tplRecover, data)
case methodPOST: case methodPOST:
primaryID, _ := ctx.FirstPostFormValue(authboss.Cfg.PrimaryID) primaryID, _ := ctx.FirstPostFormValue(authboss.a.PrimaryID)
confirmPrimaryID, _ := ctx.FirstPostFormValue(fmt.Sprintf("confirm_%s", authboss.Cfg.PrimaryID)) confirmPrimaryID, _ := ctx.FirstPostFormValue(fmt.Sprintf("confirm_%s", authboss.a.PrimaryID))
errData := authboss.NewHTMLData( errData := authboss.NewHTMLData(
"primaryID", authboss.Cfg.PrimaryID, "primaryID", authboss.a.PrimaryID,
"primaryIDValue", primaryID, "primaryIDValue", primaryID,
"confirmPrimaryIDValue", confirmPrimaryID, "confirmPrimaryIDValue", confirmPrimaryID,
) )
policies := authboss.FilterValidators(authboss.Cfg.Policies, authboss.Cfg.PrimaryID) policies := authboss.FilterValidators(authboss.a.Policies, authboss.a.PrimaryID)
if validationErrs := ctx.Validate(policies, authboss.Cfg.PrimaryID, authboss.ConfirmPrefix+authboss.Cfg.PrimaryID).Map(); len(validationErrs) > 0 { if validationErrs := ctx.Validate(policies, authboss.a.PrimaryID, authboss.ConfirmPrefix+authboss.a.PrimaryID).Map(); len(validationErrs) > 0 {
errData.MergeKV("errs", validationErrs) errData.MergeKV("errs", validationErrs)
return rec.templates.Render(ctx, w, r, tplRecover, errData) return rec.templates.Render(ctx, w, r, tplRecover, errData)
} }
// redirect to login when user not found to prevent username sniffing // redirect to login when user not found to prevent username sniffing
if err := ctx.LoadUser(primaryID); err == authboss.ErrUserNotFound { if err := ctx.LoadUser(primaryID); err == authboss.ErrUserNotFound {
return authboss.ErrAndRedirect{err, authboss.Cfg.RecoverOKPath, recoverInitiateSuccessFlash, ""} return authboss.ErrAndRedirect{err, authboss.a.RecoverOKPath, recoverInitiateSuccessFlash, ""}
} else if err != nil { } else if err != nil {
return err return err
} }
@ -159,7 +159,7 @@ func (rec *Recover) startHandlerFunc(ctx *authboss.Context, w http.ResponseWrite
} }
ctx.User[StoreRecoverToken] = encodedChecksum ctx.User[StoreRecoverToken] = encodedChecksum
ctx.User[StoreRecoverTokenExpiry] = time.Now().Add(authboss.Cfg.RecoverTokenDuration) ctx.User[StoreRecoverTokenExpiry] = time.Now().Add(authboss.a.RecoverTokenDuration)
if err := ctx.SaveUser(); err != nil { if err := ctx.SaveUser(); err != nil {
return err return err
@ -168,7 +168,7 @@ func (rec *Recover) startHandlerFunc(ctx *authboss.Context, w http.ResponseWrite
goRecoverEmail(rec, email, encodedToken) goRecoverEmail(rec, email, encodedToken)
ctx.SessionStorer.Put(authboss.FlashSuccessKey, recoverInitiateSuccessFlash) ctx.SessionStorer.Put(authboss.FlashSuccessKey, recoverInitiateSuccessFlash)
response.Redirect(ctx, w, r, authboss.Cfg.RecoverOKPath, "", "", true) response.Redirect(ctx, w, r, authboss.a.RecoverOKPath, "", "", true)
default: default:
w.WriteHeader(http.StatusMethodNotAllowed) w.WriteHeader(http.StatusMethodNotAllowed)
} }
@ -191,17 +191,17 @@ var goRecoverEmail = func(r *Recover, to, encodedToken string) {
} }
func (r *Recover) sendRecoverEmail(to, encodedToken string) { func (r *Recover) sendRecoverEmail(to, encodedToken string) {
p := path.Join(authboss.Cfg.MountPath, "recover/complete") p := path.Join(authboss.a.MountPath, "recover/complete")
url := fmt.Sprintf("%s%s?token=%s", authboss.Cfg.RootURL, p, encodedToken) url := fmt.Sprintf("%s%s?token=%s", authboss.a.RootURL, p, encodedToken)
email := authboss.Email{ email := authboss.Email{
To: []string{to}, To: []string{to},
From: authboss.Cfg.EmailFrom, From: authboss.a.EmailFrom,
Subject: authboss.Cfg.EmailSubjectPrefix + "Password Reset", Subject: authboss.a.EmailSubjectPrefix + "Password Reset",
} }
if err := response.Email(email, r.emailHTMLTemplates, tplInitHTMLEmail, r.emailTextTemplates, tplInitTextEmail, url); err != nil { if err := response.Email(email, r.emailHTMLTemplates, tplInitHTMLEmail, r.emailTextTemplates, tplInitTextEmail, url); err != nil {
fmt.Fprintln(authboss.Cfg.LogWriter, "recover: failed to send recover email:", err) fmt.Fprintln(authboss.a.LogWriter, "recover: failed to send recover email:", err)
} }
} }
@ -227,7 +227,7 @@ func (r *Recover) completeHandlerFunc(ctx *authboss.Context, w http.ResponseWrit
password, _ := ctx.FirstPostFormValue("password") password, _ := ctx.FirstPostFormValue("password")
confirmPassword, _ := ctx.FirstPostFormValue("confirmPassword") confirmPassword, _ := ctx.FirstPostFormValue("confirmPassword")
policies := authboss.FilterValidators(authboss.Cfg.Policies, "password") policies := authboss.FilterValidators(authboss.a.Policies, "password")
if validationErrs := ctx.Validate(policies, authboss.StorePassword, authboss.ConfirmPrefix+authboss.StorePassword).Map(); len(validationErrs) > 0 { if validationErrs := ctx.Validate(policies, authboss.StorePassword, authboss.ConfirmPrefix+authboss.StorePassword).Map(); len(validationErrs) > 0 {
data := authboss.NewHTMLData( data := authboss.NewHTMLData(
"token", token, "token", token,
@ -242,7 +242,7 @@ func (r *Recover) completeHandlerFunc(ctx *authboss.Context, w http.ResponseWrit
return err return err
} }
encryptedPassword, err := bcrypt.GenerateFromPassword([]byte(password), authboss.Cfg.BCryptCost) encryptedPassword, err := bcrypt.GenerateFromPassword([]byte(password), authboss.a.BCryptCost)
if err != nil { if err != nil {
return err return err
} }
@ -252,7 +252,7 @@ func (r *Recover) completeHandlerFunc(ctx *authboss.Context, w http.ResponseWrit
var nullTime time.Time var nullTime time.Time
ctx.User[StoreRecoverTokenExpiry] = nullTime ctx.User[StoreRecoverTokenExpiry] = nullTime
primaryID, err := ctx.User.StringErr(authboss.Cfg.PrimaryID) primaryID, err := ctx.User.StringErr(authboss.a.PrimaryID)
if err != nil { if err != nil {
return err return err
} }
@ -261,12 +261,12 @@ func (r *Recover) completeHandlerFunc(ctx *authboss.Context, w http.ResponseWrit
return err return err
} }
if err := authboss.Cfg.Callbacks.FireAfter(authboss.EventPasswordReset, ctx); err != nil { if err := authboss.a.Callbacks.FireAfter(authboss.EventPasswordReset, ctx); err != nil {
return err return err
} }
ctx.SessionStorer.Put(authboss.SessionKey, primaryID) ctx.SessionStorer.Put(authboss.SessionKey, primaryID)
response.Redirect(ctx, w, req, authboss.Cfg.AuthLoginOKPath, "", "", true) response.Redirect(ctx, w, req, authboss.a.AuthLoginOKPath, "", "", true)
default: default:
w.WriteHeader(http.StatusMethodNotAllowed) w.WriteHeader(http.StatusMethodNotAllowed)
} }
@ -287,7 +287,7 @@ func verifyToken(ctx *authboss.Context) (attrs authboss.Attributes, err error) {
} }
sum := md5.Sum(decoded) sum := md5.Sum(decoded)
storer := authboss.Cfg.Storer.(RecoverStorer) storer := authboss.a.Storer.(RecoverStorer)
userInter, err := storer.RecoverUser(base64.StdEncoding.EncodeToString(sum[:])) userInter, err := storer.RecoverUser(base64.StdEncoding.EncodeToString(sum[:]))
if err != nil { if err != nil {

View File

@ -26,16 +26,16 @@ func testSetup() (r *Recover, s *mocks.MockStorer, l *bytes.Buffer) {
l = &bytes.Buffer{} l = &bytes.Buffer{}
authboss.Cfg = authboss.NewConfig() authboss.Cfg = authboss.NewConfig()
authboss.Cfg.Layout = template.Must(template.New("").Parse(`{{template "authboss" .}}`)) authboss.a.Layout = template.Must(template.New("").Parse(`{{template "authboss" .}}`))
authboss.Cfg.LayoutHTMLEmail = template.Must(template.New("").Parse(`<strong>{{template "authboss" .}}</strong>`)) authboss.a.LayoutHTMLEmail = template.Must(template.New("").Parse(`<strong>{{template "authboss" .}}</strong>`))
authboss.Cfg.LayoutTextEmail = template.Must(template.New("").Parse(`{{template "authboss" .}}`)) authboss.a.LayoutTextEmail = template.Must(template.New("").Parse(`{{template "authboss" .}}`))
authboss.Cfg.Storer = s authboss.a.Storer = s
authboss.Cfg.XSRFName = "xsrf" authboss.a.XSRFName = "xsrf"
authboss.Cfg.XSRFMaker = func(_ http.ResponseWriter, _ *http.Request) string { authboss.a.XSRFMaker = func(_ http.ResponseWriter, _ *http.Request) string {
return "xsrfvalue" return "xsrfvalue"
} }
authboss.Cfg.PrimaryID = authboss.StoreUsername authboss.a.PrimaryID = authboss.StoreUsername
authboss.Cfg.LogWriter = l authboss.a.LogWriter = l
r = &Recover{} r = &Recover{}
if err := r.Initialize(); err != nil { if err := r.Initialize(); err != nil {
@ -62,8 +62,8 @@ func TestRecover(t *testing.T) {
r, _, _ := testSetup() r, _, _ := testSetup()
storage := r.Storage() storage := r.Storage()
if storage[authboss.Cfg.PrimaryID] != authboss.String { if storage[authboss.a.PrimaryID] != authboss.String {
t.Error("Expected storage KV:", authboss.Cfg.PrimaryID, authboss.String) t.Error("Expected storage KV:", authboss.a.PrimaryID, authboss.String)
} }
if storage[authboss.StoreEmail] != authboss.String { if storage[authboss.StoreEmail] != authboss.String {
t.Error("Expected storage KV:", authboss.StoreEmail, authboss.String) t.Error("Expected storage KV:", authboss.StoreEmail, authboss.String)
@ -103,10 +103,10 @@ func TestRecover_startHandlerFunc_GET(t *testing.T) {
if !strings.Contains(body, `<form action="recover"`) { if !strings.Contains(body, `<form action="recover"`) {
t.Error("Should have rendered a form") t.Error("Should have rendered a form")
} }
if !strings.Contains(body, `name="`+authboss.Cfg.PrimaryID) { if !strings.Contains(body, `name="`+authboss.a.PrimaryID) {
t.Error("Form should contain the primary ID field") t.Error("Form should contain the primary ID field")
} }
if !strings.Contains(body, `name="confirm_`+authboss.Cfg.PrimaryID) { if !strings.Contains(body, `name="confirm_`+authboss.a.PrimaryID) {
t.Error("Form should contain the confirm primary ID field") t.Error("Form should contain the confirm primary ID field")
} }
} }
@ -141,7 +141,7 @@ func TestRecover_startHandlerFunc_POST_UserNotFound(t *testing.T) {
t.Error("Expected ErrAndRedirect error") t.Error("Expected ErrAndRedirect error")
} }
if rerr.Location != authboss.Cfg.RecoverOKPath { if rerr.Location != authboss.a.RecoverOKPath {
t.Error("Unexpected location:", rerr.Location) t.Error("Unexpected location:", rerr.Location)
} }
@ -187,7 +187,7 @@ func TestRecover_startHandlerFunc_POST(t *testing.T) {
} }
loc := w.Header().Get("Location") loc := w.Header().Get("Location")
if loc != authboss.Cfg.RecoverOKPath { if loc != authboss.a.RecoverOKPath {
t.Error("Unexpected location:", loc) t.Error("Unexpected location:", loc)
} }
@ -237,7 +237,7 @@ func TestRecover_sendRecoverMail_FailToSend(t *testing.T) {
mailer := mocks.NewMockMailer() mailer := mocks.NewMockMailer()
mailer.SendErr = "failed to send" mailer.SendErr = "failed to send"
authboss.Cfg.Mailer = mailer authboss.a.Mailer = mailer
a.sendRecoverEmail("", "") a.sendRecoverEmail("", "")
@ -250,9 +250,9 @@ func TestRecover_sendRecoverEmail(t *testing.T) {
a, _, _ := testSetup() a, _, _ := testSetup()
mailer := mocks.NewMockMailer() mailer := mocks.NewMockMailer()
authboss.Cfg.EmailSubjectPrefix = "foo " authboss.a.EmailSubjectPrefix = "foo "
authboss.Cfg.RootURL = "bar" authboss.a.RootURL = "bar"
authboss.Cfg.Mailer = mailer authboss.a.Mailer = mailer
a.sendRecoverEmail("a@b.c", "abc=") a.sendRecoverEmail("a@b.c", "abc=")
if len(mailer.Last.To) != 1 { if len(mailer.Last.To) != 1 {
@ -265,7 +265,7 @@ func TestRecover_sendRecoverEmail(t *testing.T) {
t.Error("Unexpected subject:", mailer.Last.Subject) t.Error("Unexpected subject:", mailer.Last.Subject)
} }
url := fmt.Sprintf("%s/recover/complete?token=abc=", authboss.Cfg.RootURL) url := fmt.Sprintf("%s/recover/complete?token=abc=", authboss.a.RootURL)
if !strings.Contains(mailer.Last.HTMLBody, url) { if !strings.Contains(mailer.Last.HTMLBody, url) {
t.Error("Expected HTMLBody to contain url:", url) t.Error("Expected HTMLBody to contain url:", url)
} }
@ -377,12 +377,12 @@ func TestRecover_completeHandlerFunc_POST_VerificationFails(t *testing.T) {
func TestRecover_completeHandlerFunc_POST(t *testing.T) { func TestRecover_completeHandlerFunc_POST(t *testing.T) {
rec, storer, _ := testSetup() rec, storer, _ := testSetup()
storer.Users["john"] = authboss.Attributes{authboss.Cfg.PrimaryID: "john", StoreRecoverToken: testStdBase64Token, StoreRecoverTokenExpiry: time.Now().Add(1 * time.Hour), authboss.StorePassword: "asdf"} storer.Users["john"] = authboss.Attributes{authboss.a.PrimaryID: "john", StoreRecoverToken: testStdBase64Token, StoreRecoverTokenExpiry: time.Now().Add(1 * time.Hour), authboss.StorePassword: "asdf"}
cbCalled := false cbCalled := false
authboss.Cfg.Callbacks = authboss.NewCallbacks() authboss.a.Callbacks = authboss.NewCallbacks()
authboss.Cfg.Callbacks.After(authboss.EventPasswordReset, func(_ *authboss.Context) error { authboss.a.Callbacks.After(authboss.EventPasswordReset, func(_ *authboss.Context) error {
cbCalled = true cbCalled = true
return nil return nil
}) })
@ -421,7 +421,7 @@ func TestRecover_completeHandlerFunc_POST(t *testing.T) {
} }
loc := w.Header().Get("Location") loc := w.Header().Get("Location")
if loc != authboss.Cfg.AuthLogoutOKPath { if loc != authboss.a.AuthLogoutOKPath {
t.Error("Unexpected location:", loc) t.Error("Unexpected location:", loc)
} }
} }

View File

@ -33,15 +33,15 @@ type Register struct {
// Initialize the module. // Initialize the module.
func (r *Register) Initialize() (err error) { func (r *Register) Initialize() (err error) {
if authboss.Cfg.Storer == nil { if authboss.a.Storer == nil {
return errors.New("register: Need a RegisterStorer") return errors.New("register: Need a RegisterStorer")
} }
if _, ok := authboss.Cfg.Storer.(RegisterStorer); !ok { if _, ok := authboss.a.Storer.(RegisterStorer); !ok {
return errors.New("register: RegisterStorer required for register functionality") return errors.New("register: RegisterStorer required for register functionality")
} }
if r.templates, err = response.LoadTemplates(authboss.Cfg.Layout, authboss.Cfg.ViewsPath, tplRegister); err != nil { if r.templates, err = response.LoadTemplates(authboss.a.Layout, authboss.a.ViewsPath, tplRegister); err != nil {
return err return err
} }
@ -58,7 +58,7 @@ func (r *Register) Routes() authboss.RouteTable {
// Storage returns storage requirements. // Storage returns storage requirements.
func (r *Register) Storage() authboss.StorageOptions { func (r *Register) Storage() authboss.StorageOptions {
return authboss.StorageOptions{ return authboss.StorageOptions{
authboss.Cfg.PrimaryID: authboss.String, authboss.a.PrimaryID: authboss.String,
authboss.StorePassword: authboss.String, authboss.StorePassword: authboss.String,
} }
} }
@ -67,7 +67,7 @@ func (reg *Register) registerHandler(ctx *authboss.Context, w http.ResponseWrite
switch r.Method { switch r.Method {
case "GET": case "GET":
data := authboss.HTMLData{ data := authboss.HTMLData{
"primaryID": authboss.Cfg.PrimaryID, "primaryID": authboss.a.PrimaryID,
"primaryIDValue": "", "primaryIDValue": "",
} }
return reg.templates.Render(ctx, w, r, tplRegister, data) return reg.templates.Render(ctx, w, r, tplRegister, data)
@ -78,15 +78,15 @@ func (reg *Register) registerHandler(ctx *authboss.Context, w http.ResponseWrite
} }
func (reg *Register) registerPostHandler(ctx *authboss.Context, w http.ResponseWriter, r *http.Request) error { func (reg *Register) registerPostHandler(ctx *authboss.Context, w http.ResponseWriter, r *http.Request) error {
key, _ := ctx.FirstPostFormValue(authboss.Cfg.PrimaryID) key, _ := ctx.FirstPostFormValue(authboss.a.PrimaryID)
password, _ := ctx.FirstPostFormValue(authboss.StorePassword) password, _ := ctx.FirstPostFormValue(authboss.StorePassword)
policies := authboss.FilterValidators(authboss.Cfg.Policies, authboss.Cfg.PrimaryID, authboss.StorePassword) policies := authboss.FilterValidators(authboss.a.Policies, authboss.a.PrimaryID, authboss.StorePassword)
validationErrs := ctx.Validate(policies, authboss.Cfg.ConfirmFields...) validationErrs := ctx.Validate(policies, authboss.a.ConfirmFields...)
if len(validationErrs) != 0 { if len(validationErrs) != 0 {
data := authboss.HTMLData{ data := authboss.HTMLData{
"primaryID": authboss.Cfg.PrimaryID, "primaryID": authboss.a.PrimaryID,
"primaryIDValue": key, "primaryIDValue": key,
"errs": validationErrs.Map(), "errs": validationErrs.Map(),
} }
@ -99,30 +99,30 @@ func (reg *Register) registerPostHandler(ctx *authboss.Context, w http.ResponseW
return err return err
} }
pass, err := bcrypt.GenerateFromPassword([]byte(password), authboss.Cfg.BCryptCost) pass, err := bcrypt.GenerateFromPassword([]byte(password), authboss.a.BCryptCost)
if err != nil { if err != nil {
return err return err
} }
attr[authboss.Cfg.PrimaryID] = key attr[authboss.a.PrimaryID] = key
attr[authboss.StorePassword] = string(pass) attr[authboss.StorePassword] = string(pass)
ctx.User = attr ctx.User = attr
if err := authboss.Cfg.Storer.(RegisterStorer).Create(key, attr); err != nil { if err := authboss.a.Storer.(RegisterStorer).Create(key, attr); err != nil {
return err return err
} }
if err := authboss.Cfg.Callbacks.FireAfter(authboss.EventRegister, ctx); err != nil { if err := authboss.a.Callbacks.FireAfter(authboss.EventRegister, ctx); err != nil {
return err return err
} }
if authboss.IsLoaded("confirm") { if authboss.IsLoaded("confirm") {
response.Redirect(ctx, w, r, authboss.Cfg.RegisterOKPath, "Account successfully created, please verify your e-mail address.", "", true) response.Redirect(ctx, w, r, authboss.a.RegisterOKPath, "Account successfully created, please verify your e-mail address.", "", true)
return nil return nil
} }
ctx.SessionStorer.Put(authboss.SessionKey, key) ctx.SessionStorer.Put(authboss.SessionKey, key)
response.Redirect(ctx, w, r, authboss.Cfg.RegisterOKPath, "Account successfully created, you are now logged in.", "", true) response.Redirect(ctx, w, r, authboss.a.RegisterOKPath, "Account successfully created, you are now logged in.", "", true)
return nil return nil
} }

View File

@ -15,14 +15,14 @@ import (
func setup() *Register { func setup() *Register {
authboss.Cfg = authboss.NewConfig() authboss.Cfg = authboss.NewConfig()
authboss.Cfg.RegisterOKPath = "/regsuccess" authboss.a.RegisterOKPath = "/regsuccess"
authboss.Cfg.Layout = template.Must(template.New("").Parse(`{{template "authboss" .}}`)) authboss.a.Layout = template.Must(template.New("").Parse(`{{template "authboss" .}}`))
authboss.Cfg.XSRFName = "xsrf" authboss.a.XSRFName = "xsrf"
authboss.Cfg.XSRFMaker = func(_ http.ResponseWriter, _ *http.Request) string { authboss.a.XSRFMaker = func(_ http.ResponseWriter, _ *http.Request) string {
return "xsrfvalue" return "xsrfvalue"
} }
authboss.Cfg.ConfirmFields = []string{"password", "confirm_password"} authboss.a.ConfirmFields = []string{"password", "confirm_password"}
authboss.Cfg.Storer = mocks.NewMockStorer() authboss.a.Storer = mocks.NewMockStorer()
reg := Register{} reg := Register{}
if err := reg.Initialize(); err != nil { if err := reg.Initialize(); err != nil {
@ -34,7 +34,7 @@ func setup() *Register {
func TestRegister(t *testing.T) { func TestRegister(t *testing.T) {
authboss.Cfg = authboss.NewConfig() authboss.Cfg = authboss.NewConfig()
authboss.Cfg.Storer = mocks.NewMockStorer() authboss.a.Storer = mocks.NewMockStorer()
r := Register{} r := Register{}
if err := r.Initialize(); err != nil { if err := r.Initialize(); err != nil {
@ -46,7 +46,7 @@ func TestRegister(t *testing.T) {
} }
sto := r.Storage() sto := r.Storage()
if sto[authboss.Cfg.PrimaryID] != authboss.String { if sto[authboss.a.PrimaryID] != authboss.String {
t.Error("Wanted primary ID to be a string.") t.Error("Wanted primary ID to be a string.")
} }
if sto[authboss.StorePassword] != authboss.String { if sto[authboss.StorePassword] != authboss.String {
@ -76,7 +76,7 @@ func TestRegisterGet(t *testing.T) {
if str := w.Body.String(); !strings.Contains(str, "<form") { if str := w.Body.String(); !strings.Contains(str, "<form") {
t.Error("It should have rendered a nice form:", str) t.Error("It should have rendered a nice form:", str)
} else if !strings.Contains(str, `name="`+authboss.Cfg.PrimaryID) { } else if !strings.Contains(str, `name="`+authboss.a.PrimaryID) {
t.Error("Form should contain the primary ID:", str) t.Error("Form should contain the primary ID:", str)
} }
} }
@ -88,7 +88,7 @@ func TestRegisterPostValidationErrs(t *testing.T) {
vals := url.Values{} vals := url.Values{}
email := "email@address.com" email := "email@address.com"
vals.Set(authboss.Cfg.PrimaryID, email) vals.Set(authboss.a.PrimaryID, email)
vals.Set(authboss.StorePassword, "pass") vals.Set(authboss.StorePassword, "pass")
vals.Set(authboss.ConfirmPrefix+authboss.StorePassword, "pass2") vals.Set(authboss.ConfirmPrefix+authboss.StorePassword, "pass2")
@ -113,7 +113,7 @@ func TestRegisterPostValidationErrs(t *testing.T) {
t.Error("Confirm password should have an error:", str) t.Error("Confirm password should have an error:", str)
} }
if _, err := authboss.Cfg.Storer.Get(email); err != authboss.ErrUserNotFound { if _, err := authboss.a.Storer.Get(email); err != authboss.ErrUserNotFound {
t.Error("The user should not have been saved.") t.Error("The user should not have been saved.")
} }
} }
@ -125,7 +125,7 @@ func TestRegisterPostSuccess(t *testing.T) {
vals := url.Values{} vals := url.Values{}
email := "email@address.com" email := "email@address.com"
vals.Set(authboss.Cfg.PrimaryID, email) vals.Set(authboss.a.PrimaryID, email)
vals.Set(authboss.StorePassword, "pass") vals.Set(authboss.StorePassword, "pass")
vals.Set(authboss.ConfirmPrefix+authboss.StorePassword, "pass") vals.Set(authboss.ConfirmPrefix+authboss.StorePassword, "pass")
@ -142,17 +142,17 @@ func TestRegisterPostSuccess(t *testing.T) {
t.Error("It should have written a redirect:", w.Code) t.Error("It should have written a redirect:", w.Code)
} }
if loc := w.Header().Get("Location"); loc != authboss.Cfg.RegisterOKPath { if loc := w.Header().Get("Location"); loc != authboss.a.RegisterOKPath {
t.Error("Redirected to the wrong location", loc) t.Error("Redirected to the wrong location", loc)
} }
user, err := authboss.Cfg.Storer.Get(email) user, err := authboss.a.Storer.Get(email)
if err == authboss.ErrUserNotFound { if err == authboss.ErrUserNotFound {
t.Error("The user have been saved.") t.Error("The user have been saved.")
} }
attrs := authboss.Unbind(user) attrs := authboss.Unbind(user)
if e, err := attrs.StringErr(authboss.Cfg.PrimaryID); err != nil { if e, err := attrs.StringErr(authboss.a.PrimaryID); err != nil {
t.Error(err) t.Error(err)
} else if e != email { } else if e != email {
t.Errorf("Email was not set properly, want: %s, got: %s", email, e) t.Errorf("Email was not set properly, want: %s, got: %s", email, e)

View File

@ -47,20 +47,20 @@ type Remember struct{}
// Initialize module // Initialize module
func (r *Remember) Initialize() error { func (r *Remember) Initialize() error {
if authboss.Cfg.Storer == nil && authboss.Cfg.OAuth2Storer == nil { if authboss.a.Storer == nil && authboss.a.OAuth2Storer == nil {
return errors.New("remember: Need a RememberStorer") return errors.New("remember: Need a RememberStorer")
} }
if _, ok := authboss.Cfg.Storer.(RememberStorer); !ok { if _, ok := authboss.a.Storer.(RememberStorer); !ok {
if _, ok := authboss.Cfg.OAuth2Storer.(RememberStorer); !ok { if _, ok := authboss.a.OAuth2Storer.(RememberStorer); !ok {
return errors.New("remember: RememberStorer required for remember functionality") return errors.New("remember: RememberStorer required for remember functionality")
} }
} }
authboss.Cfg.Callbacks.Before(authboss.EventGetUserSession, r.auth) authboss.a.Callbacks.Before(authboss.EventGetUserSession, r.auth)
authboss.Cfg.Callbacks.After(authboss.EventAuth, r.afterAuth) authboss.a.Callbacks.After(authboss.EventAuth, r.afterAuth)
authboss.Cfg.Callbacks.After(authboss.EventOAuth, r.afterOAuth) authboss.a.Callbacks.After(authboss.EventOAuth, r.afterOAuth)
authboss.Cfg.Callbacks.After(authboss.EventPasswordReset, r.afterPassword) authboss.a.Callbacks.After(authboss.EventPasswordReset, r.afterPassword)
return nil return nil
} }
@ -73,7 +73,7 @@ func (r *Remember) Routes() authboss.RouteTable {
// Storage requirements // Storage requirements
func (r *Remember) Storage() authboss.StorageOptions { func (r *Remember) Storage() authboss.StorageOptions {
return authboss.StorageOptions{ return authboss.StorageOptions{
authboss.Cfg.PrimaryID: authboss.String, authboss.a.PrimaryID: authboss.String,
} }
} }
@ -87,7 +87,7 @@ func (r *Remember) afterAuth(ctx *authboss.Context) error {
return errUserMissing return errUserMissing
} }
key, err := ctx.User.StringErr(authboss.Cfg.PrimaryID) key, err := ctx.User.StringErr(authboss.a.PrimaryID)
if err != nil { if err != nil {
return err return err
} }
@ -146,7 +146,7 @@ func (r *Remember) afterPassword(ctx *authboss.Context) error {
return nil return nil
} }
id, ok := ctx.User.String(authboss.Cfg.PrimaryID) id, ok := ctx.User.String(authboss.a.PrimaryID)
if !ok { if !ok {
return nil return nil
} }
@ -154,8 +154,8 @@ func (r *Remember) afterPassword(ctx *authboss.Context) error {
ctx.CookieStorer.Del(authboss.CookieRemember) ctx.CookieStorer.Del(authboss.CookieRemember)
var storer RememberStorer var storer RememberStorer
if storer, ok = authboss.Cfg.Storer.(RememberStorer); !ok { if storer, ok = authboss.a.Storer.(RememberStorer); !ok {
if storer, ok = authboss.Cfg.OAuth2Storer.(RememberStorer); !ok { if storer, ok = authboss.a.OAuth2Storer.(RememberStorer); !ok {
return nil return nil
} }
} }
@ -181,8 +181,8 @@ func (r *Remember) new(cstorer authboss.ClientStorer, storageKey string) (string
var storer RememberStorer var storer RememberStorer
var ok bool var ok bool
if storer, ok = authboss.Cfg.Storer.(RememberStorer); !ok { if storer, ok = authboss.a.Storer.(RememberStorer); !ok {
storer, ok = authboss.Cfg.OAuth2Storer.(RememberStorer) storer, ok = authboss.a.OAuth2Storer.(RememberStorer)
} }
// Save the token in the DB // Save the token in the DB
@ -226,8 +226,8 @@ func (r *Remember) auth(ctx *authboss.Context) (authboss.Interrupt, error) {
sum := md5.Sum(token) sum := md5.Sum(token)
var storer RememberStorer var storer RememberStorer
if storer, ok = authboss.Cfg.Storer.(RememberStorer); !ok { if storer, ok = authboss.a.Storer.(RememberStorer); !ok {
storer, ok = authboss.Cfg.OAuth2Storer.(RememberStorer) storer, ok = authboss.a.OAuth2Storer.(RememberStorer)
} }
err = storer.UseToken(givenKey, base64.StdEncoding.EncodeToString(sum[:])) err = storer.UseToken(givenKey, base64.StdEncoding.EncodeToString(sum[:]))

View File

@ -19,13 +19,13 @@ func TestInitialize(t *testing.T) {
t.Error("Expected error about token storers.") t.Error("Expected error about token storers.")
} }
authboss.Cfg.Storer = mocks.MockFailStorer{} authboss.a.Storer = mocks.MockFailStorer{}
err = r.Initialize() err = r.Initialize()
if err == nil { if err == nil {
t.Error("Expected error about token storers.") t.Error("Expected error about token storers.")
} }
authboss.Cfg.Storer = mocks.NewMockStorer() authboss.a.Storer = mocks.NewMockStorer()
err = r.Initialize() err = r.Initialize()
if err != nil { if err != nil {
t.Error("Unexpected error:", err) t.Error("Unexpected error:", err)
@ -36,7 +36,7 @@ func TestAfterAuth(t *testing.T) {
r := Remember{} r := Remember{}
authboss.NewConfig() authboss.NewConfig()
storer := mocks.NewMockStorer() storer := mocks.NewMockStorer()
authboss.Cfg.Storer = storer authboss.a.Storer = storer
cookies := mocks.NewMockClientStorer() cookies := mocks.NewMockClientStorer()
session := mocks.NewMockClientStorer() session := mocks.NewMockClientStorer()
@ -54,7 +54,7 @@ func TestAfterAuth(t *testing.T) {
ctx.SessionStorer = session ctx.SessionStorer = session
ctx.CookieStorer = cookies ctx.CookieStorer = cookies
ctx.User = authboss.Attributes{authboss.Cfg.PrimaryID: "test@email.com"} ctx.User = authboss.Attributes{authboss.a.PrimaryID: "test@email.com"}
if err := r.afterAuth(ctx); err != nil { if err := r.afterAuth(ctx); err != nil {
t.Error(err) t.Error(err)
@ -69,7 +69,7 @@ func TestAfterOAuth(t *testing.T) {
r := Remember{} r := Remember{}
authboss.NewConfig() authboss.NewConfig()
storer := mocks.NewMockStorer() storer := mocks.NewMockStorer()
authboss.Cfg.Storer = storer authboss.a.Storer = storer
cookies := mocks.NewMockClientStorer() cookies := mocks.NewMockClientStorer()
session := mocks.NewMockClientStorer(authboss.SessionOAuth2Params, `{"rm":"true"}`) session := mocks.NewMockClientStorer(authboss.SessionOAuth2Params, `{"rm":"true"}`)
@ -108,14 +108,14 @@ func TestAfterPasswordReset(t *testing.T) {
id := "test@email.com" id := "test@email.com"
storer := mocks.NewMockStorer() storer := mocks.NewMockStorer()
authboss.Cfg.Storer = storer authboss.a.Storer = storer
session := mocks.NewMockClientStorer() session := mocks.NewMockClientStorer()
cookies := mocks.NewMockClientStorer() cookies := mocks.NewMockClientStorer()
storer.Tokens[id] = []string{"one", "two"} storer.Tokens[id] = []string{"one", "two"}
cookies.Values[authboss.CookieRemember] = "token" cookies.Values[authboss.CookieRemember] = "token"
ctx := authboss.NewContext() ctx := authboss.NewContext()
ctx.User = authboss.Attributes{authboss.Cfg.PrimaryID: id} ctx.User = authboss.Attributes{authboss.a.PrimaryID: id}
ctx.SessionStorer = session ctx.SessionStorer = session
ctx.CookieStorer = cookies ctx.CookieStorer = cookies
@ -136,7 +136,7 @@ func TestNew(t *testing.T) {
r := &Remember{} r := &Remember{}
authboss.NewConfig() authboss.NewConfig()
storer := mocks.NewMockStorer() storer := mocks.NewMockStorer()
authboss.Cfg.Storer = storer authboss.a.Storer = storer
cookies := mocks.NewMockClientStorer() cookies := mocks.NewMockClientStorer()
key := "tester" key := "tester"
@ -165,7 +165,7 @@ func TestAuth(t *testing.T) {
r := &Remember{} r := &Remember{}
authboss.NewConfig() authboss.NewConfig()
storer := mocks.NewMockStorer() storer := mocks.NewMockStorer()
authboss.Cfg.Storer = storer authboss.a.Storer = storer
cookies := mocks.NewMockClientStorer() cookies := mocks.NewMockClientStorer()
session := mocks.NewMockClientStorer() session := mocks.NewMockClientStorer()

View File

@ -14,19 +14,19 @@ type HandlerFunc func(*Context, http.ResponseWriter, *http.Request) error
type RouteTable map[string]HandlerFunc type RouteTable map[string]HandlerFunc
// NewRouter returns a router to be mounted at some mountpoint. // NewRouter returns a router to be mounted at some mountpoint.
func NewRouter() http.Handler { func (a *Authboss) NewRouter() http.Handler {
mux := http.NewServeMux() mux := http.NewServeMux()
for name, mod := range modules { for name, mod := range modules {
for route, handler := range mod.Routes() { for route, handler := range mod.Routes() {
fmt.Fprintf(Cfg.LogWriter, "%-10s Route: %s\n", "["+name+"]", path.Join(Cfg.MountPath, route)) fmt.Fprintf(a.LogWriter, "%-10s Route: %s\n", "["+name+"]", path.Join(a.MountPath, route))
mux.Handle(path.Join(Cfg.MountPath, route), contextRoute{handler}) mux.Handle(path.Join(a.MountPath, route), contextRoute{a, handler})
} }
} }
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
if Cfg.NotFoundHandler != nil { if a.NotFoundHandler != nil {
Cfg.NotFoundHandler.ServeHTTP(w, r) a.NotFoundHandler.ServeHTTP(w, r)
} else { } else {
w.WriteHeader(http.StatusNotFound) w.WriteHeader(http.StatusNotFound)
io.WriteString(w, "404 Page not found") io.WriteString(w, "404 Page not found")
@ -37,25 +37,26 @@ func NewRouter() http.Handler {
} }
type contextRoute struct { type contextRoute struct {
*Authboss
fn HandlerFunc fn HandlerFunc
} }
func (c contextRoute) ServeHTTP(w http.ResponseWriter, r *http.Request) { func (c contextRoute) ServeHTTP(w http.ResponseWriter, r *http.Request) {
ctx, err := ContextFromRequest(r) ctx, err := c.Authboss.ContextFromRequest(r)
if err != nil { if err != nil {
fmt.Fprintf(Cfg.LogWriter, "route: Malformed request, could not create context: %v", err) fmt.Fprintf(c.LogWriter, "route: Malformed request, could not create context: %v", err)
return return
} }
ctx.CookieStorer = clientStoreWrapper{Cfg.CookieStoreMaker(w, r)} ctx.CookieStorer = clientStoreWrapper{c.CookieStoreMaker(w, r)}
ctx.SessionStorer = clientStoreWrapper{Cfg.SessionStoreMaker(w, r)} ctx.SessionStorer = clientStoreWrapper{c.SessionStoreMaker(w, r)}
err = c.fn(ctx, w, r) err = c.fn(ctx, w, r)
if err == nil { if err == nil {
return return
} }
fmt.Fprintf(Cfg.LogWriter, "Error Occurred at %s: %v", r.URL.Path, err) fmt.Fprintf(c.LogWriter, "Error Occurred at %s: %v", r.URL.Path, err)
switch e := err.(type) { switch e := err.(type) {
case ErrAndRedirect: case ErrAndRedirect:
@ -67,15 +68,15 @@ func (c contextRoute) ServeHTTP(w http.ResponseWriter, r *http.Request) {
} }
http.Redirect(w, r, e.Location, http.StatusFound) http.Redirect(w, r, e.Location, http.StatusFound)
case ClientDataErr: case ClientDataErr:
if Cfg.BadRequestHandler != nil { if c.BadRequestHandler != nil {
Cfg.BadRequestHandler.ServeHTTP(w, r) c.BadRequestHandler.ServeHTTP(w, r)
} else { } else {
w.WriteHeader(http.StatusBadRequest) w.WriteHeader(http.StatusBadRequest)
io.WriteString(w, "400 Bad request") io.WriteString(w, "400 Bad request")
} }
default: default:
if Cfg.ErrorHandler != nil { if c.ErrorHandler != nil {
Cfg.ErrorHandler.ServeHTTP(w, r) c.ErrorHandler.ServeHTTP(w, r)
} else { } else {
w.WriteHeader(http.StatusInternalServerError) w.WriteHeader(http.StatusInternalServerError)
io.WriteString(w, "500 An error has occurred") io.WriteString(w, "500 An error has occurred")

View File

@ -9,34 +9,31 @@ import (
"testing" "testing"
) )
type testRouterMod struct { type testRouterModule struct {
handler HandlerFunc routes RouteTable
routes RouteTable
} }
func (t testRouterMod) Initialize() error { return nil } func (t testRouterModule) Initialize(ab *Authboss) error { return nil }
func (t testRouterMod) Routes() RouteTable { return t.routes } func (t testRouterModule) Routes() RouteTable { return t.routes }
func (t testRouterMod) Storage() StorageOptions { return nil } func (t testRouterModule) Storage() StorageOptions { return nil }
func testRouterSetup() (http.Handler, *bytes.Buffer) { func testRouterSetup() (*Authboss, http.Handler, *bytes.Buffer) {
Cfg = NewConfig() ab := New()
Cfg.MountPath = "/prefix" ab.MountPath = "/prefix"
Cfg.SessionStoreMaker = func(w http.ResponseWriter, r *http.Request) ClientStorer { return mockClientStore{} } ab.SessionStoreMaker = func(w http.ResponseWriter, r *http.Request) ClientStorer { return mockClientStore{} }
Cfg.CookieStoreMaker = func(w http.ResponseWriter, r *http.Request) ClientStorer { return mockClientStore{} } ab.CookieStoreMaker = func(w http.ResponseWriter, r *http.Request) ClientStorer { return mockClientStore{} }
logger := &bytes.Buffer{} logger := &bytes.Buffer{}
Cfg.LogWriter = logger ab.LogWriter = logger
return NewRouter(), logger return ab, ab.NewRouter(), logger
} }
// testRouterCallbackSetup is NOT safe for use by multiple goroutines, don't use parallel
func testRouterCallbackSetup(path string, h HandlerFunc) (w *httptest.ResponseRecorder, r *http.Request) { func testRouterCallbackSetup(path string, h HandlerFunc) (w *httptest.ResponseRecorder, r *http.Request) {
modules = map[string]Modularizer{ modules = map[string]Modularizer{}
"test": testRouterMod{ RegisterModule("testrouter", testRouterModule{
routes: map[string]HandlerFunc{ routes: map[string]HandlerFunc{path: h},
path: h, })
},
},
}
w = httptest.NewRecorder() w = httptest.NewRecorder()
r, _ = http.NewRequest("GET", "http://localhost/prefix"+path, nil) r, _ = http.NewRequest("GET", "http://localhost/prefix"+path, nil)
@ -52,7 +49,7 @@ func TestRouter(t *testing.T) {
return nil return nil
}) })
router, _ := testRouterSetup() _, router, _ := testRouterSetup()
router.ServeHTTP(w, r) router.ServeHTTP(w, r)
@ -62,7 +59,7 @@ func TestRouter(t *testing.T) {
} }
func TestRouter_NotFound(t *testing.T) { func TestRouter_NotFound(t *testing.T) {
router, _ := testRouterSetup() ab, router, _ := testRouterSetup()
w := httptest.NewRecorder() w := httptest.NewRecorder()
r, _ := http.NewRequest("GET", "http://localhost/wat", nil) r, _ := http.NewRequest("GET", "http://localhost/wat", nil)
@ -75,7 +72,7 @@ func TestRouter_NotFound(t *testing.T) {
} }
called := false called := false
Cfg.NotFoundHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ab.NotFoundHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
called = true called = true
}) })
@ -93,7 +90,7 @@ func TestRouter_BadRequest(t *testing.T) {
}, },
) )
router, logger := testRouterSetup() ab, router, logger := testRouterSetup()
logger.Reset() logger.Reset()
router.ServeHTTP(w, r) router.ServeHTTP(w, r)
@ -109,7 +106,7 @@ func TestRouter_BadRequest(t *testing.T) {
} }
called := false called := false
Cfg.BadRequestHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ab.BadRequestHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
called = true called = true
}) })
@ -132,7 +129,7 @@ func TestRouter_Error(t *testing.T) {
}, },
) )
router, logger := testRouterSetup() ab, router, logger := testRouterSetup()
logger.Reset() logger.Reset()
router.ServeHTTP(w, r) router.ServeHTTP(w, r)
@ -148,7 +145,7 @@ func TestRouter_Error(t *testing.T) {
} }
called := false called := false
Cfg.ErrorHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ab.ErrorHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
called = true called = true
}) })
@ -177,10 +174,10 @@ func TestRouter_Redirect(t *testing.T) {
}, },
) )
router, logger := testRouterSetup() ab, router, logger := testRouterSetup()
session := mockClientStore{} session := mockClientStore{}
Cfg.SessionStoreMaker = func(w http.ResponseWriter, r *http.Request) ClientStorer { return session } ab.SessionStoreMaker = func(w http.ResponseWriter, r *http.Request) ClientStorer { return session }
logger.Reset() logger.Reset()
router.ServeHTTP(w, r) router.ServeHTTP(w, r)

View File

@ -72,6 +72,8 @@ func TestAttributeMeta_Names(t *testing.T) {
} }
func TestAttributeMeta_Helpers(t *testing.T) { func TestAttributeMeta_Helpers(t *testing.T) {
t.Parallel()
now := time.Now() now := time.Now()
attr := Attributes{ attr := Attributes{
"integer": int64(5), "integer": int64(5),

View File

@ -64,7 +64,8 @@ func TestErrorList_Map(t *testing.T) {
func TestValidate(t *testing.T) { func TestValidate(t *testing.T) {
t.Parallel() t.Parallel()
ctx := mockRequestContext(StoreUsername, "john", StoreEmail, "john@john.com") ab := New()
ctx := mockRequestContext(ab, StoreUsername, "john", StoreEmail, "john@john.com")
errList := ctx.Validate([]Validator{ errList := ctx.Validate([]Validator{
mockValidator{ mockValidator{
@ -95,19 +96,20 @@ func TestValidate(t *testing.T) {
func TestValidate_Confirm(t *testing.T) { func TestValidate_Confirm(t *testing.T) {
t.Parallel() t.Parallel()
ctx := mockRequestContext(StoreUsername, "john", "confirmUsername", "johnny") ab := New()
ctx := mockRequestContext(ab, StoreUsername, "john", "confirmUsername", "johnny")
errs := ctx.Validate(nil, StoreUsername, "confirmUsername").Map() errs := ctx.Validate(nil, StoreUsername, "confirmUsername").Map()
if errs["confirmUsername"][0] != "Does not match username" { if errs["confirmUsername"][0] != "Does not match username" {
t.Error("Expected a different error for confirmUsername:", errs["confirmUsername"][0]) t.Error("Expected a different error for confirmUsername:", errs["confirmUsername"][0])
} }
ctx = mockRequestContext(StoreUsername, "john", "confirmUsername", "john") ctx = mockRequestContext(ab, StoreUsername, "john", "confirmUsername", "john")
errs = ctx.Validate(nil, StoreUsername, "confirmUsername").Map() errs = ctx.Validate(nil, StoreUsername, "confirmUsername").Map()
if len(errs) != 0 { if len(errs) != 0 {
t.Error("Expected no errors:", errs) t.Error("Expected no errors:", errs)
} }
ctx = mockRequestContext(StoreUsername, "john", "confirmUsername", "john") ctx = mockRequestContext(ab, StoreUsername, "john", "confirmUsername", "john")
errs = ctx.Validate(nil, StoreUsername).Map() errs = ctx.Validate(nil, StoreUsername).Map()
if len(errs) != 0 { if len(errs) != 0 {
t.Error("Expected no errors:", errs) t.Error("Expected no errors:", errs)

View File

@ -3,6 +3,8 @@ package authboss
import "testing" import "testing"
func TestHTMLData(t *testing.T) { func TestHTMLData(t *testing.T) {
t.Parallel()
data := NewHTMLData("a", "b").MergeKV("c", "d").Merge(NewHTMLData("e", "f")) data := NewHTMLData("a", "b").MergeKV("c", "d").Merge(NewHTMLData("e", "f"))
if data["a"].(string) != "b" { if data["a"].(string) != "b" {
t.Error("A was wrong:", data["a"]) t.Error("A was wrong:", data["a"])
@ -16,6 +18,8 @@ func TestHTMLData(t *testing.T) {
} }
func TestHTMLData_Panics(t *testing.T) { func TestHTMLData_Panics(t *testing.T) {
t.Parallel()
nPanics := 0 nPanics := 0
panicCount := func() { panicCount := func() {
if r := recover(); r != nil { if r := recover(); r != nil {