1
0
mirror of https://github.com/volatiletech/authboss.git synced 2024-11-28 08:58:38 +02:00

Add redirection on pages when logged in.

- Stop logged in users from accessing pages like auth/recover etc.
- Ensure that half-authed users are allowed access to auth-like pages.
- Make sure that if users have a remember token, it's processed before
  we decide if a user is logged in or not, preventing or granting access
  to these pages.
- Fix #58
This commit is contained in:
Aaron L 2015-04-10 22:23:54 -07:00
parent e7eda3c62e
commit 704697472f
6 changed files with 151 additions and 34 deletions

View File

@ -71,13 +71,6 @@ func (a *Auth) Storage() authboss.StorageOptions {
func (a *Auth) loginHandlerFunc(ctx *authboss.Context, w http.ResponseWriter, r *http.Request) error {
switch r.Method {
case methodGET:
if _, ok := ctx.SessionStorer.Get(authboss.SessionKey); ok {
if halfAuthed, ok := ctx.SessionStorer.Get(authboss.SessionHalfAuthKey); !ok || halfAuthed == "false" {
response.Redirect(ctx, w, r, a.AuthLoginOKPath, "", "", true)
return nil
}
}
data := authboss.NewHTMLData(
"showRemember", a.IsLoaded("remember"),
"showRecover", a.IsLoaded("recover"),

View File

@ -70,31 +70,6 @@ func TestAuth(t *testing.T) {
}
}
func TestAuth_loginHandlerFunc_GET_RedirectsWhenHalfAuthed(t *testing.T) {
t.Parallel()
a, _ := testSetup()
ctx, w, r, sessionStore := testRequest(a.Authboss, "GET")
sessionStore.Put(authboss.SessionKey, "a")
sessionStore.Put(authboss.SessionHalfAuthKey, "false")
a.AuthLoginOKPath = "/dashboard"
if err := a.loginHandlerFunc(ctx, w, r); err != nil {
t.Error("Unexpeced error:", err)
}
if w.Code != http.StatusFound {
t.Error("Unexpcted status:", w.Code)
}
loc := w.Header().Get("Location")
if loc != a.AuthLoginOKPath {
t.Error("Unexpected redirect:", loc)
}
}
func TestAuth_loginHandlerFunc_GET(t *testing.T) {
t.Parallel()

View File

@ -72,7 +72,11 @@ func (a *Authboss) CurrentUser(w http.ResponseWriter, r *http.Request) (interfac
ctx.SessionStorer = clientStoreWrapper{a.SessionStoreMaker(w, r)}
ctx.CookieStorer = clientStoreWrapper{a.CookieStoreMaker(w, r)}
_, err = a.Callbacks.FireBefore(EventGetUserSession, ctx)
return a.currentUser(ctx, w, r)
}
func (a *Authboss) currentUser(ctx *Context, w http.ResponseWriter, r *http.Request) (interface{}, error) {
_, err := a.Callbacks.FireBefore(EventGetUserSession, ctx)
if err != nil {
return nil, err
}

View File

@ -49,10 +49,13 @@ type expireMiddleware struct {
// 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
// expired (a.ExpireAfter duration since SessionLastAction).
// This middleware conflicts with use of the Remember module, don't enable both
// at the same time.
func (a *Authboss) ExpireMiddleware(next http.Handler) http.Handler {
return expireMiddleware{a, next}
}
// ServeHTTP removes the session if it's passed the expire time.
func (m expireMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Request) {
session := m.ab.SessionStoreMaker(w, r)
if _, ok := session.Get(SessionKey); ok {

View File

@ -5,6 +5,7 @@ import (
"io"
"net/http"
"path"
"strings"
)
// HandlerFunc augments http.HandlerFunc with a context and error handling.
@ -45,22 +46,30 @@ type contextRoute struct {
}
func (c contextRoute) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// Instantiate the context
ctx, err := c.Authboss.ContextFromRequest(r)
if err != nil {
fmt.Fprintf(c.LogWriter, "route: Malformed request, could not create context: %v", err)
return
}
ctx.CookieStorer = clientStoreWrapper{c.CookieStoreMaker(w, r)}
ctx.SessionStorer = clientStoreWrapper{c.SessionStoreMaker(w, r)}
// Check to make sure we actually need to visit this route
if redirectIfLoggedIn(ctx, w, r) {
return
}
// Call the handler
err = c.fn(ctx, w, r)
if err == nil {
return
}
// Log the error
fmt.Fprintf(c.LogWriter, "Error Occurred at %s: %v", r.URL.Path, err)
// Do specific error handling for special kinds of errors.
switch e := err.(type) {
case ErrAndRedirect:
if len(e.FlashSuccess) > 0 {
@ -86,3 +95,39 @@ func (c contextRoute) ServeHTTP(w http.ResponseWriter, r *http.Request) {
}
}
}
// redirectIfLoggedIn checks a user's existence by using currentUser. This is done instead of
// a simple Session cookie check so that the remember module has a chance to log the user in
// before they are determined to "not be logged in".
//
// The exceptional routes are sort of hardcoded in a terrible way in here, later on this could move to some
// configuration or something more interesting.
func redirectIfLoggedIn(ctx *Context, w http.ResponseWriter, r *http.Request) (handled bool) {
// If it's a log out url, always let it pass through.
if strings.HasSuffix(r.URL.Path, "/logout") {
return false
}
// If it's an auth url, allow them through if they're half-authed.
if strings.HasSuffix(r.URL.Path, "/auth") || strings.Contains(r.URL.Path, "/oauth2/") {
if halfAuthed, ok := ctx.SessionStorer.Get(SessionHalfAuthKey); ok && halfAuthed == "true" {
return false
}
}
if cu, err := ctx.currentUser(ctx, w, r); err != nil {
fmt.Fprintf(ctx.LogWriter, "error occurred reading current user at %s: %v", r.URL.Path, err)
w.WriteHeader(http.StatusInternalServerError)
io.WriteString(w, "500 An error has occurred")
return true
} else if cu != nil {
if redir, ok := ctx.FirstFormValue(FormValueRedirect); ok && len(redir) > 0 {
http.Redirect(w, r, redir, http.StatusFound)
} else {
http.Redirect(w, r, ctx.AuthLoginOKPath, http.StatusFound)
}
return true
}
return false
}

View File

@ -3,6 +3,7 @@ package authboss
import (
"bytes"
"errors"
"io/ioutil"
"net/http"
"net/http/httptest"
"strings"
@ -203,3 +204,99 @@ func TestRouter_Redirect(t *testing.T) {
t.Error(fail, ok)
}
}
func TestRouter_redirectIfLoggedIn(t *testing.T) {
t.Parallel()
tests := []struct {
Path string
LoggedIn bool
HalfAuthed bool
ShouldRedirect bool
}{
// These routes will be accessed depending on logged in and half auth's value
{"/auth", false, false, false},
{"/auth", true, false, true},
{"/auth", true, true, false},
{"/oauth2/facebook", false, false, false},
{"/oauth2/facebook", true, false, true},
{"/oauth2/facebook", true, true, false},
{"/oauth2/callback/facebook", false, false, false},
{"/oauth2/callback/facebook", true, false, true},
{"/oauth2/callback/facebook", true, true, false},
// These are logout routes and never redirect
{"/logout", true, false, false},
{"/logout", true, true, false},
{"/oauth2/logout", true, false, false},
{"/oauth2/logout", true, true, false},
// These routes should always redirect despite half auth
{"/register", true, true, true},
{"/recover", true, true, true},
{"/register", false, false, false},
{"/recover", false, false, false},
}
storer := mockStorer{"john@john.com": Attributes{
StoreEmail: "john@john.com",
StorePassword: "password",
}}
ab := New()
ab.Storer = storer
for i, test := range tests {
session := mockClientStore{}
cookies := mockClientStore{}
ctx := ab.NewContext()
ctx.SessionStorer = session
ctx.CookieStorer = cookies
if test.LoggedIn {
session[SessionKey] = "john@john.com"
}
if test.HalfAuthed {
session[SessionHalfAuthKey] = "true"
}
r, _ := http.NewRequest("GET", test.Path, nil)
w := httptest.NewRecorder()
handled := redirectIfLoggedIn(ctx, w, r)
if test.ShouldRedirect && (!handled || w.Code != http.StatusFound) {
t.Errorf("%d) It should have redirected the request: %q %t %d", i, test.Path, handled, w.Code)
} else if !test.ShouldRedirect && (handled || w.Code != http.StatusOK) {
t.Errorf("%d) It should have NOT redirected the request: %q %t %d", i, test.Path, handled, w.Code)
}
}
}
type deathStorer struct{}
func (d deathStorer) Create(key string, attributes Attributes) error { return nil }
func (d deathStorer) Put(key string, attributes Attributes) error { return nil }
func (d deathStorer) Get(key string) (interface{}, error) { return nil, errors.New("explosion") }
func TestRouter_redirectIfLoggedInError(t *testing.T) {
t.Parallel()
ab := New()
ab.LogWriter = ioutil.Discard
ab.Storer = deathStorer{}
session := mockClientStore{SessionKey: "john"}
cookies := mockClientStore{}
ctx := ab.NewContext()
ctx.SessionStorer = session
ctx.CookieStorer = cookies
r, _ := http.NewRequest("GET", "/auth", nil)
w := httptest.NewRecorder()
handled := redirectIfLoggedIn(ctx, w, r)
if !handled {
t.Error("It should have been handled.")
}
if w.Code != http.StatusInternalServerError {
t.Error("It should have internal server error'd:", w.Code)
}
}