package api import ( "crypto/rand" "encoding/hex" "sync" "time" ) // sessionStore mantém sessões de login em memória: o cookie do navegador // carrega um id de sessão opaco — nunca o token mestre. Reiniciar o processo // revoga tudo (o usuário faz login de novo); logout individual revoga na hora. // Não há crypto própria: id vem de crypto/rand. type sessionStore struct { mu sync.Mutex m map[string]time.Time // id → expiração ttl time.Duration } func newSessionStore(ttl time.Duration) *sessionStore { return &sessionStore{m: map[string]time.Time{}, ttl: ttl} } // new cria uma sessão e devolve (id, expiração). func (ss *sessionStore) new() (string, time.Time) { var buf [32]byte if _, err := rand.Read(buf[:]); err != nil { // crypto/rand falhar é condição fatal de sistema; não há fallback seguro panic("crypto/rand indisponível: " + err.Error()) } id := hex.EncodeToString(buf[:]) exp := time.Now().Add(ss.ttl) ss.mu.Lock() ss.m[id] = exp ss.mu.Unlock() return id, exp } // valid confere se o id existe e não expirou; expirados são removidos no passe. func (ss *sessionStore) valid(id string) bool { if id == "" { return false } now := time.Now() ss.mu.Lock() defer ss.mu.Unlock() exp, ok := ss.m[id] if !ok { return false } if now.After(exp) { delete(ss.m, id) return false } // limpeza oportunista: mapa cresceu além do saudável → poda expirados if len(ss.m) > 4096 { for k, e := range ss.m { if now.After(e) { delete(ss.m, k) } } } return true } // revoke remove a sessão; devolve true se existia. func (ss *sessionStore) revoke(id string) bool { ss.mu.Lock() defer ss.mu.Unlock() _, ok := ss.m[id] delete(ss.m, id) return ok }