feat: migrate auth0mock from Node.js to Go
Refactor the application to a Go-based architecture for improved performance and maintainability. Replace the Dockerfile to utilize a multi-stage build process, enhancing image efficiency. Implement comprehensive session store tests to ensure reliability and create new OAuth handlers for managing authentication efficiently. Update documentation to reflect these structural changes.
This commit is contained in:
@@ -0,0 +1,133 @@
|
||||
package store
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log/slog"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
// SessionTTL is the time-to-live for sessions
|
||||
SessionTTL = 5 * time.Minute
|
||||
// CleanupInterval is how often expired sessions are cleaned up
|
||||
CleanupInterval = 60 * time.Second
|
||||
)
|
||||
|
||||
// Session represents an OAuth session
|
||||
type Session struct {
|
||||
Email string
|
||||
Password string
|
||||
State string
|
||||
Nonce string
|
||||
ClientID string
|
||||
CodeChallenge string
|
||||
CodeVerifier string
|
||||
CustomClaims []map[string]interface{}
|
||||
CreatedAt time.Time
|
||||
}
|
||||
|
||||
// SessionStore provides thread-safe session storage with TTL
|
||||
type SessionStore struct {
|
||||
mu sync.RWMutex
|
||||
sessions map[string]*Session
|
||||
challenges map[string]string
|
||||
logger *slog.Logger
|
||||
}
|
||||
|
||||
// NewSessionStore creates a new session store
|
||||
func NewSessionStore(logger *slog.Logger) *SessionStore {
|
||||
return &SessionStore{
|
||||
sessions: make(map[string]*Session),
|
||||
challenges: make(map[string]string),
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// Create stores a new session
|
||||
func (s *SessionStore) Create(code string, session *Session) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
session.CreatedAt = time.Now()
|
||||
s.sessions[code] = session
|
||||
s.challenges[code] = code
|
||||
}
|
||||
|
||||
// Get retrieves a session by code
|
||||
func (s *SessionStore) Get(code string) (*Session, bool) {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
|
||||
session, ok := s.sessions[code]
|
||||
return session, ok
|
||||
}
|
||||
|
||||
// Update updates an existing session and optionally re-indexes it
|
||||
func (s *SessionStore) Update(oldCode, newCode string, updateFn func(*Session)) bool {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
session, ok := s.sessions[oldCode]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
updateFn(session)
|
||||
session.CreatedAt = time.Now() // Refresh timestamp
|
||||
|
||||
if oldCode != newCode {
|
||||
s.sessions[newCode] = session
|
||||
s.challenges[newCode] = newCode
|
||||
delete(s.sessions, oldCode)
|
||||
delete(s.challenges, oldCode)
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// Delete removes a session
|
||||
func (s *SessionStore) Delete(code string) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
delete(s.sessions, code)
|
||||
delete(s.challenges, code)
|
||||
}
|
||||
|
||||
// Cleanup removes expired sessions
|
||||
func (s *SessionStore) Cleanup() int {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
now := time.Now()
|
||||
cleaned := 0
|
||||
|
||||
for code, session := range s.sessions {
|
||||
if now.Sub(session.CreatedAt) > SessionTTL {
|
||||
delete(s.sessions, code)
|
||||
delete(s.challenges, code)
|
||||
cleaned++
|
||||
}
|
||||
}
|
||||
|
||||
return cleaned
|
||||
}
|
||||
|
||||
// StartCleanup starts a background goroutine to clean up expired sessions
|
||||
func (s *SessionStore) StartCleanup(ctx context.Context) {
|
||||
ticker := time.NewTicker(CleanupInterval)
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
ticker.Stop()
|
||||
return
|
||||
case <-ticker.C:
|
||||
if cleaned := s.Cleanup(); cleaned > 0 {
|
||||
s.logger.Info("cleaned up expired sessions", "count", cleaned)
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
Reference in New Issue
Block a user