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:
parent
cbfc1d8388
commit
de1c2ed081
20
CHANGELOG.md
20
CHANGELOG.md
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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"}
|
||||
|
33
authboss.go
33
authboss.go
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
119
config.go
@ -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.
|
||||
|
@ -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`))
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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{
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
45
module.go
Normal 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
70
module_test.go
Normal 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)
|
||||
}
|
||||
}
|
@ -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"
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
11
renderer.go
11
renderer.go
@ -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)
|
||||
}
|
||||
|
10
response.go
10
response.go
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user