Updated qr code events and tls server
Some checks failed
CI / Test (1.22) (push) Failing after -25m23s
CI / Test (1.23) (push) Failing after -25m25s
CI / Build (push) Failing after -25m51s
CI / Lint (push) Failing after -25m40s

This commit is contained in:
2025-12-29 17:22:06 +02:00
parent bb9aa01519
commit 94fc899bab
17 changed files with 929 additions and 26 deletions

View File

@@ -131,3 +131,48 @@ func WithSQLiteDatabase(sqlitePath string) Option {
c.Database.SQLitePath = sqlitePath
}
}
// WithTLS configures TLS settings
func WithTLS(enabled bool, mode string) Option {
return func(c *config.Config) {
c.Server.TLS.Enabled = enabled
c.Server.TLS.Mode = mode
}
}
// WithTLSConfig configures TLS with full config
func WithTLSConfig(cfg config.TLSConfig) Option {
return func(c *config.Config) {
c.Server.TLS = cfg
}
}
// WithSelfSignedTLS enables HTTPS with self-signed certificates
func WithSelfSignedTLS(certDir string) Option {
return func(c *config.Config) {
c.Server.TLS.Enabled = true
c.Server.TLS.Mode = "self-signed"
c.Server.TLS.CertDir = certDir
}
}
// WithCustomTLS enables HTTPS with custom certificate files
func WithCustomTLS(certFile, keyFile string) Option {
return func(c *config.Config) {
c.Server.TLS.Enabled = true
c.Server.TLS.Mode = "custom"
c.Server.TLS.CertFile = certFile
c.Server.TLS.KeyFile = keyFile
}
}
// WithAutocertTLS enables HTTPS with Let's Encrypt autocert
func WithAutocertTLS(domain, email string, production bool) Option {
return func(c *config.Config) {
c.Server.TLS.Enabled = true
c.Server.TLS.Mode = "autocert"
c.Server.TLS.Domain = domain
c.Server.TLS.Email = email
c.Server.TLS.Production = production
}
}

View File

@@ -2,15 +2,18 @@ package whatshooked
import (
"context"
"crypto/tls"
"fmt"
"net/http"
"time"
"git.warky.dev/wdevs/whatshooked/pkg/config"
"git.warky.dev/wdevs/whatshooked/pkg/events"
"git.warky.dev/wdevs/whatshooked/pkg/hooks"
"git.warky.dev/wdevs/whatshooked/pkg/logging"
"git.warky.dev/wdevs/whatshooked/pkg/utils"
"go.mau.fi/whatsmeow/types"
"golang.org/x/crypto/acme/autocert"
)
// Server is the optional built-in HTTP server
@@ -26,7 +29,7 @@ func NewServer(wh *WhatsHooked) *Server {
}
}
// Start starts the HTTP server
// Start starts the HTTP/HTTPS server
func (s *Server) Start() error {
// Subscribe to hook success events for two-way communication
s.wh.EventBus().Subscribe(events.EventHookSuccess, s.handleHookResponse)
@@ -40,20 +43,25 @@ func (s *Server) Start() error {
Handler: mux,
}
logging.Info("Starting HTTP server",
"host", s.wh.config.Server.Host,
"port", s.wh.config.Server.Port,
"address", addr)
// Connect to WhatsApp accounts
// Connect to WhatsApp accounts after server starts
go func() {
time.Sleep(100 * time.Millisecond) // Give HTTP server a moment to start
logging.Info("HTTP server ready, connecting to WhatsApp accounts")
time.Sleep(100 * time.Millisecond) // Give server a moment to start
logging.Info("Server ready, connecting to WhatsApp accounts")
if err := s.wh.ConnectAll(context.Background()); err != nil {
logging.Error("Failed to connect to WhatsApp accounts", "error", err)
}
}()
// Start server with or without TLS
if s.wh.config.Server.TLS.Enabled {
return s.startTLS()
}
logging.Info("Starting HTTP server",
"host", s.wh.config.Server.Host,
"port", s.wh.config.Server.Port,
"address", addr)
// Start server (blocking)
if err := s.httpServer.ListenAndServe(); err != nil && err != http.ErrServerClosed {
return err
@@ -62,6 +70,127 @@ func (s *Server) Start() error {
return nil
}
// startTLS starts the server with TLS based on the configured mode
func (s *Server) startTLS() error {
tlsConfig := &s.wh.config.Server.TLS
addr := fmt.Sprintf("%s:%d", s.wh.config.Server.Host, s.wh.config.Server.Port)
switch tlsConfig.Mode {
case "self-signed":
return s.startSelfSignedTLS(tlsConfig, addr)
case "custom":
return s.startCustomTLS(tlsConfig, addr)
case "autocert":
return s.startAutocertTLS(tlsConfig, addr)
default:
return fmt.Errorf("invalid TLS mode: %s (must be 'self-signed', 'custom', or 'autocert')", tlsConfig.Mode)
}
}
// startSelfSignedTLS starts the server with a self-signed certificate
func (s *Server) startSelfSignedTLS(tlsConfig *config.TLSConfig, addr string) error {
logging.Info("Generating/loading self-signed certificate",
"cert_dir", tlsConfig.CertDir,
"host", s.wh.config.Server.Host)
certPath, keyPath, err := utils.GenerateSelfSignedCert(tlsConfig.CertDir, s.wh.config.Server.Host)
if err != nil {
return fmt.Errorf("failed to generate self-signed certificate: %w", err)
}
logging.Info("Starting HTTPS server with self-signed certificate",
"host", s.wh.config.Server.Host,
"port", s.wh.config.Server.Port,
"address", addr,
"cert", certPath,
"key", keyPath)
if err := s.httpServer.ListenAndServeTLS(certPath, keyPath); err != nil && err != http.ErrServerClosed {
return err
}
return nil
}
// startCustomTLS starts the server with custom certificate files
func (s *Server) startCustomTLS(tlsConfig *config.TLSConfig, addr string) error {
if tlsConfig.CertFile == "" || tlsConfig.KeyFile == "" {
return fmt.Errorf("custom TLS mode requires cert_file and key_file to be specified")
}
logging.Info("Validating custom TLS certificates",
"cert", tlsConfig.CertFile,
"key", tlsConfig.KeyFile)
// Validate certificate files
if err := utils.ValidateCertificateFiles(tlsConfig.CertFile, tlsConfig.KeyFile); err != nil {
return fmt.Errorf("invalid certificate files: %w", err)
}
logging.Info("Starting HTTPS server with custom certificate",
"host", s.wh.config.Server.Host,
"port", s.wh.config.Server.Port,
"address", addr,
"cert", tlsConfig.CertFile,
"key", tlsConfig.KeyFile)
if err := s.httpServer.ListenAndServeTLS(tlsConfig.CertFile, tlsConfig.KeyFile); err != nil && err != http.ErrServerClosed {
return err
}
return nil
}
// startAutocertTLS starts the server with Let's Encrypt autocert
func (s *Server) startAutocertTLS(tlsConfig *config.TLSConfig, addr string) error {
if tlsConfig.Domain == "" {
return fmt.Errorf("autocert mode requires domain to be specified")
}
logging.Info("Setting up Let's Encrypt autocert",
"domain", tlsConfig.Domain,
"email", tlsConfig.Email,
"cache_dir", tlsConfig.CacheDir,
"production", tlsConfig.Production)
// Create autocert manager
certManager := &autocert.Manager{
Prompt: autocert.AcceptTOS,
HostPolicy: autocert.HostWhitelist(tlsConfig.Domain),
Cache: autocert.DirCache(tlsConfig.CacheDir),
Email: tlsConfig.Email,
}
// Configure TLS
s.httpServer.TLSConfig = &tls.Config{
GetCertificate: certManager.GetCertificate,
MinVersion: tls.VersionTLS12,
}
// Start HTTP-01 challenge server on port 80 if we're listening on 443
if s.wh.config.Server.Port == 443 {
go func() {
httpAddr := fmt.Sprintf("%s:80", s.wh.config.Server.Host)
logging.Info("Starting HTTP server for ACME challenges", "address", httpAddr)
if err := http.ListenAndServe(httpAddr, certManager.HTTPHandler(nil)); err != nil {
logging.Error("Failed to start HTTP challenge server", "error", err)
}
}()
}
logging.Info("Starting HTTPS server with Let's Encrypt",
"host", s.wh.config.Server.Host,
"port", s.wh.config.Server.Port,
"address", addr,
"domain", tlsConfig.Domain)
if err := s.httpServer.ListenAndServeTLS("", ""); err != nil && err != http.ErrServerClosed {
return err
}
return nil
}
// Stop stops the HTTP server gracefully
func (s *Server) Stop(ctx context.Context) error {
if s.httpServer != nil {
@@ -96,6 +225,9 @@ func (s *Server) setupRoutes() *http.ServeMux {
// Serve media files (with auth)
mux.HandleFunc("/api/media/", h.ServeMedia)
// Serve QR codes (no auth - needed during pairing)
mux.HandleFunc("/api/qr/", h.ServeQRCode)
// Business API webhooks (no auth - Meta validates via verify_token)
mux.HandleFunc("/webhooks/whatsapp/", h.BusinessAPIWebhook)
@@ -103,7 +235,8 @@ func (s *Server) setupRoutes() *http.ServeMux {
"health", "/health",
"hooks", "/api/hooks",
"accounts", "/api/accounts",
"send", "/api/send")
"send", "/api/send",
"qr", "/api/qr")
return mux
}