Skip to content

Commit b3ce154

Browse files
Add offline JWT-based license validation system for React on Rails Pro (#1857)
* Add offline JWT-based license validation system for React on Rails Pro Implements a pure offline license validation system using JWT tokens signed with RSA-256. No internet connectivity required for validation. Key features: - JWT-based licenses verified with embedded public key (RSA-256) - Offline validation in Ruby gem and Node renderer - Environment variable or config file support - Development-friendly (warnings) vs production (errors) - Zero impact on browser bundle size - Comprehensive test coverage Changes: - Add JWT dependencies (Ruby jwt gem, Node jsonwebtoken) - Create license validation modules for Ruby and Node - Integrate validation into Rails context (rorPro field) - Add license check on Node renderer startup - Update .gitignore for license file - Add comprehensive tests for both Ruby and Node - Create LICENSE_SETUP.md documentation The system validates licenses at: 1. Ruby gem initialization (Rails startup) 2. Node renderer startup 3. Browser relies on server validation (railsContext.rorPro) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]> * Add React on Rails Pro license file to .gitignore in multiple locations * add needed licence rake tasks * Require exp field in license validation * Add tests for required exp field validation * Require valid license in all environments (dev, test, prod) Breaking Change: All environments now require valid licenses * Add license validation on startup for both Rails and Node renderer * Change license field from 'license_type' to 'plan' and add 'issued_by' support * Remove obsolete license key file from the dummy config * Add React on Rails Pro license file to .gitignore and Gemfile.lock * Fix require statement in license validator spec to use spec_helper * update yarn.lock * Fix license validator spec by stubbing Rails.logger and Rails.root * Fix Node.js license validator tests and disable auto-expiration check - Add ignoreExpiration: true to jwt.verify() to match Ruby behavior - Mock process.exit globally in tests to prevent actual exit - Mock console.error and console.log to suppress test output - Update all invalid license tests to check process.exit was called - Simplify file-based license test to use ENV variable - All 9 Node.js tests now passing Changes align Node.js validator with Ruby validator: - Both manually check expiration after disabling auto-check - Both call exit/raise on invalid licenses - Both provide consistent error messages 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]> * Remove react_on_rails_pro_licence_valid? and consolidate to react_on_rails_pro? * Make react_on_rails_pro? actively validate license and remove backward compatibility * Remove redundant 'before: :load_config_initializers' hook from license validation * Rename validation methods to use ! convention for exception-throwing * Update license public key documentation and source URL * Refactor license validation to remove unnecessary conditional logging * Refactor LicensePublicKey module documentation for clarity and consistency * Refactor LicenseValidator to simplify validation logic and remove unnecessary checks * Refactor Node.js license validator from singleton class to functional pattern * Enhance license data structure and improve security in license validation * Refactor validateLicense to void function for clearer API semantics * Enhance public key update task with local URL handling and add usage instructions * Enhance ReactOnRailsHelper spec by mocking additional utility methods for improved test coverage * Enhance pro features context by allowing multiple message stubs for improved test flexibility * Enhance pro features context by adding support for RSC in immediate hydration * fix TS problems * Enhance license validation test by generating a separate key pair for invalid signature scenario * Refactor license claims in JWT to use standard 'iss' identifier and update related tests for improved clarity and consistency * Implement license expiration handling with a 1-month grace period for production environments and update related tests for clarity and coverage * Add license attribution comment and refactor license validation methods for clarity * Refactor license validation methods to use 'validated_license_data!' for improved clarity and consistency * Add tests for react_on_rails_attribution_comment to verify Pro and open source license comments * Add tests for Pro and open source attribution comments in rendered output * Add tests for single attribution comment inclusion in React on Rails Pro and non-Pro scenarios * Refactor license evaluation logic and improve error handling for missing license * Remove unnecessary setup in pro_attribution_comment tests for cleaner context * Stub ReactOnRailsPro::Utils.pro_attribution_comment for consistent test behavior across all contexts * Update tests to expect HTML comments instead of script tags for React components * Stub ReactOnRailsPro::Utils.pro_attribution_comment for consistent test behavior * Update documentation to clarify license usage and add HTML comment attribution for React on Rails * Refactor license validation logic to use getValidatedLicenseData and update tests for consistency * Update grace period message format to use "day(s)" for consistency in attribution comments --------- Co-authored-by: Claude <[email protected]>
1 parent 5edfa62 commit b3ce154

33 files changed

+2587
-54
lines changed

CHANGELOG.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ After a release, please make sure to run `bundle exec rake update_changelog`. Th
2323

2424
Changes since the last non-beta release.
2525

26+
#### Added
27+
28+
- **Attribution Comment**: Added HTML comment attribution to Rails views containing React on Rails functionality. The comment automatically displays which version is in use (open source React on Rails or React on Rails Pro) and, for Pro users, shows the license status. This helps identify React on Rails usage across your application. [PR #1857](https://github.com/shakacode/react_on_rails/pull/1857) by [AbanoubGhadban](https://github.com/AbanoubGhadban).
29+
2630
#### Breaking Changes
2731

2832
- **React on Rails Core Package**: Several Pro-only methods have been removed from the core package and are now exclusively available in the `react-on-rails-pro` package. If you're using any of the following methods, you'll need to migrate to React on Rails Pro:
@@ -65,7 +69,7 @@ To migrate to React on Rails Pro:
6569
import ReactOnRails from 'react-on-rails-pro';
6670
```
6771

68-
4. If you're using a free license for personal (non-production) use, you can obtain one at [React on Rails Pro License](https://www.shakacode.com/react-on-rails-pro). The Pro package is free for personal, educational, and non-production usage.
72+
4. If you're using a free license, you can obtain one at [React on Rails Pro License](https://www.shakacode.com/react-on-rails-pro). **Important: The free 3-month evaluation license is intended for personal, educational, and evaluation purposes only. It should NOT be used for production deployments.** Production use requires a paid license.
6973

7074
**Note:** If you're not using any of the Pro-only methods listed above, no changes are required.
7175

lib/react_on_rails/helper.rb

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -620,10 +620,23 @@ def rails_context_if_not_already_rendered
620620

621621
@rendered_rails_context = true
622622

623-
content_tag(:script,
624-
json_safe_and_pretty(data).html_safe,
625-
type: "application/json",
626-
id: "js-react-on-rails-context")
623+
attribution_comment = react_on_rails_attribution_comment
624+
script_tag = content_tag(:script,
625+
json_safe_and_pretty(data).html_safe,
626+
type: "application/json",
627+
id: "js-react-on-rails-context")
628+
629+
"#{attribution_comment}\n#{script_tag}".html_safe
630+
end
631+
632+
# Generates the HTML attribution comment
633+
# Pro version calls ReactOnRailsPro::Utils for license-specific details
634+
def react_on_rails_attribution_comment
635+
if ReactOnRails::Utils.react_on_rails_pro?
636+
ReactOnRailsPro::Utils.pro_attribution_comment
637+
else
638+
"<!-- Powered by React on Rails (c) ShakaCode | Open Source -->"
639+
end
627640
end
628641

629642
# prepend the rails_context if not yet applied

lib/react_on_rails/pro_utils.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@ module ProUtils
55
PRO_ONLY_OPTIONS = %i[immediate_hydration].freeze
66

77
# Checks if React on Rails Pro features are available
8-
# @return [Boolean] true if Pro license is valid, false otherwise
8+
# @return [Boolean] true if Pro is installed and licensed, false otherwise
99
def self.support_pro_features?
10-
ReactOnRails::Utils.react_on_rails_pro_licence_valid?
10+
ReactOnRails::Utils.react_on_rails_pro?
1111
end
1212

1313
def self.disable_pro_render_options_if_not_licensed(raw_options)

lib/react_on_rails/utils.rb

Lines changed: 10 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -228,11 +228,19 @@ def self.gem_available?(name)
228228
end
229229
end
230230

231-
# Todo -- remove this for v13, as we don't need both boolean and number
231+
# Checks if React on Rails Pro is installed and licensed.
232+
# This method validates the license and will raise an exception if invalid.
233+
#
234+
# @return [Boolean] true if Pro is available with valid license
235+
# @raise [ReactOnRailsPro::Error] if license is invalid
232236
def self.react_on_rails_pro?
233237
return @react_on_rails_pro if defined?(@react_on_rails_pro)
234238

235-
@react_on_rails_pro = gem_available?("react_on_rails_pro")
239+
@react_on_rails_pro = begin
240+
return false unless gem_available?("react_on_rails_pro")
241+
242+
ReactOnRailsPro::Utils.validated_license_data!.present?
243+
end
236244
end
237245

238246
# Return an empty string if React on Rails Pro is not installed
@@ -246,21 +254,6 @@ def self.react_on_rails_pro_version
246254
end
247255
end
248256

249-
def self.react_on_rails_pro_licence_valid?
250-
return @react_on_rails_pro_licence_valid if defined?(@react_on_rails_pro_licence_valid)
251-
252-
@react_on_rails_pro_licence_valid = begin
253-
return false unless react_on_rails_pro?
254-
255-
# Maintain compatibility with legacy versions of React on Rails Pro:
256-
# Earlier releases did not require license validation, as they were distributed as private gems.
257-
# This check ensures that the method works correctly regardless of the installed version.
258-
return true unless ReactOnRailsPro::Utils.respond_to?(:licence_valid?)
259-
260-
ReactOnRailsPro::Utils.licence_valid?
261-
end
262-
end
263-
264257
def self.rsc_support_enabled?
265258
return false unless react_on_rails_pro?
266259

react_on_rails_pro/.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,3 +72,6 @@ yalc.lock
7272

7373
# File Generated by ROR FS-based Registry
7474
**/generated
75+
76+
# React on Rails Pro License Key
77+
config/react_on_rails_pro_license.key

react_on_rails_pro/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ You can find the **package** version numbers from this repo's tags and below in
1818

1919
### Added
2020
- Added `cached_stream_react_component` helper method, similar to `cached_react_component` but for streamed components.
21+
- **License Validation System**: Implemented comprehensive JWT-based license validation with offline verification using RSA-256 signatures. License validation occurs at startup in both Ruby and Node.js environments. Supports required fields (`sub`, `iat`, `exp`) and optional fields (`plan`, `organization`, `iss`). FREE evaluation licenses are available for 3 months at [shakacode.com/react-on-rails-pro](https://shakacode.com/react-on-rails-pro). [PR #1857](https://github.com/shakacode/react_on_rails/pull/1857) by [AbanoubGhadban](https://github.com/AbanoubGhadban).
2122

2223
### Changed (Breaking)
2324
- `config.prerender_caching`, which controls caching for non-streaming components, now also controls caching for streamed components. To disable caching for an individual render, pass `internal_option(:skip_prerender_cache)`.

0 commit comments

Comments
 (0)