1
0
mirror of https://github.com/volatiletech/authboss.git synced 2024-12-12 10:45:11 +02:00
authboss/auth/auth_test.go
Aaron L be041cbae6 remember: Context+Request separation ripple
- Re-add the age-old "Values" from the Context. This was originally
  there for exactly the documented purpose. However the Context holding
  the request form values negated it's use. It's back because of this
  new separation.
- Make the auth success path set the authboss.CookieRemember value in
  the context before calling it's callback.
2015-08-02 14:02:14 -07:00

380 lines
10 KiB
Go

package auth
import (
"bytes"
"errors"
"html/template"
"io/ioutil"
"net/http"
"net/http/httptest"
"strings"
"testing"
"gopkg.in/authboss.v0"
"gopkg.in/authboss.v0/internal/mocks"
)
func testSetup() (a *Auth, s *mocks.MockStorer) {
s = mocks.NewMockStorer()
ab := authboss.New()
ab.LogWriter = ioutil.Discard
ab.Layout = template.Must(template.New("").Parse(`{{template "authboss" .}}`))
ab.Storer = s
ab.XSRFName = "xsrf"
ab.XSRFMaker = func(_ http.ResponseWriter, _ *http.Request) string {
return "xsrfvalue"
}
ab.PrimaryID = authboss.StoreUsername
a = &Auth{}
if err := a.Initialize(ab); err != nil {
panic(err)
}
return a, s
}
func testRequest(ab *authboss.Authboss, method string, postFormValues ...string) (*authboss.Context, *httptest.ResponseRecorder, *http.Request, authboss.ClientStorerErr) {
sessionStorer := mocks.NewMockClientStorer()
ctx := ab.NewContext()
r := mocks.MockRequest(method, postFormValues...)
ctx.SessionStorer = sessionStorer
return ctx, httptest.NewRecorder(), r, sessionStorer
}
func TestAuth(t *testing.T) {
t.Parallel()
a, _ := testSetup()
storage := a.Storage()
if storage[a.PrimaryID] != authboss.String {
t.Error("Expected storage KV:", a.PrimaryID, authboss.String)
}
if storage[authboss.StorePassword] != authboss.String {
t.Error("Expected storage KV:", authboss.StorePassword, authboss.String)
}
routes := a.Routes()
if routes["/login"] == nil {
t.Error("Expected route '/login' with handleFunc")
}
if routes["/logout"] == nil {
t.Error("Expected route '/logout' with handleFunc")
}
}
func TestAuth_loginHandlerFunc_GET(t *testing.T) {
t.Parallel()
a, _ := testSetup()
ctx, w, r, _ := testRequest(a.Authboss, "GET")
if err := a.loginHandlerFunc(ctx, w, r); err != nil {
t.Error("Unexpected error:", err)
}
if w.Code != http.StatusOK {
t.Error("Unexpected status:", w.Code)
}
body := w.Body.String()
if !strings.Contains(body, "<form") {
t.Error("Should have rendered a form")
}
if !strings.Contains(body, `name="`+a.PrimaryID) {
t.Error("Form should contain the primary ID field:", body)
}
if !strings.Contains(body, `name="password"`) {
t.Error("Form should contain password field:", body)
}
}
func TestAuth_loginHandlerFunc_POST_ReturnsErrorOnCallbackFailure(t *testing.T) {
t.Parallel()
a, storer := testSetup()
storer.Users["john"] = authboss.Attributes{"password": "$2a$10$B7aydtqVF9V8RSNx3lCKB.l09jqLV/aMiVqQHajtL7sWGhCS9jlOu"}
a.Callbacks = authboss.NewCallbacks()
a.Callbacks.Before(authboss.EventAuth, func(_ *authboss.Context) (authboss.Interrupt, error) {
return authboss.InterruptNone, errors.New("explode")
})
ctx, w, r, _ := testRequest(a.Authboss, "POST", "username", "john", "password", "1234")
if err := a.loginHandlerFunc(ctx, w, r); err.Error() != "explode" {
t.Error("Unexpected error:", err)
}
}
func TestAuth_loginHandlerFunc_POST_RedirectsWhenInterrupted(t *testing.T) {
t.Parallel()
a, storer := testSetup()
storer.Users["john"] = authboss.Attributes{"password": "$2a$10$B7aydtqVF9V8RSNx3lCKB.l09jqLV/aMiVqQHajtL7sWGhCS9jlOu"}
a.Callbacks = authboss.NewCallbacks()
a.Callbacks.Before(authboss.EventAuth, func(_ *authboss.Context) (authboss.Interrupt, error) {
return authboss.InterruptAccountLocked, nil
})
ctx, w, r, sessionStore := testRequest(a.Authboss, "POST", "username", "john", "password", "1234")
if err := a.loginHandlerFunc(ctx, w, r); err != nil {
t.Error("Unexpected error:", err)
}
if w.Code != http.StatusFound {
t.Error("Unexpected status:", w.Code)
}
loc := w.Header().Get("Location")
if loc != a.AuthLoginFailPath {
t.Error("Unexpeced location:", loc)
}
expectedMsg := "Your account has been locked."
if msg, ok := sessionStore.Get(authboss.FlashErrorKey); !ok || msg != expectedMsg {
t.Error("Expected error flash message:", expectedMsg)
}
a.Callbacks = authboss.NewCallbacks()
a.Callbacks.Before(authboss.EventAuth, func(_ *authboss.Context) (authboss.Interrupt, error) {
return authboss.InterruptAccountNotConfirmed, nil
})
if err := a.loginHandlerFunc(ctx, w, r); err != nil {
t.Error("Unexpected error:", err)
}
if w.Code != http.StatusFound {
t.Error("Unexpected status:", w.Code)
}
loc = w.Header().Get("Location")
if loc != a.AuthLoginFailPath {
t.Error("Unexpeced location:", loc)
}
expectedMsg = "Your account has not been confirmed."
if msg, ok := sessionStore.Get(authboss.FlashErrorKey); !ok || msg != expectedMsg {
t.Error("Expected error flash message:", expectedMsg)
}
}
func TestAuth_loginHandlerFunc_POST_AuthenticationFailure(t *testing.T) {
t.Parallel()
a, _ := testSetup()
log := &bytes.Buffer{}
a.LogWriter = log
ctx, w, r, _ := testRequest(a.Authboss, "POST", "username", "john", "password", "1")
if err := a.loginHandlerFunc(ctx, w, r); err != nil {
t.Error("Unexpected error:", err)
}
if w.Code != http.StatusOK {
t.Error("Unexpected status:", w.Code)
}
body := w.Body.String()
if !strings.Contains(body, "invalid username and/or password") {
t.Error("Should have rendered with error")
}
ctx, w, r, _ = testRequest(a.Authboss, "POST", "username", "john", "password", "1234")
if err := a.loginHandlerFunc(ctx, w, r); err != nil {
t.Error("Unexpected error:", err)
}
if w.Code != http.StatusOK {
t.Error("Unexpected status:", w.Code)
}
body = w.Body.String()
if !strings.Contains(body, "invalid username and/or password") {
t.Error("Should have rendered with error")
}
ctx, w, r, _ = testRequest(a.Authboss, "POST", "username", "jake", "password", "1")
if err := a.loginHandlerFunc(ctx, w, r); err != nil {
t.Error("Unexpected error:", err)
}
if w.Code != http.StatusOK {
t.Error("Unexpected status:", w.Code)
}
body = w.Body.String()
if !strings.Contains(body, "invalid username and/or password") {
t.Error("Should have rendered with error")
}
}
func TestAuth_loginHandlerFunc_POST(t *testing.T) {
t.Parallel()
a, storer := testSetup()
storer.Users["john"] = authboss.Attributes{"password": "$2a$10$B7aydtqVF9V8RSNx3lCKB.l09jqLV/aMiVqQHajtL7sWGhCS9jlOu"}
ctx, w, r, _ := testRequest(a.Authboss, "POST", "username", "john", "password", "1234")
cb := mocks.NewMockAfterCallback()
a.Callbacks = authboss.NewCallbacks()
a.Callbacks.After(authboss.EventAuth, cb.Fn)
a.AuthLoginOKPath = "/dashboard"
sessions := mocks.NewMockClientStorer()
ctx.SessionStorer = sessions
if err := a.loginHandlerFunc(ctx, w, r); err != nil {
t.Error("Unexpected error:", err)
}
if _, ok := ctx.Values[authboss.CookieRemember]; !ok {
t.Error("Authboss cookie remember should be set for the callback")
}
if !cb.HasBeenCalled {
t.Error("Expected after callback to have been called")
}
if w.Code != http.StatusFound {
t.Error("Unexpected status:", w.Code)
}
loc := w.Header().Get("Location")
if loc != a.AuthLoginOKPath {
t.Error("Unexpeced location:", loc)
}
val, ok := sessions.Values[authboss.SessionKey]
if !ok {
t.Error("Expected session to be set")
} else if val != "john" {
t.Error("Expected session value to be authed username")
}
}
func TestAuth_loginHandlerFunc_OtherMethods(t *testing.T) {
t.Parallel()
a, _ := testSetup()
methods := []string{"HEAD", "PUT", "DELETE", "TRACE", "CONNECT"}
for i, method := range methods {
r, err := http.NewRequest(method, "/login", nil)
if err != nil {
t.Errorf("%d> Unexpected error '%s'", i, err)
}
w := httptest.NewRecorder()
if err := a.loginHandlerFunc(nil, w, r); err != nil {
t.Errorf("%d> Unexpected error: %s", i, err)
}
if http.StatusMethodNotAllowed != w.Code {
t.Errorf("%d> Expected status code %d, got %d", i, http.StatusMethodNotAllowed, w.Code)
continue
}
}
}
func TestAuth_validateCredentials(t *testing.T) {
t.Parallel()
ab := authboss.New()
storer := mocks.NewMockStorer()
ab.Storer = storer
ctx := ab.NewContext()
storer.Users["john"] = authboss.Attributes{"password": "$2a$10$pgFsuQwdhwOdZp/v52dvHeEi53ZaI7dGmtwK4bAzGGN5A4nT6doqm"}
if _, err := validateCredentials(ctx, "john", "a"); err != nil {
t.Error("Unexpected error:", err)
}
ctx = ab.NewContext()
if valid, err := validateCredentials(ctx, "jake", "a"); err != nil {
t.Error("Expect no error when user not found:", err)
} else if valid {
t.Error("Expect invalid when not user found")
}
ctx = ab.NewContext()
storer.GetErr = "Failed to load user"
if _, err := validateCredentials(ctx, "", ""); err.Error() != "Failed to load user" {
t.Error("Unexpected error:", err)
}
}
func TestAuth_logoutHandlerFunc_GET(t *testing.T) {
t.Parallel()
a, _ := testSetup()
a.AuthLogoutOKPath = "/dashboard"
ctx, w, r, sessionStorer := testRequest(a.Authboss, "GET")
sessionStorer.Put(authboss.SessionKey, "asdf")
sessionStorer.Put(authboss.SessionLastAction, "1234")
cookieStorer := mocks.NewMockClientStorer(authboss.CookieRemember, "qwert")
ctx.CookieStorer = cookieStorer
if err := a.logoutHandlerFunc(ctx, w, r); err != nil {
t.Error("Unexpected error:", err)
}
if val, ok := sessionStorer.Get(authboss.SessionKey); ok {
t.Error("Unexpected session key:", val)
}
if val, ok := sessionStorer.Get(authboss.SessionLastAction); ok {
t.Error("Unexpected last action:", val)
}
if val, ok := cookieStorer.Get(authboss.CookieRemember); ok {
t.Error("Unexpected rm cookie:", val)
}
if http.StatusFound != w.Code {
t.Errorf("Expected status code %d, got %d", http.StatusFound, w.Code)
}
location := w.Header().Get("Location")
if location != "/dashboard" {
t.Errorf("Expected lcoation %s, got %s", "/dashboard", location)
}
}
func TestAuth_logoutHandlerFunc_OtherMethods(t *testing.T) {
a, _ := testSetup()
methods := []string{"HEAD", "POST", "PUT", "DELETE", "TRACE", "CONNECT"}
for i, method := range methods {
r, err := http.NewRequest(method, "/logout", nil)
if err != nil {
t.Errorf("%d> Unexpected error '%s'", i, err)
}
w := httptest.NewRecorder()
if err := a.logoutHandlerFunc(nil, w, r); err != nil {
t.Errorf("%d> Unexpected error: %s", i, err)
}
if http.StatusMethodNotAllowed != w.Code {
t.Errorf("%d> Expected status code %d, got %d", i, http.StatusMethodNotAllowed, w.Code)
continue
}
}
}