Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions internal/auth/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ func (m *Manager) SetSessionCookie(w http.ResponseWriter) string {
MaxAge: int(SessionDuration.Seconds()),
HttpOnly: true,
SameSite: http.SameSiteStrictMode,
Secure: true,
})
return token
}
Expand All @@ -160,6 +161,7 @@ func (m *Manager) ClearSessionCookie(w http.ResponseWriter) {
MaxAge: -1,
HttpOnly: true,
SameSite: http.SameSiteStrictMode,
Secure: true,
})
}

Expand Down
4 changes: 4 additions & 0 deletions internal/daemon/constants.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package daemon

// StatusError represents a generic error status across the daemon components
const StatusError = "error"
3 changes: 1 addition & 2 deletions internal/daemon/daemon.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ func (p *Program) Start() error {
Uptime: int(time.Since(p.startTime).Seconds()),
}

if response.Printers.Status == "error" {
if response.Printers.Status == StatusError {
response.Status = "degraded"
}

Expand Down Expand Up @@ -318,7 +318,6 @@ func serveDashboard(tmpl *template.Template) http.HandlerFunc {
return
}
w.Header().Set("Content-Type", "text/html; charset=utf-8")
//nolint:gosec // False positive: passing token to internal template
data := struct{ AuthToken string }{AuthToken: config.AuthToken}
if err := tmpl.Execute(w, data); err != nil {
log.Printf("[X] Error rendering dashboard: %v", err)
Expand Down
4 changes: 2 additions & 2 deletions internal/daemon/printer_discovery.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ func (pd *PrinterDiscovery) GetPrinters(forceRefresh bool) ([]connection.Printer
func (pd *PrinterDiscovery) GetSummary() posprinter.Summary {
printers, err := pd.GetPrinters(false)
if err != nil {
return posprinter.Summary{Status: "error", DetectedCount: 0}
return posprinter.Summary{Status: StatusError, DetectedCount: 0}
}

thermal := connection.FilterThermalPrinters(printers)
Expand All @@ -74,7 +74,7 @@ func (pd *PrinterDiscovery) GetSummary() posprinter.Summary {
if len(thermal) == 0 && len(physical) > 0 {
status = "warning"
} else if len(physical) == 0 {
status = "error"
status = StatusError
}

return posprinter.Summary{
Expand Down
133 changes: 133 additions & 0 deletions internal/server/clients_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
package server

import (
"sync"
"testing"

"github.com/coder/websocket"
)

func TestClientRegistry_BasicOperations(t *testing.T) {
registry := NewClientRegistry()

// Create dummy connections
// We use an array to make sure they have different addresses
conns := make([]websocket.Conn, 3)
conn1 := &conns[0]
conn2 := &conns[1]
conn3 := &conns[2]

// Initial state
if count := registry.Count(); count != 0 {
t.Errorf("Expected 0 clients, got %d", count)
}

// Add clients
registry.Add(conn1)
registry.Add(conn2)

if count := registry.Count(); count != 2 {
t.Errorf("Expected 2 clients, got %d", count)
}
if !registry.Contains(conn1) {
t.Error("Expected registry to contain conn1")
}
if registry.Contains(conn3) {
t.Error("Expected registry to not contain conn3")
}

// Remove client
registry.Remove(conn1)

if count := registry.Count(); count != 1 {
t.Errorf("Expected 1 client, got %d", count)
}
if registry.Contains(conn1) {
t.Error("Expected registry to not contain conn1 after removal")
}
if !registry.Contains(conn2) {
t.Error("Expected registry to contain conn2")
}
}

func TestClientRegistry_ForEachAndBroadcast(t *testing.T) {
registry := NewClientRegistry()

conns := make([]websocket.Conn, 2)
conn1 := &conns[0]
conn2 := &conns[1]

registry.Add(conn1)
registry.Add(conn2)

// Test ForEach
visited := make(map[*websocket.Conn]bool)
registry.ForEach(func(c *websocket.Conn) {
visited[c] = true
})

if len(visited) != 2 || !visited[conn1] || !visited[conn2] {
t.Errorf("ForEach did not visit all clients correctly: %v", visited)
}

// Test Broadcast
visitedBroadcast := make(map[*websocket.Conn]bool)
registry.Broadcast(func(c *websocket.Conn) error {
visitedBroadcast[c] = true
return nil
})

if len(visitedBroadcast) != 2 || !visitedBroadcast[conn1] || !visitedBroadcast[conn2] {
t.Errorf("Broadcast did not visit all clients correctly: %v", visitedBroadcast)
}
}

func TestClientRegistry_Concurrency(t *testing.T) {
registry := NewClientRegistry()
var wg sync.WaitGroup

numGoroutines := 100
conns := make([]websocket.Conn, numGoroutines)

// Concurrent Add
for i := 0; i < numGoroutines; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
registry.Add(&conns[i])
}(i)
}
wg.Wait()

if count := registry.Count(); count != numGoroutines {
t.Errorf("Expected %d clients, got %d", numGoroutines, count)
}

// Concurrent Contains, ForEach, Broadcast
for i := 0; i < numGoroutines; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
_ = registry.Contains(&conns[i])
if i%10 == 0 {
registry.ForEach(func(_ *websocket.Conn) {})
registry.Broadcast(func(_ *websocket.Conn) error { return nil })
}
}(i)
}
wg.Wait()

// Concurrent Remove
for i := 0; i < numGoroutines; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
registry.Remove(&conns[i])
}(i)
}
wg.Wait()

if count := registry.Count(); count != 0 {
t.Errorf("Expected 0 clients after removal, got %d", count)
}
}
1 change: 0 additions & 1 deletion internal/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@
Tipo string `json:"tipo"`
ID string `json:"id,omitempty"`
Datos json.RawMessage `json:"datos,omitempty"`
//nolint:gosec // Required JSON key for client payload
AuthToken string `json:"auth_token,omitempty"`
}

Expand Down Expand Up @@ -188,7 +187,7 @@
// ── RATE LIMIT ────────────────────────────────────────
clientAddr := fmt.Sprintf("%p", conn) // Using pointer address as client identifier
if !s.jobLimiter.Allow(clientAddr) {
log.Printf("[AUDIT] JOB_RATE_LIMITED | client=%s", clientAddr)

Check failure on line 190 in internal/server/server.go

View workflow job for this annotation

GitHub Actions / πŸ” Linting

G706: Log injection via taint analysis (gosec)
s.sendError(ctx, conn, jobID, "Rate limit exceeded: please wait before submitting more jobs")
return
}
Expand All @@ -197,7 +196,7 @@
if config.AuthToken != "" {
token := msg.AuthToken
if token != config.AuthToken {
log.Printf("[AUDIT] JOB_REJECTED | reason=invalid_token | client=%s", clientAddr)

Check failure on line 199 in internal/server/server.go

View workflow job for this annotation

GitHub Actions / πŸ” Linting

G706: Log injection via taint analysis (gosec)
s.sendError(ctx, conn, jobID, "Invalid or missing auth token")
return
}
Expand Down
Loading