|  | 
|  | 1 | +# frozen_string_literal: true | 
|  | 2 | + | 
|  | 3 | +require "net/http" | 
|  | 4 | +require "json" | 
|  | 5 | +require "uri" | 
|  | 6 | + | 
|  | 7 | +# React on Rails Pro License Public Key Management Tasks | 
|  | 8 | +# | 
|  | 9 | +# Usage: | 
|  | 10 | +#   rake react_on_rails_pro:update_public_key              # From production (shakacode.com) | 
|  | 11 | +#   rake react_on_rails_pro:update_public_key[local]       # From localhost:8788 | 
|  | 12 | +#   rake react_on_rails_pro:update_public_key[custom.com]  # From custom hostname | 
|  | 13 | +#   rake react_on_rails_pro:verify_public_key              # Verify current configuration | 
|  | 14 | +#   rake react_on_rails_pro:public_key_help                # Show help | 
|  | 15 | + | 
|  | 16 | +namespace :react_on_rails_pro do | 
|  | 17 | +  desc "Update the public key for React on Rails Pro license validation" | 
|  | 18 | +  task :update_public_key, [:source] do |_task, args| | 
|  | 19 | +    source = args[:source] || "production" | 
|  | 20 | + | 
|  | 21 | +    # Determine the API URL based on the source | 
|  | 22 | +    api_url = case source | 
|  | 23 | +              when "local", "localhost" | 
|  | 24 | +                "http://localhost:8788/api/public-key" | 
|  | 25 | +              when "production", "prod" | 
|  | 26 | +                "https://www.shakacode.com/api/public-key" | 
|  | 27 | +              else | 
|  | 28 | +                # Check if it's a custom URL or hostname | 
|  | 29 | +                if source.start_with?("http://", "https://") | 
|  | 30 | +                  # Full URL provided | 
|  | 31 | +                  source.end_with?("/api/public-key") ? source : "#{source}/api/public-key" | 
|  | 32 | +                else | 
|  | 33 | +                  # Just a hostname provided | 
|  | 34 | +                  "https://#{source}/api/public-key" | 
|  | 35 | +                end | 
|  | 36 | +              end | 
|  | 37 | + | 
|  | 38 | +    puts "Fetching public key from: #{api_url}" | 
|  | 39 | + | 
|  | 40 | +    begin | 
|  | 41 | +      uri = URI(api_url) | 
|  | 42 | +      response = Net::HTTP.get_response(uri) | 
|  | 43 | + | 
|  | 44 | +      if response.code != "200" | 
|  | 45 | +        puts "❌ Failed to fetch public key. HTTP Status: #{response.code}" | 
|  | 46 | +        puts "Response: #{response.body}" | 
|  | 47 | +        exit 1 | 
|  | 48 | +      end | 
|  | 49 | + | 
|  | 50 | +      data = JSON.parse(response.body) | 
|  | 51 | +      public_key = data["publicKey"] | 
|  | 52 | + | 
|  | 53 | +      if public_key.nil? || public_key.empty? | 
|  | 54 | +        puts "❌ No public key found in response" | 
|  | 55 | +        exit 1 | 
|  | 56 | +      end | 
|  | 57 | + | 
|  | 58 | +      # Update Ruby public key file | 
|  | 59 | +      ruby_file_path = File.join(File.dirname(__FILE__), "..", "lib", "react_on_rails_pro", "license_public_key.rb") | 
|  | 60 | +      ruby_content = <<~RUBY.strip_heredoc | 
|  | 61 | +        # frozen_string_literal: true | 
|  | 62 | +
 | 
|  | 63 | +        module ReactOnRailsPro | 
|  | 64 | +          module LicensePublicKey | 
|  | 65 | +            # ShakaCode's public key for React on Rails Pro license verification | 
|  | 66 | +            # The private key corresponding to this public key is held by ShakaCode | 
|  | 67 | +            # and is never committed to the repository | 
|  | 68 | +            # Last updated: #{Time.now.utc.strftime("%Y-%m-%d %H:%M:%S UTC")} | 
|  | 69 | +            # Source: #{api_url} | 
|  | 70 | +            KEY = OpenSSL::PKey::RSA.new(<<~PEM.strip.strip_heredoc) | 
|  | 71 | +              #{public_key.strip} | 
|  | 72 | +            PEM | 
|  | 73 | +          end | 
|  | 74 | +        end | 
|  | 75 | +      RUBY | 
|  | 76 | + | 
|  | 77 | +      File.write(ruby_file_path, ruby_content) | 
|  | 78 | +      puts "✅ Updated Ruby public key: #{ruby_file_path}" | 
|  | 79 | + | 
|  | 80 | +      # Update Node/TypeScript public key file | 
|  | 81 | +      node_file_path = File.join(File.dirname(__FILE__), "..", "packages", "node-renderer", "src", "shared", "licensePublicKey.ts") | 
|  | 82 | +      node_content = <<~TYPESCRIPT | 
|  | 83 | +        // ShakaCode's public key for React on Rails Pro license verification | 
|  | 84 | +        // The private key corresponding to this public key is held by ShakaCode | 
|  | 85 | +        // and is never committed to the repository | 
|  | 86 | +        // Last updated: #{Time.now.utc.strftime("%Y-%m-%d %H:%M:%S UTC")} | 
|  | 87 | +        // Source: #{api_url} | 
|  | 88 | +        export const PUBLIC_KEY = `#{public_key.strip}`; | 
|  | 89 | +      TYPESCRIPT | 
|  | 90 | + | 
|  | 91 | +      File.write(node_file_path, node_content) | 
|  | 92 | +      puts "✅ Updated Node public key: #{node_file_path}" | 
|  | 93 | + | 
|  | 94 | +      puts "\n✅ Successfully updated public keys from #{api_url}" | 
|  | 95 | +      puts "\nPublic key info:" | 
|  | 96 | +      puts "  Algorithm: #{data['algorithm'] || 'RSA-2048'}" | 
|  | 97 | +      puts "  Format: #{data['format'] || 'PEM'}" | 
|  | 98 | +      puts "  Usage: #{data['usage'] || 'React on Rails Pro license verification'}" | 
|  | 99 | +    rescue SocketError, Net::OpenTimeout, Net::ReadTimeout => e | 
|  | 100 | +      puts "❌ Network error: #{e.message}" | 
|  | 101 | +      puts "Please check your internet connection and the API URL." | 
|  | 102 | +      exit 1 | 
|  | 103 | +    rescue JSON::ParserError => e | 
|  | 104 | +      puts "❌ Failed to parse JSON response: #{e.message}" | 
|  | 105 | +      exit 1 | 
|  | 106 | +    rescue StandardError => e | 
|  | 107 | +      puts "❌ Error: #{e.message}" | 
|  | 108 | +      puts e.backtrace.first(5) | 
|  | 109 | +      exit 1 | 
|  | 110 | +    end | 
|  | 111 | +  end | 
|  | 112 | + | 
|  | 113 | +  desc "Verify the current public key configuration" | 
|  | 114 | +  task :verify_public_key do | 
|  | 115 | +    puts "Verifying public key configuration..." | 
|  | 116 | + | 
|  | 117 | +    begin | 
|  | 118 | +      # Load and check Ruby public key | 
|  | 119 | +      require "openssl" | 
|  | 120 | + | 
|  | 121 | +      # Need to define OpenSSL before loading the public key | 
|  | 122 | +      require_relative "../lib/react_on_rails_pro/license_public_key" | 
|  | 123 | +      ruby_key = ReactOnRailsPro::LicensePublicKey::KEY | 
|  | 124 | +      puts "✅ Ruby public key loaded successfully" | 
|  | 125 | +      puts "   Key size: #{ruby_key.n.num_bits} bits" | 
|  | 126 | + | 
|  | 127 | +      # Check Node public key file exists | 
|  | 128 | +      node_file_path = File.join(File.dirname(__FILE__), "..", "packages", "node-renderer", "src", "shared", "licensePublicKey.ts") | 
|  | 129 | +      if File.exist?(node_file_path) | 
|  | 130 | +        node_content = File.read(node_file_path) | 
|  | 131 | +        if node_content.include?("BEGIN PUBLIC KEY") | 
|  | 132 | +          puts "✅ Node public key file exists and contains a public key" | 
|  | 133 | +        else | 
|  | 134 | +          puts "⚠️  Node public key file exists but may not contain a valid key" | 
|  | 135 | +        end | 
|  | 136 | +      else | 
|  | 137 | +        puts "❌ Node public key file not found: #{node_file_path}" | 
|  | 138 | +      end | 
|  | 139 | + | 
|  | 140 | +      # Try to validate with current license if one exists (simplified check without Rails) | 
|  | 141 | +      license_file = File.join(File.dirname(__FILE__), "..", "spec", "dummy", "config", "react_on_rails_pro_license.key") | 
|  | 142 | +      if ENV["REACT_ON_RAILS_PRO_LICENSE"] || File.exist?(license_file) | 
|  | 143 | +        puts "\n✅ License configuration detected" | 
|  | 144 | +        puts "   ENV variable set" if ENV["REACT_ON_RAILS_PRO_LICENSE"] | 
|  | 145 | +        puts "   Config file exists: #{license_file}" if File.exist?(license_file) | 
|  | 146 | + | 
|  | 147 | +        # Basic JWT validation test | 
|  | 148 | +        require "jwt" | 
|  | 149 | +        license = ENV["REACT_ON_RAILS_PRO_LICENSE"] || File.read(license_file).strip | 
|  | 150 | + | 
|  | 151 | +        begin | 
|  | 152 | +          payload, _header = JWT.decode(license, ruby_key, true, { algorithm: "RS256" }) | 
|  | 153 | +          puts "   ✅ License signature valid" | 
|  | 154 | +          puts "   License email: #{payload['sub']}" if payload['sub'] | 
|  | 155 | +          puts "   Organization: #{payload['organization']}" if payload['organization'] | 
|  | 156 | +        rescue JWT::ExpiredSignature | 
|  | 157 | +          puts "   ⚠️  License expired" | 
|  | 158 | +        rescue JWT::DecodeError => e | 
|  | 159 | +          puts "   ⚠️  License validation failed: #{e.message}" | 
|  | 160 | +        end | 
|  | 161 | +      else | 
|  | 162 | +        puts "\n⚠️  No license configured" | 
|  | 163 | +        puts "   Set REACT_ON_RAILS_PRO_LICENSE env variable or create config/react_on_rails_pro_license.key" | 
|  | 164 | +      end | 
|  | 165 | +    rescue LoadError => e | 
|  | 166 | +      puts "❌ Failed to load required module: #{e.message}" | 
|  | 167 | +      puts "   You may need to run 'bundle install' first" | 
|  | 168 | +      exit 1 | 
|  | 169 | +    rescue StandardError => e | 
|  | 170 | +      puts "❌ Error during verification: #{e.message}" | 
|  | 171 | +      exit 1 | 
|  | 172 | +    end | 
|  | 173 | +  end | 
|  | 174 | + | 
|  | 175 | +  desc "Show usage examples for updating the public key" | 
|  | 176 | +  task :public_key_help do | 
|  | 177 | +    puts <<~HELP | 
|  | 178 | +      React on Rails Pro - Public Key Management | 
|  | 179 | +      ========================================== | 
|  | 180 | +
 | 
|  | 181 | +      Update public key from different sources: | 
|  | 182 | +
 | 
|  | 183 | +      1. From production (ShakaCode's official server): | 
|  | 184 | +         rake react_on_rails_pro:update_public_key | 
|  | 185 | +         rake react_on_rails_pro:update_public_key[production] | 
|  | 186 | +
 | 
|  | 187 | +      2. From local development server: | 
|  | 188 | +         rake react_on_rails_pro:update_public_key[local] | 
|  | 189 | +
 | 
|  | 190 | +      3. From a custom hostname: | 
|  | 191 | +         rake react_on_rails_pro:update_public_key[staging.example.com] | 
|  | 192 | +
 | 
|  | 193 | +      4. From a custom full URL: | 
|  | 194 | +         rake react_on_rails_pro:update_public_key[https://api.example.com/api/public-key] | 
|  | 195 | +
 | 
|  | 196 | +      Verify current public key: | 
|  | 197 | +         rake react_on_rails_pro:verify_public_key | 
|  | 198 | +
 | 
|  | 199 | +      Note: The public key is used to verify JWT licenses for React on Rails Pro. | 
|  | 200 | +            The corresponding private key is held securely by ShakaCode. | 
|  | 201 | +    HELP | 
|  | 202 | +  end | 
|  | 203 | +end | 
0 commit comments