// Package auth provides authentication and session management for ExpenseFlow. package auth import ( "crypto/rand" "encoding/hex" "log" "sync" "time" ) // sessionData represents the data stored for each session. type sessionData struct { userID string expiresAt time.Time } // SessionStore is an in-memory, thread-safe session store that maps // session tokens to user sessions with expiration handling. type SessionStore struct { mu sync.RWMutex sessions map[string]sessionData } // NewSessionStore creates and returns a new empty SessionStore. func NewSessionStore() *SessionStore { return &SessionStore{ sessions: make(map[string]sessionData), } } // Generate creates a new session for the given userID with a 24-hour TTL. // It returns a cryptographically secure random hex-encoded token string. func (s *SessionStore) Generate(userID string) (string, error) { token, err := generateRandomToken() if err != nil { log.Printf("auth: failed to generate session token: %v", err) return "", err } s.mu.Lock() s.sessions[token] = sessionData{ userID: userID, expiresAt: time.Now().Add(24 * time.Hour), } s.mu.Unlock() return token, nil } // Get returns the userID associated with the given token if the session // exists and has not expired. Returns "", false otherwise. func (s *SessionStore) Get(token string) (string, bool) { s.mu.RLock() data, ok := s.sessions[token] s.mu.RUnlock() if !ok { return "", false } if time.Now().After(data.expiresAt) { // Session is expired; clean it up. s.Delete(token) return "", false } return data.userID, true } // Delete removes the session identified by the given token. func (s *SessionStore) Delete(token string) { s.mu.Lock() delete(s.sessions, token) s.mu.Unlock() } // Cleanup removes all expired sessions from the store. This method is // safe to call periodically from a background goroutine. func (s *SessionStore) Cleanup() { s.mu.Lock() defer s.mu.Unlock() now := time.Now() for token, data := range s.sessions { if now.After(data.expiresAt) { delete(s.sessions, token) } } } // generateRandomToken creates a 32-byte cryptographically random token // and returns its hex-encoded representation (64 hex characters). func generateRandomToken() (string, error) { b := make([]byte, 32) if _, err := rand.Read(b); err != nil { return "", err } return hex.EncodeToString(b), nil }