github.com/greenpau/go-authcrunch@v1.0.50/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 return &SessionCache{ 54 cleanupInternal: defaultSessionCleanupInternal, 55 Entries: make(map[string]*SessionCacheEntry), 56 exit: make(chan bool), 57 } 58 } 59 60 // SetCleanupInterval sets cache management interval. 61 func (c *SessionCache) SetCleanupInterval(i int) error { 62 if i < 1 { 63 return fmt.Errorf("session cache cleanup interval must be equal to or greater than %d", minSessionCleanupInternal) 64 } 65 c.cleanupInternal = i 66 return nil 67 68 } 69 70 func manageSessionCache(c *SessionCache) { 71 c.managed = true 72 intervals := time.NewTicker(time.Second * time.Duration(c.cleanupInternal)) 73 for range intervals.C { 74 if c == nil { 75 continue 76 } 77 c.mu.Lock() 78 select { 79 case <-c.exit: 80 c.managed = false 81 break 82 default: 83 break 84 } 85 if !c.managed { 86 c.mu.Unlock() 87 break 88 } 89 if c.Entries == nil { 90 c.mu.Unlock() 91 continue 92 } 93 deleteList := []string{} 94 for sessionID, entry := range c.Entries { 95 if err := entry.Valid(); err != nil { 96 deleteList = append(deleteList, sessionID) 97 continue 98 } 99 } 100 if len(deleteList) > 0 { 101 for _, sessionID := range deleteList { 102 delete(c.Entries, sessionID) 103 } 104 } 105 c.mu.Unlock() 106 } 107 return 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 return nil, fmt.Errorf("cached session id error: %s", err) 170 } 171 return entry.user, nil 172 } 173 return nil, errors.New("cached session id not found") 174 } 175 176 // Valid checks whether SessionCacheEntry is not expired. 177 func (e *SessionCacheEntry) Valid() error { 178 if err := e.user.Claims.Valid(); err != nil { 179 return err 180 } 181 return nil 182 } 183 184 // parseCacheID checks the id associated with the cached entry for format 185 // requirements. 186 func parseCacheID(s string) error { 187 if len(s) > 96 || len(s) < 32 { 188 return errors.New("cached id length is outside of 32-96 character range") 189 } 190 for _, c := range s { 191 if (c < 'A' || c > 'Z') && (c < 'a' || c > 'z') && (c < '0' || c > '9') && (c != '-') { 192 return errors.New("cached id contains invalid characters") 193 } 194 } 195 return nil 196 }