Files
UNITRONIX 1b240543bd feat(auth): implement SQLite auth consolidation and admin password validation
- Added functionality for safely consolidating legacy auth.db into the selected SQLite database, with options for dry runs and rollbacks.
- Introduced command-line flags for SQLite auth consolidation, including backup directory and rollback options.
- Enhanced admin interface security by requiring a password when the admin port is enabled, preventing unauthorized access.
- Updated related tests to ensure proper handling of admin password requirements and relay authorization logic.
2026-08-05 23:49:42 +02:00

164 lines
4.0 KiB
Go

package admin
import (
"bufio"
"context"
"fmt"
"net"
"strings"
"testing"
"time"
"github.com/unitronix/betterdesk-server/config"
"github.com/unitronix/betterdesk-server/db"
"github.com/unitronix/betterdesk-server/peer"
"github.com/unitronix/betterdesk-server/security"
)
const testAdminPassword = "test-admin-password"
func setupTestAdmin(t *testing.T) (*Server, int) {
t.Helper()
cfg := config.DefaultConfig()
// Find a free port
ln, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatalf("failed to find free port: %v", err)
}
port := ln.Addr().(*net.TCPAddr).Port
ln.Close()
cfg.AdminPort = port
cfg.AdminPassword = testAdminPassword
database, err := db.Open(":memory:")
if err != nil {
t.Fatalf("failed to open db: %v", err)
}
database.Migrate()
t.Cleanup(func() { database.Close() })
peerMap := peer.NewMap()
srv := New(cfg, database, peerMap, "test")
srv.SetAdminPassword(testAdminPassword)
srv.SetBlocklist(security.NewBlocklist())
ctx := context.Background()
if err := srv.Start(ctx); err != nil {
t.Fatalf("failed to start admin: %v", err)
}
t.Cleanup(srv.Stop)
return srv, port
}
func connectAdmin(t *testing.T, port int) net.Conn {
t.Helper()
conn, err := net.DialTimeout("tcp", fmt.Sprintf("127.0.0.1:%d", port), 2*time.Second)
if err != nil {
t.Fatalf("failed to connect to admin: %v", err)
}
t.Cleanup(func() { conn.Close() })
// Read banner
conn.SetReadDeadline(time.Now().Add(2 * time.Second))
reader := bufio.NewReader(conn)
if prompt, err := reader.ReadString(' '); err != nil || prompt != "Password: " {
t.Fatalf("expected password prompt, got %q (err: %v)", prompt, err)
}
fmt.Fprintf(conn, "%s\r\n", testAdminPassword)
reader.ReadString('\n') // Authenticated.
reader.ReadString('\n') // BetterDesk Admin Console
reader.ReadString('\n') // Type 'help'...
reader.ReadString('\n') // blank line
return conn
}
func sendCommand(conn net.Conn, cmd string) string {
// Read the prompt first
buf := make([]byte, 4096)
conn.SetReadDeadline(time.Now().Add(500 * time.Millisecond))
conn.Read(buf) // "> " prompt
// Send command
fmt.Fprintf(conn, "%s\r\n", cmd)
// Read response
conn.SetReadDeadline(time.Now().Add(2 * time.Second))
var response strings.Builder
for {
n, err := conn.Read(buf)
if n > 0 {
response.Write(buf[:n])
}
if err != nil || n == 0 {
break
}
// Check if we've received the next prompt
if strings.Contains(response.String(), "> ") && response.Len() > 2 {
break
}
}
return response.String()
}
func TestAdminStatusCommand(t *testing.T) {
_, port := setupTestAdmin(t)
conn := connectAdmin(t, port)
resp := sendCommand(conn, "status")
if !strings.Contains(resp, "BetterDesk") {
t.Errorf("status should contain server name, got: %s", resp)
}
if !strings.Contains(resp, "DB Peers") {
t.Errorf("status should contain peer info, got: %s", resp)
}
}
func TestAdminHelpCommand(t *testing.T) {
_, port := setupTestAdmin(t)
conn := connectAdmin(t, port)
resp := sendCommand(conn, "help")
if !strings.Contains(resp, "Available commands") {
t.Errorf("help should list commands, got: %s", resp)
}
}
func TestAdminPeersCount(t *testing.T) {
_, port := setupTestAdmin(t)
conn := connectAdmin(t, port)
resp := sendCommand(conn, "peers count")
if !strings.Contains(resp, "Total:") {
t.Errorf("peers count should show total, got: %s", resp)
}
}
func TestAdminDisabledByDefault(t *testing.T) {
cfg := config.DefaultConfig()
// AdminPort defaults to 0 (disabled)
database, _ := db.Open(":memory:")
database.Migrate()
defer database.Close()
srv := New(cfg, database, peer.NewMap(), "test")
err := srv.Start(context.Background())
if err != nil {
t.Errorf("should not error when disabled, got: %v", err)
}
srv.Stop()
}
func TestAdminRejectsEnabledPortWithoutPassword(t *testing.T) {
cfg := config.DefaultConfig()
cfg.AdminPort = 21115
srv := New(cfg, nil, peer.NewMap(), "test")
if err := srv.Start(context.Background()); err == nil {
t.Fatal("expected enabled admin interface without password to be rejected")
}
}