Skip to content

Commit

Permalink
feat: add Destroy() which deletes the dession from backend
Browse files Browse the repository at this point in the history
  • Loading branch information
vividvilla committed May 30, 2024
1 parent 4ee7fae commit 13804b4
Show file tree
Hide file tree
Showing 13 changed files with 230 additions and 32 deletions.
28 changes: 28 additions & 0 deletions go.work.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc=
github.com/alicebob/miniredis/v2 v2.32.1 h1:Bz7CciDnYSaa0mX5xODh6GUITRSx+cVhjNoOR4JssBo=
github.com/alicebob/miniredis/v2 v2.32.1/go.mod h1:AqkLNAfUm0K07J28hnAyyQKf/x0YkCY/g5DCtuL01Mw=
github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA=
github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo=
github.com/klauspost/compress v1.15.0/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/redis/go-redis/v9 v9.5.1 h1:H1X4D3yHPaYrkL5X06Wh6xNVM/pX0Ft4RV0vMGvLBh8=
github.com/redis/go-redis/v9 v9.5.1/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasthttp v1.40.0/go.mod h1:t/G+3rLek+CyY9bnIE+YlMRddxVAAGjhxndDB4i4C0I=
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
github.com/vividvilla/simplesessions/conv v1.0.0/go.mod h1:Ym8WXzo3bGfpHVAEeS3PbpUgXBaIQvK69DvZCbM6yoE=
golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
16 changes: 13 additions & 3 deletions session.go
Original file line number Diff line number Diff line change
Expand Up @@ -250,14 +250,24 @@ func (s *Session) Delete(key ...string) error {
return errAs(err)
}

// Clear clears session data from store and clears the cookie.
// Clear empties the data for the given session id.
// Use `Destroy()` to delete entire session from store and clear the cookie.
func (s *Session) Clear() error {
err := s.manager.store.Clear(s.id)
if err != nil {
return errAs(err)
} else {
s.ResetCache()
}
s.ResetCache()
return nil
}

// Clear deletes the entire session from store and clears the cookie.
func (s *Session) Destroy() error {
err := s.manager.store.Destroy(s.id)
if err != nil {
return errAs(err)
}
s.ResetCache()
return s.clearCookie()
}

Expand Down
70 changes: 57 additions & 13 deletions session_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -267,14 +267,14 @@ func TestResetCache(t *testing.T) {

func TestGetStore(t *testing.T) {
str := newMockStore()
mgr := newMockManager(str)
sess, err := mgr.NewSession(nil, nil)
assert.NoError(t, err)
str.data = map[string]interface{}{
"key1": 1,
"key2": 2,
"key3": 3,
}
mgr := newMockManager(str)
sess, err := mgr.NewSession(nil, nil)
assert.NoError(t, err)

// GetAll.
v1, err := sess.GetAll()
Expand Down Expand Up @@ -417,14 +417,14 @@ func TestSetMulti(t *testing.T) {

func TestDelete(t *testing.T) {
str := newMockStore()
mgr := newMockManager(str)
sess, err := mgr.NewSession(nil, nil)
assert.NoError(t, err)
str.data = map[string]interface{}{
"key1": 1,
"key2": 2,
"key3": 3,
}
mgr := newMockManager(str)
sess, err := mgr.NewSession(nil, nil)
assert.NoError(t, err)

assert.Contains(t, str.data, "key1")
err = sess.Delete("key1")
Expand Down Expand Up @@ -455,13 +455,19 @@ func TestClear(t *testing.T) {
err = sess.Clear()
assert.ErrorIs(t, str.err, err)

// Test cookie write error.
str.err = nil
ckErr := errors.New("cookie error")
mgr.SetCookieHooks(nil, func(*http.Cookie, interface{}) error { return ckErr })

// Test clear.
str = newMockStore()
str.data = map[string]interface{}{
"key1": 1,
"key2": 2,
}
mgr = newMockManager(str)
sess, err = mgr.NewSession(nil, nil)
assert.NoError(t, err)
err = sess.Clear()
assert.ErrorIs(t, ckErr, err)
assert.NoError(t, err)
assert.Equal(t, 0, len(str.data))
assert.Nil(t, sess.cache)

// Test clear.
str = newMockStore()
Expand All @@ -472,19 +478,56 @@ func TestClear(t *testing.T) {
mgr = newMockManager(str)
sess, err = mgr.NewSession(nil, nil)
assert.NoError(t, err)
err = sess.CacheAll()
assert.NoError(t, err)
assert.NotNil(t, sess.cache)
err = sess.Clear()
assert.NoError(t, err)
assert.Equal(t, 0, len(str.data))
assert.Nil(t, sess.cache)
}

func TestDestroy(t *testing.T) {
// Test errors.
str := newMockStore()
mgr := newMockManager(str)
sess, err := mgr.NewSession(nil, nil)
assert.NoError(t, err)
str.err = errors.New("store error")
err = sess.Destroy()
assert.ErrorIs(t, str.err, err)

// Test cookie write error.
str.err = nil
ckErr := errors.New("cookie error")
mgr.SetCookieHooks(nil, func(*http.Cookie, interface{}) error { return ckErr })

str.data = map[string]interface{}{"foo": "bar"}
err = sess.Destroy()
assert.ErrorIs(t, ckErr, err)

// Test clear.
str = newMockStore()
mgr = newMockManager(str)
sess, err = mgr.NewSession(nil, nil)
str.data = map[string]interface{}{
"key1": 1,
"key2": 2,
}
assert.NoError(t, err)
err = sess.Destroy()
assert.NoError(t, err)
assert.Nil(t, str.data)
assert.Nil(t, sess.cache)

// Test clear.
str = newMockStore()
mgr = newMockManager(str)
sess, err = mgr.NewSession(nil, nil)
str.data = map[string]interface{}{
"key1": 1,
"key2": 2,
}
assert.NoError(t, err)
err = sess.CacheAll()
assert.NoError(t, err)
Expand All @@ -504,9 +547,10 @@ func TestClear(t *testing.T) {
isCb = true
return nil
})
err = sess.Clear()
err = sess.Destroy()
assert.NoError(t, err)
assert.Equal(t, 0, len(str.data))
assert.True(t, isCb)
assert.NotNil(t, receCk)
assert.Greater(t, time.Now(), receCk.Expires)
}
5 changes: 4 additions & 1 deletion store.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,12 @@ type Store interface {
// Delete a given list of keys from session.
Delete(id string, key ...string) error

// Clear clears the entire session.
// Clear empties the session.
Clear(id string) error

// Destroy deletes the entire session.
Destroy(id string) error

// Helper method for typecasting/asserting.
Int(interface{}, error) (int, error)
Int64(interface{}, error) (int64, error)
Expand Down
26 changes: 18 additions & 8 deletions store_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,12 @@ type MockStore struct {
}

func (s *MockStore) Create(id string) error {
s.data = make(map[string]interface{})
return s.err
}

func (s *MockStore) Get(id, key string) (interface{}, error) {
if s.id == "" {
if s.id == "" || s.data == nil {
return nil, ErrInvalidSession
}

Expand All @@ -24,7 +25,7 @@ func (s *MockStore) Get(id, key string) (interface{}, error) {
}

func (s *MockStore) GetMulti(id string, keys ...string) (values map[string]interface{}, err error) {
if s.id == "" {
if s.id == "" || s.data == nil {
return nil, ErrInvalidSession
}

Expand All @@ -41,15 +42,15 @@ func (s *MockStore) GetMulti(id string, keys ...string) (values map[string]inter
}

func (s *MockStore) GetAll(id string) (values map[string]interface{}, err error) {
if s.id == "" {
if s.id == "" || s.data == nil {
return nil, ErrInvalidSession
}

return s.data, s.err
}

func (s *MockStore) Set(cv, key string, value interface{}) error {
if s.id == "" {
if s.id == "" || s.data == nil {
return ErrInvalidSession
}

Expand All @@ -58,7 +59,7 @@ func (s *MockStore) Set(cv, key string, value interface{}) error {
}

func (s *MockStore) SetMulti(id string, data map[string]interface{}) error {
if s.id == "" {
if s.id == "" || s.data == nil {
return ErrInvalidSession
}

Expand All @@ -69,7 +70,7 @@ func (s *MockStore) SetMulti(id string, data map[string]interface{}) error {
}

func (s *MockStore) Delete(id string, key ...string) error {
if s.id == "" {
if s.id == "" || s.data == nil {
return ErrInvalidSession
}

Expand All @@ -80,11 +81,20 @@ func (s *MockStore) Delete(id string, key ...string) error {
}

func (s *MockStore) Clear(id string) error {
if s.id == "" {
if s.id == "" || s.data == nil {
return ErrInvalidSession
}

s.data = map[string]interface{}{}
s.data = make(map[string]interface{})
return s.err
}

func (s *MockStore) Destroy(id string) error {
if s.id == "" || s.data == nil {
return ErrInvalidSession
}

s.data = nil
return s.err
}

Expand Down
16 changes: 15 additions & 1 deletion stores/memory/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ func (s *Store) Delete(id string, key string) error {
return nil
}

// Clear clears session in redis.
// Clear empties the session.
func (s *Store) Clear(id string) error {
s.mu.Lock()
defer s.mu.Unlock()
Expand All @@ -176,6 +176,20 @@ func (s *Store) Clear(id string) error {
return nil
}

// Destroy deletes the entire session.
func (s *Store) Destroy(id string) error {
s.mu.Lock()
defer s.mu.Unlock()

_, ok := s.sessions[id]
if !ok {
return ErrInvalidSession
}
delete(s.sessions, id)

return nil
}

// Int is a helper method to type assert as integer
func (s *Store) Int(r interface{}, err error) (int, error) {
if err != nil {
Expand Down
15 changes: 15 additions & 0 deletions stores/memory/store_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,21 @@ func TestClear(t *testing.T) {
assert.Equal(t, len(str.sessions[id]), 0)
}

func TestDestroy(t *testing.T) {
// Test should only set in internal map and not in redis
str := New()
err := str.Destroy("invalidkey")
assert.ErrorIs(t, ErrInvalidSession, err)

// this id is unique across all tests
id := "test_id"
str.sessions[id] = make(map[string]interface{})

err = str.Destroy(id)
assert.NoError(t, err)
assert.NotContains(t, str.sessions, id)
}

func TestInt(t *testing.T) {
str := New()

Expand Down
38 changes: 32 additions & 6 deletions stores/postgres/postgres.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,13 @@ func (e *Err) Code() int {
}

type queries struct {
create *sql.Stmt
get *sql.Stmt
update *sql.Stmt
delete *sql.Stmt
clear *sql.Stmt
prune *sql.Stmt
create *sql.Stmt
get *sql.Stmt
update *sql.Stmt
delete *sql.Stmt
clear *sql.Stmt
prune *sql.Stmt
destroy *sql.Stmt
}

// Store represents redis session store for simple sessions.
Expand Down Expand Up @@ -241,6 +242,26 @@ func (s *Store) Clear(id string) error {
return nil
}

// Destroy deletes the entire session from backend.
func (s *Store) Destroy(id string) error {
res, err := s.q.destroy.Exec(id)
if err != nil {
return err
}

num, err := res.RowsAffected()
if err != nil {
return err
}

// No row was updated. The session didn't exist.
if num == 0 {
return ErrInvalidSession
}

return nil
}

// Int is a helper method to type assert as integer.
func (s *Store) Int(r interface{}, err error) (int, error) {
if err != nil {
Expand Down Expand Up @@ -382,5 +403,10 @@ func (s *Store) prepareQueries() (*queries, error) {
return nil, err
}

q.destroy, err = s.db.Prepare(fmt.Sprintf("DELETE FROM %s WHERE id=$1", s.opt.Table))
if err != nil {
return nil, err
}

return q, err
}
Loading

0 comments on commit 13804b4

Please sign in to comment.