github.com/blend/go-sdk@v1.20220411.3/web/auth_manager_test.go (about)

     1  /*
     2  
     3  Copyright (c) 2022 - Present. Blend Labs, Inc. All rights reserved
     4  Use of this source code is governed by a MIT license that can be found in the LICENSE file.
     5  
     6  */
     7  
     8  package web
     9  
    10  import (
    11  	"bytes"
    12  	"context"
    13  	"fmt"
    14  	"net/http"
    15  	"net/url"
    16  	"testing"
    17  	"time"
    18  
    19  	"github.com/blend/go-sdk/assert"
    20  	"github.com/blend/go-sdk/uuid"
    21  	"github.com/blend/go-sdk/webutil"
    22  )
    23  
    24  func Test_MustNewAuthManager(t *testing.T) {
    25  	its := assert.New(t)
    26  
    27  	am := MustNewAuthManager(OptAuthManagerCookieName("X-FOO"))
    28  	its.Equal("X-FOO", am.CookieDefaults.Name)
    29  
    30  	// test panics
    31  	var recovered interface{}
    32  	func() {
    33  		defer func() {
    34  			r := recover()
    35  			if r != nil {
    36  				recovered = r
    37  			}
    38  		}()
    39  		am = MustNewAuthManager(func(_ *AuthManager) error { return fmt.Errorf("this is just a test") })
    40  	}()
    41  	its.NotNil(recovered)
    42  }
    43  
    44  func Test_NewAuthManager(t *testing.T) {
    45  	its := assert.New(t)
    46  
    47  	am, err := NewAuthManager()
    48  	its.Nil(err)
    49  	its.Equal(DefaultCookieName, am.CookieDefaults.Name)
    50  	its.Equal(DefaultCookiePath, am.CookieDefaults.Path)
    51  	its.Equal(DefaultCookieHTTPOnly, am.CookieDefaults.HttpOnly)
    52  	its.Equal(DefaultCookieSecure, am.CookieDefaults.Secure)
    53  	its.Equal(DefaultCookieSameSiteMode, am.CookieDefaults.SameSite)
    54  
    55  	am, err = NewAuthManager(OptAuthManagerCookieDefaults(http.Cookie{
    56  		Name:     "_FOO_AUTH_",
    57  		Path:     "/admin",
    58  		HttpOnly: true,
    59  		Secure:   true,
    60  		SameSite: http.SameSiteLaxMode,
    61  	}))
    62  	its.Nil(err)
    63  	its.Equal("_FOO_AUTH_", am.CookieDefaults.Name)
    64  	its.Equal("/admin", am.CookieDefaults.Path)
    65  	its.Equal(true, am.CookieDefaults.HttpOnly)
    66  	its.Equal(true, am.CookieDefaults.Secure)
    67  	its.Equal(http.SameSiteLaxMode, am.CookieDefaults.SameSite)
    68  
    69  	am, err = NewAuthManager(OptAuthManagerCookieName("X-FOO"))
    70  	its.Nil(err)
    71  	its.Equal("X-FOO", am.CookieDefaults.Name)
    72  
    73  	am, err = NewAuthManager(OptAuthManagerCookiePath("/foo"))
    74  	its.Nil(err)
    75  	its.Equal("/foo", am.CookieDefaults.Path)
    76  
    77  	am, err = NewAuthManager(OptAuthManagerCookieHTTPOnly(true))
    78  	its.Nil(err)
    79  	its.Equal(true, am.CookieDefaults.HttpOnly)
    80  
    81  	am, err = NewAuthManager(OptAuthManagerCookieSecure(true))
    82  	its.Nil(err)
    83  	its.Equal(true, am.CookieDefaults.Secure)
    84  
    85  	am, err = NewAuthManager(OptAuthManagerCookieSameSite(http.SameSiteLaxMode))
    86  	its.Nil(err)
    87  	its.Equal(http.SameSiteLaxMode, am.CookieDefaults.SameSite)
    88  
    89  	am, err = NewAuthManager(OptAuthManagerSerializeHandler(func(context.Context, *Session) (string, error) {
    90  		return "blabla", nil
    91  	}))
    92  	its.Nil(err)
    93  	its.NotNil(am.SerializeHandler)
    94  
    95  	am, err = NewAuthManager(OptAuthManagerPersistHandler(func(context.Context, *Session) error {
    96  		return nil
    97  	}))
    98  	its.Nil(err)
    99  	its.NotNil(am.PersistHandler)
   100  
   101  	am, err = NewAuthManager(OptAuthManagerFetchHandler(func(context.Context, string) (*Session, error) {
   102  		return &Session{SessionID: "blabla"}, nil
   103  	}))
   104  	its.Nil(err)
   105  	its.NotNil(am.FetchHandler)
   106  
   107  	am, err = NewAuthManager(OptAuthManagerRemoveHandler(func(context.Context, string) error {
   108  		return nil
   109  	}))
   110  	its.Nil(err)
   111  	its.NotNil(am.RemoveHandler)
   112  
   113  	am, err = NewAuthManager(OptAuthManagerValidateHandler(func(context.Context, *Session) error {
   114  		return nil
   115  	}))
   116  	its.Nil(err)
   117  	its.NotNil(am.ValidateHandler)
   118  
   119  	am, err = NewAuthManager(OptAuthManagerSessionTimeoutProvider(func(*Session) time.Time {
   120  		return time.Now().UTC()
   121  	}))
   122  	its.Nil(err)
   123  	its.NotNil(am.SessionTimeoutProvider)
   124  
   125  	am, err = NewAuthManager(OptAuthManagerLoginRedirectHandler(func(*Ctx) *url.URL {
   126  		return nil
   127  	}))
   128  	its.Nil(err)
   129  	its.NotNil(am.LoginRedirectHandler)
   130  }
   131  
   132  func Test_NewLocalManagerFromCache(t *testing.T) {
   133  	its := assert.New(t)
   134  
   135  	lc := NewLocalSessionCache()
   136  	am, err := NewLocalAuthManagerFromCache(lc, OptAuthManagerCookieName("X-FOO"))
   137  	its.Nil(err)
   138  	its.Equal("X-FOO", am.CookieDefaults.Name)
   139  
   140  	am, err = NewLocalAuthManagerFromCache(lc, func(_ *AuthManager) error { return fmt.Errorf("this is just a test") })
   141  	its.NotNil(err)
   142  }
   143  
   144  func Test_AuthManager_Login(t *testing.T) {
   145  	its := assert.New(t)
   146  
   147  	am, err := NewLocalAuthManager()
   148  	its.Nil(err)
   149  
   150  	sessionExpiresUTC := time.Date(2021, 03, 04, 05, 06, 07, 8, time.UTC)
   151  	var calledSessionTimeoutProvider bool
   152  	am.SessionTimeoutProvider = func(session *Session) time.Time {
   153  		calledSessionTimeoutProvider = true
   154  		return sessionExpiresUTC
   155  	}
   156  
   157  	var calledPersistHandler bool
   158  	persistHandler := am.PersistHandler
   159  	am.PersistHandler = func(ctx context.Context, session *Session) error {
   160  		calledPersistHandler = true
   161  		if persistHandler == nil {
   162  			return nil
   163  		}
   164  		return persistHandler(ctx, session)
   165  	}
   166  
   167  	var calledSerializeHandler bool
   168  	serializeHandler := am.SerializeHandler
   169  	am.SerializeHandler = func(ctx context.Context, session *Session) (string, error) {
   170  		calledSerializeHandler = true
   171  		if serializeHandler == nil {
   172  			return session.SessionID, nil
   173  		}
   174  		return serializeHandler(ctx, session)
   175  	}
   176  
   177  	var calledRemoveHandler bool
   178  	removeHandler := am.RemoveHandler
   179  	am.RemoveHandler = func(ctx context.Context, sessionID string) error {
   180  		calledRemoveHandler = true
   181  		if removeHandler == nil {
   182  			return nil
   183  		}
   184  		return removeHandler(ctx, sessionID)
   185  	}
   186  
   187  	res := webutil.NewMockResponse(new(bytes.Buffer))
   188  	r := NewCtx(res, webutil.NewMockRequest("GET", "/"))
   189  
   190  	session, err := am.Login("example-string@blend.com", r)
   191  	its.Nil(err)
   192  	its.NotNil(session)
   193  	its.NotEmpty(session.SessionID)
   194  	its.NotEmpty(session.RemoteAddr)
   195  	its.NotEmpty(session.UserAgent)
   196  	its.Equal("example-string@blend.com", session.UserID)
   197  	its.False(session.ExpiresUTC.IsZero())
   198  	its.Equal(sessionExpiresUTC, session.ExpiresUTC)
   199  	its.True(calledPersistHandler)
   200  	its.True(calledSessionTimeoutProvider)
   201  	its.True(calledSerializeHandler)
   202  	its.False(calledRemoveHandler)
   203  
   204  	cookies := ReadSetCookies(res.Header())
   205  	its.NotEmpty(cookies)
   206  	cookie := cookies[0]
   207  	its.Equal(am.CookieDefaults.Name, cookie.Name)
   208  	its.Equal(am.CookieDefaults.Path, cookie.Path)
   209  	its.Equal(session.SessionID, cookie.Value)
   210  }
   211  
   212  func Test_AuthManager_Login_persistError(t *testing.T) {
   213  	its := assert.New(t)
   214  
   215  	am, err := NewLocalAuthManager()
   216  	its.Nil(err)
   217  
   218  	var calledPersistHandler bool
   219  	am.PersistHandler = func(ctx context.Context, session *Session) error {
   220  		calledPersistHandler = true
   221  		return fmt.Errorf("this is just a test")
   222  	}
   223  	res := webutil.NewMockResponse(new(bytes.Buffer))
   224  	r := NewCtx(res, webutil.NewMockRequest("GET", "/"))
   225  
   226  	session, err := am.Login("example-string@blend.com", r)
   227  	its.NotNil(err)
   228  	its.True(calledPersistHandler)
   229  	its.Equal("this is just a test", err.Error())
   230  	its.Nil(session)
   231  
   232  	cookies := ReadSetCookies(res.Header())
   233  	its.Empty(cookies)
   234  }
   235  
   236  func Test_AuthManager_Login_serializeError(t *testing.T) {
   237  	its := assert.New(t)
   238  
   239  	am, err := NewLocalAuthManager()
   240  	its.Nil(err)
   241  
   242  	var calledPersistHandler bool
   243  	persistHandler := am.PersistHandler
   244  	am.PersistHandler = func(ctx context.Context, session *Session) error {
   245  		calledPersistHandler = true
   246  		if persistHandler == nil {
   247  			return nil
   248  		}
   249  		return persistHandler(ctx, session)
   250  	}
   251  
   252  	var calledSerializeHandler bool
   253  	am.SerializeHandler = func(ctx context.Context, session *Session) (string, error) {
   254  		calledSerializeHandler = true
   255  		return "", fmt.Errorf("this is a serialize error")
   256  	}
   257  
   258  	res := webutil.NewMockResponse(new(bytes.Buffer))
   259  	r := NewCtx(res, webutil.NewMockRequest("GET", "/"))
   260  
   261  	session, err := am.Login("example-string@blend.com", r)
   262  	its.NotNil(err)
   263  	its.True(calledPersistHandler)
   264  	its.True(calledSerializeHandler)
   265  	its.Equal("this is a serialize error", err.Error())
   266  	its.Nil(session)
   267  
   268  	cookies := ReadSetCookies(res.Header())
   269  	its.Empty(cookies)
   270  }
   271  
   272  func Test_AuthManager_Logout(t *testing.T) {
   273  	its := assert.New(t)
   274  
   275  	am, err := NewLocalAuthManager()
   276  	its.Nil(err)
   277  
   278  	var calledRemoveHandler bool
   279  	removeHandler := am.RemoveHandler
   280  	am.RemoveHandler = func(ctx context.Context, sessionID string) error {
   281  		calledRemoveHandler = true
   282  		if removeHandler == nil {
   283  			return nil
   284  		}
   285  		return removeHandler(ctx, sessionID)
   286  	}
   287  
   288  	res := webutil.NewMockResponse(new(bytes.Buffer))
   289  	r := NewCtx(res, webutil.NewMockRequest("GET", "/"))
   290  
   291  	session, err := am.Login("example-string@blend.com", r)
   292  	its.Nil(err)
   293  	its.NotNil(session)
   294  
   295  	res = webutil.NewMockResponse(new(bytes.Buffer))
   296  	r = NewCtx(res, webutil.NewMockRequestWithCookie("GET", "/", am.CookieDefaults.Name, session.SessionID))
   297  
   298  	its.Nil(am.Logout(r))
   299  	its.True(calledRemoveHandler)
   300  
   301  	cookies := ReadSetCookies(res.Header())
   302  	its.NotEmpty(cookies)
   303  	cookie := cookies[0]
   304  	its.Equal(am.CookieDefaults.Name, cookie.Name)
   305  	its.Equal(am.CookieDefaults.Path, cookie.Path)
   306  	its.NotEqual(session.SessionID, cookie.Value, "we should randomize the session cookie on logout")
   307  	its.True(time.Now().UTC().After(cookie.Expires))
   308  }
   309  
   310  func Test_AuthManager_Logout_sessionValueUnset(t *testing.T) {
   311  	its := assert.New(t)
   312  
   313  	am, err := NewLocalAuthManager()
   314  	its.Nil(err)
   315  
   316  	res := webutil.NewMockResponse(new(bytes.Buffer))
   317  	r := NewCtx(res, webutil.NewMockRequest("GET", "/"))
   318  	its.Nil(am.Logout(r))
   319  
   320  	cookies := ReadSetCookies(res.Header())
   321  	its.Empty(cookies)
   322  }
   323  
   324  func Test_AuthManager_Logout_removeHandlerUnset(t *testing.T) {
   325  	its := assert.New(t)
   326  
   327  	am, err := NewLocalAuthManager()
   328  	am.RemoveHandler = nil
   329  	its.Nil(err)
   330  
   331  	res := webutil.NewMockResponse(new(bytes.Buffer))
   332  	r := NewCtx(res, webutil.NewMockRequest("GET", "/"))
   333  
   334  	session, err := am.Login("example-string@blend.com", r)
   335  	its.Nil(err)
   336  	its.NotNil(session)
   337  
   338  	res = webutil.NewMockResponse(new(bytes.Buffer))
   339  	r = NewCtx(res, webutil.NewMockRequestWithCookie("GET", "/", am.CookieDefaults.Name, session.SessionID))
   340  
   341  	its.Nil(am.Logout(r))
   342  
   343  	cookies := ReadSetCookies(res.Header())
   344  	its.NotEmpty(cookies)
   345  }
   346  
   347  func Test_AuthManager_VerifyOrExtendSession(t *testing.T) {
   348  	its := assert.New(t)
   349  
   350  	am, err := NewLocalAuthManager()
   351  	its.Nil(err)
   352  
   353  	var calledRestoreHandler bool
   354  	restoreHandler := am.FetchHandler
   355  	am.FetchHandler = func(ctx context.Context, sessionID string) (*Session, error) {
   356  		calledRestoreHandler = true
   357  		if restoreHandler == nil {
   358  			return nil, nil
   359  		}
   360  		return restoreHandler(ctx, sessionID)
   361  	}
   362  
   363  	var calledValidateHandler bool
   364  	validateHandler := am.ValidateHandler
   365  	am.ValidateHandler = func(ctx context.Context, session *Session) error {
   366  		calledValidateHandler = true
   367  		if validateHandler == nil {
   368  			return nil
   369  		}
   370  		return validateHandler(ctx, session)
   371  	}
   372  
   373  	r := NewCtx(webutil.NewMockResponse(new(bytes.Buffer)), webutil.NewMockRequest("GET", "/"))
   374  	session, err := am.Login("example-string@blend.com", r)
   375  	its.Nil(err)
   376  	its.NotNil(session)
   377  	its.False(calledRestoreHandler)
   378  	its.False(calledValidateHandler)
   379  
   380  	r = NewCtx(webutil.NewMockResponse(new(bytes.Buffer)), webutil.NewMockRequestWithCookie("GET", "/", am.CookieDefaults.Name, session.SessionID))
   381  	session, err = am.VerifyOrExtendSession(r)
   382  	its.Nil(err)
   383  	its.NotNil(session)
   384  	its.True(calledRestoreHandler)
   385  	its.True(calledValidateHandler)
   386  }
   387  
   388  func Test_AuthManager_VerifyOrExtendSession_sessionUnset(t *testing.T) {
   389  	its := assert.New(t)
   390  
   391  	am, err := NewLocalAuthManager()
   392  	its.Nil(err)
   393  
   394  	var calledRestoreHandler bool
   395  	restoreHandler := am.FetchHandler
   396  	am.FetchHandler = func(ctx context.Context, sessionID string) (*Session, error) {
   397  		calledRestoreHandler = true
   398  		if restoreHandler == nil {
   399  			return nil, nil
   400  		}
   401  		return restoreHandler(ctx, sessionID)
   402  	}
   403  
   404  	var calledValidateHandler bool
   405  	validateHandler := am.ValidateHandler
   406  	am.ValidateHandler = func(ctx context.Context, session *Session) error {
   407  		calledValidateHandler = true
   408  		if validateHandler == nil {
   409  			return nil
   410  		}
   411  		return validateHandler(ctx, session)
   412  	}
   413  
   414  	r := NewCtx(webutil.NewMockResponse(new(bytes.Buffer)), webutil.NewMockRequest("GET", "/"))
   415  	session, err := am.VerifyOrExtendSession(r)
   416  	its.Nil(err)
   417  	its.Nil(session)
   418  	its.False(calledRestoreHandler)
   419  	its.False(calledValidateHandler)
   420  }
   421  
   422  func Test_AuthManager_VerifyOrExtendSession_fetchHandlerUnset(t *testing.T) {
   423  	its := assert.New(t)
   424  
   425  	am, err := NewLocalAuthManager()
   426  	its.Nil(err)
   427  
   428  	r := NewCtx(webutil.NewMockResponse(new(bytes.Buffer)), webutil.NewMockRequest("GET", "/"))
   429  
   430  	session, err := am.VerifyOrExtendSession(r)
   431  	its.Nil(err)
   432  	its.Nil(session)
   433  }
   434  
   435  func Test_AuthManager_VerifyOrExtendSession_fetchErrSessionInvalid(t *testing.T) {
   436  	its := assert.New(t)
   437  
   438  	am, err := NewLocalAuthManager()
   439  	its.Nil(err)
   440  
   441  	var calledFetchHandler bool
   442  	am.FetchHandler = func(ctx context.Context, sessionID string) (*Session, error) {
   443  		calledFetchHandler = true
   444  		return nil, ErrSessionIDEmpty
   445  	}
   446  
   447  	var calledValidateHandler bool
   448  	validateHandler := am.ValidateHandler
   449  	am.ValidateHandler = func(ctx context.Context, session *Session) error {
   450  		calledValidateHandler = true
   451  		return validateHandler(ctx, session)
   452  	}
   453  
   454  	r := NewCtx(webutil.NewMockResponse(new(bytes.Buffer)), webutil.NewMockRequest("GET", "/"))
   455  	session, err := am.Login("example-string@blend.com", r)
   456  	its.Nil(err)
   457  	its.NotNil(session)
   458  	its.False(calledFetchHandler)
   459  	its.False(calledValidateHandler)
   460  
   461  	r = NewCtx(webutil.NewMockResponse(new(bytes.Buffer)), webutil.NewMockRequestWithCookie("GET", "/", am.CookieDefaults.Name, session.SessionID))
   462  	session, err = am.VerifyOrExtendSession(r)
   463  	its.NotNil(err)
   464  	its.Equal(ErrSessionIDEmpty, err)
   465  	its.Nil(session)
   466  	its.True(calledFetchHandler)
   467  	its.False(calledValidateHandler)
   468  
   469  	cookies := ReadSetCookies(r.Response.Header())
   470  	its.NotEmpty(cookies)
   471  	cookie := cookies[0]
   472  	its.Equal(am.CookieDefaults.Name, cookie.Name)
   473  	its.Equal(am.CookieDefaults.Path, cookie.Path)
   474  	its.True(time.Now().UTC().After(cookie.Expires))
   475  }
   476  
   477  func Test_AuthManager_VerifyOrExtendSession_sessionExpired(t *testing.T) {
   478  	its := assert.New(t)
   479  
   480  	am, err := NewLocalAuthManager()
   481  	its.Nil(err)
   482  
   483  	am.SessionTimeoutProvider = nil
   484  	am.FetchHandler = func(ctx context.Context, sessionID string) (*Session, error) {
   485  		return &Session{UserID: uuid.V4().String(), SessionID: sessionID, ExpiresUTC: time.Now().UTC().Add(-time.Hour)}, nil
   486  	}
   487  
   488  	var calledValidateHandler bool
   489  	am.ValidateHandler = func(ctx context.Context, session *Session) error {
   490  		calledValidateHandler = true
   491  		return nil
   492  	}
   493  
   494  	res := webutil.NewMockResponse(new(bytes.Buffer))
   495  	r := NewCtx(res, webutil.NewMockRequestWithCookie("GET", "/", am.CookieDefaults.Name, NewSessionID()))
   496  	session, err := am.VerifyOrExtendSession(r)
   497  	its.Nil(err)
   498  	its.Nil(session)
   499  	its.False(calledValidateHandler)
   500  
   501  	// assert the cookie is expired ...
   502  	cookies := ReadSetCookies(res.Header())
   503  	its.NotEmpty(cookies)
   504  
   505  	cookie := cookies[0]
   506  	its.Equal(am.CookieDefaults.Name, cookie.Name)
   507  	its.Equal(am.CookieDefaults.Path, cookie.Path)
   508  	its.True(cookie.Expires.Before(time.Now().UTC()), "the cookie should be expired")
   509  }
   510  
   511  func Test_AuthManager_VerifyOrExtendSession_sessionExpired_nil(t *testing.T) {
   512  	its := assert.New(t)
   513  
   514  	am, err := NewLocalAuthManager()
   515  	its.Nil(err)
   516  
   517  	am.SessionTimeoutProvider = nil
   518  	am.FetchHandler = func(ctx context.Context, sessionID string) (*Session, error) {
   519  		return nil, nil
   520  	}
   521  
   522  	var calledValidateHandler bool
   523  	am.ValidateHandler = func(ctx context.Context, session *Session) error {
   524  		calledValidateHandler = true
   525  		return nil
   526  	}
   527  
   528  	res := webutil.NewMockResponse(new(bytes.Buffer))
   529  	r := NewCtx(res, webutil.NewMockRequestWithCookie("GET", "/", am.CookieDefaults.Name, NewSessionID()))
   530  	session, err := am.VerifyOrExtendSession(r)
   531  	its.Nil(err)
   532  	its.Nil(session)
   533  	its.False(calledValidateHandler)
   534  
   535  	// assert the cookie is expired ...
   536  	cookies := ReadSetCookies(res.Header())
   537  	its.NotEmpty(cookies)
   538  
   539  	cookie := cookies[0]
   540  	its.Equal(am.CookieDefaults.Name, cookie.Name)
   541  	its.Equal(am.CookieDefaults.Path, cookie.Path)
   542  	its.True(cookie.Expires.Before(time.Now().UTC()), "the cookie should be expired")
   543  }
   544  
   545  func Test_AuthManager_VerifyOrExtendSession_sessionExpired_zero(t *testing.T) {
   546  	its := assert.New(t)
   547  
   548  	am, err := NewLocalAuthManager()
   549  	its.Nil(err)
   550  
   551  	am.SessionTimeoutProvider = nil
   552  	am.FetchHandler = func(ctx context.Context, sessionID string) (*Session, error) {
   553  		return &Session{}, nil
   554  	}
   555  
   556  	var calledValidateHandler bool
   557  	am.ValidateHandler = func(ctx context.Context, session *Session) error {
   558  		calledValidateHandler = true
   559  		return nil
   560  	}
   561  
   562  	res := webutil.NewMockResponse(new(bytes.Buffer))
   563  	r := NewCtx(res, webutil.NewMockRequestWithCookie("GET", "/", am.CookieDefaults.Name, NewSessionID()))
   564  	session, err := am.VerifyOrExtendSession(r)
   565  	its.Nil(err)
   566  	its.Nil(session)
   567  	its.False(calledValidateHandler)
   568  
   569  	// assert the cookie is expired ...
   570  	cookies := ReadSetCookies(res.Header())
   571  	its.NotEmpty(cookies)
   572  
   573  	cookie := cookies[0]
   574  	its.Equal(am.CookieDefaults.Name, cookie.Name)
   575  	its.Equal(am.CookieDefaults.Path, cookie.Path)
   576  	its.True(cookie.Expires.Before(time.Now().UTC()), "the cookie should be expired")
   577  }
   578  
   579  func Test_AuthManager_VerifyOrExtendSession_failsValidation(t *testing.T) {
   580  	its := assert.New(t)
   581  
   582  	am, err := NewLocalAuthManager()
   583  	its.Nil(err)
   584  
   585  	var calledFetchHandler bool
   586  	fetchHandler := am.FetchHandler
   587  	am.FetchHandler = func(ctx context.Context, sessionID string) (*Session, error) {
   588  		calledFetchHandler = true
   589  		return fetchHandler(ctx, sessionID)
   590  	}
   591  
   592  	var calledValidateHandler bool
   593  	am.ValidateHandler = func(ctx context.Context, session *Session) error {
   594  		calledValidateHandler = true
   595  		return fmt.Errorf("this is just a test")
   596  	}
   597  
   598  	r := NewCtx(webutil.NewMockResponse(new(bytes.Buffer)), webutil.NewMockRequest("GET", "/"))
   599  	session, err := am.Login("example-string@blend.com", r)
   600  	its.Nil(err)
   601  	its.NotNil(session)
   602  	its.False(calledFetchHandler)
   603  	its.False(calledValidateHandler)
   604  
   605  	r = NewCtx(webutil.NewMockResponse(new(bytes.Buffer)), webutil.NewMockRequestWithCookie("GET", "/", am.CookieDefaults.Name, session.SessionID))
   606  	session, err = am.VerifyOrExtendSession(r)
   607  	its.NotNil(err)
   608  	its.Nil(session)
   609  	its.True(calledFetchHandler)
   610  	its.True(calledValidateHandler)
   611  
   612  	// assert the cookie is expired ...
   613  	cookies := ReadSetCookies(r.Response.Header())
   614  	// for now, we should not expire the cookie on a validatin failure
   615  	its.Empty(cookies)
   616  }
   617  
   618  func Test_AuthManager_VerifyOrExtendSession_sessionTimeout_unchanged(t *testing.T) {
   619  	its := assert.New(t)
   620  
   621  	am, err := NewLocalAuthManager()
   622  	its.Nil(err)
   623  
   624  	var calledPersistHandler bool
   625  	persistHandler := am.PersistHandler
   626  	am.PersistHandler = func(ctx context.Context, session *Session) error {
   627  		calledPersistHandler = true
   628  		return persistHandler(ctx, session)
   629  	}
   630  
   631  	var calledFetchHandler bool
   632  	fetchHandler := am.FetchHandler
   633  	am.FetchHandler = func(ctx context.Context, sessionID string) (*Session, error) {
   634  		calledFetchHandler = true
   635  		return fetchHandler(ctx, sessionID)
   636  	}
   637  
   638  	var calledValidateHandler bool
   639  	am.ValidateHandler = func(ctx context.Context, session *Session) error {
   640  		calledValidateHandler = true
   641  		return nil
   642  	}
   643  	var calledSessionTimeoutProvider bool
   644  	am.SessionTimeoutProvider = func(session *Session) time.Time {
   645  		calledSessionTimeoutProvider = true
   646  		return session.ExpiresUTC
   647  	}
   648  
   649  	r := NewCtx(webutil.NewMockResponse(new(bytes.Buffer)), webutil.NewMockRequest("GET", "/"))
   650  	session, err := am.Login("example-string@blend.com", r)
   651  	its.Nil(err)
   652  	its.NotNil(session)
   653  	its.False(calledFetchHandler)
   654  	its.False(calledValidateHandler)
   655  	its.True(calledPersistHandler)
   656  	its.True(calledSessionTimeoutProvider)
   657  
   658  	calledPersistHandler = false
   659  	calledSessionTimeoutProvider = false
   660  
   661  	r = NewCtx(webutil.NewMockResponse(new(bytes.Buffer)), webutil.NewMockRequestWithCookie("GET", "/", am.CookieDefaults.Name, session.SessionID))
   662  	session, err = am.VerifyOrExtendSession(r)
   663  	its.Nil(err)
   664  	its.NotNil(session)
   665  	its.True(calledFetchHandler)
   666  	its.True(calledValidateHandler)
   667  	its.False(calledPersistHandler)
   668  	its.True(calledSessionTimeoutProvider)
   669  }
   670  
   671  func Test_AuthManager_VerifyOrExtendSession_sessionTimeout_changed(t *testing.T) {
   672  	its := assert.New(t)
   673  
   674  	am, err := NewLocalAuthManager()
   675  	its.Nil(err)
   676  
   677  	var calledFetchHandler bool
   678  	fetchHandler := am.FetchHandler
   679  	am.FetchHandler = func(ctx context.Context, sessionID string) (*Session, error) {
   680  		calledFetchHandler = true
   681  		return fetchHandler(ctx, sessionID)
   682  	}
   683  
   684  	var calledValidateHandler bool
   685  	am.ValidateHandler = func(ctx context.Context, session *Session) error {
   686  		calledValidateHandler = true
   687  		return nil
   688  	}
   689  
   690  	r := NewCtx(webutil.NewMockResponse(new(bytes.Buffer)), webutil.NewMockRequest("GET", "/"))
   691  	session, err := am.Login("example-string@blend.com", r)
   692  	its.Nil(err)
   693  	its.NotNil(session)
   694  	its.False(calledFetchHandler)
   695  	its.False(calledValidateHandler)
   696  
   697  	var calledPersistHandler bool
   698  	persistHandler := am.PersistHandler
   699  	am.PersistHandler = func(ctx context.Context, session *Session) error {
   700  		calledPersistHandler = true
   701  		return persistHandler(ctx, session)
   702  	}
   703  
   704  	expiresUTC := time.Now().UTC()
   705  	var calledSessionTimeoutProvider bool
   706  	am.SessionTimeoutProvider = func(session *Session) time.Time {
   707  		calledSessionTimeoutProvider = true
   708  		return expiresUTC
   709  	}
   710  
   711  	r = NewCtx(webutil.NewMockResponse(new(bytes.Buffer)), webutil.NewMockRequestWithCookie("GET", "/", am.CookieDefaults.Name, session.SessionID))
   712  	session, err = am.VerifyOrExtendSession(r)
   713  	its.Nil(err)
   714  	its.NotNil(session)
   715  	its.True(calledFetchHandler)
   716  	its.True(calledValidateHandler)
   717  	its.True(calledPersistHandler)
   718  	its.True(calledSessionTimeoutProvider)
   719  
   720  	// assert the cookie is expired ...
   721  	cookies := ReadSetCookies(r.Response.Header())
   722  	its.NotEmpty(cookies)
   723  
   724  	cookie := cookies[0]
   725  	its.Equal(am.CookieDefaults.Name, cookie.Name)
   726  	its.Equal(am.CookieDefaults.Path, cookie.Path)
   727  	its.InTimeDelta(expiresUTC, cookie.Expires, time.Second, "the cookie have an expiration")
   728  }
   729  
   730  func Test_AuthManager_VerifyOrExpireSession_sessionTimeout_persistError(t *testing.T) {
   731  	its := assert.New(t)
   732  
   733  	am, err := NewLocalAuthManager()
   734  	its.Nil(err)
   735  
   736  	var calledFetchHandler bool
   737  	fetchHandler := am.FetchHandler
   738  	am.FetchHandler = func(ctx context.Context, sessionID string) (*Session, error) {
   739  		calledFetchHandler = true
   740  		return fetchHandler(ctx, sessionID)
   741  	}
   742  
   743  	var calledValidateHandler bool
   744  	am.ValidateHandler = func(ctx context.Context, session *Session) error {
   745  		calledValidateHandler = true
   746  		return nil
   747  	}
   748  
   749  	r := NewCtx(webutil.NewMockResponse(new(bytes.Buffer)), webutil.NewMockRequest("GET", "/"))
   750  	session, err := am.Login("example-string@blend.com", r)
   751  	its.Nil(err)
   752  	its.NotNil(session)
   753  	its.False(calledFetchHandler)
   754  	its.False(calledValidateHandler)
   755  
   756  	var calledPersistHandler bool
   757  	am.PersistHandler = func(ctx context.Context, session *Session) error {
   758  		calledPersistHandler = true
   759  		return fmt.Errorf("this is just a test")
   760  	}
   761  
   762  	expiresUTC := time.Now().UTC()
   763  	var calledSessionTimeoutProvider bool
   764  	am.SessionTimeoutProvider = func(session *Session) time.Time {
   765  		calledSessionTimeoutProvider = true
   766  		return expiresUTC
   767  	}
   768  
   769  	r = NewCtx(webutil.NewMockResponse(new(bytes.Buffer)), webutil.NewMockRequestWithCookie("GET", "/", am.CookieDefaults.Name, session.SessionID))
   770  	session, err = am.VerifyOrExtendSession(r)
   771  	its.NotNil(err)
   772  	its.Equal("this is just a test", err.Error())
   773  	its.Nil(session)
   774  	its.True(calledFetchHandler)
   775  	its.True(calledValidateHandler)
   776  	its.True(calledSessionTimeoutProvider)
   777  	its.True(calledPersistHandler)
   778  
   779  	// assert the cookie is expired ...
   780  	cookies := ReadSetCookies(r.Response.Header())
   781  	its.Empty(cookies)
   782  }
   783  
   784  func Test_AuthManager_LoginRedirect_loginRedirectHandlerUnset(t *testing.T) {
   785  	its := assert.New(t)
   786  
   787  	am := AuthManager{}
   788  	ctx := MockCtx(http.MethodGet, "/api/foo/bar", OptCtxDefaultProvider(Text))
   789  	res := am.LoginRedirect(ctx)
   790  	its.NotNil(res)
   791  	typed, ok := res.(*RawResult)
   792  	its.True(ok)
   793  	its.Equal(http.StatusUnauthorized, typed.StatusCode)
   794  }
   795  
   796  func Test_AuthManager_LoginRedirect_loginRedirectHandler(t *testing.T) {
   797  	its := assert.New(t)
   798  
   799  	am := AuthManager{
   800  		LoginRedirectHandler: func(r *Ctx) *url.URL {
   801  			return &url.URL{
   802  				Path: "/foo",
   803  			}
   804  		},
   805  	}
   806  	ctx := MockCtx(http.MethodGet, "/api/foo/bar", OptCtxDefaultProvider(Text))
   807  	res := am.LoginRedirect(ctx)
   808  	its.NotNil(res)
   809  	typed, ok := res.(*RedirectResult)
   810  	its.True(ok)
   811  	its.Empty(typed.Method)
   812  	its.Equal("/foo", typed.RedirectURI)
   813  }
   814  
   815  func Test_AuthManager_expire(t *testing.T) {
   816  	its := assert.New(t)
   817  
   818  	expectedSessionID := uuid.V4().String()
   819  	cookieName := "my-auth-cookie"
   820  
   821  	var didCallRemoveHandler, sessionIDCorrect bool
   822  	am := AuthManager{
   823  		CookieDefaults: http.Cookie{
   824  			Name: cookieName,
   825  		},
   826  		RemoveHandler: func(c context.Context, sessionID string) error {
   827  			didCallRemoveHandler = true
   828  			sessionIDCorrect = sessionID == expectedSessionID
   829  			return nil
   830  		},
   831  	}
   832  	ctx := MockCtx(http.MethodGet, "/api/foo/bar", OptCtxDefaultProvider(Text), OptCtxCookieValue(cookieName, expectedSessionID))
   833  	err := am.expire(ctx, expectedSessionID)
   834  	its.Nil(err)
   835  	its.True(didCallRemoveHandler)
   836  	its.True(sessionIDCorrect)
   837  
   838  	// assert the cookie is expired ...
   839  	cookies := ReadSetCookies(ctx.Response.Header())
   840  	its.NotEmpty(cookies)
   841  
   842  	cookie := cookies[0]
   843  	its.Equal(am.CookieDefaults.Name, cookie.Name)
   844  	its.Equal(am.CookieDefaults.Path, cookie.Path)
   845  	its.True(cookie.Expires.Before(time.Now().UTC()), "the cookie should be expired")
   846  }
   847  
   848  func Test_AuthManager_expire_removeError(t *testing.T) {
   849  	its := assert.New(t)
   850  
   851  	var didCallRemoveHandler bool
   852  	am := AuthManager{
   853  		RemoveHandler: func(c context.Context, sessionID string) error {
   854  			didCallRemoveHandler = true
   855  			return fmt.Errorf("this is just a test")
   856  		},
   857  	}
   858  	ctx := MockCtx(http.MethodGet, "/api/foo/bar", OptCtxDefaultProvider(Text))
   859  	err := am.expire(ctx, uuid.V4().String())
   860  	its.NotNil(err)
   861  	its.Equal("this is just a test", err.Error())
   862  	its.True(didCallRemoveHandler)
   863  
   864  	// assert the cookie is expired ...
   865  	cookies := ReadSetCookies(ctx.Response.Header())
   866  	its.Empty(cookies)
   867  }