package auth import ( "context" "crypto/rand" "crypto/sha256" "encoding/base64" "encoding/hex" "errors" "time" "github.com/google/uuid" ) var ErrNoSession = errors.New("auth: no such session") type SessionStore interface { CreateSession(ctx context.Context, userID uuid.UUID, tokenHash string, expiresAt time.Time) error GetSessionUser(ctx context.Context, tokenHash string) (uuid.UUID, error) DeleteSession(ctx context.Context, tokenHash string) error } type Sessions struct { store SessionStore ttl time.Duration } func NewSessions(store SessionStore, ttl time.Duration) *Sessions { return &Sessions{store: store, ttl: ttl} } func TokenHash(token string) string { sum := sha256.Sum256([]byte(token)) return hex.EncodeToString(sum[:]) } func (s *Sessions) Create(ctx context.Context, userID uuid.UUID) (string, time.Time, error) { raw := make([]byte, 32) if _, err := rand.Read(raw); err != nil { return "", time.Time{}, err } token := base64.RawURLEncoding.EncodeToString(raw) exp := time.Now().Add(s.ttl) if err := s.store.CreateSession(ctx, userID, TokenHash(token), exp); err != nil { return "", time.Time{}, err } return token, exp, nil } func (s *Sessions) Validate(ctx context.Context, token string) (uuid.UUID, error) { return s.store.GetSessionUser(ctx, TokenHash(token)) } func (s *Sessions) Destroy(ctx context.Context, token string) error { return s.store.DeleteSession(ctx, TokenHash(token)) }