package entity import ( "fmt" "time" gc "github.com/patrickmn/go-cache" "github.com/photoprism/photoprism/internal/event" "github.com/photoprism/photoprism/pkg/clean" "github.com/photoprism/photoprism/pkg/rnd" ) // Create a new session cache with an expiration time of 15 minutes. var sessionCacheExpiration = 15 * time.Minute var sessionCache = gc.New(sessionCacheExpiration, 5*time.Minute) // FindSession returns an existing session or nil if not found. func FindSession(id string) (*Session, error) { found := &Session{} // Valid id? if !rnd.IsSessionID(id) { return found, fmt.Errorf("id %s is invalid", clean.LogQuote(id)) } // Find the session in the cache with a fallback to the database. if cacheData, ok := sessionCache.Get(id); ok && cacheData != nil { if cached := cacheData.(*Session); !cached.Expired() { cached.LastActive = UnixTime() return cached, nil } else if err := cached.Delete(); err != nil { event.AuditErr([]string{cached.IP(), "session %s", "failed to delete after expiration", "%s"}, cached.RefID, err) } } else if res := Db().First(&found, "id = ?", id); res.RecordNotFound() { return found, fmt.Errorf("not found") } else if res.Error != nil { return found, res.Error } else if !rnd.IsSessionID(found.ID) { return found, fmt.Errorf("has invalid id %s", clean.LogQuote(found.ID)) } else if !found.Expired() { found.UpdateLastActive() CacheSession(found, sessionCacheExpiration) return found, nil } else if err := found.Delete(); err != nil { event.AuditErr([]string{found.IP(), "session %s", "failed to delete after expiration", "%s"}, found.RefID, err) } return found, fmt.Errorf("expired") } // FlushSessionCache resets the session cache. func FlushSessionCache() { sessionCache.Flush() } // CacheSession adds a session to the cache if its ID is valid. func CacheSession(s *Session, d time.Duration) { if s == nil { return } else if !rnd.IsSessionID(s.ID) { return } if d == 0 { d = sessionCacheExpiration } if s.PreviewToken != "" { PreviewToken.Set(s.PreviewToken, s.ID) } if s.DownloadToken != "" { DownloadToken.Set(s.DownloadToken, s.ID) } sessionCache.Set(s.ID, s, d) } // DeleteSession permanently deletes a session. func DeleteSession(s *Session) error { if s == nil { return nil } else if !rnd.IsSessionID(s.ID) { return fmt.Errorf("invalid session id") } DeleteFromSessionCache(s.ID) if s.PreviewToken != "" { PreviewToken.Set(s.PreviewToken, s.ID) } if s.DownloadToken != "" { DownloadToken.Set(s.DownloadToken, s.ID) } return UnscopedDb().Delete(s).Error } // DeleteFromSessionCache deletes a session from the cache. func DeleteFromSessionCache(id string) { if id == "" { return } sessionCache.Delete(id) }