Files
nerd-monitor/internal/auth/middleware.go
Ducky SSH User 50dcfcdc83
All checks were successful
Build and Release / build (push) Successful in 35s
Add logging and fix /agents/ route error
2025-12-20 07:34:02 +00:00

118 lines
3.0 KiB
Go

package auth
import (
"crypto/rand"
"encoding/hex"
"log/slog"
"net/http"
"sync"
"time"
)
// Session represents an authenticated user session.
type Session struct {
Token string
ExpiresAt time.Time
}
// Manager handles authentication and session management.
type Manager struct {
mu sync.RWMutex
sessions map[string]*Session
username string
password string
expiryDur time.Duration
}
// New creates a new authentication manager with default credentials.
func New(username, password string) *Manager {
return &Manager{
sessions: make(map[string]*Session),
username: username,
password: password,
expiryDur: 24 * time.Hour,
}
}
// Login validates credentials and creates a session.
func (m *Manager) Login(username, password string) (string, error) {
if username != m.username || password != m.password {
slog.Debug("Login failed - invalid credentials", "username", username)
return "", ErrInvalidCredentials
}
token, err := generateToken()
if err != nil {
slog.Error("Failed to generate session token", "error", err)
return "", err
}
m.mu.Lock()
defer m.mu.Unlock()
m.sessions[token] = &Session{
Token: token,
ExpiresAt: time.Now().Add(m.expiryDur),
}
slog.Debug("Login successful, session created", "username", username, "token", token[:8]+"...")
return token, nil
}
// Validate checks if a session token is valid.
func (m *Manager) Validate(token string) bool {
m.mu.RLock()
defer m.mu.RUnlock()
session, ok := m.sessions[token]
if !ok {
slog.Debug("Session validation failed - token not found", "token", token[:8]+"...")
return false
}
if !session.ExpiresAt.After(time.Now()) {
slog.Debug("Session validation failed - token expired", "token", token[:8]+"...", "expiredAt", session.ExpiresAt)
return false
}
slog.Debug("Session validation successful", "token", token[:8]+"...")
return true
}
// Logout invalidates a session.
func (m *Manager) Logout(token string) {
m.mu.Lock()
defer m.mu.Unlock()
delete(m.sessions, token)
slog.Debug("Session logged out", "token", token[:8]+"...")
}
// Middleware returns a Chi middleware for authentication.
func (m *Manager) Middleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
cookie, err := r.Cookie("session_token")
if err != nil {
slog.Debug("Authentication failed - no session cookie", "path", r.URL.Path, "remoteAddr", r.RemoteAddr)
http.Redirect(w, r, "/login", http.StatusSeeOther)
return
}
if !m.Validate(cookie.Value) {
slog.Debug("Authentication failed - invalid session", "path", r.URL.Path, "remoteAddr", r.RemoteAddr)
http.Redirect(w, r, "/login", http.StatusSeeOther)
return
}
slog.Debug("Authentication successful", "path", r.URL.Path, "remoteAddr", r.RemoteAddr)
next.ServeHTTP(w, r)
})
}
// generateToken creates a random hex token.
func generateToken() (string, error) {
b := make([]byte, 16)
if _, err := rand.Read(b); err != nil {
return "", err
}
return hex.EncodeToString(b), nil
}