package session import ( "errors" "time" "github.com/jonboulle/clockwork" ) type SessionRepo interface { Get(string) (*Session, error) Create(Session) error Update(Session) error } type SessionKeyRepo interface { Push(SessionKey, time.Duration) error Pop(string) (string, error) } func NewSessionRepo() SessionRepo { return NewSessionRepoWithClock(clockwork.NewRealClock()) } func NewSessionRepoWithClock(clock clockwork.Clock) SessionRepo { return &memSessionRepo{ store: make(map[string]Session), clock: clock, } } type memSessionRepo struct { store map[string]Session clock clockwork.Clock } func (m *memSessionRepo) Get(sessionID string) (*Session, error) { s, ok := m.store[sessionID] if !ok || s.ExpiresAt.Before(m.clock.Now()) { return nil, errors.New("unrecognized ID") } return &s, nil } func (m *memSessionRepo) Create(s Session) error { if _, ok := m.store[s.ID]; ok { return errors.New("ID exists") } m.store[s.ID] = s return nil } func (m *memSessionRepo) Update(s Session) error { if _, ok := m.store[s.ID]; !ok { return errors.New("unrecognized ID") } m.store[s.ID] = s return nil } type expiringSessionKey struct { SessionKey expiresAt time.Time } func NewSessionKeyRepo() SessionKeyRepo { return NewSessionKeyRepoWithClock(clockwork.NewRealClock()) } func NewSessionKeyRepoWithClock(clock clockwork.Clock) SessionKeyRepo { return &memSessionKeyRepo{ store: make(map[string]expiringSessionKey), clock: clock, } } type memSessionKeyRepo struct { store map[string]expiringSessionKey clock clockwork.Clock } func (m *memSessionKeyRepo) Pop(key string) (string, error) { esk, ok := m.store[key] if !ok { return "", errors.New("unrecognized key") } defer delete(m.store, key) if esk.expiresAt.Before(m.clock.Now()) { return "", errors.New("expired key") } return esk.SessionKey.SessionID, nil } func (m *memSessionKeyRepo) Push(sk SessionKey, ttl time.Duration) error { m.store[sk.Key] = expiringSessionKey{ SessionKey: sk, expiresAt: m.clock.Now().Add(ttl), } return nil }