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

Get tests working after latest refactors

- Change changelog format to use keepachangelog standard
- Refactor the config to be made of substructs to help organize all the
  pieces
- Add the new interfaces to the configuration
- Clean up module loading (no unnecessary reflection to create new value)
- Change User interface to have a Get/SetPID not E-mail/Username, this
  way we don't ever have to refer to one or the other, we just always
  assume pid. In the case of Confirm/Recover we'll have to make a GetEmail
  or there won't be a way for us to get the e-mail to send to.
- Delete the xsrf nonsense in the core
This commit is contained in:
Aaron L 2018-02-01 15:42:48 -08:00
parent cbfc1d8388
commit de1c2ed081
28 changed files with 313 additions and 178 deletions

View File

@ -1,7 +1,20 @@
Changelog
=========
# Changelog
## 2015-08-02 Change the way Bind/Unbind works
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
## [Unreleased]
## [2.0.0] - 2018-01-??
### Added
### Changed
### Deprecated
### Removed
### Fixed
### Security
## 2015-08-02
### Changed
This change is potentially breaking, it did break the sample since the supporting struct was wrong for the data we were using.
**Lock:** The documentation was updated to reflect that the struct value for AttemptNumber is indeed an int64.
@ -11,6 +24,7 @@ and make them into a map. Now the field list will contain all types found in the
the type in the attribute map matches what's in the struct before assignment.
## 2015-04-01 Refactor for Multi-tenancy
### Changed
This breaking change allows multiple sites running off the same code base to each use different configurations of Authboss. To migrate
your code simply use authboss.New() to get an instance of Authboss and all the old things that used to be in the authboss package are
now there. See [this commit to the sample](https://github.com/volatiletech/authboss-sample/commit/eea55fc3b03855d4e9fb63577d72ce8ff0cd4079)

View File

@ -13,9 +13,6 @@ import (
)
const (
methodGET = "GET"
methodPOST = "POST"
tplLogin = "login.html.tpl"
)
@ -26,7 +23,6 @@ func init() {
// Auth module
type Auth struct {
*authboss.Authboss
templates response.Templates
}
// Initialize module

View File

@ -21,7 +21,7 @@ func testSetup() (a *Auth, s *mocks.MockStorer) {
ab := authboss.New()
ab.LogWriter = ioutil.Discard
ab.Layout = template.Must(template.New("").Parse(`{{template "authboss" .}}`))
ab.Storer = s
ab.Storage.Server = s
ab.XSRFName = "xsrf"
ab.XSRFMaker = func(_ http.ResponseWriter, _ *http.Request) string {
return "xsrfvalue"
@ -293,7 +293,7 @@ func TestAuth_validateCredentials(t *testing.T) {
ab := authboss.New()
storer := mocks.NewMockStorer()
ab.Storer = storer
ab.Storage.Server = storer
ctx := ab.NewContext()
storer.Users["john"] = authboss.Attributes{"password": "$2a$10$pgFsuQwdhwOdZp/v52dvHeEi53ZaI7dGmtwK4bAzGGN5A4nT6doqm"}

View File

@ -11,34 +11,37 @@ import "github.com/pkg/errors"
// Authboss contains a configuration and other details for running.
type Authboss struct {
Config
loadedModules map[string]bool
viewRenderer Renderer
mailRenderer Renderer
loadedModules map[string]Moduler
}
// New makes a new instance of authboss with a default
// configuration.
func New() *Authboss {
ab := &Authboss{}
ab.loadedModules = make(map[string]Moduler)
ab.Config.Defaults()
return ab
}
// Init authboss, modules, renderers
func (a *Authboss) Init() error {
//TODO(aarondl): Figure the template names out along with new "module" loading.
views := []string{"all"}
var err error
a.viewRenderer, err = a.Config.ViewLoader.Init(views)
if err != nil {
return errors.Wrap(err, "failed to load the view renderer")
func (a *Authboss) Init(modulesToLoad ...string) error {
if len(modulesToLoad) == 0 {
modulesToLoad = RegisteredModules()
}
a.mailRenderer, err = a.Config.MailViewLoader.Init(views)
if err != nil {
return errors.Wrap(err, "failed to load the mail view renderer")
for _, name := range modulesToLoad {
mod, ok := registeredModules[name]
if !ok {
return errors.Errorf("module %s was supposed to be loaded but is not registered", name)
}
a.loadedModules[name] = mod
// Initialize the module
if err := mod.Init(a); err != nil {
return errors.Wrapf(err, "failed to init module: %s", name)
}
}
return nil

View File

@ -1,7 +1,6 @@
package authboss
import (
"io/ioutil"
"testing"
)
@ -9,9 +8,6 @@ func TestAuthBossInit(t *testing.T) {
t.Parallel()
ab := New()
ab.LogWriter = ioutil.Discard
ab.ViewLoader = mockRenderLoader{}
ab.MailViewLoader = mockRenderLoader{}
err := ab.Init()
if err != nil {
t.Error("Unexpected error:", err)

View File

@ -95,8 +95,8 @@ func (a *Authboss) NewResponse(w http.ResponseWriter, r *http.Request) *ClientSt
// LoadClientState loads the state from sessions and cookies into the request context
func (a *Authboss) LoadClientState(w http.ResponseWriter, r *http.Request) (*http.Request, error) {
if a.SessionStateStorer != nil {
state, err := a.SessionStateStorer.ReadState(w, r)
if a.Storage.SessionState != nil {
state, err := a.Storage.SessionState.ReadState(w, r)
if err != nil {
return nil, err
} else if state == nil {
@ -106,8 +106,8 @@ func (a *Authboss) LoadClientState(w http.ResponseWriter, r *http.Request) (*htt
ctx := context.WithValue(r.Context(), ctxKeySessionState, state)
r = r.WithContext(ctx)
}
if a.CookieStateStorer != nil {
state, err := a.CookieStateStorer.ReadState(w, r)
if a.Storage.CookieState != nil {
state, err := a.Storage.CookieState.ReadState(w, r)
if err != nil {
return nil, err
} else if state == nil {
@ -184,14 +184,14 @@ func (c *ClientStateResponseWriter) putClientState() error {
cookie = cookieStateIntf.(ClientState)
}
if c.ab.SessionStateStorer != nil {
err := c.ab.SessionStateStorer.WriteState(c, session, c.sessionStateEvents)
if c.ab.Storage.SessionState != nil {
err := c.ab.Storage.SessionState.WriteState(c, session, c.sessionStateEvents)
if err != nil {
return err
}
}
if c.ab.CookieStateStorer != nil {
err := c.ab.CookieStateStorer.WriteState(c, cookie, c.cookieStateEvents)
if c.ab.Storage.CookieState != nil {
err := c.ab.Storage.CookieState.WriteState(c, cookie, c.cookieStateEvents)
if err != nil {
return err
}

View File

@ -12,8 +12,8 @@ func TestStateGet(t *testing.T) {
t.Parallel()
ab := New()
ab.SessionStateStorer = newMockClientStateRW("one", "two")
ab.CookieStateStorer = newMockClientStateRW("three", "four")
ab.Storage.SessionState = newMockClientStateRW("one", "two")
ab.Storage.CookieState = newMockClientStateRW("three", "four")
r := httptest.NewRequest("GET", "/", nil)
w := ab.NewResponse(httptest.NewRecorder(), r)
@ -36,7 +36,7 @@ func TestStateResponseWriterDoubleWritePanic(t *testing.T) {
t.Parallel()
ab := New()
ab.SessionStateStorer = newMockClientStateRW("one", "two")
ab.Storage.SessionState = newMockClientStateRW("one", "two")
r := httptest.NewRequest("GET", "/", nil)
w := ab.NewResponse(httptest.NewRecorder(), r)
@ -58,8 +58,8 @@ func TestStateResponseWriterLastSecondWriteWithPrevious(t *testing.T) {
t.Parallel()
ab := New()
ab.SessionStateStorer = newMockClientStateRW("one", "two")
ab.CookieStateStorer = newMockClientStateRW("three", "four")
ab.Storage.SessionState = newMockClientStateRW("one", "two")
ab.Storage.CookieState = newMockClientStateRW("three", "four")
r := httptest.NewRequest("GET", "/", nil)
var w http.ResponseWriter = httptest.NewRecorder()
@ -85,7 +85,7 @@ func TestStateResponseWriterLastSecondWriteHeader(t *testing.T) {
t.Parallel()
ab := New()
ab.SessionStateStorer = newMockClientStateRW()
ab.Storage.SessionState = newMockClientStateRW()
r := httptest.NewRequest("GET", "/", nil)
w := ab.NewResponse(httptest.NewRecorder(), r)
@ -103,7 +103,7 @@ func TestStateResponseWriterLastSecondWriteWrite(t *testing.T) {
t.Parallel()
ab := New()
ab.SessionStateStorer = newMockClientStateRW()
ab.Storage.SessionState = newMockClientStateRW()
r := httptest.NewRequest("GET", "/", nil)
w := ab.NewResponse(httptest.NewRecorder(), r)
@ -155,7 +155,7 @@ func TestFlashClearer(t *testing.T) {
t.Parallel()
ab := New()
ab.SessionStateStorer = newMockClientStateRW(FlashSuccessKey, "a", FlashErrorKey, "b")
ab.Storage.SessionState = newMockClientStateRW(FlashSuccessKey, "a", FlashErrorKey, "b")
r := httptest.NewRequest("GET", "/", nil)
w := ab.NewResponse(httptest.NewRecorder(), r)

119
config.go
View File

@ -7,51 +7,35 @@ import (
// Config holds all the configuration for both authboss and it's modules.
type Config struct {
// MountPath is the path to mount authboss's routes at (eg /auth).
MountPath string
// ViewsPath is the path to search for overridden templates.
ViewsPath string
Paths struct {
// Mount is the path to mount authboss's routes at (eg /auth).
Mount string
// AuthLoginOK is the redirect path after a successful authentication.
AuthLoginOK string
// AuthLoginFail is the redirect path after a failed authentication.
AuthLoginFail string
// AuthLogoutOK is the redirect path after a log out.
AuthLogoutOK string
// RecoverOK is the redirect path after a successful recovery of a password.
RecoverOK string
// RegisterOK is the redirect path after a successful registration.
RegisterOK string
// RootURL is the scheme+host+port of the web application (eg https://www.happiness.com:8080) for url generation. No trailing slash.
RootURL string
}
Modules struct {
// BCryptCost is the cost of the bcrypt password hashing function.
BCryptCost int
// PrimaryID is the primary identifier of the user. Set to one of:
// authboss.StoreEmail, authboss.StoreUsername (StoreEmail is default)
PrimaryID string
// ViewLoader loads the templates for the application.
ViewLoader RenderLoader
// MailViewLoader loads the templates for mail. If this is nil, it will
// fall back to using the Renderer created from the ViewLoader instead.
MailViewLoader RenderLoader
// OAuth2Providers lists all providers that can be used. See
// OAuthProvider documentation for more details.
OAuth2Providers map[string]OAuth2Provider
// AuthLoginOKPath is the redirect path after a successful authentication.
AuthLoginOKPath string
// AuthLoginFailPath is the redirect path after a failed authentication.
AuthLoginFailPath string
// AuthLogoutOKPath is the redirect path after a log out.
AuthLogoutOKPath string
// RecoverOKPath is the redirect path after a successful recovery of a password.
RecoverOKPath string
// RecoverTokenDuration controls how long a token sent via email for password
// recovery is valid for.
RecoverTokenDuration time.Duration
// RegisterOKPath is the redirect path after a successful registration.
RegisterOKPath string
// Policies control validation of form fields and are automatically run
// against form posts that include the fields.
Policies []Validator
// ConfirmFields are fields that are supposed to be submitted with confirmation
// fields alongside them, passwords, emails etc.
ConfirmFields []string
// PreserveFields are fields used with registration that are to be rendered when
// post fails.
PreserveFields []string
@ -60,37 +44,70 @@ type Config struct {
// by the ExpireMiddleware.
ExpireAfter time.Duration
// RecoverTokenDuration controls how long a token sent via email for password
// recovery is valid for.
RecoverTokenDuration time.Duration
// LockAfter this many tries.
LockAfter int
// LockWindow is the waiting time before the number of attemps are reset.
LockWindow time.Duration
// LockDuration is how long an account is locked for.
LockDuration time.Duration
}
// EmailFrom is the email address authboss e-mails come from.
EmailFrom string
// EmailSubjectPrefix is used to add something to the front of the authboss
Mail struct {
// From is the email address authboss e-mails come from.
From string
// SubjectPrefix is used to add something to the front of the authboss
// email subjects.
EmailSubjectPrefix string
SubjectPrefix string
}
Storage struct {
// Storer is the interface through which Authboss accesses the web apps database
// for user operations.
Storer ServerStorer
Server ServerStorer
// CookieStateStorer must be defined to provide an interface capapable of
// CookieState must be defined to provide an interface capapable of
// storing cookies for the given response, and reading them from the request.
CookieStateStorer ClientStateReadWriter
// SessionStateStorer must be defined to provide an interface capable of
CookieState ClientStateReadWriter
// SessionState must be defined to provide an interface capable of
// storing session-only values for the given response, and reading them
// from the request.
SessionStateStorer ClientStateReadWriter
// LogWriter is written to when errors occur, as well as on startup to show
// which modules are loaded and which routes they registered. By default
// writes to io.Discard.
LogWriter io.Writer
// Mailer is the mailer being used to send e-mails out. Authboss defines two loggers for use
// LogMailer and SMTPMailer, the default is a LogMailer to io.Discard.
SessionState ClientStateReadWriter
}
Core struct {
// Router is the entity that controls all routing to authboss routes
// modules will register their routes with it.
Router Router
// Responder takes a generic response from a controller and prepares
// the response, uses a renderer to create the body, and replies to the
// http request.
Responder HTTPResponder
// Redirector can redirect a response, similar to Responder but responsible
// only for redirection.
Redirector HTTPRedirector
// Validator helps validate an http request, it's given a name that describes
// the form it's validating so that conditional logic may be applied.
Validator Validator
// ViewRenderer loads the templates for the application.
ViewRenderer Renderer
// MailRenderer loads the templates for mail. If this is nil, it will
// fall back to using the Renderer created from the ViewLoader instead.
MailRenderer Renderer
// Mailer is the mailer being used to send e-mails out via smtp
Mailer Mailer
// LogWriter is written to when errors occur
LogWriter io.Writer
}
}
// Defaults sets the configuration's default values.

View File

@ -19,7 +19,7 @@ import (
func setup() *Confirm {
ab := authboss.New()
ab.Storer = mocks.NewMockStorer()
ab.Storage.Server = mocks.NewMockStorer()
ab.LayoutHTMLEmail = template.Must(template.New("").Parse(`email ^_^`))
ab.LayoutTextEmail = template.Must(template.New("").Parse(`email`))

View File

@ -76,7 +76,7 @@ func (a *Authboss) CurrentUserP(w http.ResponseWriter, r *http.Request) User {
}
func (a *Authboss) currentUser(ctx context.Context, pid string) (User, error) {
user, err := a.Storer.Load(ctx, pid)
user, err := a.Storage.Server.Load(ctx, pid)
if err != nil {
return nil, err
}

View File

@ -17,8 +17,8 @@ func loadClientStateP(ab *Authboss, w http.ResponseWriter, r *http.Request) *htt
func testSetupContext() (*Authboss, *http.Request) {
ab := New()
ab.SessionStateStorer = newMockClientStateRW(SessionKey, "george-pid")
ab.Storer = mockServerStorer{
ab.Storage.SessionState = newMockClientStateRW(SessionKey, "george-pid")
ab.Storage.Server = mockServerStorer{
"george-pid": mockUser{Email: "george-pid", Password: "unreadable"},
}
r := loadClientStateP(ab, nil, httptest.NewRequest("GET", "/", nil))
@ -39,8 +39,8 @@ func testSetupContextCached() (*Authboss, mockUser, *http.Request) {
func testSetupContextPanic() *Authboss {
ab := New()
ab.SessionStateStorer = newMockClientStateRW(SessionKey, "george-pid")
ab.Storer = mockServerStorer{}
ab.Storage.SessionState = newMockClientStateRW(SessionKey, "george-pid")
ab.Storage.Server = mockServerStorer{}
return ab
}
@ -80,7 +80,7 @@ func TestCurrentUserIDP(t *testing.T) {
ab := testSetupContextPanic()
// Overwrite the setup functions state storer
ab.SessionStateStorer = newMockClientStateRW()
ab.Storage.SessionState = newMockClientStateRW()
defer func() {
if recover().(error) != ErrUserNotFound {
@ -101,7 +101,7 @@ func TestCurrentUser(t *testing.T) {
t.Error(err)
}
if got, err := user.GetEmail(context.TODO()); err != nil {
if got, err := user.GetPID(context.TODO()); err != nil {
t.Error(err)
} else if got != "george-pid" {
t.Error("got:", got)
@ -118,7 +118,7 @@ func TestCurrentUserContext(t *testing.T) {
t.Error(err)
}
if got, err := user.GetEmail(context.TODO()); err != nil {
if got, err := user.GetPID(context.TODO()); err != nil {
t.Error(err)
} else if got != "george-pid" {
t.Error("got:", got)
@ -198,7 +198,7 @@ func TestLoadCurrentUser(t *testing.T) {
t.Error(err)
}
if got, err := user.GetEmail(context.TODO()); err != nil {
if got, err := user.GetPID(context.TODO()); err != nil {
t.Error(err)
} else if got != "george-pid" {
t.Error("got:", got)

View File

@ -16,6 +16,10 @@ type testRenderer struct {
Callback func(context.Context, string, authboss.HTMLData) ([]byte, string, error)
}
func (t testRenderer) Load(names ...string) error {
return nil
}
func (t testRenderer) Render(ctx context.Context, name string, data authboss.HTMLData) ([]byte, string, error) {
return t.Callback(ctx, name, data)
}
@ -189,8 +193,8 @@ func TestResponseRedirectNonAPI(t *testing.T) {
w := httptest.NewRecorder()
ab := authboss.New()
ab.Config.SessionStateStorer = mocks.NewClientRW()
ab.Config.CookieStateStorer = mocks.NewClientRW()
ab.Config.Storage.SessionState = mocks.NewClientRW()
ab.Config.Storage.CookieState = mocks.NewClientRW()
aw := ab.NewResponse(w, r)
ro := authboss.RedirectOptions{
@ -228,8 +232,8 @@ func TestResponseRedirectNonAPIFollowRedir(t *testing.T) {
w := httptest.NewRecorder()
ab := authboss.New()
ab.Config.SessionStateStorer = mocks.NewClientRW()
ab.Config.CookieStateStorer = mocks.NewClientRW()
ab.Config.Storage.SessionState = mocks.NewClientRW()
ab.Config.Storage.CookieState = mocks.NewClientRW()
aw := ab.NewResponse(w, r)
ro := authboss.RedirectOptions{

View File

@ -61,7 +61,7 @@ func (a *Authboss) ExpireMiddleware(next http.Handler) http.Handler {
// below it.
func (m expireMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if _, ok := GetSession(r, SessionKey); ok {
ttl := timeToExpiry(r, m.ab.ExpireAfter)
ttl := timeToExpiry(r, m.ab.Modules.ExpireAfter)
if ttl == 0 {
DelSession(w, SessionKey)
DelSession(w, SessionLastAction)

View File

@ -10,7 +10,7 @@ import (
func TestExpireIsExpired(t *testing.T) {
ab := New()
ab.SessionStateStorer = newMockClientStateRW(
ab.Storage.SessionState = newMockClientStateRW(
SessionKey, "username",
SessionLastAction, time.Now().UTC().Format(time.RFC3339),
)
@ -26,7 +26,7 @@ func TestExpireIsExpired(t *testing.T) {
// No t.Parallel() - Also must be after refreshExpiry() call
nowTime = func() time.Time {
return time.Now().UTC().Add(ab.ExpireAfter * 2)
return time.Now().UTC().Add(ab.Modules.ExpireAfter * 2)
}
defer func() {
nowTime = time.Now
@ -72,8 +72,8 @@ func TestExpireIsExpired(t *testing.T) {
func TestExpireNotExpired(t *testing.T) {
ab := New()
ab.Config.ExpireAfter = time.Hour
ab.SessionStateStorer = newMockClientStateRW(
ab.Config.Modules.ExpireAfter = time.Hour
ab.Storage.SessionState = newMockClientStateRW(
SessionKey, "username",
SessionLastAction, time.Now().UTC().Format(time.RFC3339),
)
@ -90,7 +90,7 @@ func TestExpireNotExpired(t *testing.T) {
}
// No t.Parallel() - Also must be after refreshExpiry() call
newTime := time.Now().UTC().Add(ab.ExpireAfter / 2)
newTime := time.Now().UTC().Add(ab.Modules.ExpireAfter / 2)
nowTime = func() time.Time {
return newTime
}

View File

@ -31,7 +31,7 @@ type User struct {
}
func (m User) GetUsername(context.Context) (string, error) { return m.Username, nil }
func (m User) GetEmail(context.Context) (string, error) { return m.Email, nil }
func (m User) GetPID(context.Context) (string, error) { return m.Email, nil }
func (m User) GetPassword(context.Context) (string, error) { return m.Password, nil }
func (m User) GetRecoverToken(context.Context) (string, error) { return m.RecoverToken, nil }
func (m User) GetRecoverTokenExpiry(context.Context) (time.Time, error) {

View File

@ -58,7 +58,7 @@ func TestAfterAuth(t *testing.T) {
}
storer := mocks.NewMockStorer()
ab.Storer = storer
ab.Storage.Server = storer
ctx.User = authboss.Attributes{ab.PrimaryID: "john@john.com"}
if err := lock.afterAuth(ctx); err != nil {
@ -81,7 +81,7 @@ func TestAfterAuthFail_Lock(t *testing.T) {
ctx := ab.NewContext()
storer := mocks.NewMockStorer()
ab.Storer = storer
ab.Storage.Server = storer
lock := Lock{ab}
ab.LockWindow = 30 * time.Minute
ab.LockDuration = 30 * time.Minute
@ -133,7 +133,7 @@ func TestAfterAuthFail_Reset(t *testing.T) {
storer := mocks.NewMockStorer()
lock := Lock{ab}
ab.LockWindow = 30 * time.Minute
ab.Storer = storer
ab.Storage.Server = storer
old = time.Now().UTC().Add(-time.Hour)
@ -175,7 +175,7 @@ func TestLock(t *testing.T) {
ab := authboss.New()
storer := mocks.NewMockStorer()
ab.Storer = storer
ab.Storage.Server = storer
lock := Lock{ab}
email := "john@john.com"
@ -199,7 +199,7 @@ func TestUnlock(t *testing.T) {
ab := authboss.New()
storer := mocks.NewMockStorer()
ab.Storer = storer
ab.Storage.Server = storer
lock := Lock{ab}
ab.LockWindow = 1 * time.Hour

View File

@ -29,7 +29,7 @@ func (m mockServerStorer) Load(ctx context.Context, key string) (User, error) {
}
func (m mockServerStorer) Save(ctx context.Context, user User) error {
e, err := user.GetEmail(ctx)
e, err := user.GetPID(ctx)
if err != nil {
panic(err)
}
@ -39,7 +39,7 @@ func (m mockServerStorer) Save(ctx context.Context, user User) error {
return nil
}
func (m mockUser) PutEmail(ctx context.Context, email string) error {
func (m mockUser) PutPID(ctx context.Context, email string) error {
m.Email = email
return nil
}
@ -53,7 +53,7 @@ func (m mockUser) PutPassword(ctx context.Context, password string) error {
return nil
}
func (m mockUser) GetEmail(ctx context.Context) (email string, err error) {
func (m mockUser) GetPID(ctx context.Context) (email string, err error) {
return m.Email, nil
}
@ -153,16 +153,14 @@ func newMockAPIRequest(postKeyValues ...string) *http.Request {
return req
}
type mockRenderLoader struct{}
func (m mockRenderLoader) Init(names []string) (Renderer, error) {
return mockRenderer{}, nil
}
type mockRenderer struct {
expectName string
}
func (m mockRenderer) Load(names ...string) error {
return nil
}
func (m mockRenderer) Render(ctx context.Context, name string, data HTMLData) ([]byte, string, error) {
if len(m.expectName) != 0 && m.expectName != name {
panic(fmt.Sprintf("want template name: %s, but got: %s", m.expectName, name))

45
module.go Normal file
View File

@ -0,0 +1,45 @@
package authboss
var registeredModules = make(map[string]Moduler)
// Moduler should be implemented by all the authboss modules.
type Moduler interface {
// Init the module
Init(*Authboss) error
}
// RegisterModule with the core providing all the necessary information to
// integrate into authboss.
func RegisterModule(name string, m Moduler) {
registeredModules[name] = m
}
// RegisteredModules returns a list of modules that are currently registered.
func RegisteredModules() []string {
mods := make([]string, len(registeredModules))
i := 0
for k := range registeredModules {
mods[i] = k
i++
}
return mods
}
// LoadedModules returns a list of modules that are currently loaded.
func (a *Authboss) LoadedModules() []string {
mods := make([]string, len(a.loadedModules))
i := 0
for k := range a.loadedModules {
mods[i] = k
i++
}
return mods
}
// IsLoaded checks if a specific module is loaded.
func (a *Authboss) IsLoaded(mod string) bool {
_, ok := a.loadedModules[mod]
return ok
}

70
module_test.go Normal file
View File

@ -0,0 +1,70 @@
package authboss
import (
"net/http"
"testing"
)
const (
testModName = "testmodule"
)
var (
testMod = &testModule{}
)
func init() {
RegisterModule(testModName, testMod)
}
type testModule struct {
}
func testHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("testhandler", "test")
}
func (t *testModule) Init(a *Authboss) error { return nil }
func TestRegister(t *testing.T) {
t.Parallel()
// RegisterModule called by init()
if _, ok := registeredModules[testModName]; !ok {
t.Error("Expected module to be saved.")
}
}
func TestLoadedModules(t *testing.T) {
t.Parallel()
// RegisterModule called by init()
registered := RegisteredModules()
if len(registered) != 1 {
t.Error("Expected only a single module to be loaded.")
} else {
found := false
for _, name := range registered {
if name == testModName {
found = true
break
}
}
if !found {
t.Error("It should have found the module:", registered)
}
}
}
func TestIsLoaded(t *testing.T) {
t.Parallel()
ab := New()
if err := ab.Init(); err != nil {
t.Error(err)
}
if loaded := ab.LoadedModules(); len(loaded) == 0 || loaded[0] != testModName {
t.Error("Loaded modules wrong:", loaded)
}
}

View File

@ -29,7 +29,7 @@ func testSetup() (r *Recover, s *mocks.MockStorer, l *bytes.Buffer) {
ab.Layout = template.Must(template.New("").Parse(`{{template "authboss" .}}`))
ab.LayoutHTMLEmail = template.Must(template.New("").Parse(`<strong>{{template "authboss" .}}</strong>`))
ab.LayoutTextEmail = template.Must(template.New("").Parse(`{{template "authboss" .}}`))
ab.Storer = s
ab.Storage.Server = s
ab.XSRFName = "xsrf"
ab.XSRFMaker = func(_ http.ResponseWriter, _ *http.Request) string {
return "xsrfvalue"

View File

@ -22,7 +22,7 @@ func setup() *Register {
return "xsrfvalue"
}
ab.ConfirmFields = []string{"password", "confirm_password"}
ab.Storer = mocks.NewMockStorer()
ab.Storage.Server = mocks.NewMockStorer()
reg := Register{}
if err := reg.Initialize(ab); err != nil {
@ -34,7 +34,7 @@ func setup() *Register {
func TestRegister(t *testing.T) {
ab := authboss.New()
ab.Storer = mocks.NewMockStorer()
ab.Storage.Server = mocks.NewMockStorer()
r := Register{}
if err := r.Initialize(ab); err != nil {
t.Error(err)

View File

@ -19,13 +19,13 @@ func TestInitialize(t *testing.T) {
t.Error("Expected error about token storers.")
}
ab.Storer = mocks.MockFailStorer{}
ab.Storage.Server = mocks.MockFailStorer{}
err = r.Initialize(ab)
if err == nil {
t.Error("Expected error about token storers.")
}
ab.Storer = mocks.NewMockStorer()
ab.Storage.Server = mocks.NewMockStorer()
err = r.Initialize(ab)
if err != nil {
t.Error("Unexpected error:", err)

View File

@ -2,14 +2,11 @@ package authboss
import "context"
// RenderLoader is an object that understands how to load display templates.
// It's possible that Init() is a no-op if the responses are JSON or anything
// else.
type RenderLoader interface {
Init(names []string) (Renderer, error)
}
// Renderer is a type that can render a given template with some data.
type Renderer interface {
// Load the given templates, will most likely be called multiple times
Load(name ...string) error
// Render the given template
Render(ctx context.Context, name string, data HTMLData) (output []byte, contentType string, err error)
}

View File

@ -48,10 +48,10 @@ type HTTPResponder interface {
Respond(w http.ResponseWriter, r *http.Request, code int, templateName string, data HTMLData) error
}
// Redirector redirects http requests to a different url (must handle both json and html)
// HTTPRedirector redirects http requests to a different url (must handle both json and html)
// When an authboss controller wants to redirect a user to a different path, it will use
// this interface.
type Redirector interface {
type HTTPRedirector interface {
Redirect(w http.ResponseWriter, r *http.Request, ro RedirectOptions) error
}
@ -60,7 +60,7 @@ func (a *Authboss) Email(w http.ResponseWriter, r *http.Request, email Email, ro
ctx := r.Context()
if len(ro.HTMLTemplate) != 0 {
htmlBody, _, err := a.mailRenderer.Render(ctx, ro.HTMLTemplate, ro.Data)
htmlBody, _, err := a.Core.MailRenderer.Render(ctx, ro.HTMLTemplate, ro.Data)
if err != nil {
return errors.Wrap(err, "failed to render e-mail html body")
}
@ -68,12 +68,12 @@ func (a *Authboss) Email(w http.ResponseWriter, r *http.Request, email Email, ro
}
if len(ro.TextTemplate) != 0 {
textBody, _, err := a.mailRenderer.Render(ctx, ro.TextTemplate, ro.Data)
textBody, _, err := a.Core.MailRenderer.Render(ctx, ro.TextTemplate, ro.Data)
if err != nil {
return errors.Wrap(err, "failed to render e-mail text body")
}
email.TextBody = string(textBody)
}
return a.Mailer.Send(ctx, email)
return a.Core.Mailer.Send(ctx, email)
}

View File

@ -45,13 +45,14 @@ type ServerStorer interface {
// User has functions for each piece of data it requires.
// Data should not be persisted on each function call.
// User has a PID (primary ID) that is used on the site as
// a single unique identifier to any given user (very typically e-mail
// or username).
type User interface {
PutEmail(ctx context.Context, email string) error
PutUsername(ctx context.Context, username string) error
PutPID(ctx context.Context, pid string) error
PutPassword(ctx context.Context, password string) error
GetEmail(ctx context.Context) (email string, err error)
GetUsername(ctx context.Context) (username string, err error)
GetPID(ctx context.Context) (pid string, err error)
GetPassword(ctx context.Context) (password string, err error)
}

View File

@ -1,6 +0,0 @@
package authboss
import "net/http"
// XSRF returns a token that should be written to forms to prevent xsrf attacks.
type XSRF func(http.ResponseWriter, *http.Request) (token string)