mirror of
https://github.com/volatiletech/authboss.git
synced 2024-11-28 08:58:38 +02:00
Redo events again
- The purpose of this change is to make it so that a different module can hijack the response to the client.
This commit is contained in:
parent
2137c827d3
commit
9eb5731a3d
123
events.go
123
events.go
@ -1,13 +1,15 @@
|
||||
package authboss
|
||||
|
||||
import "context"
|
||||
import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
//go:generate stringer -output stringers.go -type "Event,Interrupt"
|
||||
//go:generate stringer -output stringers.go -type "Event"
|
||||
|
||||
// Event is used for callback registration.
|
||||
// Event type is for describing events
|
||||
type Event int
|
||||
|
||||
// Event values
|
||||
// Event kinds
|
||||
const (
|
||||
EventRegister Event = iota
|
||||
EventAuth
|
||||
@ -21,93 +23,74 @@ const (
|
||||
EventPasswordReset
|
||||
)
|
||||
|
||||
// Interrupt is used to signal to callback mechanisms
|
||||
// that the current process should not continue.
|
||||
type Interrupt int
|
||||
// EventHandler reacts to events that are fired by Authboss controllers.
|
||||
// These controllers will normally process a request by themselves, but if
|
||||
// there is special consideration for example a successful login, but the
|
||||
// user is locked, the lock module's controller may seize control over the
|
||||
// request.
|
||||
//
|
||||
// Very much a controller level middleware.
|
||||
type EventHandler func(w http.ResponseWriter, r *http.Request, handled bool) (bool, error)
|
||||
|
||||
// Interrupt values
|
||||
const (
|
||||
// InterruptNone means there was no interrupt present and the process should continue.
|
||||
InterruptNone Interrupt = iota
|
||||
// InterruptAccountLocked occurs if a user's account has been locked
|
||||
// by the lock module.
|
||||
InterruptAccountLocked
|
||||
// InterruptAccountNotConfirmed occurs if a user's account is not confirmed
|
||||
// and therefore cannot be used yet.
|
||||
InterruptAccountNotConfirmed
|
||||
// InterruptSessionExpired occurs when the user's account has had no activity for the
|
||||
// configured duration.
|
||||
InterruptSessionExpired
|
||||
)
|
||||
|
||||
// Before Events can interrupt the flow by returning an interrupt value.
|
||||
// This is used to stop the callback chain and the original handler from
|
||||
// continuing execution. The execution should also stopped if there is an error.
|
||||
type Before func(context.Context) (Interrupt, error)
|
||||
|
||||
// After is a request callback that happens after the event.
|
||||
type After func(context.Context) error
|
||||
|
||||
// Events is a collection of Events that fire before and after certain
|
||||
// methods.
|
||||
// Events is a collection of Events that fire before and after certain methods.
|
||||
type Events struct {
|
||||
before map[Event][]Before
|
||||
after map[Event][]After
|
||||
before map[Event][]EventHandler
|
||||
after map[Event][]EventHandler
|
||||
}
|
||||
|
||||
// NewEvents creates a new set of before and after Events.
|
||||
// Called only by authboss internals and for testing.
|
||||
func NewEvents() *Events {
|
||||
return &Events{
|
||||
before: make(map[Event][]Before),
|
||||
after: make(map[Event][]After),
|
||||
before: make(map[Event][]EventHandler),
|
||||
after: make(map[Event][]EventHandler),
|
||||
}
|
||||
}
|
||||
|
||||
// Before event, call f.
|
||||
func (c *Events) Before(e Event, f Before) {
|
||||
Events := c.before[e]
|
||||
Events = append(Events, f)
|
||||
c.before[e] = Events
|
||||
func (c *Events) Before(e Event, f EventHandler) {
|
||||
events := c.before[e]
|
||||
events = append(events, f)
|
||||
c.before[e] = events
|
||||
}
|
||||
|
||||
// After event, call f.
|
||||
func (c *Events) After(e Event, f After) {
|
||||
Events := c.after[e]
|
||||
Events = append(Events, f)
|
||||
c.after[e] = Events
|
||||
func (c *Events) After(e Event, f EventHandler) {
|
||||
events := c.after[e]
|
||||
events = append(events, f)
|
||||
c.after[e] = events
|
||||
}
|
||||
|
||||
// FireBefore event to all the Events with a context. The error
|
||||
// should be passed up despite being logged once here already so it
|
||||
// can write an error out to the HTTP Client. If err is nil then
|
||||
// check the value of interrupted. If error is nil then the interrupt
|
||||
// value should be checked. If it is not InterruptNone then there is a reason
|
||||
// the current process should stop it's course of action.
|
||||
func (c *Events) FireBefore(ctx context.Context, e Event) (interrupt Interrupt, err error) {
|
||||
Events := c.before[e]
|
||||
for _, fn := range Events {
|
||||
interrupt, err = fn(ctx)
|
||||
if err != nil {
|
||||
return InterruptNone, err
|
||||
}
|
||||
if interrupt != InterruptNone {
|
||||
return interrupt, nil
|
||||
}
|
||||
}
|
||||
|
||||
return InterruptNone, nil
|
||||
// FireBefore executes the handlers that were registered to fire before
|
||||
// the event passed in.
|
||||
//
|
||||
// If it encounters an error it will stop immediately without calling
|
||||
// other handlers.
|
||||
//
|
||||
// If a handler handles the request, it will pass this information both
|
||||
// to handlers further down the chain (to let them know that w has been used)
|
||||
// as well as set w to nil as a precaution.
|
||||
func (c *Events) FireBefore(e Event, w http.ResponseWriter, r *http.Request) (bool, error) {
|
||||
return c.call(c.before[e], w, r)
|
||||
}
|
||||
|
||||
// FireAfter event to all the Events with a context. The error can safely be
|
||||
// ignored as it is logged.
|
||||
func (c *Events) FireAfter(ctx context.Context, e Event) (err error) {
|
||||
Events := c.after[e]
|
||||
for _, fn := range Events {
|
||||
if err = fn(ctx); err != nil {
|
||||
return err
|
||||
func (c *Events) FireAfter(e Event, w http.ResponseWriter, r *http.Request) (bool, error) {
|
||||
return c.call(c.after[e], w, r)
|
||||
}
|
||||
|
||||
func (c *Events) call(evs []EventHandler, w http.ResponseWriter, r *http.Request) (bool, error) {
|
||||
handled := false
|
||||
|
||||
for _, fn := range evs {
|
||||
interrupt, err := fn(w, r, handled)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if interrupt {
|
||||
handled = true
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
return handled, nil
|
||||
}
|
||||
|
159
events_test.go
159
events_test.go
@ -1,9 +1,10 @@
|
||||
package authboss
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func TestEvents(t *testing.T) {
|
||||
@ -13,25 +14,25 @@ func TestEvents(t *testing.T) {
|
||||
afterCalled := false
|
||||
beforeCalled := false
|
||||
|
||||
ab.Events.Before(EventRegister, func(ctx context.Context) (Interrupt, error) {
|
||||
ab.Events.Before(EventRegister, func(http.ResponseWriter, *http.Request, bool) (bool, error) {
|
||||
beforeCalled = true
|
||||
return InterruptNone, nil
|
||||
return false, nil
|
||||
})
|
||||
ab.Events.After(EventRegister, func(ctx context.Context) error {
|
||||
ab.Events.After(EventRegister, func(http.ResponseWriter, *http.Request, bool) (bool, error) {
|
||||
afterCalled = true
|
||||
return nil
|
||||
return false, nil
|
||||
})
|
||||
|
||||
if beforeCalled || afterCalled {
|
||||
t.Error("Neither should be called.")
|
||||
}
|
||||
|
||||
interrupt, err := ab.Events.FireBefore(context.Background(), EventRegister)
|
||||
handled, err := ab.Events.FireBefore(EventRegister, nil, nil)
|
||||
if err != nil {
|
||||
t.Error("Unexpected error:", err)
|
||||
}
|
||||
if interrupt != InterruptNone {
|
||||
t.Error("It should not have been stopped.")
|
||||
if handled {
|
||||
t.Error("It should not have been handled.")
|
||||
}
|
||||
|
||||
if !beforeCalled {
|
||||
@ -41,106 +42,84 @@ func TestEvents(t *testing.T) {
|
||||
t.Error("Expected after not to be called.")
|
||||
}
|
||||
|
||||
ab.Events.FireAfter(context.Background(), EventRegister)
|
||||
ab.Events.FireAfter(EventRegister, nil, nil)
|
||||
if !afterCalled {
|
||||
t.Error("Expected after to be called.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCallbacksInterrupt(t *testing.T) {
|
||||
func TestEventsHandled(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ev := NewEvents()
|
||||
before1 := false
|
||||
before2 := false
|
||||
ab := New()
|
||||
firstCalled := false
|
||||
secondCalled := false
|
||||
|
||||
ev.Before(EventRegister, func(ctx context.Context) (Interrupt, error) {
|
||||
before1 = true
|
||||
return InterruptAccountLocked, nil
|
||||
firstHandled := false
|
||||
secondHandled := false
|
||||
|
||||
ab.Events.Before(EventRegister, func(w http.ResponseWriter, r *http.Request, handled bool) (bool, error) {
|
||||
firstCalled = true
|
||||
firstHandled = handled
|
||||
return true, nil
|
||||
})
|
||||
ev.Before(EventRegister, func(ctx context.Context) (Interrupt, error) {
|
||||
before2 = true
|
||||
return InterruptNone, nil
|
||||
ab.Events.Before(EventRegister, func(w http.ResponseWriter, r *http.Request, handled bool) (bool, error) {
|
||||
secondCalled = true
|
||||
secondHandled = handled
|
||||
return false, nil
|
||||
})
|
||||
|
||||
interrupt, err := ev.FireBefore(context.Background(), EventRegister)
|
||||
handled, err := ab.Events.FireBefore(EventRegister, nil, nil)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
t.Error("Unexpected error:", err)
|
||||
}
|
||||
if interrupt != InterruptAccountLocked {
|
||||
t.Error("The interrupt signal was not account locked:", interrupt)
|
||||
if !handled {
|
||||
t.Error("it should have been handled")
|
||||
}
|
||||
|
||||
if !before1 {
|
||||
t.Error("Before1 should have been called.")
|
||||
if !firstCalled {
|
||||
t.Error("expected first to have been called")
|
||||
}
|
||||
if before2 {
|
||||
t.Error("Before2 should not have been called.")
|
||||
if !secondCalled {
|
||||
t.Error("expected second to have been called")
|
||||
}
|
||||
|
||||
if firstHandled {
|
||||
t.Error("first should not see the event as being handled")
|
||||
}
|
||||
if !secondHandled {
|
||||
t.Error("second should see the event as being handled")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCallbacksBeforeErrors(t *testing.T) {
|
||||
func TestEventsErrors(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ev := NewEvents()
|
||||
before1 := false
|
||||
before2 := false
|
||||
ab := New()
|
||||
firstCalled := false
|
||||
secondCalled := false
|
||||
|
||||
errValue := errors.New("Problem occured")
|
||||
expect := errors.New("error")
|
||||
|
||||
ev.Before(EventRegister, func(ctx context.Context) (Interrupt, error) {
|
||||
before1 = true
|
||||
return InterruptNone, errValue
|
||||
ab.Events.Before(EventRegister, func(http.ResponseWriter, *http.Request, bool) (bool, error) {
|
||||
firstCalled = true
|
||||
return false, expect
|
||||
})
|
||||
ev.Before(EventRegister, func(ctx context.Context) (Interrupt, error) {
|
||||
before2 = true
|
||||
return InterruptNone, nil
|
||||
ab.Events.Before(EventRegister, func(http.ResponseWriter, *http.Request, bool) (bool, error) {
|
||||
secondCalled = true
|
||||
return false, nil
|
||||
})
|
||||
|
||||
interrupt, err := ev.FireBefore(context.Background(), EventRegister)
|
||||
if err != errValue {
|
||||
t.Error("Expected an error to come back.")
|
||||
}
|
||||
if interrupt != InterruptNone {
|
||||
t.Error("It should not have been stopped.")
|
||||
_, err := ab.Events.FireBefore(EventRegister, nil, nil)
|
||||
if err != expect {
|
||||
t.Error("got the wrong error back:", err)
|
||||
}
|
||||
|
||||
if !before1 {
|
||||
t.Error("Before1 should have been called.")
|
||||
if !firstCalled {
|
||||
t.Error("expected first to have been called")
|
||||
}
|
||||
if before2 {
|
||||
t.Error("Before2 should not have been called.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCallbacksAfterErrors(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ev := NewEvents()
|
||||
after1 := false
|
||||
after2 := false
|
||||
|
||||
errValue := errors.New("Problem occured")
|
||||
|
||||
ev.After(EventRegister, func(ctx context.Context) error {
|
||||
after1 = true
|
||||
return errValue
|
||||
})
|
||||
ev.After(EventRegister, func(ctx context.Context) error {
|
||||
after2 = true
|
||||
return nil
|
||||
})
|
||||
|
||||
err := ev.FireAfter(context.Background(), EventRegister)
|
||||
if err != errValue {
|
||||
t.Error("Expected an error to come back.")
|
||||
}
|
||||
|
||||
if !after1 {
|
||||
t.Error("After1 should have been called.")
|
||||
}
|
||||
if after2 {
|
||||
t.Error("After2 should not have been called.")
|
||||
if secondCalled {
|
||||
t.Error("expected second to not have been called")
|
||||
}
|
||||
}
|
||||
|
||||
@ -169,23 +148,3 @@ func TestEventString(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestInterruptString(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
in Interrupt
|
||||
str string
|
||||
}{
|
||||
{InterruptNone, "InterruptNone"},
|
||||
{InterruptAccountLocked, "InterruptAccountLocked"},
|
||||
{InterruptAccountNotConfirmed, "InterruptAccountNotConfirmed"},
|
||||
{InterruptSessionExpired, "InterruptSessionExpired"},
|
||||
}
|
||||
|
||||
for i, test := range tests {
|
||||
if got := test.in.String(); got != test.str {
|
||||
t.Errorf("%d) Wrong string for Event(%d) expected: %v got: %s", i, test.in, test.str, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
13
stringers.go
13
stringers.go
@ -1,4 +1,4 @@
|
||||
// Code generated by "stringer -output stringers.go -type Event,Interrupt"; DO NOT EDIT.
|
||||
// Code generated by "stringer -output stringers.go -type Event"; DO NOT EDIT.
|
||||
|
||||
package authboss
|
||||
|
||||
@ -14,14 +14,3 @@ func (i Event) String() string {
|
||||
}
|
||||
return _Event_name[_Event_index[i]:_Event_index[i+1]]
|
||||
}
|
||||
|
||||
const _Interrupt_name = "InterruptNoneInterruptAccountLockedInterruptAccountNotConfirmedInterruptSessionExpired"
|
||||
|
||||
var _Interrupt_index = [...]uint8{0, 13, 35, 63, 86}
|
||||
|
||||
func (i Interrupt) String() string {
|
||||
if i < 0 || i >= Interrupt(len(_Interrupt_index)-1) {
|
||||
return "Interrupt(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
return _Interrupt_name[_Interrupt_index[i]:_Interrupt_index[i+1]]
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user