package auth import ( "crypto/rand" "encoding/hex" "errors" "fmt" "sync" "time" "github.com/brasse-b/archivum/internal/config" "github.com/go-ldap/ldap/v3" ) // Session represents an authenticated user session. type Session struct { Username string Token string ExpiresAt time.Time } // Manager handles LDAP authentication and in-memory session tracking. type Manager struct { cfg *config.Config mu sync.RWMutex sessions map[string]*Session } func NewManager(cfg *config.Config) *Manager { return &Manager{ cfg: cfg, sessions: make(map[string]*Session), } } // Login authenticates against LDAP and returns a bearer token on success. func (m *Manager) Login(username, password string) (string, error) { l, err := ldap.Dial("tcp", fmt.Sprintf("%s:%d", m.cfg.LDAP.Host, m.cfg.LDAP.Port)) if err != nil { return "", err } defer l.Close() if err := l.Bind(m.cfg.LDAP.BindDN, m.cfg.LDAP.BindPassword); err != nil { return "", err } sr, err := l.Search(&ldap.SearchRequest{ BaseDN: m.cfg.LDAP.BaseDN, Filter: fmt.Sprintf("(uid=%s)", ldap.EscapeFilter(username)), Scope: ldap.ScopeWholeSubtree, }) if err != nil { return "", err } if len(sr.Entries) != 1 { return "", errors.New("user not found") } userDN := sr.Entries[0].DN if err := l.Bind(userDN, password); err != nil { return "", errors.New("invalid credentials") } token, err := generateToken() if err != nil { return "", err } m.mu.Lock() m.sessions[token] = &Session{ Username: username, Token: token, ExpiresAt: time.Now().Add(24 * time.Hour), } m.mu.Unlock() return token, nil } // Validate checks a bearer token and returns the associated session. func (m *Manager) Validate(token string) (*Session, error) { m.mu.RLock() s, ok := m.sessions[token] m.mu.RUnlock() if !ok { return nil, errors.New("invalid token") } if time.Now().After(s.ExpiresAt) { m.mu.Lock() delete(m.sessions, token) m.mu.Unlock() return nil, errors.New("token expired") } return s, nil } // Logout removes a session. func (m *Manager) Logout(token string) { m.mu.Lock() delete(m.sessions, token) m.mu.Unlock() } func generateToken() (string, error) { b := make([]byte, 32) if _, err := rand.Read(b); err != nil { return "", err } return hex.EncodeToString(b), nil }