From ff63742a2fa138c230fb2e080708d03ed888c248 Mon Sep 17 00:00:00 2001 From: Ahmed AbdelHalim Date: Sun, 7 Sep 2025 17:50:36 +0200 Subject: Add basic login and clean up config --- internal/auth.go | 107 +++++++++++++++++++++++++++++++++++++++++++++++++++++ internal/config.go | 17 +++------ 2 files changed, 113 insertions(+), 11 deletions(-) create mode 100644 internal/auth.go (limited to 'internal') diff --git a/internal/auth.go b/internal/auth.go new file mode 100644 index 0000000..6caa785 --- /dev/null +++ b/internal/auth.go @@ -0,0 +1,107 @@ +package internal + +import ( + "crypto/rand" + "crypto/sha256" + "encoding/hex" + "fmt" + "sync" + "time" +) + +type Session struct { + Expires time.Time +} + +type SessionManager struct { + sessions map[string]*Session + mutex sync.RWMutex +} + +func NewSessionManager() *SessionManager { + sm := &SessionManager{ + sessions: make(map[string]*Session), + } + // Start cleanup goroutine + go sm.cleanupExpiredSessions() + return sm +} + +// Using the same logic that powers the pi-hole authentication +// GeneratePasswordHash creates a double SHA256 hash from the password param to +// validate against config.PasswordHash +func GeneratePasswordHash(password string) string { + first := sha256.Sum256([]byte(password)) + firstHex := hex.EncodeToString(first[:]) + second := sha256.Sum256([]byte(firstHex)) + return hex.EncodeToString(second[:]) +} + +func ValidatePassword(password, hash string) bool { + return GeneratePasswordHash(password) == hash +} + +func (sm *SessionManager) CreateSession() (string, time.Time, error) { + sm.mutex.Lock() + defer sm.mutex.Unlock() + + sessionID, err := generateSecureToken() + if err != nil { + return "", time.Time{}, fmt.Errorf("failed to generate session ID: %w", err) + } + + expires := time.Now().Add(1 * time.Hour) + sm.sessions[sessionID] = &Session{ + Expires: expires, + } + + return sessionID, expires, nil +} + +func (sm *SessionManager) ValidateSession(sessionID string) (*Session, bool) { + sm.mutex.RLock() + defer sm.mutex.RUnlock() + + session, exists := sm.sessions[sessionID] + if !exists { + return nil, false + } + + if time.Now().After(session.Expires) { + return nil, false + } + + return session, true +} + +func (sm *SessionManager) DeleteSession(sessionID string) { + sm.mutex.Lock() + defer sm.mutex.Unlock() + delete(sm.sessions, sessionID) +} + +func generateSecureToken() (string, error) { + bytes := make([]byte, 32) + _, err := rand.Read(bytes) + if err != nil { + return "", err + } + return hex.EncodeToString(bytes), nil +} + +// cleanupExpiredSessions periodically removes expired sessions every 1 hour +func (sm *SessionManager) cleanupExpiredSessions() { + ticker := time.NewTicker(1 * time.Hour) + defer ticker.Stop() + + for range ticker.C { + sm.mutex.Lock() + now := time.Now() + for sessionID, session := range sm.sessions { + if now.After(session.Expires) { + delete(sm.sessions, sessionID) + } + } + sm.mutex.Unlock() + } +} diff --git a/internal/config.go b/internal/config.go index 8d9d961..558630d 100644 --- a/internal/config.go +++ b/internal/config.go @@ -7,22 +7,17 @@ import ( "gopkg.in/yaml.v3" ) -type ServerConfig struct { - Host string `yaml:"host"` - Port string `yaml:"port"` - Title string `yaml:"title"` -} - type Config struct { - Server ServerConfig `yaml:"server"` + Host string `yaml:"host"` + Port string `yaml:"port"` + PasswordHash string `yaml:"password_hash"` } // Default configuration values func DefaultConfig() *Config { config := &Config{} - config.Server.Host = "0.0.0.0" - config.Server.Port = "8080" - config.Server.Title = "WireGuard Gateway Portal" + config.Host = "0.0.0.0" + config.Port = "8080" return config } @@ -52,5 +47,5 @@ func LoadConfig(configPath string) (*Config, error) { // GetAddress returns the server address in host:port format func (c *Config) GetAddress() string { - return fmt.Sprintf("%s:%s", c.Server.Host, c.Server.Port) + return fmt.Sprintf("%s:%s", c.Host, c.Port) } -- cgit v1.2.3