diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index d17a521a..28cf7c71 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -38,6 +38,12 @@ jobs: with: languages: ${{ matrix.language }} + - name: Setup Go + if: matrix.language == 'go' + uses: actions/setup-go@v4 + with: + go-version: '1.25.4' + - name: Autobuild uses: github/codeql-action/autobuild@fdbfb4d2750291e159f0156def62b853c2798ca2 # v4 diff --git a/backend/cmd/seed/main.go b/backend/cmd/seed/main.go index 68a16688..d8ca3c6c 100644 --- a/backend/cmd/seed/main.go +++ b/backend/cmd/seed/main.go @@ -3,6 +3,7 @@ package main import ( "fmt" "log" + "os" "github.com/google/uuid" "gorm.io/driver/sqlite" @@ -182,23 +183,66 @@ func main() { } // Seed default admin user (for future authentication) + defaultAdminEmail := os.Getenv("CHARON_DEFAULT_ADMIN_EMAIL") + if defaultAdminEmail == "" { + defaultAdminEmail = "admin@localhost" + } + defaultAdminPassword := os.Getenv("CHARON_DEFAULT_ADMIN_PASSWORD") + // If a default password is not specified, leave the hashed placeholder (non-loginable) + forceAdmin := os.Getenv("CHARON_FORCE_DEFAULT_ADMIN") == "1" + user := models.User{ - UUID: uuid.NewString(), - Email: "admin@localhost", - Name: "Administrator", - PasswordHash: "$2a$10$example_hashed_password", // This would be properly hashed in production - Role: "admin", - Enabled: true, + UUID: uuid.NewString(), + Email: defaultAdminEmail, + Name: "Administrator", + Role: "admin", + Enabled: true, } - result := db.Where("email = ?", user.Email).FirstOrCreate(&user) - if result.Error != nil { - log.Printf("Failed to seed user: %v", result.Error) - } else if result.RowsAffected > 0 { - fmt.Printf("✓ Created default user: %s\n", user.Email) + + // If a default password provided, use SetPassword to generate a proper bcrypt hash + if defaultAdminPassword != "" { + if err := user.SetPassword(defaultAdminPassword); err != nil { + log.Printf("Failed to hash default admin password: %v", err) + } } else { - fmt.Printf(" User already exists: %s\n", user.Email) + // Keep previous behavior: using example hashed password (not valid) + user.PasswordHash = "$2a$10$example_hashed_password" } + var existing models.User + // Find by email first + if err := db.Where("email = ?", user.Email).First(&existing).Error; err != nil { + // Not found -> create + result := db.Create(&user) + if result.Error != nil { + log.Printf("Failed to seed user: %v", result.Error) + } else if result.RowsAffected > 0 { + fmt.Printf("✓ Created default user: %s\n", user.Email) + } + } else { + // Found existing user - optionally update if forced + if forceAdmin { + existing.Email = user.Email + existing.Name = user.Name + existing.Role = user.Role + existing.Enabled = user.Enabled + if defaultAdminPassword != "" { + if err := existing.SetPassword(defaultAdminPassword); err == nil { + db.Save(&existing) + fmt.Printf("✓ Updated existing admin user password for: %s\n", existing.Email) + } else { + log.Printf("Failed to update existing admin password: %v", err) + } + } else { + db.Save(&existing) + fmt.Printf(" User already exists: %s\n", existing.Email) + } + } else { + fmt.Printf(" User already exists: %s\n", existing.Email) + } + } + // result handling is done inline above + fmt.Println("\n✓ Database seeding completed successfully!") fmt.Println(" You can now start the application and see sample data.") } diff --git a/scripts/integration-test.sh b/scripts/integration-test.sh index 2ff8c7da..c6117295 100755 --- a/scripts/integration-test.sh +++ b/scripts/integration-test.sh @@ -22,6 +22,21 @@ if [ "$code" != "200" ]; then exit 1 fi +echo "Checking setup status..." +SETUP_REQUIRED=$(curl -s $API_URL/setup | jq -r .setupRequired) +if [ "$SETUP_REQUIRED" = "true" ]; then + echo "Setup is required; attempting to create initial admin..." + SETUP_RESPONSE=$(curl -s -X POST $API_URL/setup \ + -H "Content-Type: application/json" \ + -d "{\"name\":\"Administrator\",\"email\":\"$ADMIN_EMAIL\",\"password\":\"$ADMIN_PASSWORD\"}") + echo "Setup response: $SETUP_RESPONSE" + if echo "$SETUP_RESPONSE" | jq -e .user >/dev/null 2>&1; then + echo "✅ Setup completed" + else + echo "⚠️ Setup request returned unexpected response; continuing to login attempt" + fi +fi + echo "Logging in..." TOKEN=$(curl -s -X POST $API_URL/auth/login \ -H "Content-Type: application/json" \ @@ -34,17 +49,37 @@ fi echo "✅ Login successful" echo "Creating Proxy Host..." +# Remove existing proxy host for the domain to make the test idempotent +EXISTING_ID=$(curl -s -H "Authorization: Bearer $TOKEN" $API_URL/proxy-hosts | jq -r --arg domain "test.localhost" '.[] | select(.domain_names == $domain) | .uuid' | head -n1) +if [ -n "$EXISTING_ID" ]; then + echo "Found existing proxy host (ID: $EXISTING_ID), deleting..." + curl -s -X DELETE $API_URL/proxy-hosts/$EXISTING_ID -H "Authorization: Bearer $TOKEN" +fi +# Start a lightweight test upstream server to ensure proxy has a target (local-only) +python3 -c "import http.server, socketserver +class Handler(http.server.BaseHTTPRequestHandler): + def do_GET(self): + self.send_response(200) + self.end_headers() + self.wfile.write(b'Hostname: local-test') + def log_message(self, format, *args): + pass +httpd=socketserver.TCPServer((\"0.0.0.0\", 8081), Handler) +import threading +threading.Thread(target=httpd.serve_forever, daemon=True).start() +" & + # We use 'whoami' as the forward host because they are on the same docker network RESPONSE=$(curl -s -X POST $API_URL/proxy-hosts \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{ - "domain_names": ["test.localhost"], + "domain_names": "test.localhost", "forward_scheme": "http", - "forward_host": "whoami", - "forward_port": 80, - "access_list_id": "", - "certificate_id": "", + "forward_host": "127.0.0.1", + "forward_port": 8081, + "access_list_id": null, + "certificate_id": null, "ssl_forced": false, "caching_enabled": false, "block_exploits": false,