package api import ( "crypto/rand" "encoding/hex" "sync" "time" ) // Sessions is an in-memory token store. Replace with a signed JWT or // a Redis-backed store when we need multi-replica CP. For MVP one process // is enough. type Sessions struct { mu sync.RWMutex store map[string]session } type session struct { user string expires time.Time } func NewSessions() *Sessions { return &Sessions{store: make(map[string]session)} } func (s *Sessions) Issue(user string) string { b := make([]byte, 16) _, _ = rand.Read(b) tok := hex.EncodeToString(b) s.mu.Lock() s.store[tok] = session{user: user, expires: time.Now().Add(12 * time.Hour)} s.mu.Unlock() return tok } func (s *Sessions) Valid(tok string) bool { s.mu.RLock() sess, ok := s.store[tok] s.mu.RUnlock() if !ok { return false } return time.Now().Before(sess.expires) } // User returns the username for a valid token, or "" if the token is // unknown or expired. func (s *Sessions) User(tok string) (string, bool) { s.mu.RLock() sess, ok := s.store[tok] s.mu.RUnlock() if !ok { return "", false } if !time.Now().Before(sess.expires) { return "", false } return sess.user, true } // Revoke drops a session. Used by /api/logout. func (s *Sessions) Revoke(tok string) { s.mu.Lock() delete(s.store, tok) s.mu.Unlock() }