NextReceipt/internal/auth/session.go
cclohmar f0c576c543 feat: transform NextExpense into NextReceipt — receipt saver for warranty & returns
- Replace events+expenses with flat purchases table
- Add warranty_months, return_days, product_name fields
- Remove currency conversion, CSV/PDF reporting, event filing
- Simplify auth (no onboarding/department/profile)
- Update AI extraction prompts for product/warranty info
- Update all branding: templates, install.sh, Makefile, service file
2026-06-21 18:57:29 +00:00

100 lines
2.4 KiB
Go

// 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
}