Files
bri-sandbox-development-pla…/control-plane/internal/api/sessions.go
T
Achmad f12d4f0b12 Slice 2 (follow-up): add Sessions.User / Revoke for /api/logout and audit-trail attribution
The original auth commit shipped the in-memory session store with
just Issue and Valid. The Slice-2 /api/logout handler and the
audit-trail (user column on each deployment) need:
- User(tok): look up the username for a valid session.
- Revoke(tok): drop a session; used by /api/logout.

Tiny follow-up — kept as its own commit because the rest of the
auth work had already shipped in the parent commit by the time the
dashboard's logout button and the deployment-audit-trail surfaced
the need for these methods.
2026-06-24 04:01:53 +00:00

68 lines
1.3 KiB
Go

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()
}