Remove banned imports (fmt, strings, os, errors, path/filepath) across all production and test files, replace with core.* primitives, coreio.ReadStream, and coreerr.E. Upgrade dappco.re/go/core v0.5.0 → v0.7.0 for core.PathBase and core.Is. Fix isRepoScoped to exclude pr.* capabilities (enforcement is at the forge layer, not the policy engine). Add Good/Bad/Ugly test coverage to all packages missing the mandatory three-category naming convention. Co-Authored-By: Virgil <virgil@lethean.io>
139 lines
3.3 KiB
Go
139 lines
3.3 KiB
Go
package auth
|
|
|
|
import (
|
|
"encoding/json"
|
|
"sync"
|
|
"time"
|
|
|
|
core "dappco.re/go/core"
|
|
coreerr "dappco.re/go/core/log"
|
|
"forge.lthn.ai/core/go-store"
|
|
)
|
|
|
|
const sessionGroup = "sessions"
|
|
|
|
// SQLiteSessionStore is a SessionStore backed by go-store (SQLite KV).
|
|
// A mutex serialises all operations because SQLite is single-writer.
|
|
type SQLiteSessionStore struct {
|
|
mu sync.Mutex
|
|
store *store.Store
|
|
}
|
|
|
|
// NewSQLiteSessionStore creates a new SQLite-backed session store.
|
|
//
|
|
// sessionStore, err := auth.NewSQLiteSessionStore("/var/lib/agent/sessions.db")
|
|
// authenticator := auth.New(medium, auth.WithSessionStore(sessionStore))
|
|
func NewSQLiteSessionStore(databasePath string) (*SQLiteSessionStore, error) {
|
|
s, err := store.New(databasePath)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &SQLiteSessionStore{store: s}, nil
|
|
}
|
|
|
|
// Get retrieves a session by token from SQLite.
|
|
func (s *SQLiteSessionStore) Get(token string) (*Session, error) {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
value, err := s.store.Get(sessionGroup, token)
|
|
if err != nil {
|
|
if core.Is(err, store.ErrNotFound) {
|
|
return nil, ErrSessionNotFound
|
|
}
|
|
return nil, err
|
|
}
|
|
|
|
var session Session
|
|
if err := json.Unmarshal([]byte(value), &session); err != nil {
|
|
return nil, coreerr.E("auth.SQLiteSessionStore.Get", "failed to unmarshal session", err)
|
|
}
|
|
return &session, nil
|
|
}
|
|
|
|
// Set stores a session in SQLite, keyed by its token.
|
|
func (s *SQLiteSessionStore) Set(session *Session) error {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
data, err := json.Marshal(session)
|
|
if err != nil {
|
|
return coreerr.E("auth.SQLiteSessionStore.Set", "failed to marshal session", err)
|
|
}
|
|
return s.store.Set(sessionGroup, session.Token, string(data))
|
|
}
|
|
|
|
// Delete removes a session by token from SQLite.
|
|
func (s *SQLiteSessionStore) Delete(token string) error {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
// Check existence first to return ErrSessionNotFound
|
|
_, err := s.store.Get(sessionGroup, token)
|
|
if err != nil {
|
|
if core.Is(err, store.ErrNotFound) {
|
|
return ErrSessionNotFound
|
|
}
|
|
return err
|
|
}
|
|
return s.store.Delete(sessionGroup, token)
|
|
}
|
|
|
|
// DeleteByUser removes all sessions belonging to the given user.
|
|
func (s *SQLiteSessionStore) DeleteByUser(userID string) error {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
all, err := s.store.GetAll(sessionGroup)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for token, value := range all {
|
|
var session Session
|
|
if err := json.Unmarshal([]byte(value), &session); err != nil {
|
|
continue // Skip malformed entries
|
|
}
|
|
if session.UserID == userID {
|
|
if err := s.store.Delete(sessionGroup, token); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Cleanup removes all expired sessions and returns the count removed.
|
|
func (s *SQLiteSessionStore) Cleanup() (int, error) {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
all, err := s.store.GetAll(sessionGroup)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
now := time.Now()
|
|
count := 0
|
|
for token, value := range all {
|
|
var session Session
|
|
if err := json.Unmarshal([]byte(value), &session); err != nil {
|
|
continue // Skip malformed entries
|
|
}
|
|
if now.After(session.ExpiresAt) {
|
|
if err := s.store.Delete(sessionGroup, token); err != nil {
|
|
return count, err
|
|
}
|
|
count++
|
|
}
|
|
}
|
|
return count, nil
|
|
}
|
|
|
|
// Close closes the underlying SQLite store.
|
|
func (s *SQLiteSessionStore) Close() error {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
return s.store.Close()
|
|
}
|