mirror of
https://github.com/Dvorinka/SEEN.git
synced 2026-06-04 12:33:02 +00:00
206 lines
5.3 KiB
Go
206 lines
5.3 KiB
Go
package cache
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
)
|
|
|
|
// SessionData represents cached session information
|
|
type SessionData struct {
|
|
SessionID string `json:"sessionId"`
|
|
UserID string `json:"userId"`
|
|
RefreshToken string `json:"refreshToken"`
|
|
UserAgent string `json:"userAgent"`
|
|
IP string `json:"ip"`
|
|
ExpiresAt time.Time `json:"expiresAt"`
|
|
CreatedAt time.Time `json:"createdAt"`
|
|
}
|
|
|
|
// SessionCache provides session caching operations
|
|
type SessionCache struct {
|
|
service *Service
|
|
keys *KeyBuilder
|
|
}
|
|
|
|
// NewSessionCache creates a new session cache
|
|
func NewSessionCache(service *Service, namespace string) *SessionCache {
|
|
return &SessionCache{
|
|
service: service,
|
|
keys: NewKeyBuilder(namespace),
|
|
}
|
|
}
|
|
|
|
// SetSession stores session data in cache
|
|
func (sc *SessionCache) SetSession(ctx context.Context, session SessionData) error {
|
|
key := sc.keys.SessionKey(session.SessionID)
|
|
ttl := time.Until(session.ExpiresAt)
|
|
|
|
if ttl <= 0 {
|
|
return fmt.Errorf("session already expired")
|
|
}
|
|
|
|
return sc.service.Set(ctx, key, session, ttl)
|
|
}
|
|
|
|
// GetSession retrieves session data from cache
|
|
func (sc *SessionCache) GetSession(ctx context.Context, sessionID string) (*SessionData, error) {
|
|
key := sc.keys.SessionKey(sessionID)
|
|
var session SessionData
|
|
|
|
if err := sc.service.Get(ctx, key, &session); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &session, nil
|
|
}
|
|
|
|
// DeleteSession removes session data from cache
|
|
func (sc *SessionCache) DeleteSession(ctx context.Context, sessionID string) error {
|
|
key := sc.keys.SessionKey(sessionID)
|
|
return sc.service.Delete(ctx, key)
|
|
}
|
|
|
|
// GetSessionByRefreshToken retrieves session by refresh token
|
|
// Note: This requires scanning, which is slower. Consider using a secondary index.
|
|
func (sc *SessionCache) GetSessionByRefreshToken(ctx context.Context, refreshToken string) (*SessionData, error) {
|
|
pattern := sc.keys.Build(PrefixSession, "*")
|
|
keys, err := sc.service.Keys(ctx, pattern)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
for _, key := range keys {
|
|
var session SessionData
|
|
if err := sc.service.Get(ctx, key, &session); err != nil {
|
|
continue
|
|
}
|
|
|
|
if session.RefreshToken == refreshToken {
|
|
return &session, nil
|
|
}
|
|
}
|
|
|
|
return nil, ErrCacheMiss
|
|
}
|
|
|
|
// ExtendSession extends the TTL of a session
|
|
func (sc *SessionCache) ExtendSession(ctx context.Context, sessionID string, duration time.Duration) error {
|
|
key := sc.keys.SessionKey(sessionID)
|
|
return sc.service.Expire(ctx, key, duration)
|
|
}
|
|
|
|
// UserData represents cached user information
|
|
type UserData struct {
|
|
ID string `json:"id"`
|
|
Email string `json:"email"`
|
|
DisplayName string `json:"displayName"`
|
|
Role string `json:"role"`
|
|
CachedAt time.Time `json:"cachedAt"`
|
|
}
|
|
|
|
// SetUser stores user data in cache
|
|
func (sc *SessionCache) SetUser(ctx context.Context, user UserData) error {
|
|
key := sc.keys.UserKey(user.ID)
|
|
return sc.service.Set(ctx, key, user, TTLUser)
|
|
}
|
|
|
|
// GetUser retrieves user data from cache
|
|
func (sc *SessionCache) GetUser(ctx context.Context, userID string) (*UserData, error) {
|
|
key := sc.keys.UserKey(userID)
|
|
var user UserData
|
|
|
|
if err := sc.service.Get(ctx, key, &user); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &user, nil
|
|
}
|
|
|
|
// DeleteUser removes user data from cache
|
|
func (sc *SessionCache) DeleteUser(ctx context.Context, userID string) error {
|
|
key := sc.keys.UserKey(userID)
|
|
return sc.service.Delete(ctx, key)
|
|
}
|
|
|
|
// InvalidateUserSessions removes all sessions for a user
|
|
func (sc *SessionCache) InvalidateUserSessions(ctx context.Context, userID string) error {
|
|
pattern := sc.keys.Build(PrefixSession, "*")
|
|
keys, err := sc.service.Keys(ctx, pattern)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
toDelete := make([]string, 0)
|
|
for _, key := range keys {
|
|
var session SessionData
|
|
if err := sc.service.Get(ctx, key, &session); err != nil {
|
|
continue
|
|
}
|
|
|
|
if session.UserID == userID {
|
|
toDelete = append(toDelete, key)
|
|
}
|
|
}
|
|
|
|
if len(toDelete) > 0 {
|
|
return sc.service.Delete(ctx, toDelete...)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// RateLimitCheck checks if an action is rate limited
|
|
func (sc *SessionCache) RateLimitCheck(ctx context.Context, identifier, action string, limit int64, window time.Duration) (bool, error) {
|
|
key := sc.keys.RateLimitKey(identifier, action)
|
|
|
|
count, err := sc.service.Increment(ctx, key)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
|
|
// Set expiry on first increment
|
|
if count == 1 {
|
|
if err := sc.service.Expire(ctx, key, window); err != nil {
|
|
return false, err
|
|
}
|
|
}
|
|
|
|
return count <= limit, nil
|
|
}
|
|
|
|
// AcquireLock attempts to acquire a distributed lock
|
|
func (sc *SessionCache) AcquireLock(ctx context.Context, resource string, ttl time.Duration) (string, bool, error) {
|
|
lockID := uuid.New().String()
|
|
key := sc.keys.LockKey(resource)
|
|
|
|
acquired, err := sc.service.SetNX(ctx, key, lockID, ttl)
|
|
if err != nil {
|
|
return "", false, err
|
|
}
|
|
|
|
return lockID, acquired, nil
|
|
}
|
|
|
|
// ReleaseLock releases a distributed lock
|
|
func (sc *SessionCache) ReleaseLock(ctx context.Context, resource, lockID string) error {
|
|
key := sc.keys.LockKey(resource)
|
|
|
|
// Verify we own the lock before deleting
|
|
var storedLockID string
|
|
if err := sc.service.Get(ctx, key, &storedLockID); err != nil {
|
|
if err == ErrCacheMiss {
|
|
return nil // Lock already released
|
|
}
|
|
return err
|
|
}
|
|
|
|
if storedLockID != lockID {
|
|
return fmt.Errorf("lock owned by different process")
|
|
}
|
|
|
|
return sc.service.Delete(ctx, key)
|
|
}
|