github.com/greenpau/go-authcrunch@v1.1.4/pkg/authn/cache/session.go (about) 1 // Copyright 2022 Paul Greenberg greenpau@outlook.com 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package cache 16 17 import ( 18 "errors" 19 "fmt" 20 "sync" 21 "time" 22 23 "github.com/greenpau/go-authcrunch/pkg/user" 24 ) 25 26 const defaultSessionCleanupInternal int = 60 27 const minSessionCleanupInternal int = 0 28 29 // SessionCacheEntry is an entry in SessionCache. 30 type SessionCacheEntry struct { 31 sessionID string 32 createdAt time.Time 33 user *user.User 34 } 35 36 // SessionCache contains cached tokens 37 type SessionCache struct { 38 mu sync.RWMutex 39 // The interval (in seconds) at which cache maintenance task are being triggered. 40 // The default is 5 minutes (300 seconds) 41 cleanupInternal int 42 // The maximum number of seconds the cached entry is available to a user. 43 maxEntryLifetime int64 44 // If set to true, then the cache is being managed. 45 managed bool 46 // exit channel 47 exit chan bool 48 Entries map[string]*SessionCacheEntry `json:"entries,omitempty" xml:"entries,omitempty" yaml:"entries,omitempty"` 49 } 50 51 // NewSessionCache returns SessionCache instance. 52 func NewSessionCache() *SessionCache { 53 c := &SessionCache{ 54 cleanupInternal: defaultSessionCleanupInternal, 55 Entries: make(map[string]*SessionCacheEntry), 56 exit: make(chan bool), 57 } 58 return c 59 } 60 61 // SetCleanupInterval sets cache management interval. 62 func (c *SessionCache) SetCleanupInterval(i int) error { 63 if i < 1 { 64 return fmt.Errorf("session cache cleanup interval must be equal to or greater than %d", minSessionCleanupInternal) 65 } 66 c.cleanupInternal = i 67 return nil 68 69 } 70 71 func manageSessionCache(c *SessionCache) { 72 c.managed = true 73 intervals := time.NewTicker(time.Second * time.Duration(c.cleanupInternal)) 74 for range intervals.C { 75 if c == nil { 76 continue 77 } 78 c.mu.Lock() 79 select { 80 case <-c.exit: 81 c.managed = false 82 break 83 default: 84 break 85 } 86 if !c.managed { 87 c.mu.Unlock() 88 break 89 } 90 if c.Entries == nil { 91 c.mu.Unlock() 92 continue 93 } 94 deleteList := []string{} 95 for sessionID, entry := range c.Entries { 96 if err := entry.Valid(); err != nil { 97 deleteList = append(deleteList, sessionID) 98 continue 99 } 100 } 101 if len(deleteList) > 0 { 102 for _, sessionID := range deleteList { 103 delete(c.Entries, sessionID) 104 } 105 } 106 c.mu.Unlock() 107 } 108 } 109 110 // Run starts management of SessionCache instance. 111 func (c *SessionCache) Run() { 112 if c.managed { 113 return 114 } 115 go manageSessionCache(c) 116 } 117 118 // Stop stops management of SessionCache instance. 119 func (c *SessionCache) Stop() { 120 c.mu.Lock() 121 defer c.mu.Unlock() 122 c.managed = false 123 } 124 125 // GetCleanupInterval returns cleanup interval. 126 func (c *SessionCache) GetCleanupInterval() int { 127 return c.cleanupInternal 128 } 129 130 // Add adds user to the cache. 131 func (c *SessionCache) Add(sessionID string, u *user.User) error { 132 c.mu.Lock() 133 defer c.mu.Unlock() 134 if c.Entries == nil { 135 return errors.New("session cache is not available") 136 } 137 c.Entries[sessionID] = &SessionCacheEntry{ 138 sessionID: sessionID, 139 createdAt: time.Now().UTC(), 140 user: u, 141 } 142 return nil 143 } 144 145 // Delete removes cached user entry. 146 func (c *SessionCache) Delete(sessionID string) error { 147 c.mu.Lock() 148 defer c.mu.Unlock() 149 if c.Entries == nil { 150 return errors.New("session cache is not available") 151 } 152 _, exists := c.Entries[sessionID] 153 if !exists { 154 return errors.New("cached session id not found") 155 } 156 delete(c.Entries, sessionID) 157 return nil 158 } 159 160 // Get returns cached user entry. 161 func (c *SessionCache) Get(sessionID string) (*user.User, error) { 162 if err := parseCacheID(sessionID); err != nil { 163 return nil, err 164 } 165 c.mu.RLock() 166 defer c.mu.RUnlock() 167 if entry, exists := c.Entries[sessionID]; exists { 168 if err := entry.Valid(); err != nil { 169 delete(c.Entries, sessionID) 170 return nil, fmt.Errorf("cached session id error: %s", err) 171 } 172 if entry.user == nil { 173 delete(c.Entries, sessionID) 174 return nil, fmt.Errorf("cached session id %s has nil user", sessionID) 175 } 176 return entry.user, nil 177 } 178 return nil, errors.New("cached session id not found") 179 } 180 181 // Valid checks whether SessionCacheEntry is not expired. 182 func (e *SessionCacheEntry) Valid() error { 183 if err := e.user.Claims.Valid(); err != nil { 184 return err 185 } 186 return nil 187 } 188 189 // parseCacheID checks the id associated with the cached entry for format 190 // requirements. 191 func parseCacheID(s string) error { 192 if len(s) > 96 || len(s) < 32 { 193 return errors.New("cached id length is outside of 32-96 character range") 194 } 195 for _, c := range s { 196 if (c < 'A' || c > 'Z') && (c < 'a' || c > 'z') && (c < '0' || c > '9') && (c != '-') { 197 return errors.New("cached id contains invalid characters") 198 } 199 } 200 return nil 201 }