-
-
Notifications
You must be signed in to change notification settings - Fork 638
Add offline JWT-based license validation system for React on Rails Pro #1857
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
WalkthroughAdds a JWT-based Pro license system: public RSA keys (Ruby/TS), validators in Ruby and Node with manual expiry/grace handling and caching, engine and node preflight checks, rake tasks to update public keys, attribution insertion into rendered output, docs and CI guidance, dependency updates, and tests. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor App
participant Rails as Rails App
participant Engine as ReactOnRailsPro::Engine
participant Validator as ReactOnRailsPro::LicenseValidator
App->>Rails: boot
Rails->>Engine: after_initialize
Engine->>Validator: validated_license_data!()
alt valid
Validator-->>Engine: license data (cached)
Engine-->>Rails: log validation success
else invalid
Validator-->>Engine: raises ReactOnRailsPro::Error
Engine-->>Rails: log error / abort
end
sequenceDiagram
autonumber
actor CLI
participant Master as node-renderer/master.ts
participant LV as Node LicenseValidator
participant ENV as Env
participant FS as FileSystem
CLI->>Master: masterRun()
Master->>LV: getValidatedLicenseData()
LV->>ENV: read REACT_ON_RAILS_PRO_LICENSE
alt license in ENV
LV-->>LV: decode & verify JWT (RS256, PUBLIC_KEY)
else not in ENV
LV->>FS: read `config/react_on_rails_pro_license.key`
LV-->>LV: decode & verify JWT (RS256, PUBLIC_KEY)
end
LV->>LV: check exp and production grace window
alt valid / within grace
LV-->>Master: success (cached)
Master-->>CLI: continue startup
else invalid
LV-->>Master: log error and process.exit(1)
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested reviewers
Poem
Pre-merge checks and finishing touches✅ Passed checks (3 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Code Review: Offline JWT-based License Validation SystemOverall AssessmentThis is a well-structured implementation of an offline license validation system. The code is clean, well-tested, and follows good security practices. However, there are several important considerations before merging. Critical Issues1. Placeholder Public Keys in Production Code (BLOCKER)Files: license_public_key.rb:8-17, licensePublicKey.ts:4-10 The PR includes placeholder RSA public keys with comments indicating they should be replaced. This is a critical security risk if merged as-is. Recommendations:
2. Potential Race Condition in Ruby Validator (MEDIUM)File: license_validator.rb:9-13 The defined? check is not thread-safe. If multiple threads call valid? simultaneously, they could all trigger validation concurrently. Recommendation: Add mutex synchronization for thread safety 3. File I/O on Every Failed Validation (MEDIUM)File: licenseValidator.ts:109-111 Failed reads will attempt file I/O on every Node renderer startup. Recommendation: Cache the license string itself, not just the validation result 4. JWT Library Version Pinning (MEDIUM)Files: package.json:47, react_on_rails_pro.gemspec:35 The JWT libraries use caret versioning which allows minor version updates that could introduce breaking changes. Recommendation: Pin to exact versions for security-critical dependencies StrengthsCode Quality
Security
Testing
Test Coverage AssessmentBoth Ruby and TypeScript tests are excellent with comprehensive coverage of:
Recommendation: Add integration tests for:
Security ConsiderationsGood:
Areas for improvement:
PerformanceGood practices:
Potential improvements:
DocumentationStrengths:
Suggestions:
Code StyleRuby:
TypeScript:
Checklist Before Merge
RecommendationConditional Approval - This is high-quality code that is well-architected and thoroughly tested. However, the placeholder public keys are a blocker that must be resolved before merge. The implementation demonstrates strong understanding of JWT security, proper error handling, and good testing practices. The dual Ruby/TypeScript architecture is well-designed and maintainable. Great work! 🚀 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 6
🧹 Nitpick comments (15)
react_on_rails_pro/LICENSE_SETUP.md (2)
71-71
: Add language identifier to the fenced code block.The code block is missing a language specifier, which affects rendering and syntax highlighting.
Apply this diff:
-``` +```text [React on Rails Pro] License validation successfulAs per coding guidelines (markdownlint) --- `128-129`: **Format bare URLs properly.** The bare URLs should be properly formatted as links or enclosed in angle brackets for better markdown rendering. Apply this diff: ```diff -3. Contact ShakaCode support at [email protected] -4. Visit https://shakacode.com/react-on-rails-pro for license management +3. Contact ShakaCode support at <[email protected]> +4. Visit <https://shakacode.com/react-on-rails-pro> for license management
As per coding guidelines (markdownlint)
react_on_rails_pro/react_on_rails_pro.gemspec (1)
35-35
: Consider bumping the JWT dependency to~> 3.0
Latest stable jwt is 3.1.2; updating to~> 3.0
will allow 3.x security and bug fixes.lib/react_on_rails/helper.rb (1)
380-390
: DRY: cache licence validity in a local var within rails_contextYou call ReactOnRails::Utils.react_on_rails_pro_licence_valid? twice. It’s memoized, but caching locally improves readability and avoids repeated calls.
Apply this diff:
- # TODO: v13 just use the version if existing - rorPro: ReactOnRails::Utils.react_on_rails_pro_licence_valid? + # TODO: v13 just use the version if existing + rorPro: (lic_valid = ReactOnRails::Utils.react_on_rails_pro_licence_valid?) } - if ReactOnRails::Utils.react_on_rails_pro_licence_valid? + if lic_valid result[:rorProVersion] = ReactOnRails::Utils.react_on_rails_pro_versionNote: No behavior change. Based on learnings.
react_on_rails_pro/packages/node-renderer/tests/licenseValidator.test.ts (2)
5-9
: Redundant licensePublicKey mocksYou mock licensePublicKey at the top and again in beforeEach. Keep only the beforeEach doMock to inject the per-test key and remove the top-level mock to reduce confusion.
Apply this diff:
-// Mock modules -jest.mock('fs'); -jest.mock('../src/shared/licensePublicKey', () => ({ - PUBLIC_KEY: '' -})); +// Mock modules +jest.mock('fs');Also applies to: 36-39
52-55
: Restore NODE_ENV after tests to avoid cross-test bleedTests mutate process.env.NODE_ENV but never restore it, risking order-dependent failures.
Apply this diff:
beforeEach(() => { + originalNodeEnv = process.env.NODE_ENV; // Clear the module cache to get a fresh instance jest.resetModules(); @@ afterEach(() => { delete process.env.REACT_ON_RAILS_PRO_LICENSE; jest.restoreAllMocks(); + if (originalNodeEnv === undefined) { + delete process.env.NODE_ENV; + } else { + process.env.NODE_ENV = originalNodeEnv; + } });And add the variable at the top of the describe:
describe('LicenseValidator', () => { let licenseValidator: any; let testPrivateKey: string; let testPublicKey: string; + let originalNodeEnv: string | undefined;
Also applies to: 81-82, 101-102, 133-134, 146-147
react_on_rails_pro/lib/react_on_rails_pro/license_validator.rb (1)
40-47
: Harden JWT claim validation and add clock skew toleranceCurrent logic only checks exp manually. Consider:
- Add a small leeway for clock skew to reduce false negatives.
- Optionally validate issuer/audience if you rely on them.
Two incremental options:
- Minimal: add leeway to the manual expiry check:
- if license["exp"] && Time.now.to_i > license["exp"] + leeway = 60 # seconds + if license["exp"] && Time.now.to_i > (license["exp"] + leeway) @validation_error = "License has expired"
- Library-based: delegate exp check to JWT with leeway (keeps your messaging):
- JWT.decode( - license_string, - public_key, - true, - algorithm: "RS256" - ).first + JWT.decode( + license_string, + public_key, + true, + algorithm: "RS256", + leeway: 60 + ).firstIf you later introduce iss/aud, also pass verify_iss/verify_aud and expected values. As per coding guidelines.
Also applies to: 62-68, 90-100
react_on_rails_pro/spec/react_on_rails_pro/license_validator_spec.rb (1)
107-112
: Spec description vs behavior mismatchDescription says “returns false in production with error” but the expectation asserts a raised error. Consider renaming for clarity.
- it "returns false in production with error" do + it "raises error in production when no license found" doreact_on_rails_pro/packages/node-renderer/src/shared/licenseValidator.ts (7)
1-4
: Use JwtPayload for safer typing instead of any-based custom interfaceLeverage jsonwebtoken’s JwtPayload to avoid any and align with standard claims.
-import * as jwt from 'jsonwebtoken'; +import * as jwt from 'jsonwebtoken'; +import type { JwtPayload } from 'jsonwebtoken'; @@ -interface LicenseData { - sub?: string; - iat?: number; - exp?: number; - [key: string]: any; -} +type LicenseData = JwtPayload & Record<string, unknown>;Also applies to: 6-12
63-69
: Drop redundant manual exp check; handle TokenExpiredError explicitlyverify() already enforces exp; simplify and improve error classification.
- // Check expiry if present - if (license.exp && Date.now() / 1000 > license.exp) { - this.validationError = 'License has expired'; - this.handleInvalidLicense(isDevelopment, this.validationError); - return isDevelopment; - } + // Additional claim validations can be added here if needed (e.g., custom fields) @@ - } catch (error: any) { - if (error.name === 'JsonWebTokenError') { - this.validationError = `Invalid license signature: ${error.message}`; - } else { - this.validationError = `License validation error: ${error.message}`; - } + } catch (error: unknown) { + if (error instanceof jwt.TokenExpiredError) { + this.validationError = 'License has expired'; + } else if (error instanceof jwt.JsonWebTokenError) { + this.validationError = `Invalid license signature: ${(error as Error).message}`; + } else { + this.validationError = `License validation error: ${(error as Error).message}`; + } this.handleInvalidLicense(isDevelopment, this.validationError); return isDevelopment; }Also applies to: 71-79
88-98
: Simplify decode path and add small clock toleranceRemove no-op try/catch and allow a few seconds of skew.
- try { - const decoded = jwt.verify(licenseString, PUBLIC_KEY, { - algorithms: ['RS256'] - }) as LicenseData; - - this.licenseData = decoded; - return decoded; - } catch (error) { - throw error; - } + const decoded = jwt.verify(licenseString, PUBLIC_KEY, { + algorithms: ['RS256'], + clockTolerance: 5, + }) as LicenseData; + this.licenseData = decoded; + return decoded;
88-91
: Consider enforcing issuer/audience claims in verify()Strengthen verification by validating iss/aud once you finalize them (keeps tokens scoped and prevents key misuse).
What issuer/audience do you plan to set on license JWTs? Once decided, pass them to verify(), e.g.:
const verifyOptions: jwt.VerifyOptions = { algorithms: ['RS256'], clockTolerance: 5, issuer: process.env.REACT_ON_RAILS_PRO_ISSUER, // or a constant audience: 'react-on-rails-pro', }; jwt.verify(licenseString, PUBLIC_KEY, verifyOptions);
101-106
: Trim ENV value to avoid whitespace-induced failuresProtect against accidental newlines/spaces in env vars.
- const envLicense = process.env.REACT_ON_RAILS_PRO_LICENSE; + const envLicense = process.env.REACT_ON_RAILS_PRO_LICENSE?.trim(); if (envLicense) { return envLicense; }
107-116
: Avoid existsSync TOCTOU; read and catch directlySimpler and race-free.
- try { - const configPath = path.join(process.cwd(), 'config', 'react_on_rails_pro_license.key'); - if (fs.existsSync(configPath)) { - return fs.readFileSync(configPath, 'utf8').trim(); - } - } catch (error) { - // File doesn't exist or can't be read - } + try { + const configPath = path.join(process.cwd(), 'config', 'react_on_rails_pro_license.key'); + return fs.readFileSync(configPath, 'utf8').trim(); + } catch { + // File doesn't exist or can't be read + }
4-4
: Confirm RSA key strength and rotation planEnsure PUBLIC_KEY is RSA ≥ 2048 bits and document rotation.
Run locally:
- Check key size: openssl pkey -pubin -in <(printf "%s" "$PUBLIC_KEY") -text -noout | rg 'Public-Key'
- Plan: version keys with kid and support rotation by selecting key via header.kid (future-proof for multiple public keys).
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (14)
.gitignore
(1 hunks)lib/react_on_rails/helper.rb
(1 hunks)react_on_rails_pro/LICENSE_SETUP.md
(1 hunks)react_on_rails_pro/lib/react_on_rails_pro.rb
(1 hunks)react_on_rails_pro/lib/react_on_rails_pro/license_public_key.rb
(1 hunks)react_on_rails_pro/lib/react_on_rails_pro/license_validator.rb
(1 hunks)react_on_rails_pro/lib/react_on_rails_pro/utils.rb
(1 hunks)react_on_rails_pro/package.json
(1 hunks)react_on_rails_pro/packages/node-renderer/src/master.ts
(1 hunks)react_on_rails_pro/packages/node-renderer/src/shared/licensePublicKey.ts
(1 hunks)react_on_rails_pro/packages/node-renderer/src/shared/licenseValidator.ts
(1 hunks)react_on_rails_pro/packages/node-renderer/tests/licenseValidator.test.ts
(1 hunks)react_on_rails_pro/react_on_rails_pro.gemspec
(1 hunks)react_on_rails_pro/spec/react_on_rails_pro/license_validator_spec.rb
(1 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
{Gemfile,Rakefile,config.ru,**/*.{rb,rake,gemspec,ru}}
📄 CodeRabbit inference engine (CLAUDE.md)
{Gemfile,Rakefile,config.ru,**/*.{rb,rake,gemspec,ru}}
: All Ruby code must pass RuboCop with zero offenses before commit/push
RuboCop is the sole authority for Ruby file formatting; never manually format Ruby files
Files:
react_on_rails_pro/lib/react_on_rails_pro/utils.rb
react_on_rails_pro/lib/react_on_rails_pro/license_public_key.rb
react_on_rails_pro/react_on_rails_pro.gemspec
react_on_rails_pro/lib/react_on_rails_pro.rb
react_on_rails_pro/spec/react_on_rails_pro/license_validator_spec.rb
lib/react_on_rails/helper.rb
react_on_rails_pro/lib/react_on_rails_pro/license_validator.rb
**/*.{js,jsx,ts,tsx,css,scss,json,yml,yaml,md}
📄 CodeRabbit inference engine (CLAUDE.md)
Prettier is the sole authority for formatting all non-Ruby files; never manually format them
Files:
react_on_rails_pro/packages/node-renderer/src/shared/licensePublicKey.ts
react_on_rails_pro/package.json
react_on_rails_pro/LICENSE_SETUP.md
react_on_rails_pro/packages/node-renderer/src/master.ts
react_on_rails_pro/packages/node-renderer/src/shared/licenseValidator.ts
react_on_rails_pro/packages/node-renderer/tests/licenseValidator.test.ts
**/*.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Use ESLint for JS/TS code (lint via rake lint or yarn lint)
Files:
react_on_rails_pro/packages/node-renderer/src/shared/licensePublicKey.ts
react_on_rails_pro/packages/node-renderer/src/master.ts
react_on_rails_pro/packages/node-renderer/src/shared/licenseValidator.ts
react_on_rails_pro/packages/node-renderer/tests/licenseValidator.test.ts
🧠 Learnings (3)
📓 Common learnings
Learnt from: AbanoubGhadban
PR: shakacode/react_on_rails#1644
File: lib/react_on_rails/helper.rb:190-197
Timestamp: 2025-02-18T13:08:01.477Z
Learning: RSC support validation in React on Rails Pro is handled through a chain of validations:
1. Pro version check in `run_stream_inside_fiber`
2. RSC support check during pack generation via `ReactOnRailsPro.configuration.enable_rsc_support`
3. RSC support validation during component registration
This makes additional validation in the helper methods unnecessary.
📚 Learning: 2025-02-18T13:08:01.477Z
Learnt from: AbanoubGhadban
PR: shakacode/react_on_rails#1644
File: lib/react_on_rails/helper.rb:190-197
Timestamp: 2025-02-18T13:08:01.477Z
Learning: RSC support validation in React on Rails Pro is handled through a chain of validations:
1. Pro version check in `run_stream_inside_fiber`
2. RSC support check during pack generation via `ReactOnRailsPro.configuration.enable_rsc_support`
3. RSC support validation during component registration
This makes additional validation in the helper methods unnecessary.
Applied to files:
react_on_rails_pro/lib/react_on_rails_pro.rb
lib/react_on_rails/helper.rb
react_on_rails_pro/lib/react_on_rails_pro/license_validator.rb
📚 Learning: 2025-09-15T21:24:48.207Z
Learnt from: AbanoubGhadban
PR: shakacode/react_on_rails#1781
File: node_package/src/ClientSideRenderer.ts:82-95
Timestamp: 2025-09-15T21:24:48.207Z
Learning: In React on Rails, the force_load feature includes both explicit `data-force-load="true"` usage and the ability to hydrate components during the page loading state (`document.readyState === 'loading'`). Both capabilities require a Pro license, so the condition `!railsContext.rorPro && (isComponentForceLoaded || document.readyState === 'loading')` correctly gates both scenarios.
Applied to files:
lib/react_on_rails/helper.rb
react_on_rails_pro/lib/react_on_rails_pro/license_validator.rb
🧬 Code graph analysis (7)
react_on_rails_pro/lib/react_on_rails_pro/utils.rb (1)
react_on_rails_pro/lib/react_on_rails_pro/license_validator.rb (2)
valid?
(7-102)valid?
(9-13)
react_on_rails_pro/lib/react_on_rails_pro.rb (1)
react_on_rails_pro/spec/dummy/client/node-renderer.js (1)
require
(8-8)
react_on_rails_pro/spec/react_on_rails_pro/license_validator_spec.rb (1)
react_on_rails_pro/lib/react_on_rails_pro/license_validator.rb (6)
public_key
(86-88)reset!
(15-19)valid?
(7-102)valid?
(9-13)license_data
(21-23)validation_error
(25-27)
lib/react_on_rails/helper.rb (1)
lib/react_on_rails/utils.rb (1)
react_on_rails_pro_licence_valid?
(249-262)
react_on_rails_pro/packages/node-renderer/src/master.ts (2)
react_on_rails_pro/packages/node-renderer/src/shared/configBuilder.ts (1)
Config
(25-83)react_on_rails_pro/packages/node-renderer/src/shared/licenseValidator.ts (2)
isLicenseValid
(142-144)getLicenseValidationError
(150-152)
react_on_rails_pro/packages/node-renderer/src/shared/licenseValidator.ts (1)
react_on_rails_pro/packages/node-renderer/src/shared/licensePublicKey.ts (1)
PUBLIC_KEY
(4-11)
react_on_rails_pro/packages/node-renderer/tests/licenseValidator.test.ts (1)
react_on_rails_pro/packages/node-renderer/src/shared/licenseValidator.ts (1)
licenseValidator
(140-140)
🪛 markdownlint-cli2 (0.18.1)
react_on_rails_pro/LICENSE_SETUP.md
71-71: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
128-128: Bare URL used
(MD034, no-bare-urls)
129-129: Bare URL used
(MD034, no-bare-urls)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (10)
- GitHub Check: dummy-app-integration-tests (3.2, 20)
- GitHub Check: dummy-app-integration-tests (3.4, 22)
- GitHub Check: claude-review
- GitHub Check: markdown-link-check
- GitHub Check: rspec-package-tests (3.4, minimum)
- GitHub Check: rspec-package-tests (3.2, minimum)
- GitHub Check: build
- GitHub Check: rspec-package-tests (3.4, latest)
- GitHub Check: rspec-package-tests (3.2, latest)
- GitHub Check: build-and-test
🔇 Additional comments (5)
.gitignore (1)
78-80
: LGTM!The addition of the license key file to
.gitignore
is correct and follows security best practices by preventing sensitive license information from being committed to version control.react_on_rails_pro/package.json (1)
47-47
: LGTM!The
jsonwebtoken
dependency version^9.0.2
is appropriate. This is the latest stable version (as of August 2023) and includes important security fixes from the v9 series, including hardened key validation, stricter algorithm enforcement, and fixes for multiple CVEs.Based on learnings
react_on_rails_pro/lib/react_on_rails_pro.rb (1)
12-13
: LGTM!The placement of the license module requires is appropriate. Loading them early in the initialization sequence ensures license validation is available before any Pro features that depend on it.
react_on_rails_pro/packages/node-renderer/src/master.ts (1)
15-29
: License validation logic is well-implemented.The pre-flight license check correctly gates the startup sequence:
- Validates before configuration and worker initialization
- Handles development vs. production environments appropriately
- Provides clear logging for all scenarios
The hard exit in production (
process.exit(1)
) is appropriate for license validation failures.react_on_rails_pro/packages/node-renderer/src/shared/licenseValidator.ts (1)
128-137
: No action needed: production exit for invalid license is correctly wired
# frozen_string_literal: true | ||
|
||
module ReactOnRailsPro | ||
module LicensePublicKey | ||
# This is a placeholder public key for development/testing | ||
# In production, this should be replaced with ShakaCode's actual public key | ||
# The private key corresponding to this public key should NEVER be committed to the repository | ||
KEY = OpenSSL::PKey::RSA.new(<<~PEM) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
Missing require 'openssl' causes load-time NameError
OpenSSL is used but not required. This can raise NameError when the file loads (especially since this file is required before jwt).
Apply this diff:
# frozen_string_literal: true
+require "openssl"
+
module ReactOnRailsPro
module LicensePublicKey
Also, ensure the placeholder key is replaced before release and stays in sync with the Node PUBLIC_KEY.
🏁 Script executed:
#!/bin/bash
rg -n "require.*openssl" -C2 react_on_rails_pro/lib/react_on_rails_pro/license_public_key.rb
Length of output: 0
🏁 Script executed:
#!/bin/bash
sed -n '1,20p' react_on_rails_pro/lib/react_on_rails_pro/license_public_key.rb
Length of output: 874
Add missing require "openssl" to avoid NameError
This file invokes OpenSSL::PKey without requiring it, which can raise a NameError at load time.
Apply:
# frozen_string_literal: true
+require "openssl"
module ReactOnRailsPro
module LicensePublicKey
Ensure the placeholder public key is replaced in production and kept in sync with the Node PUBLIC_KEY.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
# frozen_string_literal: true | |
module ReactOnRailsPro | |
module LicensePublicKey | |
# This is a placeholder public key for development/testing | |
# In production, this should be replaced with ShakaCode's actual public key | |
# The private key corresponding to this public key should NEVER be committed to the repository | |
KEY = OpenSSL::PKey::RSA.new(<<~PEM) | |
# frozen_string_literal: true | |
require "openssl" | |
module ReactOnRailsPro | |
module LicensePublicKey | |
# This is a placeholder public key for development/testing | |
# In production, this should be replaced with ShakaCode's actual public key | |
# The private key corresponding to this public key should NEVER be committed to the repository | |
KEY = OpenSSL::PKey::RSA.new(<<~PEM) |
🤖 Prompt for AI Agents
In react_on_rails_pro/lib/react_on_rails_pro/license_public_key.rb lines 1-8,
the file uses OpenSSL::PKey::RSA but does not require "openssl", which can raise
a NameError at load time; add require "openssl" at the top of the file before
referencing OpenSSL, and verify the placeholder public key remains for
dev/testing only and is replaced in production and kept in sync with the Node
PUBLIC_KEY.
def self.licence_valid? | ||
LicenseValidator.valid? | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion | 🟠 Major
Consider spelling consistency for the method name.
The method is named licence_valid?
(British spelling) while the rest of the codebase uses American spelling "license" (e.g., LicenseValidator
, license_public_key
, etc.). This inconsistency could cause confusion for developers.
Consider renaming to license_valid?
for consistency:
-def self.licence_valid?
+def self.license_valid?
LicenseValidator.valid?
end
Note: If you make this change, also update:
- Documentation in
LICENSE_SETUP.md
(line 63) - Any other references in the codebase
- Tests that may reference this method
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
def self.licence_valid? | |
LicenseValidator.valid? | |
end | |
def self.license_valid? | |
LicenseValidator.valid? | |
end |
🤖 Prompt for AI Agents
In react_on_rails_pro/lib/react_on_rails_pro/utils.rb around lines 19 to 21, the
method is named `licence_valid?` using British spelling which is inconsistent
with the rest of the codebase that uses American spelling (`LicenseValidator`,
`license_public_key`, etc.); rename the method to `license_valid?` and update
all call sites, tests, and documentation (specifically LICENSE_SETUP.md line 63)
to the new name to maintain consistency and ensure nothing breaks.
react_on_rails_pro/LICENSE_SETUP.md
Outdated
> ReactOnRails::Utils.react_on_rails_pro_licence_valid? | ||
# Should return true if license is valid |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
Verify the method name matches the implementation.
The documentation references ReactOnRails::Utils.react_on_rails_pro_licence_valid?
, but based on the implementation in utils.rb
(line 19), the method is licence_valid?
(not react_on_rails_pro_licence_valid?
). Ensure this example accurately reflects the actual API.
🏁 Script executed:
#!/bin/bash
# Verify the correct method name in the implementation
rg -n "def (self\.)?.*licence.*valid" react_on_rails_pro/lib/react_on_rails_pro/utils.rb
Length of output: 30
Use the correct method in docs: replace ReactOnRails::Utils.react_on_rails_pro_licence_valid?
with ReactOnRailsPro::Utils.licence_valid?
.
🤖 Prompt for AI Agents
In react_on_rails_pro/LICENSE_SETUP.md around lines 63 to 64, the documented
method name is incorrect; replace the reference to
ReactOnRails::Utils.react_on_rails_pro_licence_valid? with the correct API
ReactOnRailsPro::Utils.licence_valid? so the docs call the right module and
method name; update the text accordingly to reflect the exact namespace and
method.
// This is a placeholder public key for development/testing | ||
// In production, this should be replaced with ShakaCode's actual public key | ||
// The private key corresponding to this public key should NEVER be committed to the repository | ||
export const PUBLIC_KEY = `-----BEGIN PUBLIC KEY----- | ||
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0Z3VS5JJcds3xfn/ygWyF/NP | ||
lRQkqfph3x6TEOirFCpDfRjowDXAk66dPmLzw5qVOmGVPKgpJBjZR7oMIMgxBPUoj00F | ||
DwlhUGmOVoqnVWGFHVUHDL5qYQaZzRdp4Bh9fxnN52Yk8+FuHsT+5lxLcaRV6mRtX7OT | ||
5pQbxV0o0/OxPFC1Hz9RdLPUevnWNbLe8f5ePHivmqsoAH9HE4g03WkFZEqBLmjqpJj8 | ||
VqGR0q8CPPRCFGAr9S4WCQqBhLDH0j/JR+FpPX9Df8vfFJhHdBGdTGjN4g9g6qwPYmVH | ||
ukAErHNIJMNmzYjFIT4+Xwp6xKHyUqL3w3JZDQnFywIDAQAB | ||
-----END PUBLIC KEY-----`; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
❓ Verification inconclusive
CRITICAL: Ensure placeholder key is replaced before production deployment.
The placeholder public key is appropriately marked for development/testing. However, deploying with this key in production would create a critical security vulnerability, as anyone could generate valid licenses using the corresponding private key.
Recommendations:
- Add a runtime check to detect placeholder keys in production:
// At the top of licensePublicKey.ts or in licenseValidator.ts
const PLACEHOLDER_KEY_MARKER = 'MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0Z3VS5JJcds3xfn';
if (process.env.NODE_ENV === 'production' && PUBLIC_KEY.includes(PLACEHOLDER_KEY_MARKER)) {
throw new Error(
'SECURITY ERROR: Placeholder license key detected in production. ' +
'Replace with production key from ShakaCode before deploying.'
);
}
- Verify the production key meets security requirements:
- RSA key size >= 2048 bits (4096 bits recommended)
- Algorithm: RS256
Based on learnings
- Document the key replacement process in the deployment checklist.
Do you want me to generate a verification script to check if placeholder keys are still in use, or open an issue to track this critical task?
🏁 Script executed:
#!/bin/bash
cat << 'EOF' > tmp_public_key.pem
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0Z3VS5JJcds3xfn/ygWyF/NP
lRQkqfph3x6TEOirFCpDfRjowDXAk66dPmLzw5qVOmGVPKgpJBjZR7oMIMgxBPUoj00F
DwlhUGmOVoqnVWGFHVUHDL5qYQaZzRdp4Bh9fxnN52Yk8+FuHsT+5lxLcaRV6mRtX7OT
5pQbxV0o0/OxPFC1Hz9RdLPUevnWNbLe8f5ePHivmqsoAH9HE4g03WkFZEqBLmjqpJj8
VqGR0q8CPPRCFGAr9S4WCQqBhLDH0j/JR+FpPX9Df8vfFJhHdBGdTGjN4g9g6qwPYmVH
ukAErHNIJMNmzYjFIT4+Xwp6xKHyUqL3w3JZDQnFywIDAQAB
-----END PUBLIC KEY-----
EOF
openssl pkey -pubin -in tmp_public_key.pem -text -noout | grep "Public-Key"
Length of output: 46
🏁 Script executed:
#!/bin/bash
# Write the placeholder key to a temp file
cat << 'EOF' > tmp_public_key.pem
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0Z3VS5JJcds3xfn/ygWyF/NP
lRQkqfph3x6TEOirFCpDfRjowDXAk66dPmLzw5qVOmGVPKgpJBjZR7oMIMgxBPUoj00F
DwlhUGmOVoqnVWGFHVUHDL5qYQaZzRdp4Bh9fxnN52Yk8+FuHsT+5lxLcaRV6mRtX7OT
5pQbxV0o0/OxPFC1Hz9RdLPUevnWNbLe8f5ePHivmqsoAH9HE4g03WkFZEqBLmjqpJj8
VqGR0q8CPPRCFGAr9S4WCQqBhLDH0j/JR+FpPX9Df8vfFJhHdBGdTGjN4g9g6qwPYmVH
ukAErHNIJMNmzYjFIT4+Xwp6xKHyUqL3w3JZDQnFywIDAQAB
-----END PUBLIC KEY-----
EOF
# Install cryptography library
pip install cryptography --quiet
# Use Python to load the key and print its size
python3 - << 'EOF'
from cryptography.hazmat.primitives.serialization import load_pem_public_key
from cryptography.hazmat.backends import default_backend
with open('tmp_public_key.pem', 'rb') as f:
data = f.read()
key = load_pem_public_key(data, backend=default_backend())
print("Key size:", key.key_size)
EOF
Length of output: 365
🏁 Script executed:
#!/bin/bash
# Re-create the placeholder key file
cat << 'EOF' > tmp_public_key.pem
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0Z3VS5JJcds3xfn/ygWyF/NP
lRQkqfph3x6TEOirFCpDfRjowDXAk66dPmLzw5qVOmGVPKgpJBjZR7oMIMgxBPUoj00F
DwlhUGmOVoqnVWGFHVUHDL5qYQaZzRdp4Bh9fxnN52Yk8+FuHsT+5lxLcaRV6mRtX7OT
5pQbxV0o0/OxPFC1Hz9RdLPUevnWNbLe8f5ePHivmqsoAH9HE4g03WkFZEqBLmjqpJj8
VqGR0q8CPPRCFGAr9S4WCQqBhLDH0j/JR+FpPX9Df8vfFJhHdBGdTGjN4g9g6qwPYmVH
ukAErHNIJMNmzYjFIT4+Xwp6xKHyUqL3w3JZDQnFywIDAQAB
-----END PUBLIC KEY-----
EOF
# Install PyCryptodome
pip install pycryptodome --quiet
# Use PyCryptodome to load the PEM and print key size
python3 - << 'EOF'
from Crypto.PublicKey import RSA
import re, base64
with open('tmp_public_key.pem') as f:
pem = f.read()
# Extract base64 content between the PEM markers
b64 = ''.join(re.findall(r'^[A-Za-z0-9+/=]+$', pem, re.MULTILINE))
data = base64.b64decode(b64)
key = RSA.import_key(data)
print("Key size:", key.size_in_bits())
EOF
Length of output: 495
CRITICAL: Placeholder public key must not reach production
Releasing with this dev key enables license forgery. In react_on_rails_pro/packages/node-renderer/src/shared/licensePublicKey.ts:
- Add a runtime guard to throw if NODE_ENV=production and PUBLIC_KEY matches the placeholder.
- Replace with ShakaCode’s official RSA ≥2048-bit public key before deployment and document the steps.
🤖 Prompt for AI Agents
In react_on_rails_pro/packages/node-renderer/src/shared/licensePublicKey.ts
lines 1-11, the file currently exports a development placeholder public key
which must not be used in production; add a runtime guard that checks if
process.env.NODE_ENV === 'production' and PUBLIC_KEY equals the placeholder
string and, if so, immediately throw a clear error preventing startup; replace
the placeholder with ShakaCode’s official RSA ≥2048-bit public key before
deployment and add a short developer-only comment or README note describing the
replacement steps and where to source/store the official key (e.g., secure
secret manager or env/CI variable).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (5)
react_on_rails_pro/lib/react_on_rails_pro/license_validator.rb (2)
4-4
: Remove unused require.The
pathname
library is required but never used in this file.Apply this diff to remove the unused require:
-require "pathname"
80-94
: Consider handling file read errors.While
config_path.exist?
checks for file existence,File.read(config_path)
at line 87 could still raise an error if the file is not readable due to permissions. Consider wrapping it in a rescue block to provide a clearer error message.Apply this diff to handle read errors:
# Then try config file config_path = Rails.root.join("config", "react_on_rails_pro_license.key") -return File.read(config_path).strip if config_path.exist? +if config_path.exist? + begin + return File.read(config_path).strip + rescue => e + @validation_error = "License file exists but cannot be read: #{e.message}" + handle_invalid_license(Rails.env.development? || Rails.env.test?, @validation_error) + return nil + end +endreact_on_rails_pro/packages/node-renderer/src/shared/licenseValidator.ts (3)
37-42
: Consider error handling in getLicenseData().The
getLicenseData()
method callsloadAndDecodeLicense()
which can throw errors (lines 102-104), but doesn't catch them. This differs fromisValid()
which catches errors invalidateLicense()
. Consider whether callers expect this method to throw or return undefined on error.If you want consistent error handling, apply this diff:
public getLicenseData(): LicenseData | undefined { if (!this.licenseData) { - this.loadAndDecodeLicense(); + try { + this.loadAndDecodeLicense(); + } catch (error) { + // License data remains undefined if loading fails + } } return this.licenseData; }
89-105
: Simplify error handling.The verification correctly uses an explicit
algorithms: ['RS256']
array, which is a best practice per the jsonwebtoken 9.x security guidelines. However, the catch block at lines 102-104 immediately re-throws the error without modification, making it redundant.Based on learnings
Apply this diff to remove redundant error handling:
private loadAndDecodeLicense(): LicenseData | undefined { const licenseString = this.loadLicenseString(); if (!licenseString) { return undefined; } - try { - const decoded = jwt.verify(licenseString, PUBLIC_KEY, { - algorithms: ['RS256'] - }) as LicenseData; + const decoded = jwt.verify(licenseString, PUBLIC_KEY, { + algorithms: ['RS256'] + }) as LicenseData; - this.licenseData = decoded; - return decoded; - } catch (error) { - throw error; - } + this.licenseData = decoded; + return decoded; }
107-133
: Consider logging file read errors.The catch block at lines 120-122 silently swallows all errors with only a comment. While this prevents crashes, it could hide real issues like permission errors or filesystem problems. Consider logging these errors in development mode for easier debugging.
Apply this diff to log file read errors in development:
} catch (error) { - // File doesn't exist or can't be read + if (process.env.NODE_ENV === 'development' || process.env.NODE_ENV === 'test') { + console.warn('[React on Rails Pro] Failed to read license file:', error); + } }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
react_on_rails_pro/lib/react_on_rails_pro/license_validator.rb
(1 hunks)react_on_rails_pro/packages/node-renderer/src/shared/licenseValidator.ts
(1 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
{Gemfile,Rakefile,config.ru,**/*.{rb,rake,gemspec,ru}}
📄 CodeRabbit inference engine (CLAUDE.md)
{Gemfile,Rakefile,config.ru,**/*.{rb,rake,gemspec,ru}}
: All Ruby code must pass RuboCop with zero offenses before commit/push
RuboCop is the sole authority for Ruby file formatting; never manually format Ruby files
Files:
react_on_rails_pro/lib/react_on_rails_pro/license_validator.rb
**/*.{js,jsx,ts,tsx,css,scss,json,yml,yaml,md}
📄 CodeRabbit inference engine (CLAUDE.md)
Prettier is the sole authority for formatting all non-Ruby files; never manually format them
Files:
react_on_rails_pro/packages/node-renderer/src/shared/licenseValidator.ts
**/*.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Use ESLint for JS/TS code (lint via rake lint or yarn lint)
Files:
react_on_rails_pro/packages/node-renderer/src/shared/licenseValidator.ts
🧠 Learnings (1)
📚 Learning: 2025-02-18T13:08:01.477Z
Learnt from: AbanoubGhadban
PR: shakacode/react_on_rails#1644
File: lib/react_on_rails/helper.rb:190-197
Timestamp: 2025-02-18T13:08:01.477Z
Learning: RSC support validation in React on Rails Pro is handled through a chain of validations:
1. Pro version check in `run_stream_inside_fiber`
2. RSC support check during pack generation via `ReactOnRailsPro.configuration.enable_rsc_support`
3. RSC support validation during component registration
This makes additional validation in the helper methods unnecessary.
Applied to files:
react_on_rails_pro/lib/react_on_rails_pro/license_validator.rb
🧬 Code graph analysis (1)
react_on_rails_pro/packages/node-renderer/src/shared/licenseValidator.ts (2)
react_on_rails_pro/spec/dummy/client/node-renderer.js (1)
process
(5-5)react_on_rails_pro/packages/node-renderer/src/shared/licensePublicKey.ts (1)
PUBLIC_KEY
(6-14)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (10)
- GitHub Check: dummy-app-integration-tests (3.4, 22)
- GitHub Check: dummy-app-integration-tests (3.2, 20)
- GitHub Check: rspec-package-tests (3.4, minimum)
- GitHub Check: rspec-package-tests (3.2, minimum)
- GitHub Check: rspec-package-tests (3.4, latest)
- GitHub Check: rspec-package-tests (3.2, latest)
- GitHub Check: build
- GitHub Check: markdown-link-check
- GitHub Check: claude-review
- GitHub Check: build-and-test
🔇 Additional comments (9)
react_on_rails_pro/lib/react_on_rails_pro/license_validator.rb (5)
15-19
: LGTM!The reset! method correctly clears all memoized state using proper Ruby idioms with
defined?
checks.
21-27
: LGTM!The accessor methods follow standard Ruby patterns with appropriate memoization for
license_data
.
65-78
: LGTM! Excellent security practices.The JWT verification correctly:
- Hardcodes the RS256 algorithm to prevent algorithm substitution attacks
- Includes a helpful security comment referencing the Auth0 vulnerability
- Uses
.first
to extract the payload from the decode result
96-110
: LGTM!The error handling appropriately:
- Delegates to a centralized public key
- Differentiates between development and production environments
- Provides clear, actionable error messages with visual distinction via ANSI colors
- Raises an appropriate error in production
31-63
: Catching JWT::DecodeError only in validate_license is correct
load_and_decode_license doesn’t rescue JWT errors, so handling them in validate_license is necessary and not duplicated.react_on_rails_pro/packages/node-renderer/src/shared/licenseValidator.ts (4)
1-11
: LGTM!The imports and interface definition are correct. The
LicenseData
interface properly marksexp
as required, which aligns with the validation logic.
13-26
: LGTM!The singleton pattern is correctly implemented with a private constructor and lazy initialization, mirroring the Ruby implementation's class-level singleton approach.
54-87
: LGTM!The validation logic correctly:
- Detects development vs production environments
- Uses
Date.now() / 1000
to compare with Unix timestamp expiry- Distinguishes between JWT errors and general errors via
error.name
- Implements environment-aware validation (warns in dev, blocks in prod)
Based on learnings
135-159
: LGTM!The error handling and exports are well-designed:
- Environment-appropriate console output with ANSI colors matching the Ruby implementation
- Clean public API via singleton instance and convenience functions
- Matches Ruby API surface for consistency across languages
PR Review: JWT-based License Validation SystemOverviewThis PR implements a well-designed offline JWT-based license validation system for React on Rails Pro. The implementation demonstrates good security practices and comprehensive error handling. ✅ Strengths1. Excellent Security Design
2. Comprehensive Error Handling
3. Well-Structured Testing
4. Good Documentation
5. Developer Experience
🔍 Issues and RecommendationsCritical Issues (Must Fix Before Merge)1. Inconsistent Error Behavior Between Ruby and NodeThe Node licenseValidator.ts:handleInvalidLicense() calls process.exit(1) unconditionally, making the development mode check in master.ts:14-28 unreachable when validation fails. Recommendation: Make handleInvalidLicense() not exit, just set error state, and let the caller decide based on environment. 2. Missing Newlines at End of FilesPer CLAUDE.md requirements, all files must end with a newline. Run bundle exec rubocop before pushing. 3. Placeholder Public KeysThe public keys show Source: http://localhost:8788/api/public-key - these need to be replaced with production keys before merge. 4. RuboCop Compliance RequiredYou MUST run bundle exec rubocop and fix ALL violations before pushing per CLAUDE.md requirements. Security Recommendations5. Add JWT Format ValidationAdd basic JWT format validation before decoding to prevent confusing errors when users set malformed values. Code Quality Issues6. Redundant Exception HandlingThe rescue ReactOnRailsPro::Error; raise block is redundant in license_validator.rb:59. 7. Missing TypeScript Type SafetylicenseValidator.ts:150 uses (license as any).license_type - consider adding proper interface fields. 8. CI Documentation AccuracyCI_SETUP.md line 349 suggests validation returns false, but it actually raises exceptions on invalid licenses. 📊 Test Coverage AnalysisRuby Tests: ~95% coverageValid licenses, expired licenses, missing exp field, invalid signatures, missing licenses, config file loading, caching, error messages TypeScript Tests: ~95% coverageAll major scenarios covered with proper mocking of process.exit() 🔒 Security AssessmentOverall: GOOD ✅ The implementation follows JWT security best practices:
📝 Final VerdictThis is a well-engineered solution with solid security foundations and comprehensive documentation. Main blocking issues:
Once these are addressed, this PR will be in excellent shape for merge. Estimated Review Score: 8.5/10 Great work! 🎉 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 6
♻️ Duplicate comments (2)
react_on_rails_pro/LICENSE_SETUP.md (1)
121-123
: Correct the Ruby console example to the actual APIUse the proper module and method name.
-> ReactOnRails::Utils.react_on_rails_pro_licence_valid? +> ReactOnRailsPro::Utils.licence_valid?react_on_rails_pro/spec/react_on_rails_pro/license_validator_spec.rb (1)
137-141
: Config-file stubbing won’t apply to new Pathname instancesThe code builds a fresh Pathname with Rails.root.join; stub on a specific instance may not intercept. Stub any Pathname or the exact File.read path.
- allow(config_path).to receive(:exist?).and_return(true) - allow(File).to receive(:read).with(config_path).and_return(valid_token) + allow_any_instance_of(Pathname).to receive(:exist?).and_return(true) + allow(File).to receive(:read) + .with(Rails.root.join("config", "react_on_rails_pro_license.key")) + .and_return(valid_token)
🧹 Nitpick comments (13)
react_on_rails_pro/CI_SETUP.md (2)
361-362
: Avoid printing full JWT payload in CI logsDecoding and printing can expose PII (email). Log minimal, masked fields instead or run locally.
Example:
bundle exec rails runner "require 'jwt'; d=JWT.decode(ENV['REACT_ON_RAILS_PRO_LICENSE'], nil, false).first; puts({sub: (d['sub']&.gsub(/.(?=.{3})/,'*')), exp: d['exp'], license_type: d['license_type']}.to_json)"
469-469
: Wrap support email to satisfy markdownlint (MD034) and clarityUse a mailto link.
-- **Email Support**: [email protected] +- **Email Support**: [[email protected]](mailto:[email protected])react_on_rails_pro/LICENSE_SETUP.md (1)
214-214
: Format support email as a linkImproves clarity and satisfies markdownlint (MD034).
-3. **Email**: [email protected] +3. **Email**: [[email protected]](mailto:[email protected])react_on_rails_pro/lib/react_on_rails_pro/license_validator.rb (3)
33-37
: Unreachable branch due to raise in load_license_stringload_and_decode_license raises on missing license; this early return won’t be hit.
- # If no license found, load_license_string already handled the error - return false unless license + # load_and_decode_license raises on missing license via handle_invalid_license
95-97
: Trim ENV-sourced license to avoid decode failures from whitespaceCI secrets often include trailing newlines/spaces; strip before use.
- license = ENV["REACT_ON_RAILS_PRO_LICENSE"] + license = ENV["REACT_ON_RAILS_PRO_LICENSE"]&.strip return license if license.present?
80-91
: Optional: Validate issuer/audience to harden JWT verificationIf licenses include iss/aud, enable checks to prevent token confusion.
Example (adjust claims/values to your issuance):
JWT.decode( license_string, public_key, true, algorithm: "RS256", verify_expiration: false, verify_iss: true, iss: "shakacode.com", # verify_aud: true, aud: "react-on-rails-pro" ).firstBased on learnings
react_on_rails_pro/packages/node-renderer/tests/licenseValidator.test.ts (3)
7-9
: Remove redundant mocking of licensePublicKeyUsing both jest.mock and jest.doMock for the same module is unnecessary and can confuse module state.
-jest.mock('../src/shared/licensePublicKey', () => ({ - PUBLIC_KEY: '' -})); ... - jest.doMock('../src/shared/licensePublicKey', () => ({ - PUBLIC_KEY: testPublicKey - })); + jest.doMock('../src/shared/licensePublicKey', () => ({ + PUBLIC_KEY: testPublicKey + }));Also applies to: 37-39
16-20
: Make process.exit assertions deterministic by setting NODE_ENVExplicitly set production in tests that expect exit; restore afterward.
beforeEach(() => { + process.env.NODE_ENV = 'production'; // Clear the module cache to get a fresh instance jest.resetModules(); @@ afterEach(() => { delete process.env.REACT_ON_RAILS_PRO_LICENSE; + delete process.env.NODE_ENV; jest.restoreAllMocks(); });Also applies to: 52-55
197-217
: Optional: Reduce RSA key generation overheadGenerating keys per test is slow. Generate once in beforeAll and reuse unless a test needs a different key.
react_on_rails_pro/packages/node-renderer/src/shared/licenseValidator.ts (4)
103-106
: Add issuer and audience validation to strengthen JWT verification.According to JWT best practices and the learnings from the jsonwebtoken library documentation, you should validate
issuer
andaudience
claims when applicable to prevent token misuse.Based on learnings from jsonwebtoken documentation.
const decoded = jwt.verify(licenseString, PUBLIC_KEY, { - algorithms: ['RS256'] + algorithms: ['RS256'], + issuer: 'shakacode.com', // Adjust to actual issuer value + clockTolerance: 60 // Allow 60 seconds clock skew }) as LicenseData;If
audience
is included in your license tokens, add it as well. This prevents tokens issued for other purposes from being accepted.
124-124
: Consider making the license file path configurable.Using
process.cwd()
assumes the current working directory is the project root, which may not hold true in all deployment scenarios (e.g., when the application is started from a different directory or in containerized environments).Consider one of these approaches:
- Accept an optional base path parameter (via constructor or environment variable)
- Search up the directory tree for a
package.json
to find the project root- Document the path resolution behavior clearly
Example for environment variable approach:
- const configPath = path.join(process.cwd(), 'config', 'react_on_rails_pro_license.key'); + const basePath = process.env.REACT_ON_RAILS_PRO_BASE_PATH || process.cwd(); + const configPath = path.join(basePath, 'config', 'react_on_rails_pro_license.key');
83-94
: Consider extracting the ShakaCode URL to a constant.The URL
https://shakacode.com/react-on-rails-pro
is repeated in multiple error messages throughout the file (lines 65, 73, 87, 90, 135). Extracting it to a constant improves maintainability.+const SHAKACODE_LICENSE_URL = 'https://shakacode.com/react-on-rails-pro'; + class LicenseValidator { // ... existing code ... this.validationError = 'License is missing required expiration field. ' + 'Your license may be from an older version. ' + - 'Get a FREE evaluation license at https://shakacode.com/react-on-rails-pro'; + `Get a FREE evaluation license at ${SHAKACODE_LICENSE_URL}`;Apply similar changes to other error messages at lines 73, 87, 90, and 135.
6-11
: Consider defining specific optional fields for better type safety.The
[key: string]: any
index signature makes the interface very permissive. If there are known optional fields (likelicense_type
used at line 150), consider defining them explicitly.interface LicenseData { sub?: string; iat?: number; exp: number; // Required: expiration timestamp + license_type?: string; + iss?: string; // issuer + aud?: string; // audience [key: string]: any; }This provides better autocomplete and type checking while maintaining flexibility for unknown fields.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (6)
react_on_rails_pro/CI_SETUP.md
(1 hunks)react_on_rails_pro/LICENSE_SETUP.md
(1 hunks)react_on_rails_pro/lib/react_on_rails_pro/license_validator.rb
(1 hunks)react_on_rails_pro/packages/node-renderer/src/shared/licenseValidator.ts
(1 hunks)react_on_rails_pro/packages/node-renderer/tests/licenseValidator.test.ts
(1 hunks)react_on_rails_pro/spec/react_on_rails_pro/license_validator_spec.rb
(1 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{js,jsx,ts,tsx,css,scss,json,yml,yaml,md}
📄 CodeRabbit inference engine (CLAUDE.md)
Prettier is the sole authority for formatting all non-Ruby files; never manually format them
Files:
react_on_rails_pro/CI_SETUP.md
react_on_rails_pro/packages/node-renderer/src/shared/licenseValidator.ts
react_on_rails_pro/packages/node-renderer/tests/licenseValidator.test.ts
react_on_rails_pro/LICENSE_SETUP.md
**/*.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Use ESLint for JS/TS code (lint via rake lint or yarn lint)
Files:
react_on_rails_pro/packages/node-renderer/src/shared/licenseValidator.ts
react_on_rails_pro/packages/node-renderer/tests/licenseValidator.test.ts
{Gemfile,Rakefile,config.ru,**/*.{rb,rake,gemspec,ru}}
📄 CodeRabbit inference engine (CLAUDE.md)
{Gemfile,Rakefile,config.ru,**/*.{rb,rake,gemspec,ru}}
: All Ruby code must pass RuboCop with zero offenses before commit/push
RuboCop is the sole authority for Ruby file formatting; never manually format Ruby files
Files:
react_on_rails_pro/spec/react_on_rails_pro/license_validator_spec.rb
react_on_rails_pro/lib/react_on_rails_pro/license_validator.rb
🧠 Learnings (2)
📚 Learning: 2025-02-13T14:29:49.267Z
Learnt from: AbanoubGhadban
PR: shakacode/react_on_rails#1644
File: spec/react_on_rails/utils_spec.rb:218-218
Timestamp: 2025-02-13T14:29:49.267Z
Learning: In RSpec tests, prefer using local variables over constants within test blocks to avoid constant redefinition warnings and maintain better test isolation.
Applied to files:
react_on_rails_pro/spec/react_on_rails_pro/license_validator_spec.rb
📚 Learning: 2025-02-18T13:08:01.477Z
Learnt from: AbanoubGhadban
PR: shakacode/react_on_rails#1644
File: lib/react_on_rails/helper.rb:190-197
Timestamp: 2025-02-18T13:08:01.477Z
Learning: RSC support validation in React on Rails Pro is handled through a chain of validations:
1. Pro version check in `run_stream_inside_fiber`
2. RSC support check during pack generation via `ReactOnRailsPro.configuration.enable_rsc_support`
3. RSC support validation during component registration
This makes additional validation in the helper methods unnecessary.
Applied to files:
react_on_rails_pro/lib/react_on_rails_pro/license_validator.rb
🧬 Code graph analysis (3)
react_on_rails_pro/packages/node-renderer/src/shared/licenseValidator.ts (1)
react_on_rails_pro/packages/node-renderer/src/shared/licensePublicKey.ts (1)
PUBLIC_KEY
(6-14)
react_on_rails_pro/spec/react_on_rails_pro/license_validator_spec.rb (1)
react_on_rails_pro/lib/react_on_rails_pro/license_validator.rb (6)
public_key
(109-111)reset!
(15-19)valid?
(7-128)valid?
(9-13)license_data
(21-23)validation_error
(25-27)
react_on_rails_pro/packages/node-renderer/tests/licenseValidator.test.ts (1)
react_on_rails_pro/packages/node-renderer/src/shared/licenseValidator.ts (1)
licenseValidator
(157-157)
🪛 markdownlint-cli2 (0.18.1)
react_on_rails_pro/CI_SETUP.md
214-214: Bare URL used
(MD034, no-bare-urls)
react_on_rails_pro/LICENSE_SETUP.md
214-214: Bare URL used
(MD034, no-bare-urls)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (10)
- GitHub Check: dummy-app-integration-tests (3.4, 22)
- GitHub Check: dummy-app-integration-tests (3.2, 20)
- GitHub Check: rspec-package-tests (3.4, latest)
- GitHub Check: rspec-package-tests (3.4, minimum)
- GitHub Check: rspec-package-tests (3.2, minimum)
- GitHub Check: rspec-package-tests (3.2, latest)
- GitHub Check: build
- GitHub Check: claude-review
- GitHub Check: markdown-link-check
- GitHub Check: build-and-test
🔇 Additional comments (1)
react_on_rails_pro/packages/node-renderer/src/shared/licenseValidator.ts (1)
1-4
: LGTM: Clean imports with appropriate dependencies.The imports are well-organized and include all necessary dependencies for JWT validation and file system operations.
react_on_rails_pro/CI_SETUP.md
Outdated
bundle exec rails runner "puts ReactOnRails::Utils.react_on_rails_pro_licence_valid? ? '✅ License valid' : '❌ License invalid'" | ||
``` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix Ruby API reference in verification snippet
Use the actual helper API and namespace.
-bundle exec rails runner "puts ReactOnRails::Utils.react_on_rails_pro_licence_valid? ? '✅ License valid' : '❌ License invalid'"
+bundle exec rails runner "puts ReactOnRailsPro::Utils.licence_valid? ? '✅ License valid' : '❌ License invalid'"
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
bundle exec rails runner "puts ReactOnRails::Utils.react_on_rails_pro_licence_valid? ? '✅ License valid' : '❌ License invalid'" | |
``` | |
bundle exec rails runner "puts ReactOnRailsPro::Utils.licence_valid? ? '✅ License valid' : '❌ License invalid'" |
🤖 Prompt for AI Agents
In react_on_rails_pro/CI_SETUP.md around lines 349-350, the verification snippet
is using a non-existent Utils API and British spelling; replace the call with
the actual helper namespace and method (ReactOnRails::Pro.license_valid?) and
update the printed message accordingly so the snippet invokes the real Ruby API
for verifying the Pro license.
public getLicenseData(): LicenseData | undefined { | ||
if (!this.licenseData) { | ||
this.loadAndDecodeLicense(); | ||
} | ||
return this.licenseData; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Critical: Unhandled errors can crash the application.
getLicenseData()
calls loadAndDecodeLicense()
which can throw errors (line 111), but no error handling is present. If called before isValid()
, this will result in an unhandled exception.
Wrap the call in a try-catch block:
public getLicenseData(): LicenseData | undefined {
- if (!this.licenseData) {
- this.loadAndDecodeLicense();
+ if (!this.licenseData) {
+ try {
+ this.loadAndDecodeLicense();
+ } catch (error) {
+ // Return undefined on error; validation state tracked separately
+ return undefined;
+ }
}
return this.licenseData;
}
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
public getLicenseData(): LicenseData | undefined { | |
if (!this.licenseData) { | |
this.loadAndDecodeLicense(); | |
} | |
return this.licenseData; | |
} | |
public getLicenseData(): LicenseData | undefined { | |
if (!this.licenseData) { | |
try { | |
this.loadAndDecodeLicense(); | |
} catch (error) { | |
// Return undefined on error; validation state tracked separately | |
return undefined; | |
} | |
} | |
return this.licenseData; | |
} |
🤖 Prompt for AI Agents
In react_on_rails_pro/packages/node-renderer/src/shared/licenseValidator.ts
around lines 37 to 42, getLicenseData() currently calls loadAndDecodeLicense()
which can throw (see line 111); wrap that call in a try-catch so any thrown
errors are caught and do not bubble up and crash the app—on catch, log the error
(or use the existing logger) and return undefined so callers safely receive no
license data instead of an unhandled exception.
react_on_rails_pro/packages/node-renderer/src/shared/licenseValidator.ts
Outdated
Show resolved
Hide resolved
// Check expiry | ||
if (Date.now() / 1000 > license.exp) { | ||
this.validationError = 'License has expired. ' + | ||
'Get a FREE evaluation license (3 months) at https://shakacode.com/react-on-rails-pro ' + | ||
'or upgrade to a paid license for production use.'; | ||
this.handleInvalidLicense(this.validationError); | ||
return false; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Major: Expiry check lacks clock tolerance for clock skew.
The manual expiry comparison doesn't account for clock differences between systems, which can cause false rejections. The jsonwebtoken
library supports a clockTolerance
option for this purpose.
Move the expiry check into jwt.verify()
options instead of manual comparison:
- // Check expiry
- if (Date.now() / 1000 > license.exp) {
- this.validationError = 'License has expired. ' +
- 'Get a FREE evaluation license (3 months) at https://shakacode.com/react-on-rails-pro ' +
- 'or upgrade to a paid license for production use.';
- this.handleInvalidLicense(this.validationError);
- return false;
- }
And update jwt.verify()
at lines 104-106:
const decoded = jwt.verify(licenseString, PUBLIC_KEY, {
- algorithms: ['RS256']
+ algorithms: ['RS256'],
+ clockTolerance: 60 // Allow 60 seconds clock skew
}) as LicenseData;
The library will throw a TokenExpiredError
with a clear message when the token is expired (accounting for clock skew).
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
// Check expiry | |
if (Date.now() / 1000 > license.exp) { | |
this.validationError = 'License has expired. ' + | |
'Get a FREE evaluation license (3 months) at https://shakacode.com/react-on-rails-pro ' + | |
'or upgrade to a paid license for production use.'; | |
this.handleInvalidLicense(this.validationError); | |
return false; | |
} | |
// --- Remove the manual expiry check around line 70 --- | |
- // Check expiry | |
- if (Date.now() / 1000 > license.exp) { | |
- this.validationError = 'License has expired. ' + | |
- 'Get a FREE evaluation license (3 months) at https://shakacode.com/react-on-rails-pro ' + | |
- 'or upgrade to a paid license for production use.'; | |
- this.handleInvalidLicense(this.validationError); | |
- return false; | |
- } | |
// --- Update the jwt.verify call around lines 104–106 --- | |
const decoded = jwt.verify(licenseString, PUBLIC_KEY, { | |
algorithms: ['RS256'], | |
clockTolerance: 60 // Allow 60 seconds clock skew | |
}) as LicenseData; |
🤖 Prompt for AI Agents
In react_on_rails_pro/packages/node-renderer/src/shared/licenseValidator.ts
around lines 70 to 77 and the jwt.verify call at lines ~104-106, the manual
expiry comparison using Date.now() should be removed and replaced by passing a
clockTolerance option to jwt.verify so token expiry is validated by the
jsonwebtoken library (which will throw TokenExpiredError when appropriate,
accounting for clock skew); update the jwt.verify() invocation to include {
clockTolerance: <seconds> } (choose a sensible tolerance like 30 or 60), remove
the manual expiry branch, and ensure TokenExpiredError is handled in the
existing catch/invalid-license flow to produce the same validationError path.
private loadLicenseString(): string | undefined { | ||
// First try environment variable | ||
const envLicense = process.env.REACT_ON_RAILS_PRO_LICENSE; | ||
if (envLicense) { | ||
return envLicense; | ||
} | ||
|
||
// Then try config file (relative to project root) | ||
try { | ||
const configPath = path.join(process.cwd(), 'config', 'react_on_rails_pro_license.key'); | ||
if (fs.existsSync(configPath)) { | ||
return fs.readFileSync(configPath, 'utf8').trim(); | ||
} | ||
} catch (error) { | ||
// File doesn't exist or can't be read | ||
} | ||
|
||
this.validationError = | ||
'No license found. Please set REACT_ON_RAILS_PRO_LICENSE environment variable ' + | ||
'or create config/react_on_rails_pro_license.key file. ' + | ||
'Get a FREE evaluation license at https://shakacode.com/react-on-rails-pro'; | ||
|
||
this.handleInvalidLicense(this.validationError); | ||
|
||
return undefined; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unreachable return statement after process.exit() call.
Similar to validateLicense()
, this method returns undefined
after calling handleInvalidLicense()
(line 137), but the return is never reached due to process.exit()
on line 146.
Once you refactor handleInvalidLicense()
to not exit the process (as suggested earlier), this return statement will become meaningful.
🤖 Prompt for AI Agents
In react_on_rails_pro/packages/node-renderer/src/shared/licenseValidator.ts
around lines 115 to 140, the method currently calls handleInvalidLicense() which
exits the process, making the subsequent "return undefined" unreachable;
refactor handleInvalidLicense to not call process.exit() (instead return a value
or throw a controlled error) and update its callers to handle that
return/exception; then leave the "return undefined" in loadLicenseString as the
normal non-exceptional fallback so the function can return cleanly when no
license is found.
react_on_rails_pro/packages/node-renderer/src/shared/licenseValidator.ts
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
🧹 Nitpick comments (1)
react_on_rails_pro/lib/react_on_rails_pro/engine.rb (1)
22-25
: Redundant logging on successful validation.Line 22 logs "Validating license..." and line 25 logs "License validation successful." If validation is fast, the first message may not provide value and could clutter logs.
Consider removing the initial log message to reduce noise:
- Rails.logger.info "[React on Rails Pro] Validating license..." - if ReactOnRailsPro::LicenseValidator.valid? Rails.logger.info "[React on Rails Pro] License validation successful"
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
react_on_rails_pro/lib/react_on_rails_pro/engine.rb
(1 hunks)react_on_rails_pro/packages/node-renderer/src/master.ts
(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- react_on_rails_pro/packages/node-renderer/src/master.ts
🧰 Additional context used
📓 Path-based instructions (1)
{Gemfile,Rakefile,config.ru,**/*.{rb,rake,gemspec,ru}}
📄 CodeRabbit inference engine (CLAUDE.md)
{Gemfile,Rakefile,config.ru,**/*.{rb,rake,gemspec,ru}}
: All Ruby code must pass RuboCop with zero offenses before commit/push
RuboCop is the sole authority for Ruby file formatting; never manually format Ruby files
Files:
react_on_rails_pro/lib/react_on_rails_pro/engine.rb
🧠 Learnings (1)
📚 Learning: 2025-09-15T21:24:48.207Z
Learnt from: AbanoubGhadban
PR: shakacode/react_on_rails#1781
File: node_package/src/ClientSideRenderer.ts:82-95
Timestamp: 2025-09-15T21:24:48.207Z
Learning: In React on Rails, the force_load feature includes both explicit `data-force-load="true"` usage and the ability to hydrate components during the page loading state (`document.readyState === 'loading'`). Both capabilities require a Pro license, so the condition `!railsContext.rorPro && (isComponentForceLoaded || document.readyState === 'loading')` correctly gates both scenarios.
Applied to files:
react_on_rails_pro/lib/react_on_rails_pro/engine.rb
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (10)
- GitHub Check: dummy-app-integration-tests (3.4, 22)
- GitHub Check: dummy-app-integration-tests (3.2, 20)
- GitHub Check: claude-review
- GitHub Check: rspec-package-tests (3.4, minimum)
- GitHub Check: rspec-package-tests (3.2, latest)
- GitHub Check: rspec-package-tests (3.4, latest)
- GitHub Check: build
- GitHub Check: rspec-package-tests (3.2, minimum)
- GitHub Check: markdown-link-check
- GitHub Check: build-and-test
🔇 Additional comments (1)
react_on_rails_pro/lib/react_on_rails_pro/engine.rb (1)
27-29
: LicenseValidator.valid? raises on failures, so the else branch is unreachable and the comment is accurate. No changes needed.Likely an incorrect or invalid review comment.
initializer "react_on_rails_pro.validate_license", before: :load_config_initializers do | ||
config.after_initialize do | ||
# Skip license validation during asset precompilation | ||
next if defined?(Rake) && Rake::Task.task_defined?("assets:precompile") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion | 🟠 Major
🧩 Analysis chain
Rake task check may not detect active precompilation.
Rake::Task.task_defined?("assets:precompile")
checks whether the task is defined, not whether it's currently running. If the task is defined but not executing (e.g., in a Rails console), validation will still be skipped incorrectly.
Consider checking the current Rake task name instead:
- next if defined?(Rake) && Rake::Task.task_defined?("assets:precompile")
+ next if defined?(Rake) && ARGV.any? { |arg| arg.start_with?("assets:") }
Alternatively, verify the intended behavior—if skipping whenever the task is defined (regardless of execution) is acceptable, document this explicitly in the comment.
🏁 Script executed:
#!/bin/bash
rg -n "'assets:precompile'" -C5 --glob 'react_on_rails_pro/lib/react_on_rails_pro/engine.rb'
Length of output: 0
🏁 Script executed:
#!/bin/bash
rg -n 'Rake::Task.task_defined' -C5 --glob 'react_on_rails_pro/lib/react_on_rails_pro/engine.rb'
Length of output: 1146
Skip license validation only during asset precompilation
Replace the definition check with a runtime task check:
- next if defined?(Rake) && Rake::Task.task_defined?("assets:precompile")
+ next if defined?(Rake) && Rake.application.top_level_tasks.include?("assets:precompile")
🤖 Prompt for AI Agents
In react_on_rails_pro/lib/react_on_rails_pro/engine.rb around line 16, replace
the current definition check with a runtime task check: change the line to call
Rake::Task.task_defined?("assets:precompile") and guard against Rake not being
loaded by rescuing NameError (e.g. use `next if
Rake::Task.task_defined?("assets:precompile") rescue false`) so we skip license
validation only when the assets:precompile task actually exists at runtime.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
♻️ Duplicate comments (7)
react_on_rails_pro/lib/react_on_rails_pro/engine.rb (1)
13-25
: Timing configuration inconsistency remains unresolved.The initializer is still declared with
before: :load_config_initializers
but wraps the validation logic insideconfig.after_initialize
. This creates a timing contradiction—validation will run after all initializers complete, not beforeload_config_initializers
as the option suggests.Additionally, the else branch (lines 19-22) is unreachable because
ReactOnRailsPro::LicenseValidator.valid?
raises aReactOnRailsPro::Error
when validation fails (see license_validator.rb lines 116).Apply this diff to fix the timing and remove unreachable code:
- initializer "react_on_rails_pro.validate_license", before: :load_config_initializers do - config.after_initialize do - Rails.logger.info "[React on Rails Pro] Validating license..." + initializer "react_on_rails_pro.validate_license", before: :load_config_initializers do + Rails.logger.info "[React on Rails Pro] Validating license..." - if ReactOnRailsPro::LicenseValidator.valid? - Rails.logger.info "[React on Rails Pro] License validation successful" - else - # License validation will raise an error, so this line won't be reached - # But we include it for clarity - Rails.logger.error "[React on Rails Pro] License validation failed" - end - end + if ReactOnRailsPro::LicenseValidator.valid? + Rails.logger.info "[React on Rails Pro] License validation successful" + end endreact_on_rails_pro/LICENSE_SETUP.md (1)
119-123
: Incorrect method reference in documentation.The method name is incorrect. Based on the implementation, it should be
ReactOnRailsPro::Utils.licence_valid?
(notReactOnRails::Utils.react_on_rails_pro_licence_valid?
).Apply this diff:
-> ReactOnRails::Utils.react_on_rails_pro_licence_valid? +> ReactOnRailsPro::Utils.licence_valid? # Should return: truereact_on_rails_pro/packages/node-renderer/src/shared/licenseValidator.ts (5)
37-42
: Unhandled errors can crash the application.
loadAndDecodeLicense()
can throw errors (line 111), but no error handling is present ingetLicenseData()
. If called beforeisValid()
, this will result in an unhandled exception.Wrap the call in a try-catch block:
public getLicenseData(): LicenseData | undefined { if (!this.licenseData) { - this.loadAndDecodeLicense(); + try { + this.loadAndDecodeLicense(); + } catch (error) { + // Return undefined on error; validation state tracked separately + return undefined; + } } return this.licenseData; }
54-95
: Unreachable return statements after process.exit() calls.The method returns
false
after callinghandleInvalidLicense()
, but sincehandleInvalidLicense()
exits the process (line 146), these return statements are never reached.Once you refactor
handleInvalidLicense()
to not exit the process (as suggested in the comment for lines 142-147), these return statements will become meaningful. The preferred solution is to remove theprocess.exit()
fromhandleInvalidLicense()
and let the caller (e.g., master.ts) decide whether to exit based on environment.
70-77
: Expiry check lacks clock tolerance for clock skew.The manual expiry comparison doesn't account for clock differences between systems, which can cause false rejections. The
jsonwebtoken
library supports aclockTolerance
option for this purpose.Move the expiry check into
jwt.verify()
options:- // Check expiry - if (Date.now() / 1000 > license.exp) { - this.validationError = 'License has expired. ' + - 'Get a FREE evaluation license (3 months) at https://shakacode.com/react-on-rails-pro ' + - 'or upgrade to a paid license for production use.'; - this.handleInvalidLicense(this.validationError); - return false; - }And update
jwt.verify()
at lines 104-106:const decoded = jwt.verify(licenseString, PUBLIC_KEY, { - algorithms: ['RS256'] + algorithms: ['RS256'], + clockTolerance: 60 // Allow 60 seconds clock skew }) as LicenseData;The library will throw a
TokenExpiredError
with a clear message when the token is expired (accounting for clock skew).Based on learnings.
142-147
: Unconditional process exit breaks testing and development workflows.The
handleInvalidLicense()
method always exits the process, which:
- Makes the validator untestable (tests cannot verify error handling without mocking
process.exit
)- Prevents development workflows where invalid licenses should warn but not crash
- Violates separation of concerns—libraries should not control process lifecycle
Remove the
process.exit()
and let the caller decide:private handleInvalidLicense(message: string): void { const fullMessage = `[React on Rails Pro] ${message}`; console.error(fullMessage); - // Validation errors should prevent the application from starting - process.exit(1); + // Let the caller decide whether to exit (e.g., master.ts checks environment) }Then in
master.ts
, handle the exit decision based on environment:if (!isLicenseValid()) { const error = getLicenseValidationError(); console.error(error); if (process.env.NODE_ENV === 'production') { process.exit(1); } }
115-140
: Unreachable return statement after process exit.The
return undefined
on line 139 is unreachable becausehandleInvalidLicense()
(line 137) exits the process (line 146).Once you refactor
handleInvalidLicense()
to not exit the process, this return statement will become meaningful.
🧹 Nitpick comments (2)
react_on_rails_pro/lib/react_on_rails_pro/license_validator.rb (1)
46-53
: Recommend adding clock tolerance for expiration check.The manual expiry comparison doesn't account for clock skew between systems, which can cause false rejections. While the
jwt
gem'sverify_expiration: false
disables automatic checking (line 89), consider adding a tolerance window to the manual check.Apply this diff to add clock tolerance:
# Check expiry - if Time.now.to_i > license["exp"] + # Allow 60 seconds clock tolerance to handle clock skew between systems + if Time.now.to_i > (license["exp"] + 60) @validation_error = "License has expired. " \ "Get a FREE evaluation license (3 months) at https://shakacode.com/react-on-rails-pro " \ "or upgrade to a paid license for production use."Alternatively, enable automatic verification with tolerance in
JWT.decode
(line 80-90) if you prefer to let the library handle it.react_on_rails_pro/packages/node-renderer/src/shared/licenseValidator.ts (1)
103-112
: LGTM! Good JWT security practices.The implementation correctly:
- Hardcodes the
algorithms
array to prevent algorithm confusion attacks- Uses RS256 with an asymmetric public key
- Follows jsonwebtoken v9.0.2 security defaults (enforces RSA >= 2048 bits, rejects unsigned tokens)
Optional: Consider validating standard claims if your license generation includes them:
const decoded = jwt.verify(licenseString, PUBLIC_KEY, { algorithms: ['RS256'], clockTolerance: 60, issuer: 'shakacode.com', // If licenses include 'iss' claim // audience: 'react-on-rails-pro' // If licenses include 'aud' claim }) as LicenseData;Based on learnings.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
react_on_rails_pro/LICENSE_SETUP.md
(1 hunks)react_on_rails_pro/lib/react_on_rails_pro/engine.rb
(1 hunks)react_on_rails_pro/lib/react_on_rails_pro/license_validator.rb
(1 hunks)react_on_rails_pro/packages/node-renderer/src/shared/licenseValidator.ts
(1 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
{Gemfile,Rakefile,config.ru,**/*.{rb,rake,gemspec,ru}}
📄 CodeRabbit inference engine (CLAUDE.md)
{Gemfile,Rakefile,config.ru,**/*.{rb,rake,gemspec,ru}}
: All Ruby code must pass RuboCop with zero offenses before commit/push
RuboCop is the sole authority for Ruby file formatting; never manually format Ruby files
Files:
react_on_rails_pro/lib/react_on_rails_pro/engine.rb
react_on_rails_pro/lib/react_on_rails_pro/license_validator.rb
**/*.{js,jsx,ts,tsx,css,scss,json,yml,yaml,md}
📄 CodeRabbit inference engine (CLAUDE.md)
Prettier is the sole authority for formatting all non-Ruby files; never manually format them
Files:
react_on_rails_pro/packages/node-renderer/src/shared/licenseValidator.ts
react_on_rails_pro/LICENSE_SETUP.md
**/*.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Use ESLint for JS/TS code (lint via rake lint or yarn lint)
Files:
react_on_rails_pro/packages/node-renderer/src/shared/licenseValidator.ts
🧠 Learnings (1)
📚 Learning: 2025-02-18T13:08:01.477Z
Learnt from: AbanoubGhadban
PR: shakacode/react_on_rails#1644
File: lib/react_on_rails/helper.rb:190-197
Timestamp: 2025-02-18T13:08:01.477Z
Learning: RSC support validation in React on Rails Pro is handled through a chain of validations:
1. Pro version check in `run_stream_inside_fiber`
2. RSC support check during pack generation via `ReactOnRailsPro.configuration.enable_rsc_support`
3. RSC support validation during component registration
This makes additional validation in the helper methods unnecessary.
Applied to files:
react_on_rails_pro/lib/react_on_rails_pro/license_validator.rb
🧬 Code graph analysis (2)
react_on_rails_pro/lib/react_on_rails_pro/engine.rb (1)
react_on_rails_pro/lib/react_on_rails_pro/license_validator.rb (2)
valid?
(7-129)valid?
(9-13)
react_on_rails_pro/packages/node-renderer/src/shared/licenseValidator.ts (1)
react_on_rails_pro/packages/node-renderer/src/shared/licensePublicKey.ts (1)
PUBLIC_KEY
(6-14)
🪛 markdownlint-cli2 (0.18.1)
react_on_rails_pro/LICENSE_SETUP.md
215-215: Bare URL used
(MD034, no-bare-urls)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (9)
- GitHub Check: dummy-app-integration-tests (3.4, 22)
- GitHub Check: dummy-app-integration-tests (3.2, 20)
- GitHub Check: build
- GitHub Check: markdown-link-check
- GitHub Check: claude-review
- GitHub Check: rspec-package-tests (3.4, minimum)
- GitHub Check: rspec-package-tests (3.4, latest)
- GitHub Check: rspec-package-tests (3.2, minimum)
- GitHub Check: rspec-package-tests (3.2, latest)
🔇 Additional comments (5)
react_on_rails_pro/lib/react_on_rails_pro/license_validator.rb (5)
9-13
: LGTM! Proper memoization pattern.The caching strategy is appropriate for license validation, avoiding repeated JWT decoding.
15-19
: LGTM! Clean reset implementation.Properly clears all cached state, which is essential for testing scenarios.
76-91
: LGTM! Excellent security practices.The implementation correctly:
- Hardcodes the algorithm to prevent algorithm confusion attacks
- Includes a security comment with reference to Auth0's vulnerability disclosure
- Disables automatic expiration to enable custom error messaging
This follows JWT security best practices.
113-117
: LGTM! Clear error handling.Proper logging and exception raising with descriptive messages.
119-127
: LGTM! Safe optional field logging.Proper nil checks and conditional logging for analytics data.
react_on_rails_pro/packages/node-renderer/src/shared/licenseValidator.ts
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (1)
react_on_rails_pro/spec/execjs-compatible-dummy/.gitignore (1)
48-49
: Anchor license ignore path and secure packaging
- In react_on_rails_pro/spec/execjs-compatible-dummy/.gitignore, change
config/react_on_rails_pro_license.key
→/config/react_on_rails_pro_license.key
- Mirror the same update in react_on_rails_pro/spec/dummy/.gitignore
- Add to root .gitignore:
**/config/react_on_rails_pro_license.key
- In both gemspecs, exclude
config/react_on_rails_pro_license.key
from packaged files- In .npmignore (root & react_on_rails_pro/.npmignore), add:
/config/react_on_rails_pro_license.key
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (3)
react_on_rails_pro/spec/dummy/yarn.lock
is excluded by!**/yarn.lock
,!**/*.lock
react_on_rails_pro/spec/execjs-compatible-dummy/Gemfile.lock
is excluded by!**/*.lock
react_on_rails_pro/yarn.lock
is excluded by!**/yarn.lock
,!**/*.lock
📒 Files selected for processing (4)
react_on_rails_pro/package.json
(2 hunks)react_on_rails_pro/spec/dummy/.gitignore
(1 hunks)react_on_rails_pro/spec/execjs-compatible-dummy/.gitignore
(1 hunks)react_on_rails_pro/spec/react_on_rails_pro/license_validator_spec.rb
(1 hunks)
✅ Files skipped from review due to trivial changes (1)
- react_on_rails_pro/spec/dummy/.gitignore
🚧 Files skipped from review as they are similar to previous changes (2)
- react_on_rails_pro/package.json
- react_on_rails_pro/spec/react_on_rails_pro/license_validator_spec.rb
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: AbanoubGhadban
PR: shakacode/react_on_rails#1644
File: lib/react_on_rails/helper.rb:190-197
Timestamp: 2025-02-18T13:08:01.477Z
Learning: RSC support validation in React on Rails Pro is handled through a chain of validations:
1. Pro version check in `run_stream_inside_fiber`
2. RSC support check during pack generation via `ReactOnRailsPro.configuration.enable_rsc_support`
3. RSC support validation during component registration
This makes additional validation in the helper methods unnecessary.
📚 Learning: 2025-02-12T16:38:06.537Z
Learnt from: Romex91
PR: shakacode/react_on_rails#1697
File: package-scripts.yml:28-28
Timestamp: 2025-02-12T16:38:06.537Z
Learning: The file `node_package/lib/ReactOnRails.full.js` is autogenerated during the build process and should not be present in the repository.
Applied to files:
react_on_rails_pro/spec/execjs-compatible-dummy/.gitignore
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (9)
- GitHub Check: rspec-package-tests (3.4, minimum)
- GitHub Check: rspec-package-tests (3.4, latest)
- GitHub Check: rspec-package-tests (3.2, latest)
- GitHub Check: rspec-package-tests (3.2, minimum)
- GitHub Check: build
- GitHub Check: build-dummy-app-webpack-test-bundles (3.4, 22)
- GitHub Check: claude-review
- GitHub Check: markdown-link-check
- GitHub Check: build-and-test
Pull Request Review - Offline JWT License ValidationComprehensive review of PR 1857 completed. Overall this is a well-architected and secure implementation with excellent test coverage. STRENGTHS:
CRITICAL ISSUES:
MEDIUM PRIORITY:
MINOR ISSUES:
SECURITY ASSESSMENT: PERFORMANCE: RECOMMENDATION: Approve with minor changes primarily addressing dev vs prod behavior discrepancy and cache expiration issue. Generated with Claude Code |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (1)
react_on_rails_pro/spec/react_on_rails_pro/license_validator_spec.rb (1)
160-204
: Consider optional test enhancements.The tests for
license_data
,validation_error
, andreset!
are functional. A few optional improvements:
- Test
license_data
caching: Verify memoization by checking thatload_and_decode_license
is called once across multiplelicense_data
calls.- Test optional license fields: Add tests with
plan
andissued_by
fields to verify thelog_license_info
behavior.- Refactor repeated assertions: Lines 81-83, 105-107, 121-123, 136-139 all verify FREE license messaging. Consider extracting to a shared example.
- Style note (lines 182-186): The begin/rescue pattern works but feels awkward. An alternative could be a separate test or using
aggregate_failures
.Example shared example for FREE license messaging:
shared_examples "includes FREE license guidance" do it "includes FREE license information in error message" do expect { described_class.valid? }.to raise_error(ReactOnRailsPro::Error, /FREE evaluation license/) end endThen in each error context:
context "with expired license" do # ... setup ... it "raises error" do expect { described_class.valid? }.to raise_error(ReactOnRailsPro::Error, /License has expired/) end include_examples "includes FREE license guidance" end
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
react_on_rails_pro/spec/react_on_rails_pro/license_validator_spec.rb
(1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
{Gemfile,Rakefile,config.ru,**/*.{rb,rake,gemspec,ru}}
📄 CodeRabbit inference engine (CLAUDE.md)
{Gemfile,Rakefile,config.ru,**/*.{rb,rake,gemspec,ru}}
: All Ruby code must pass RuboCop with zero offenses before commit/push
RuboCop is the sole authority for Ruby file formatting; never manually format Ruby files
Files:
react_on_rails_pro/spec/react_on_rails_pro/license_validator_spec.rb
🧠 Learnings (1)
📚 Learning: 2025-02-13T14:29:49.267Z
Learnt from: AbanoubGhadban
PR: shakacode/react_on_rails#1644
File: spec/react_on_rails/utils_spec.rb:218-218
Timestamp: 2025-02-13T14:29:49.267Z
Learning: In RSpec tests, prefer using local variables over constants within test blocks to avoid constant redefinition warnings and maintain better test isolation.
Applied to files:
react_on_rails_pro/spec/react_on_rails_pro/license_validator_spec.rb
🧬 Code graph analysis (1)
react_on_rails_pro/spec/react_on_rails_pro/license_validator_spec.rb (1)
react_on_rails_pro/lib/react_on_rails_pro/license_validator.rb (6)
public_key
(109-111)reset!
(15-19)valid?
(7-129)valid?
(9-13)license_data
(21-23)validation_error
(25-27)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (10)
- GitHub Check: dummy-app-integration-tests (3.2, 20)
- GitHub Check: dummy-app-integration-tests (3.4, 22)
- GitHub Check: claude-review
- GitHub Check: rspec-package-tests (3.4, latest)
- GitHub Check: rspec-package-tests (3.4, minimum)
- GitHub Check: build
- GitHub Check: markdown-link-check
- GitHub Check: rspec-package-tests (3.2, minimum)
- GitHub Check: rspec-package-tests (3.2, latest)
- GitHub Check: build-and-test
🔇 Additional comments (3)
react_on_rails_pro/spec/react_on_rails_pro/license_validator_spec.rb (3)
35-46
: Previous issues successfully addressed.The use of
stub_const
for the public key constant (line 38) and the Rails mock setup correctly resolve the issues from the previous review.
53-140
: LGTM: Comprehensive validation test coverage.The test contexts thoroughly cover all validation scenarios—valid licenses, expiration, missing fields, invalid signatures, and missing licenses. Error message assertions correctly verify both the specific failure reason and the FREE license guidance.
142-157
: Config file path stubbing now correct.The test properly creates a separate
file_config_path
double withexist?: true
and overrides themock_root.join
behavior specifically for this test. The File.read stub is correctly scoped to the new path. This resolves the previous review concern.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🧹 Nitpick comments (1)
react_on_rails_pro/packages/node-renderer/tests/licenseValidator.test.ts (1)
190-210
: Consider clarifying the caching test's intent.The test correctly validates that cached results persist after environment changes. However, the test could be more explicit about verifying the cache mechanism itself.
Consider adding a comment or spy to document that the test validates "cached result persists even when ENV is deleted":
// First call expect(module.isLicenseValid()).toBe(true); - // Change ENV (shouldn't affect cached result) + // Delete ENV - cached result should persist delete process.env.REACT_ON_RAILS_PRO_LICENSE; - // Second call should use cache + // Second call should return cached true (not re-validate) expect(module.isLicenseValid()).toBe(true);Alternatively, spy on the internal validation logic to verify it's not called on the second invocation.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
react_on_rails_pro/.gitignore
(1 hunks)react_on_rails_pro/packages/node-renderer/src/shared/licenseValidator.ts
(1 hunks)react_on_rails_pro/packages/node-renderer/tests/licenseValidator.test.ts
(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- react_on_rails_pro/packages/node-renderer/src/shared/licenseValidator.ts
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{js,jsx,ts,tsx,css,scss,json,yml,yaml,md}
📄 CodeRabbit inference engine (CLAUDE.md)
Prettier is the sole authority for formatting all non-Ruby files; never manually format them
Files:
react_on_rails_pro/packages/node-renderer/tests/licenseValidator.test.ts
**/*.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Use ESLint for JS/TS code (lint via rake lint or yarn lint)
Files:
react_on_rails_pro/packages/node-renderer/tests/licenseValidator.test.ts
🧬 Code graph analysis (1)
react_on_rails_pro/packages/node-renderer/tests/licenseValidator.test.ts (1)
react_on_rails_pro/packages/node-renderer/src/shared/licenseValidator.ts (1)
licenseValidator
(164-164)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (10)
- GitHub Check: dummy-app-integration-tests (3.2, 20)
- GitHub Check: dummy-app-integration-tests (3.4, 22)
- GitHub Check: rspec-package-tests (3.4, minimum)
- GitHub Check: rspec-package-tests (3.4, latest)
- GitHub Check: rspec-package-tests (3.2, minimum)
- GitHub Check: rspec-package-tests (3.2, latest)
- GitHub Check: markdown-link-check
- GitHub Check: claude-review
- GitHub Check: build
- GitHub Check: build-and-test
🔇 Additional comments (5)
react_on_rails_pro/.gitignore (1)
76-77
: Good call on ignoring the license key.Helps keep the signed license artifact out of source control; change looks solid.
react_on_rails_pro/packages/node-renderer/tests/licenseValidator.test.ts (4)
16-64
: LGTM: Comprehensive test setup with proper isolation.The test setup correctly:
- Generates RSA key pairs with 2048-bit modulus (aligns with jsonwebtoken v9.x minimum requirement, as per learnings)
- Uses
jest.resetModules()
+ dynamicrequire()
for per-test module isolation- Mocks
process.exit
,console.error
, andconsole.log
to prevent test interruption- Dynamically mocks the public key module with test keys
Note: Mocking console.error/log (lines 27-28) keeps test output clean but may make debugging test failures harder. This is a reasonable trade-off for expected error scenarios.
Based on learnings
72-84
: LGTM: Valid license scenario is properly tested.
86-167
: LGTM: Error scenarios are thoroughly tested.All failure modes correctly verify:
process.exit(1)
is called- Error messages contain expected content
- Free evaluation license notice is displayed
The invalid signature test (lines 126-152) properly generates a different key pair to ensure signature verification fails.
213-252
: LGTM: getLicenseData and getLicenseValidationError tests are clear and correct.Both tests properly verify:
getLicenseData()
returns decoded JWT payload with standard and custom fieldsgetLicenseValidationError()
returns the error message for failed validation
react_on_rails_pro/packages/node-renderer/tests/licenseValidator.test.ts
Outdated
Show resolved
Hide resolved
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]>
Breaking Change: All environments now require valid licenses
- 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]>
bd83e3c
to
9650099
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
♻️ Duplicate comments (7)
react_on_rails_pro/LICENSE_SETUP.md (1)
119-123
: Correct the Ruby API reference in the verification example.The method name and namespace are incorrect. Use
ReactOnRailsPro::Utils.licence_valid?
instead.Apply this diff:
-> ReactOnRails::Utils.react_on_rails_pro_licence_valid? +> ReactOnRailsPro::Utils.licence_valid? # Should return: truereact_on_rails_pro/CI_SETUP.md (1)
348-350
: Correct the Ruby API reference in the verification snippet.The method name and namespace are incorrect. Use
ReactOnRailsPro::Utils.licence_valid?
instead.Apply this diff:
-bundle exec rails runner "puts ReactOnRails::Utils.react_on_rails_pro_licence_valid? ? '✅ License valid' : '❌ License invalid'" +bundle exec rails runner "puts ReactOnRailsPro::Utils.licence_valid? ? '✅ License valid' : '❌ License invalid'"react_on_rails_pro/lib/react_on_rails_pro/license_validator.rb (1)
102-106
: Remove unreachable return statement.The
nil
return on line 106 is unreachable becausehandle_invalid_license
(line 105) raises aReactOnRailsPro::Error
(line 116).Apply this diff:
@validation_error = "No license found. Please set REACT_ON_RAILS_PRO_LICENSE environment variable " \ "or create config/react_on_rails_pro_license.key file. " \ "Get a FREE evaluation license at https://shakacode.com/react-on-rails-pro" handle_invalid_license(@validation_error) - nil + # handle_invalid_license raises an error; this line is unreachablereact_on_rails_pro/packages/node-renderer/src/shared/licenseValidator.ts (4)
54-95
: Unreachable return statements after error handling.The
validateLicense()
method returnsfalse
after callinghandleInvalidLicense()
, but sincehandleInvalidLicense()
exits the process (line 148), these return statements are never reached. Once you refactorhandleInvalidLicense()
to not exit the process (as suggested above), these return statements will become meaningful.No code change needed now - this will be resolved once the
handleInvalidLicense()
refactor is complete.
37-42
: Add error handling to prevent unhandled exceptions.
getLicenseData()
callsloadAndDecodeLicense()
which can throw errors (line 113), but no error handling is present. If called beforeisValid()
, this will result in an unhandled exception.Wrap the call in a try-catch block:
public getLicenseData(): LicenseData | undefined { if (!this.licenseData) { - this.loadAndDecodeLicense(); + try { + this.loadAndDecodeLicense(); + } catch (error) { + // Return undefined on error; validation state tracked separately + return undefined; + } } return this.licenseData; }
70-77
: Add clock tolerance to expiry validation.The manual expiry check doesn't account for clock skew between systems. Based on learnings, the
jsonwebtoken
library (v9.x) supports aclockTolerance
option.Remove the manual check and use the library's built-in validation with clock tolerance:
Remove lines 70-77 (manual expiry check), and update
jwt.verify()
at lines 104-108:const decoded = jwt.verify(licenseString, PUBLIC_KEY, { algorithms: ['RS256'], - // Disable automatic expiration verification so we can handle it manually with custom logic - ignoreExpiration: true + clockTolerance: 60 // Allow 60 seconds clock skew }) as LicenseData;Then handle
TokenExpiredError
in the catch block at line 83:} catch (error: any) { - if (error.name === 'JsonWebTokenError') { + if (error.name === 'TokenExpiredError') { + this.validationError = 'License has expired. ' + + 'Get a FREE evaluation license (3 months) at https://shakacode.com/react-on-rails-pro ' + + 'or upgrade to a paid license for production use.'; + } else if (error.name === 'JsonWebTokenError') { this.validationError = `Invalid license signature: ${error.message}. ` +
144-149
: Remove unconditional process.exit() to enable testing and flexible error handling.The
handleInvalidLicense()
method always exits the process, which makes the validator untestable and prevents development workflows where invalid licenses should warn but not crash. According to the PR context,master.ts
should handle environment-specific behavior.Remove the
process.exit()
call and let the caller decide:private handleInvalidLicense(message: string): void { const fullMessage = `[React on Rails Pro] ${message}`; console.error(fullMessage); - // Validation errors should prevent the application from starting - process.exit(1); + // Let the caller decide whether to exit (e.g., master.ts checks environment) }Then update
master.ts
to handle the exit decision based on environment (this logic already exists at lines 18-24 in master.ts, so no change needed there once this is fixed).
🧹 Nitpick comments (1)
react_on_rails_pro/packages/node-renderer/src/master.ts (1)
18-24
: Remove redundant process.exit() call.The comment states that
handleInvalidLicense
already callsprocess.exit(1)
(which it does at line 148 in licenseValidator.ts), making the explicitprocess.exit(1)
here unreachable and redundant.Either remove the redundant exit or update the comment to clarify this is an intentional safety mechanism:
if (!isLicenseValid()) { const error = getLicenseValidationError() || 'Invalid license'; log.error(`[React on Rails Pro] ${error}`); - // License validation already calls process.exit(1) in handleInvalidLicense - // But we add this for safety in case the validator changes - process.exit(1); + // handleInvalidLicense in the validator already exits; this is unreachable }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (5)
react_on_rails_pro/Gemfile.lock
is excluded by!**/*.lock
react_on_rails_pro/spec/dummy/Gemfile.lock
is excluded by!**/*.lock
react_on_rails_pro/spec/dummy/yarn.lock
is excluded by!**/yarn.lock
,!**/*.lock
react_on_rails_pro/spec/execjs-compatible-dummy/Gemfile.lock
is excluded by!**/*.lock
react_on_rails_pro/yarn.lock
is excluded by!**/yarn.lock
,!**/*.lock
📒 Files selected for processing (19)
lib/react_on_rails/helper.rb
(1 hunks)react_on_rails_pro/.gitignore
(1 hunks)react_on_rails_pro/CI_SETUP.md
(1 hunks)react_on_rails_pro/LICENSE_SETUP.md
(1 hunks)react_on_rails_pro/lib/react_on_rails_pro.rb
(1 hunks)react_on_rails_pro/lib/react_on_rails_pro/engine.rb
(1 hunks)react_on_rails_pro/lib/react_on_rails_pro/license_public_key.rb
(1 hunks)react_on_rails_pro/lib/react_on_rails_pro/license_validator.rb
(1 hunks)react_on_rails_pro/lib/react_on_rails_pro/utils.rb
(1 hunks)react_on_rails_pro/package.json
(2 hunks)react_on_rails_pro/packages/node-renderer/src/master.ts
(1 hunks)react_on_rails_pro/packages/node-renderer/src/shared/licensePublicKey.ts
(1 hunks)react_on_rails_pro/packages/node-renderer/src/shared/licenseValidator.ts
(1 hunks)react_on_rails_pro/packages/node-renderer/tests/licenseValidator.test.ts
(1 hunks)react_on_rails_pro/rakelib/public_key_management.rake
(1 hunks)react_on_rails_pro/react_on_rails_pro.gemspec
(2 hunks)react_on_rails_pro/spec/dummy/.gitignore
(1 hunks)react_on_rails_pro/spec/execjs-compatible-dummy/.gitignore
(1 hunks)react_on_rails_pro/spec/react_on_rails_pro/license_validator_spec.rb
(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (12)
- react_on_rails_pro/lib/react_on_rails_pro/engine.rb
- react_on_rails_pro/package.json
- react_on_rails_pro/lib/react_on_rails_pro/license_public_key.rb
- react_on_rails_pro/spec/execjs-compatible-dummy/.gitignore
- react_on_rails_pro/packages/node-renderer/src/shared/licensePublicKey.ts
- react_on_rails_pro/lib/react_on_rails_pro.rb
- react_on_rails_pro/spec/react_on_rails_pro/license_validator_spec.rb
- react_on_rails_pro/packages/node-renderer/tests/licenseValidator.test.ts
- lib/react_on_rails/helper.rb
- react_on_rails_pro/lib/react_on_rails_pro/utils.rb
- react_on_rails_pro/spec/dummy/.gitignore
- react_on_rails_pro/react_on_rails_pro.gemspec
🧰 Additional context used
📓 Path-based instructions (3)
{Gemfile,Rakefile,config.ru,**/*.{rb,rake,gemspec,ru}}
📄 CodeRabbit inference engine (CLAUDE.md)
{Gemfile,Rakefile,config.ru,**/*.{rb,rake,gemspec,ru}}
: All Ruby code must pass RuboCop with zero offenses before commit/push
RuboCop is the sole authority for Ruby file formatting; never manually format Ruby files
Files:
react_on_rails_pro/rakelib/public_key_management.rake
react_on_rails_pro/lib/react_on_rails_pro/license_validator.rb
**/*.{js,jsx,ts,tsx,css,scss,json,yml,yaml,md}
📄 CodeRabbit inference engine (CLAUDE.md)
Prettier is the sole authority for formatting all non-Ruby files; never manually format them
Files:
react_on_rails_pro/LICENSE_SETUP.md
react_on_rails_pro/packages/node-renderer/src/shared/licenseValidator.ts
react_on_rails_pro/packages/node-renderer/src/master.ts
react_on_rails_pro/CI_SETUP.md
**/*.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Use ESLint for JS/TS code (lint via rake lint or yarn lint)
Files:
react_on_rails_pro/packages/node-renderer/src/shared/licenseValidator.ts
react_on_rails_pro/packages/node-renderer/src/master.ts
🧠 Learnings (1)
📚 Learning: 2025-02-18T13:08:01.477Z
Learnt from: AbanoubGhadban
PR: shakacode/react_on_rails#1644
File: lib/react_on_rails/helper.rb:190-197
Timestamp: 2025-02-18T13:08:01.477Z
Learning: RSC support validation in React on Rails Pro is handled through a chain of validations:
1. Pro version check in `run_stream_inside_fiber`
2. RSC support check during pack generation via `ReactOnRailsPro.configuration.enable_rsc_support`
3. RSC support validation during component registration
This makes additional validation in the helper methods unnecessary.
Applied to files:
react_on_rails_pro/lib/react_on_rails_pro/license_validator.rb
🧬 Code graph analysis (2)
react_on_rails_pro/packages/node-renderer/src/shared/licenseValidator.ts (1)
react_on_rails_pro/packages/node-renderer/src/shared/licensePublicKey.ts (1)
PUBLIC_KEY
(6-14)
react_on_rails_pro/packages/node-renderer/src/master.ts (2)
react_on_rails_pro/packages/node-renderer/src/shared/configBuilder.ts (1)
Config
(25-83)react_on_rails_pro/packages/node-renderer/src/shared/licenseValidator.ts (2)
isLicenseValid
(166-168)getLicenseValidationError
(174-176)
🪛 markdownlint-cli2 (0.18.1)
react_on_rails_pro/CI_SETUP.md
215-215: Bare URL used
(MD034, no-bare-urls)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (9)
- GitHub Check: dummy-app-integration-tests (3.4, 22)
- GitHub Check: rspec-package-tests (3.2, latest)
- GitHub Check: rspec-package-tests (3.2, minimum)
- GitHub Check: rspec-package-tests (3.4, latest)
- GitHub Check: rspec-package-tests (3.4, minimum)
- GitHub Check: claude-review
- GitHub Check: markdown-link-check
- GitHub Check: build
- GitHub Check: build-and-test
🔇 Additional comments (2)
react_on_rails_pro/rakelib/public_key_management.rake (2)
18-111
: LGTM! Comprehensive error handling and clear user feedback.The
update_public_key
task handles network errors, JSON parsing errors, and general exceptions well. The support for multiple sources (production, local, custom) provides good flexibility for development and testing.
114-173
: LGTM! Thorough verification with helpful diagnostics.The
verify_public_key
task provides comprehensive verification including key size validation, file existence checks, and optional license validation. The error messages guide users toward resolution effectively.
ruby_content = <<~RUBY.strip_heredoc | ||
# frozen_string_literal: true | ||
module ReactOnRailsPro | ||
module LicensePublicKey | ||
# ShakaCode's public key for React on Rails Pro license verification | ||
# The private key corresponding to this public key is held by ShakaCode | ||
# and is never committed to the repository | ||
# Last updated: #{Time.now.utc.strftime("%Y-%m-%d %H:%M:%S UTC")} | ||
# Source: #{api_url} | ||
KEY = OpenSSL::PKey::RSA.new(<<~PEM.strip.strip_heredoc) | ||
#{public_key.strip} | ||
PEM | ||
end | ||
end | ||
RUBY |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion | 🟠 Major
🧩 Analysis chain
Verify ActiveSupport availability and remove redundant strip calls.
- The
strip_heredoc
method requires ActiveSupport, which should be available in a Rails environment. However, ensure the rake task loads Rails/ActiveSupport properly. - Line 70 has redundant
.strip
calls:public_key.strip.strip_heredoc
- the.strip_heredoc
already strips the content.
Apply this diff to remove the redundant strip:
- KEY = OpenSSL::PKey::RSA.new(<<~PEM.strip.strip_heredoc)
+ KEY = OpenSSL::PKey::RSA.new(<<~PEM.strip_heredoc)
#{public_key.strip}
PEM
Verify that ActiveSupport is loaded when the rake task runs:
🏁 Script executed:
#!/bin/bash
# Check if the rake task requires Rails/ActiveSupport
rg -n "require.*active_support" react_on_rails_pro/rakelib/
rg -n "Rails\." react_on_rails_pro/rakelib/public_key_management.rake
Length of output: 84
Add ActiveSupport require and remove redundant strip
- Prepend to
react_on_rails_pro/rakelib/public_key_management.rake
:require "active_support/core_ext/string"
- Update heredoc invocation:
@@ react_on_rails_pro/rakelib/public_key_management.rake - KEY = OpenSSL::PKey::RSA.new(<<~PEM.strip.strip_heredoc) + KEY = OpenSSL::PKey::RSA.new(<<~PEM.strip_heredoc) #{public_key.strip} PEM
🤖 Prompt for AI Agents
In react_on_rails_pro/rakelib/public_key_management.rake around lines 60 to 75,
add require "active_support/core_ext/string" at the top of the file (before
using strip_heredoc) and simplify the heredoc by removing the redundant call to
strip on the embedded public_key; keep strip_heredoc on the outer heredoc only
and pass the raw public_key interpolation (e.g., #{public_key}) so the resulting
Ruby constant uses ActiveSupport's strip_heredoc correctly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
♻️ Duplicate comments (1)
react_on_rails_pro/LICENSE_SETUP.md (1)
119-123
: Fix Ruby API reference in verification snippetUse the actual helper and namespace.
Apply:
rails console -> ReactOnRails::Utils.react_on_rails_pro? +# Use the Utils API provided by the Pro gem +> ReactOnRailsPro::Utils.licence_valid? # Should return: trueBased on learnings
🧹 Nitpick comments (2)
react_on_rails_pro/LICENSE_SETUP.md (1)
60-71
: Add file permission guidance for the license keyPrevent world-readable license files in repos/containers.
Apply:
echo "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..." > config/react_on_rails_pro_license.key + +# Restrict permissions (recommended) +chmod 600 config/react_on_rails_pro_license.keyreact_on_rails_pro/CI_SETUP.md (1)
371-371
: Fix bare URL (MD034)Use Markdown link syntax.
- - License expired (get a new FREE license at https://shakacode.com/react-on-rails-pro) + - License expired (get a new FREE license at [shakacode.com/react-on-rails-pro](https://shakacode.com/react-on-rails-pro))As per coding guidelines
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (7)
lib/react_on_rails/pro_utils.rb
(1 hunks)lib/react_on_rails/utils.rb
(1 hunks)react_on_rails_pro/CI_SETUP.md
(1 hunks)react_on_rails_pro/LICENSE_SETUP.md
(1 hunks)spec/dummy/spec/helpers/react_on_rails_helper_spec.rb
(10 hunks)spec/dummy/spec/system/integration_spec.rb
(1 hunks)spec/react_on_rails/react_component/render_options_spec.rb
(1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
{Gemfile,Rakefile,config.ru,**/*.{rb,rake,gemspec,ru}}
📄 CodeRabbit inference engine (CLAUDE.md)
{Gemfile,Rakefile,config.ru,**/*.{rb,rake,gemspec,ru}}
: All Ruby code must pass RuboCop with zero offenses before commit/push
RuboCop is the sole authority for Ruby file formatting; never manually format Ruby files
Files:
lib/react_on_rails/pro_utils.rb
spec/react_on_rails/react_component/render_options_spec.rb
lib/react_on_rails/utils.rb
spec/dummy/spec/helpers/react_on_rails_helper_spec.rb
spec/dummy/spec/system/integration_spec.rb
**/*.{js,jsx,ts,tsx,css,scss,json,yml,yaml,md}
📄 CodeRabbit inference engine (CLAUDE.md)
Prettier is the sole authority for formatting all non-Ruby files; never manually format them
Files:
react_on_rails_pro/LICENSE_SETUP.md
react_on_rails_pro/CI_SETUP.md
🧠 Learnings (4)
📓 Common learnings
Learnt from: AbanoubGhadban
PR: shakacode/react_on_rails#1644
File: lib/react_on_rails/helper.rb:190-197
Timestamp: 2025-02-18T13:08:01.477Z
Learning: RSC support validation in React on Rails Pro is handled through a chain of validations:
1. Pro version check in `run_stream_inside_fiber`
2. RSC support check during pack generation via `ReactOnRailsPro.configuration.enable_rsc_support`
3. RSC support validation during component registration
This makes additional validation in the helper methods unnecessary.
📚 Learning: 2025-09-15T21:24:48.207Z
Learnt from: AbanoubGhadban
PR: shakacode/react_on_rails#1781
File: node_package/src/ClientSideRenderer.ts:82-95
Timestamp: 2025-09-15T21:24:48.207Z
Learning: In React on Rails, the force_load feature includes both explicit `data-force-load="true"` usage and the ability to hydrate components during the page loading state (`document.readyState === 'loading'`). Both capabilities require a Pro license, so the condition `!railsContext.rorPro && (isComponentForceLoaded || document.readyState === 'loading')` correctly gates both scenarios.
Applied to files:
lib/react_on_rails/pro_utils.rb
spec/react_on_rails/react_component/render_options_spec.rb
lib/react_on_rails/utils.rb
spec/dummy/spec/helpers/react_on_rails_helper_spec.rb
spec/dummy/spec/system/integration_spec.rb
📚 Learning: 2025-02-18T13:08:01.477Z
Learnt from: AbanoubGhadban
PR: shakacode/react_on_rails#1644
File: lib/react_on_rails/helper.rb:190-197
Timestamp: 2025-02-18T13:08:01.477Z
Learning: RSC support validation in React on Rails Pro is handled through a chain of validations:
1. Pro version check in `run_stream_inside_fiber`
2. RSC support check during pack generation via `ReactOnRailsPro.configuration.enable_rsc_support`
3. RSC support validation during component registration
This makes additional validation in the helper methods unnecessary.
Applied to files:
lib/react_on_rails/pro_utils.rb
spec/react_on_rails/react_component/render_options_spec.rb
lib/react_on_rails/utils.rb
spec/dummy/spec/helpers/react_on_rails_helper_spec.rb
📚 Learning: 2025-02-18T13:08:01.477Z
Learnt from: AbanoubGhadban
PR: shakacode/react_on_rails#1644
File: lib/react_on_rails/helper.rb:190-197
Timestamp: 2025-02-18T13:08:01.477Z
Learning: RSC support validation is handled in deeper level calls of the React on Rails Pro codebase, so it doesn't need to be validated again in the `rsc_payload_react_component` helper method.
Applied to files:
lib/react_on_rails/utils.rb
🧬 Code graph analysis (1)
lib/react_on_rails/pro_utils.rb (1)
lib/react_on_rails/utils.rb (1)
react_on_rails_pro?
(237-241)
🪛 markdownlint-cli2 (0.18.1)
react_on_rails_pro/CI_SETUP.md
217-217: Bare URL used
(MD034, no-bare-urls)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (10)
- GitHub Check: dummy-app-integration-tests (3.4, 22)
- GitHub Check: dummy-app-integration-tests (3.2, 20)
- GitHub Check: rspec-package-tests (3.4, latest)
- GitHub Check: rspec-package-tests (3.2, minimum)
- GitHub Check: rspec-package-tests (3.4, minimum)
- GitHub Check: markdown-link-check
- GitHub Check: rspec-package-tests (3.2, latest)
- GitHub Check: build
- GitHub Check: claude-review
- GitHub Check: build-and-test
🔇 Additional comments (12)
spec/dummy/spec/helpers/react_on_rails_helper_spec.rb (8)
24-26
: LGTM!The test setup correctly stubs the renamed method
react_on_rails_pro?
to returntrue
, maintaining the same test behavior as before.
388-433
: LGTM!Test scenarios correctly updated to stub
react_on_rails_pro?
instead of the previous method name. The test coverage for both licensed and unlicensed scenarios remains comprehensive.
435-450
: LGTM!Pro license validation test correctly updated to use the new method name.
482-492
: LGTM!Test properly stubs the new method and verifies the Pro licensing badge behavior.
494-506
: LGTM!Test correctly verifies behavior when Pro is installed and licensed.
540-554
: LGTM!Redux store test properly updated to use the new Pro check method.
556-564
: LGTM!Test correctly verifies that the Pro badge is not shown when license is invalid.
566-576
: LGTM!Test correctly verifies Pro licensing behavior with the updated method stub.
spec/react_on_rails/react_component/render_options_spec.rb (1)
23-25
: LGTM!Test setup correctly updated to stub the renamed method, maintaining proper test isolation for Pro feature testing.
spec/dummy/spec/system/integration_spec.rb (1)
89-99
: LGTM!Shared context correctly updated to use the new method name, maintaining consistent Pro feature enablement across integration tests.
lib/react_on_rails/utils.rb (1)
231-241
: Ignore license validation concern inreact_on_rails_pro?
license is always validated at startup via the ReactOnRailsPro engine initializer, so this helper cannot return true for an unlicensed installation.Likely an incorrect or invalid review comment.
lib/react_on_rails/pro_utils.rb (1)
8-11
: Cannot confirm support_pro_features? enforces licensing
I couldn’t locate thereact_on_rails_pro?
implementation to verify it checks license validity. Ensuresupport_pro_features?
either validates the license (e.g. viaReactOnRailsPro::LicenseValidator
) or update the @return doc to “installed” only.
…pdate related tests for improved clarity and consistency
… production environments and update related tests for clarity and coverage
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (7)
react_on_rails_pro/LICENSE_SETUP.md (1)
240-240
: Consider formatting the URL as a markdown link.The bare URL can be formatted as a markdown link for consistency with other URLs in the document.
Apply this diff:
-3. **Email**: [email protected] +3. **Email**: [[email protected]](mailto:[email protected])react_on_rails_pro/lib/react_on_rails_pro/license_validator.rb (3)
36-37
: Consider reusing license_data to avoid potential double decoding.The
validate_license
method callsload_and_decode_license
directly, butlicense_data
also calls the same method with memoization. If a caller invokes bothvalidate!
andlicense_data
, the license may be decoded twice.Apply this diff to use the memoized accessor:
def validate_license - license = load_and_decode_license + license = license_dataBased on past review comments.
47-51
: Consider adding clock tolerance for expiry validation.The expiry check
current_time > exp_time
doesn't account for clock skew between systems. In distributed environments with clock drift, this can cause false rejections of valid licenses.Add a clock tolerance constant and adjust the comparison:
# Grace period: 1 month (in seconds) GRACE_PERIOD_SECONDS = 30 * 24 * 60 * 60 + # Clock tolerance: allow 60 seconds of clock skew + CLOCK_TOLERANCE_SECONDS = 60 def validate_license license = load_and_decode_license # ... exp field check ... # Check expiry with grace period for production current_time = Time.now.to_i exp_time = license["exp"] - if current_time > exp_time + if current_time > exp_time + CLOCK_TOLERANCE_SECONDSThis prevents short clock drift from triggering false expiration errors.
Based on past review comments.
130-132
: Use config_path variable in error message for consistency.The error message hardcodes the path string instead of using the
config_path
variable defined on line 127. Using the variable ensures the error message stays in sync if the path changes.Apply this diff:
@validation_error = "No license found. Please set REACT_ON_RAILS_PRO_LICENSE environment variable " \ - "or create config/react_on_rails_pro_license.key file. " \ + "or create #{config_path} file. " \ "Get a FREE evaluation license at https://shakacode.com/react-on-rails-pro"Based on past review comments.
react_on_rails_pro/packages/node-renderer/src/shared/licenseValidator.ts (3)
221-226
: Add error handling to prevent unhandled exceptions.
getLicenseData()
callsloadAndDecodeLicense()
which can throw JWT verification errors or file system errors. If called beforevalidateLicense()
, this could result in an unhandled exception.Wrap the call in a try-catch block:
export function getLicenseData(): LicenseData | undefined { if (!cachedLicenseData) { - loadAndDecodeLicense(); + try { + loadAndDecodeLicense(); + } catch (error) { + // Return undefined on error; validation state tracked separately + console.error(`[React on Rails Pro] Error loading license data: ${(error as Error).message}`); + return undefined; + } } return cachedLicenseData; }Based on past review comments.
119-130
: Consider adding runtime validation of decoded JWT payload.The result of
jwt.verify()
is cast toLicenseData
without runtime validation. While signature verification ensures the token is authentic, it doesn't guarantee the payload contains expected fields with correct types.Add basic runtime validation before the cast:
const decoded = jwt.verify(licenseString, PUBLIC_KEY, { algorithms: ['RS256'], ignoreExpiration: true, }) as LicenseData; + + // Validate decoded payload structure + if (typeof decoded !== 'object' || decoded === null || typeof decoded.exp !== 'number') { + throw new Error('Invalid license payload structure: missing or invalid exp field'); + } cachedLicenseData = decoded; return decoded;Based on past review comments.
151-156
: Consider adding clock tolerance to manual expiry check.While
ignoreExpiration: true
allows custom validation logic, the manual expiry check at line 156 (currentTime > expTime
) could still benefit from clock tolerance to handle minor clock skew between systems.Add a clock tolerance constant:
// Grace period: 1 month (in seconds) const GRACE_PERIOD_SECONDS = 30 * 24 * 60 * 60; +// Clock tolerance: allow 60 seconds of clock skew +const CLOCK_TOLERANCE_SECONDS = 60; // ... in performValidation ... const currentTime = Date.now() / 1000; const expTime = license.exp; - if (currentTime > expTime) { + if (currentTime > expTime + CLOCK_TOLERANCE_SECONDS) {This prevents false rejections due to minor clock drift, which is especially important in distributed systems.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (5)
react_on_rails_pro/LICENSE_SETUP.md
(1 hunks)react_on_rails_pro/lib/react_on_rails_pro/license_validator.rb
(1 hunks)react_on_rails_pro/packages/node-renderer/src/shared/licenseValidator.ts
(1 hunks)react_on_rails_pro/packages/node-renderer/tests/licenseValidator.test.ts
(1 hunks)react_on_rails_pro/spec/react_on_rails_pro/license_validator_spec.rb
(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- react_on_rails_pro/packages/node-renderer/tests/licenseValidator.test.ts
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{js,jsx,ts,tsx,css,scss,json,yml,yaml,md}
📄 CodeRabbit inference engine (CLAUDE.md)
Prettier is the sole authority for formatting all non-Ruby files; never manually format them
Files:
react_on_rails_pro/packages/node-renderer/src/shared/licenseValidator.ts
react_on_rails_pro/LICENSE_SETUP.md
**/*.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Use ESLint for JS/TS code (lint via rake lint or yarn lint)
Files:
react_on_rails_pro/packages/node-renderer/src/shared/licenseValidator.ts
{Gemfile,Rakefile,config.ru,**/*.{rb,rake,gemspec,ru}}
📄 CodeRabbit inference engine (CLAUDE.md)
{Gemfile,Rakefile,config.ru,**/*.{rb,rake,gemspec,ru}}
: All Ruby code must pass RuboCop with zero offenses before commit/push
RuboCop is the sole authority for Ruby file formatting; never manually format Ruby files
Files:
react_on_rails_pro/lib/react_on_rails_pro/license_validator.rb
react_on_rails_pro/spec/react_on_rails_pro/license_validator_spec.rb
🧠 Learnings (1)
📚 Learning: 2025-02-13T14:29:49.267Z
Learnt from: AbanoubGhadban
PR: shakacode/react_on_rails#1644
File: spec/react_on_rails/utils_spec.rb:218-218
Timestamp: 2025-02-13T14:29:49.267Z
Learning: In RSpec tests, prefer using local variables over constants within test blocks to avoid constant redefinition warnings and maintain better test isolation.
Applied to files:
react_on_rails_pro/spec/react_on_rails_pro/license_validator_spec.rb
🧬 Code graph analysis (2)
react_on_rails_pro/packages/node-renderer/src/shared/licenseValidator.ts (1)
react_on_rails_pro/packages/node-renderer/src/shared/licensePublicKey.ts (1)
PUBLIC_KEY
(11-19)
react_on_rails_pro/spec/react_on_rails_pro/license_validator_spec.rb (1)
react_on_rails_pro/lib/react_on_rails_pro/license_validator.rb (5)
public_key
(136-138)reset!
(19-23)validate!
(6-154)validate!
(13-17)license_data
(25-27)
🪛 markdownlint-cli2 (0.18.1)
react_on_rails_pro/LICENSE_SETUP.md
240-240: Bare URL used
(MD034, no-bare-urls)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
- GitHub Check: rspec-package-tests (3.2, latest)
- GitHub Check: rspec-package-tests (3.4, minimum)
- GitHub Check: rspec-package-tests (3.2, minimum)
- GitHub Check: rspec-package-tests (3.4, latest)
- GitHub Check: markdown-link-check
- GitHub Check: build
- GitHub Check: claude-review
- GitHub Check: build-and-test
🔇 Additional comments (4)
react_on_rails_pro/spec/react_on_rails_pro/license_validator_spec.rb (1)
1-267
: Excellent test coverage!The test suite comprehensively covers all critical scenarios:
- Valid license with caching behavior
- Expired licenses with environment-specific grace period handling
- Missing required fields (exp)
- Invalid signatures
- Missing license with proper error messages
- Config file loading
- Public API (license_data, validation_error, reset)
The tests properly use RSpec idioms and ensure test isolation through before/after blocks.
react_on_rails_pro/lib/react_on_rails_pro/license_validator.rb (1)
108-110
: Parameter is well-documented.The third parameter to
JWT.decode
is properly explained by the inline comment on lines 108-109. The code clearly states that this must always betrue
for security, which addresses the concern from past review comments.react_on_rails_pro/packages/node-renderer/src/shared/licenseValidator.ts (1)
119-127
: Excellent security practices in JWT verification.The implementation correctly:
- Enforces RS256 algorithm exclusively to prevent "alg=none" attacks
- Includes clear comments about algorithm whitelist risks
- Uses manual expiration handling with grace period logic
- Properly documents security considerations
This demonstrates strong understanding of JWT security best practices.
Based on learnings about jsonwebtoken security requirements.
react_on_rails_pro/LICENSE_SETUP.md (1)
134-137
: Documentation is correct
The methodreact_on_rails_pro?
is defined onReactOnRails::Utils
, so the namespace and name in the docs are accurate.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
♻️ Duplicate comments (3)
react_on_rails_pro/lib/react_on_rails_pro/license_validator.rb (2)
154-167
: Consider using path interpolation in error message.The implementation correctly checks ENV first, then the config file. However, the error message on lines 163-165 hardcodes the path string instead of interpolating the
config_path
variable.Apply this diff for better maintainability:
config_path = Rails.root.join("config", "react_on_rails_pro_license.key") return File.read(config_path).strip if config_path.exist? @validation_error = "No license found. Please set REACT_ON_RAILS_PRO_LICENSE environment variable " \ - "or create config/react_on_rails_pro_license.key file. " \ + "or create #{config_path} file. " \ "Get a FREE evaluation license at https://shakacode.com/react-on-rails-pro"This ensures the path in the error message stays synchronized if the path logic changes.
Based on past review comments.
72-112
: Clock skew can cause false license rejections.The expiry check on line 86 (
current_time > exp_time
) doesn't account for clock skew between systems. In production environments with multiple servers or slight clock drift, valid licenses could be incorrectly rejected.Consider adding a small clock tolerance (e.g., 60 seconds):
# Check expiry with grace period for production + # Clock tolerance in seconds (allow 60 seconds of clock skew) + CLOCK_TOLERANCE = 60 + current_time = Time.now.to_i exp_time = license["exp"] grace_days = nil - if current_time > exp_time + if current_time > (exp_time + CLOCK_TOLERANCE) days_expired = ((current_time - exp_time) / (24 * 60 * 60)).to_iBased on past review comments.
react_on_rails_pro/lib/react_on_rails_pro/engine.rb (1)
13-22
: Critical: Missing skip guards for asset precompilation and RSpec tests.Per previous review comments, this initializer is missing essential guards to skip license validation during asset precompilation and RSpec test runs. Without these guards:
- License validation will fail during
assets:precompile
when the license file may not be available- RSpec tests that use stub keys will be disrupted
Apply this diff to restore the necessary skip guards:
initializer "react_on_rails_pro.validate_license" do # Use after_initialize to ensure Rails.logger is available config.after_initialize do + # Skip license validation during asset precompilation + next if defined?(Rake) && (Rake.application.top_level_tasks.include?("assets:precompile") rescue false) + + # Skip license validation when running RSpec tests + # Tests mock the license validation with stub keys + next if defined?(RSpec) + Rails.logger.info "[React on Rails Pro] Validating license..." ReactOnRailsPro::LicenseValidator.validated_license_data!Based on past review comments.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (5)
lib/react_on_rails/helper.rb
(1 hunks)react_on_rails_pro/lib/react_on_rails_pro/engine.rb
(1 hunks)react_on_rails_pro/lib/react_on_rails_pro/license_validator.rb
(1 hunks)react_on_rails_pro/lib/react_on_rails_pro/utils.rb
(2 hunks)react_on_rails_pro/spec/react_on_rails_pro/license_validator_spec.rb
(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- react_on_rails_pro/spec/react_on_rails_pro/license_validator_spec.rb
🧰 Additional context used
📓 Path-based instructions (1)
{Gemfile,Rakefile,config.ru,**/*.{rb,rake,gemspec,ru}}
📄 CodeRabbit inference engine (CLAUDE.md)
{Gemfile,Rakefile,config.ru,**/*.{rb,rake,gemspec,ru}}
: All Ruby code must pass RuboCop with zero offenses before commit/push
RuboCop is the sole authority for Ruby file formatting; never manually format Ruby files
Files:
lib/react_on_rails/helper.rb
react_on_rails_pro/lib/react_on_rails_pro/utils.rb
react_on_rails_pro/lib/react_on_rails_pro/license_validator.rb
react_on_rails_pro/lib/react_on_rails_pro/engine.rb
🧠 Learnings (1)
📚 Learning: 2025-04-09T12:56:10.756Z
Learnt from: AbanoubGhadban
PR: shakacode/react_on_rails#1696
File: node_package/src/RSCPayloadContainer.ts:0-0
Timestamp: 2025-04-09T12:56:10.756Z
Learning: In the react_on_rails codebase, RSC payloads are already stringified using `JSON.stringify()` before being processed by the `escapeScript` function, which handles escaping of special characters. The function only needs to handle specific HTML markers like comments and closing script tags.
Applied to files:
lib/react_on_rails/helper.rb
🧬 Code graph analysis (3)
lib/react_on_rails/helper.rb (2)
lib/react_on_rails/utils.rb (1)
react_on_rails_pro?
(236-244)react_on_rails_pro/lib/react_on_rails_pro/utils.rb (1)
pro_attribution_comment
(170-184)
react_on_rails_pro/lib/react_on_rails_pro/utils.rb (1)
react_on_rails_pro/lib/react_on_rails_pro/license_validator.rb (2)
grace_days_remaining
(54-60)evaluation?
(46-50)
react_on_rails_pro/lib/react_on_rails_pro/engine.rb (1)
react_on_rails_pro/lib/react_on_rails_pro/license_validator.rb (2)
validated_license_data!
(6-187)validated_license_data!
(12-37)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
- GitHub Check: examples (3.4, latest)
- GitHub Check: build
- GitHub Check: rspec-package-tests (3.2, minimum)
- GitHub Check: rspec-package-tests (3.4, latest)
- GitHub Check: claude-review
- GitHub Check: build-and-test
🔇 Additional comments (8)
lib/react_on_rails/helper.rb (2)
616-630
: LGTM!The modification cleanly integrates the attribution comment before the Rails context script tag. The separation of attribution logic into a dedicated helper method improves maintainability.
632-640
: LGTM!The attribution comment method correctly delegates to the Pro version when active and provides an appropriate fallback for the open source version.
react_on_rails_pro/lib/react_on_rails_pro/utils.rb (1)
168-184
: LGTM!The Pro attribution comment generation correctly handles different license states (grace period, evaluation, paid) and produces informative HTML comments. The implementation properly delegates to
LicenseValidator
methods.react_on_rails_pro/lib/react_on_rails_pro/license_validator.rb (5)
12-37
: LGTM!The license validation method implements proper caching, comprehensive error handling, and clear separation of concerns between loading/decoding and validation logic.
39-60
: LGTM!The helper methods (
reset!
,evaluation?
,grace_days_remaining
) are well-implemented with proper caching behavior and clear logic.
114-131
: LGTM!The grace period helper methods are clearly implemented with straightforward logic for production checks, grace period validation, and remaining days calculation.
133-152
: Excellent security-conscious implementation!The JWT decoding correctly enforces signature verification with RS256, and the comprehensive comments warn about critical security vulnerabilities (algorithm bypassing, signature verification). The manual expiration handling is appropriate for the custom grace period logic.
169-186
: LGTM!The helper methods correctly delegate to the public key module, provide consistent error handling with logging, and conditionally log license metadata.
…for improved clarity and consistency
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (1)
lib/react_on_rails/utils.rb (1)
236-244
: Fix memoization for the gem unavailability case.The
return false
statement on line 240 exits the method before the assignment to@react_on_rails_pro
completes, preventing memoization when the gem is unavailable. This causesgem_available?("react_on_rails_pro")
to be checked on every call when the gem is not installed.Apply this diff to fix the memoization:
def self.react_on_rails_pro? return @react_on_rails_pro if defined?(@react_on_rails_pro) - @react_on_rails_pro = begin - return false unless gem_available?("react_on_rails_pro") - - ReactOnRailsPro::Utils.validated_license_data!.present? - end + @react_on_rails_pro = if gem_available?("react_on_rails_pro") + ReactOnRailsPro::Utils.validated_license_data!.present? + else + false + end endThis ensures
@react_on_rails_pro
is set tofalse
and memoized when the gem is unavailable, eliminating redundant checks on subsequent calls.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
lib/react_on_rails/utils.rb
(1 hunks)react_on_rails_pro/lib/react_on_rails_pro/utils.rb
(2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- react_on_rails_pro/lib/react_on_rails_pro/utils.rb
🧰 Additional context used
📓 Path-based instructions (1)
{Gemfile,Rakefile,config.ru,**/*.{rb,rake,gemspec,ru}}
📄 CodeRabbit inference engine (CLAUDE.md)
{Gemfile,Rakefile,config.ru,**/*.{rb,rake,gemspec,ru}}
: All Ruby code must pass RuboCop with zero offenses before commit/push
RuboCop is the sole authority for Ruby file formatting; never manually format Ruby files
Files:
lib/react_on_rails/utils.rb
🧠 Learnings (3)
📚 Learning: 2025-02-18T13:08:01.477Z
Learnt from: AbanoubGhadban
PR: shakacode/react_on_rails#1644
File: lib/react_on_rails/helper.rb:190-197
Timestamp: 2025-02-18T13:08:01.477Z
Learning: RSC support validation in React on Rails Pro is handled through a chain of validations:
1. Pro version check in `run_stream_inside_fiber`
2. RSC support check during pack generation via `ReactOnRailsPro.configuration.enable_rsc_support`
3. RSC support validation during component registration
This makes additional validation in the helper methods unnecessary.
Applied to files:
lib/react_on_rails/utils.rb
📚 Learning: 2025-09-15T21:24:48.207Z
Learnt from: AbanoubGhadban
PR: shakacode/react_on_rails#1781
File: node_package/src/ClientSideRenderer.ts:82-95
Timestamp: 2025-09-15T21:24:48.207Z
Learning: In React on Rails, the force_load feature includes both explicit `data-force-load="true"` usage and the ability to hydrate components during the page loading state (`document.readyState === 'loading'`). Both capabilities require a Pro license, so the condition `!railsContext.rorPro && (isComponentForceLoaded || document.readyState === 'loading')` correctly gates both scenarios.
Applied to files:
lib/react_on_rails/utils.rb
📚 Learning: 2025-02-18T13:08:01.477Z
Learnt from: AbanoubGhadban
PR: shakacode/react_on_rails#1644
File: lib/react_on_rails/helper.rb:190-197
Timestamp: 2025-02-18T13:08:01.477Z
Learning: RSC support validation is handled in deeper level calls of the React on Rails Pro codebase, so it doesn't need to be validated again in the `rsc_payload_react_component` helper method.
Applied to files:
lib/react_on_rails/utils.rb
🧬 Code graph analysis (1)
lib/react_on_rails/utils.rb (2)
react_on_rails_pro/lib/react_on_rails_pro/utils.rb (1)
validated_license_data!
(23-25)react_on_rails_pro/lib/react_on_rails_pro/license_validator.rb (2)
validated_license_data!
(6-187)validated_license_data!
(12-37)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (10)
- GitHub Check: dummy-app-integration-tests (3.4, 22)
- GitHub Check: dummy-app-integration-tests (3.2, 20)
- GitHub Check: rspec-package-tests (3.2, latest)
- GitHub Check: rspec-package-tests (3.2, minimum)
- GitHub Check: rspec-package-tests (3.4, minimum)
- GitHub Check: rspec-package-tests (3.4, latest)
- GitHub Check: build
- GitHub Check: markdown-link-check
- GitHub Check: claude-review
- GitHub Check: build-and-test
…en source license comments
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (2)
react_on_rails_pro/spec/react_on_rails_pro/utils_spec.rb (2)
223-225
: Optional: Remove unnecessary Rails.env stubbingThe
pro_attribution_comment
implementation (as shown in the relevant code snippets) doesn't referenceRails.env
, making this stub unnecessary. Consider removing it unless it's defensive for planned changes.describe ".pro_attribution_comment" do - before do - allow(Rails).to receive(:env).and_return(ActiveSupport::StringInquirer.new("production")) - end - context "when license is valid and not in grace period" do
256-259
: Consider enhancing pluralization in the implementationThe test correctly verifies that when
grace_days_remaining
returns 1, the output is "1 days remaining". However, this is grammatically incorrect—it should be "1 day remaining" (singular).While the test accurately reflects current behavior, consider enhancing the implementation to pluralize correctly:
In
react_on_rails_pro/lib/react_on_rails_pro/utils.rb
, apply this enhancement:def self.pro_attribution_comment base = "Powered by React on Rails Pro (c) ShakaCode" grace_days = ReactOnRailsPro::LicenseValidator.grace_days_remaining comment = if grace_days day_word = grace_days == 1 ? "day" : "days" "#{base} | Licensed (Expired - Grace Period: #{grace_days} #{day_word} remaining)" elsif ReactOnRailsPro::LicenseValidator.evaluation? "#{base} | Evaluation License" else "#{base} | Licensed" end "<!-- #{comment} -->" endThen update the test expectation on line 259:
- "Licensed (Expired - Grace Period: 1 days remaining) -->" + "Licensed (Expired - Grace Period: 1 day remaining) -->"
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
react_on_rails_pro/spec/react_on_rails_pro/utils_spec.rb
(5 hunks)spec/dummy/spec/helpers/react_on_rails_helper_spec.rb
(11 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- spec/dummy/spec/helpers/react_on_rails_helper_spec.rb
🧰 Additional context used
📓 Path-based instructions (1)
{Gemfile,Rakefile,config.ru,**/*.{rb,rake,gemspec,ru}}
📄 CodeRabbit inference engine (CLAUDE.md)
{Gemfile,Rakefile,config.ru,**/*.{rb,rake,gemspec,ru}}
: All Ruby code must pass RuboCop with zero offenses before commit/push
RuboCop is the sole authority for Ruby file formatting; never manually format Ruby files
Files:
react_on_rails_pro/spec/react_on_rails_pro/utils_spec.rb
🧬 Code graph analysis (1)
react_on_rails_pro/spec/react_on_rails_pro/utils_spec.rb (3)
lib/react_on_rails/utils.rb (1)
rsc_bundle_js_file_path
(149-154)spec/dummy/spec/helpers/react_on_rails_helper_spec.rb (1)
pro_attribution_comment
(646-646)react_on_rails_pro/lib/react_on_rails_pro/utils.rb (1)
pro_attribution_comment
(170-184)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: build-and-test
🔇 Additional comments (2)
react_on_rails_pro/spec/react_on_rails_pro/utils_spec.rb (2)
5-5
: LGTM: RuboCop module length configurationThe disable/enable comments appropriately handle the module length as the test suite grows with new coverage.
Also applies to: 290-290
222-287
: Good test coverage for pro_attribution_commentThe test suite comprehensively covers all license state scenarios: valid, grace period (multiple cases), evaluation, and edge cases. The mocking strategy correctly isolates the unit under test.
Minor observations:
- Line 256: The description "returns attribution comment with singular day" could be clearer—it tests the behavior for one day remaining (count=1), not whether the grammar uses singular "day". The implementation currently always outputs "days" (plural) regardless of count.
- Consider adding a test for nil license (if that's a valid state) to ensure proper error handling or default behavior.
def response; end | ||
end | ||
|
||
describe ReactOnRailsProHelper, type: :helper do |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The type: :helper
metadata was removed by RuboCop (RSpecRails/InferredSpecType cop
). RSpec Rails automatically infers the spec type from the file path (spec/helpers/
), so the explicit type metadata is redundant and was removed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
🧹 Nitpick comments (1)
spec/dummy/spec/helpers/react_on_rails_helper_spec.rb (1)
641-641
: Consider extracting attribution comment strings to constants or let blocks.The attribution comment strings are repeated throughout the test file (e.g.,
"<!-- Powered by React on Rails Pro (c) ShakaCode | Licensed -->"
). Extracting these to shared constants or let blocks would improve maintainability and reduce duplication.Example:
let(:pro_attribution_comment) { "<!-- Powered by React on Rails Pro (c) ShakaCode | Licensed -->" } let(:oss_attribution_comment) { "<!-- Powered by React on Rails (c) ShakaCode | Open Source -->" }Then reference them throughout:
allow(utils_module).to receive(:pro_attribution_comment).and_return(pro_attribution_comment) expect(result).to include(pro_attribution_comment)Also applies to: 673-673, 690-690, 712-712, 728-728, 750-750, 775-775, 797-797, 813-813
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
react_on_rails_pro/spec/dummy/spec/helpers/react_on_rails_pro_helper_spec.rb
(2 hunks)spec/dummy/spec/helpers/react_on_rails_helper_spec.rb
(11 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
{Gemfile,Rakefile,config.ru,**/*.{rb,rake,gemspec,ru}}
📄 CodeRabbit inference engine (CLAUDE.md)
{Gemfile,Rakefile,config.ru,**/*.{rb,rake,gemspec,ru}}
: All Ruby code must pass RuboCop with zero offenses before commit/push
RuboCop is the sole authority for Ruby file formatting; never manually format Ruby files
Files:
spec/dummy/spec/helpers/react_on_rails_helper_spec.rb
react_on_rails_pro/spec/dummy/spec/helpers/react_on_rails_pro_helper_spec.rb
🧠 Learnings (3)
📚 Learning: 2025-02-18T13:08:01.477Z
Learnt from: AbanoubGhadban
PR: shakacode/react_on_rails#1644
File: lib/react_on_rails/helper.rb:190-197
Timestamp: 2025-02-18T13:08:01.477Z
Learning: RSC support validation in React on Rails Pro is handled through a chain of validations:
1. Pro version check in `run_stream_inside_fiber`
2. RSC support check during pack generation via `ReactOnRailsPro.configuration.enable_rsc_support`
3. RSC support validation during component registration
This makes additional validation in the helper methods unnecessary.
Applied to files:
spec/dummy/spec/helpers/react_on_rails_helper_spec.rb
📚 Learning: 2025-09-15T21:24:48.207Z
Learnt from: AbanoubGhadban
PR: shakacode/react_on_rails#1781
File: node_package/src/ClientSideRenderer.ts:82-95
Timestamp: 2025-09-15T21:24:48.207Z
Learning: In React on Rails, the force_load feature includes both explicit `data-force-load="true"` usage and the ability to hydrate components during the page loading state (`document.readyState === 'loading'`). Both capabilities require a Pro license, so the condition `!railsContext.rorPro && (isComponentForceLoaded || document.readyState === 'loading')` correctly gates both scenarios.
Applied to files:
spec/dummy/spec/helpers/react_on_rails_helper_spec.rb
📚 Learning: 2025-02-18T13:08:01.477Z
Learnt from: AbanoubGhadban
PR: shakacode/react_on_rails#1644
File: lib/react_on_rails/helper.rb:190-197
Timestamp: 2025-02-18T13:08:01.477Z
Learning: RSC support validation is handled in deeper level calls of the React on Rails Pro codebase, so it doesn't need to be validated again in the `rsc_payload_react_component` helper method.
Applied to files:
spec/dummy/spec/helpers/react_on_rails_helper_spec.rb
🧬 Code graph analysis (2)
spec/dummy/spec/helpers/react_on_rails_helper_spec.rb (2)
lib/react_on_rails/helper.rb (3)
react_component
(58-95)redux_store
(257-272)react_component_hash
(212-242)lib/react_on_rails/react_component/render_options.rb (1)
props
(30-32)
react_on_rails_pro/spec/dummy/spec/helpers/react_on_rails_pro_helper_spec.rb (2)
react_on_rails_pro/spec/react_on_rails_pro/support/mock_stream_helper.rb (1)
clear_stream_mocks
(8-10)lib/react_on_rails/helper.rb (1)
stream_react_component
(128-136)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (10)
- GitHub Check: dummy-app-integration-tests (3.2, 20)
- GitHub Check: dummy-app-integration-tests (3.4, 22)
- GitHub Check: rspec-package-tests (3.2, minimum)
- GitHub Check: markdown-link-check
- GitHub Check: rspec-package-tests (3.2, latest)
- GitHub Check: rspec-package-tests (3.4, latest)
- GitHub Check: rspec-package-tests (3.4, minimum)
- GitHub Check: claude-review
- GitHub Check: build
- GitHub Check: build-and-test
🔇 Additional comments (5)
react_on_rails_pro/spec/dummy/spec/helpers/react_on_rails_pro_helper_spec.rb (2)
16-16
: Verify removal oftype: :helper
is intentional.The
type: :helper
metadata has been removed from the describe block. While RSpec 3+ typically infers the type from the spec's file location, this removal might affect any type-specific behavior or inclusions if they were relied upon.Please confirm this change is intentional and that no type-specific RSpec behavior is needed here.
728-737
: LGTM!The test cases correctly verify that the Pro attribution comment is present in the rendered output and appears exactly once. The use of
include
for presence andscan(...).length
for counting occurrences is appropriate.spec/dummy/spec/helpers/react_on_rails_helper_spec.rb (3)
24-28
: LGTM! Mock setup aligns with new Pro API predicates.The updated mocks correctly reflect the API changes from
react_on_rails_pro_licence_valid?
toreact_on_rails_pro?
, and add the new predicatesreact_on_rails_pro_version
andrsc_support_enabled?
as expected.
637-676
: LGTM! Comprehensive test coverage for attribution comment helper.The test suite properly stubs the ReactOnRailsPro modules using
stub_const
and validates both the Pro and open source attribution comment scenarios. The verification of method calls and return values is thorough.
394-394
: LGTM! Consistent API migration across all test contexts.All references correctly updated from
react_on_rails_pro_licence_valid?
toreact_on_rails_pro?
, maintaining consistency with the new Pro API predicates throughout the test suite.Also applies to: 410-410, 426-426, 442-442, 488-488, 501-501, 546-546, 562-562, 573-573
before do | ||
@rorp_rendering_fibers = [] | ||
ReactOnRailsPro::Request.instance_variable_set(:@connection, nil) | ||
original_httpx_plugin = HTTPX.method(:plugin) | ||
allow(HTTPX).to receive(:plugin) do |*args| | ||
original_httpx_plugin.call(:mock_stream).plugin(*args) | ||
end | ||
clear_stream_mocks | ||
|
||
mock_streaming_response(%r{http://localhost:3800/bundles/[a-f0-9]{32}-test/render/[a-f0-9]{32}}, 200, | ||
count: 1) do |yielder| | ||
chunks.each do |chunk| | ||
yielder.call("#{chunk.to_json}\n") | ||
end | ||
end | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion | 🟠 Major
Refactor to reduce code duplication.
The setup in this before
block duplicates logic from the mock_request_and_response
helper method (lines 330-347). Both perform similar operations: resetting the connection, mocking HTTPX.plugin
, clearing stream mocks, and setting up mock streaming responses.
Consider extracting the common setup into a shared helper method or reusing mock_request_and_response
with appropriate parameters:
before do
@rorp_rendering_fibers = []
- ReactOnRailsPro::Request.instance_variable_set(:@connection, nil)
- original_httpx_plugin = HTTPX.method(:plugin)
- allow(HTTPX).to receive(:plugin) do |*args|
- original_httpx_plugin.call(:mock_stream).plugin(*args)
- end
- clear_stream_mocks
-
- mock_streaming_response(%r{http://localhost:3800/bundles/[a-f0-9]{32}-test/render/[a-f0-9]{32}}, 200,
- count: 1) do |yielder|
- chunks.each do |chunk|
- yielder.call("#{chunk.to_json}\n")
- end
- end
+ mock_request_and_response(chunks, count: 1)
end
This reduces duplication and centralizes the mocking setup logic.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
before do | |
@rorp_rendering_fibers = [] | |
ReactOnRailsPro::Request.instance_variable_set(:@connection, nil) | |
original_httpx_plugin = HTTPX.method(:plugin) | |
allow(HTTPX).to receive(:plugin) do |*args| | |
original_httpx_plugin.call(:mock_stream).plugin(*args) | |
end | |
clear_stream_mocks | |
mock_streaming_response(%r{http://localhost:3800/bundles/[a-f0-9]{32}-test/render/[a-f0-9]{32}}, 200, | |
count: 1) do |yielder| | |
chunks.each do |chunk| | |
yielder.call("#{chunk.to_json}\n") | |
end | |
end | |
end | |
before do | |
@rorp_rendering_fibers = [] | |
mock_request_and_response(chunks, count: 1) | |
end |
🤖 Prompt for AI Agents
In react_on_rails_pro/spec/dummy/spec/helpers/react_on_rails_pro_helper_spec.rb
around lines 711 to 726, the before block duplicates setup already implemented
in mock_request_and_response (lines ~330-347); extract the repeated steps
(resetting ReactOnRailsPro::Request connection, mocking HTTPX.plugin, calling
clear_stream_mocks, and registering the streaming response) into a shared helper
or call mock_request_and_response with the appropriate args, keeping the
@rorp_rendering_fibers = [] initialization in place; replace the duplicated
block with a single call to that helper, passing the regex, status, count and
chunks so the streaming mock is configured centrally.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
♻️ Duplicate comments (1)
react_on_rails_pro/lib/react_on_rails_pro/license_validator.rb (1)
81-105
: Add clock-skew tolerance to expiry check.
current_time > exp_time
will reject valid licenses when app servers and signing host differ by a few seconds. Add a small buffer (e.g., 300 s) before failing so minor drift does not take production down.- current_time = Time.now.to_i - exp_time = license["exp"] + current_time = Time.now.to_i + exp_time = license["exp"] + expiry_with_tolerance = exp_time + CLOCK_TOLERANCE_SECONDS grace_days = nil - if current_time > exp_time + if current_time > expiry_with_tolerance…and define the constant near
GRACE_PERIOD_SECONDS
:- GRACE_PERIOD_SECONDS = 30 * 24 * 60 * 60 + GRACE_PERIOD_SECONDS = 30 * 24 * 60 * 60 + CLOCK_TOLERANCE_SECONDS = 300
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
react_on_rails_pro/lib/react_on_rails_pro/license_validator.rb
(1 hunks)react_on_rails_pro/spec/react_on_rails_pro/utils_spec.rb
(5 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
{Gemfile,Rakefile,config.ru,**/*.{rb,rake,gemspec,ru}}
📄 CodeRabbit inference engine (CLAUDE.md)
{Gemfile,Rakefile,config.ru,**/*.{rb,rake,gemspec,ru}}
: All Ruby code must pass RuboCop with zero offenses before commit/push
RuboCop is the sole authority for Ruby file formatting; never manually format Ruby files
Files:
react_on_rails_pro/lib/react_on_rails_pro/license_validator.rb
react_on_rails_pro/spec/react_on_rails_pro/utils_spec.rb
🧬 Code graph analysis (2)
react_on_rails_pro/lib/react_on_rails_pro/license_validator.rb (1)
react_on_rails_pro/lib/react_on_rails_pro/utils.rb (1)
validated_license_data!
(23-25)
react_on_rails_pro/spec/react_on_rails_pro/utils_spec.rb (3)
lib/react_on_rails/utils.rb (1)
rsc_bundle_js_file_path
(149-154)react_on_rails_pro/lib/react_on_rails_pro/configuration.rb (1)
configuration
(9-34)react_on_rails_pro/lib/react_on_rails_pro/utils.rb (1)
pro_attribution_comment
(170-184)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (10)
- GitHub Check: dummy-app-integration-tests (3.4, 22)
- GitHub Check: dummy-app-integration-tests (3.2, 20)
- GitHub Check: claude-review
- GitHub Check: rspec-package-tests (3.2, latest)
- GitHub Check: markdown-link-check
- GitHub Check: rspec-package-tests (3.4, latest)
- GitHub Check: rspec-package-tests (3.2, minimum)
- GitHub Check: build
- GitHub Check: rspec-package-tests (3.4, minimum)
- GitHub Check: build-and-test
…st behavior across all contexts
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (1)
spec/dummy/spec/helpers/react_on_rails_helper_spec.rb (1)
692-709
: Consider removing redundantreact_on_rails_pro?
stub in Pro contexts.Multiple "Pro installed" contexts (lines 694, 732, 779, 817) explicitly set
allow(ReactOnRails::Utils).to receive(:react_on_rails_pro?).and_return(true)
, but this is already configured in the global before block (line 25). While not harmful, removing these redundant stubs would reduce duplication and rely on the global setup:context "when React on Rails Pro is installed" do before do - allow(ReactOnRails::Utils).to receive(:react_on_rails_pro?).and_return(true) allow(ReactOnRailsPro::Utils).to receive(:pro_attribution_comment) .and_return("<!-- Powered by React on Rails Pro (c) ShakaCode | Licensed -->") end
Apply this pattern to similar contexts at lines 732, 779, and 817.
Also applies to: 730-747, 777-794, 815-847
Also applies to: 730-747, 777-794, 815-847, 849-871
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
spec/dummy/spec/helpers/react_on_rails_helper_spec.rb
(13 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
{Gemfile,Rakefile,config.ru,**/*.{rb,rake,gemspec,ru}}
📄 CodeRabbit inference engine (CLAUDE.md)
{Gemfile,Rakefile,config.ru,**/*.{rb,rake,gemspec,ru}}
: All Ruby code must pass RuboCop with zero offenses before commit/push
RuboCop is the sole authority for Ruby file formatting; never manually format Ruby files
Files:
spec/dummy/spec/helpers/react_on_rails_helper_spec.rb
🧠 Learnings (2)
📚 Learning: 2025-02-18T13:08:01.477Z
Learnt from: AbanoubGhadban
PR: shakacode/react_on_rails#1644
File: lib/react_on_rails/helper.rb:190-197
Timestamp: 2025-02-18T13:08:01.477Z
Learning: RSC support validation in React on Rails Pro is handled through a chain of validations:
1. Pro version check in `run_stream_inside_fiber`
2. RSC support check during pack generation via `ReactOnRailsPro.configuration.enable_rsc_support`
3. RSC support validation during component registration
This makes additional validation in the helper methods unnecessary.
Applied to files:
spec/dummy/spec/helpers/react_on_rails_helper_spec.rb
📚 Learning: 2025-09-15T21:24:48.207Z
Learnt from: AbanoubGhadban
PR: shakacode/react_on_rails#1781
File: node_package/src/ClientSideRenderer.ts:82-95
Timestamp: 2025-09-15T21:24:48.207Z
Learning: In React on Rails, the force_load feature includes both explicit `data-force-load="true"` usage and the ability to hydrate components during the page loading state (`document.readyState === 'loading'`). Both capabilities require a Pro license, so the condition `!railsContext.rorPro && (isComponentForceLoaded || document.readyState === 'loading')` correctly gates both scenarios.
Applied to files:
spec/dummy/spec/helpers/react_on_rails_helper_spec.rb
🧬 Code graph analysis (1)
spec/dummy/spec/helpers/react_on_rails_helper_spec.rb (2)
lib/react_on_rails/helper.rb (3)
react_component
(58-95)redux_store
(257-272)react_component_hash
(212-242)lib/react_on_rails/react_component/render_options.rb (2)
props
(30-32)include
(8-175)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (9)
- GitHub Check: dummy-app-integration-tests (3.2, 20)
- GitHub Check: dummy-app-integration-tests (3.4, 22)
- GitHub Check: markdown-link-check
- GitHub Check: rspec-package-tests (3.4, latest)
- GitHub Check: rspec-package-tests (3.4, minimum)
- GitHub Check: rspec-package-tests (3.2, latest)
- GitHub Check: rspec-package-tests (3.2, minimum)
- GitHub Check: claude-review
- GitHub Check: build-and-test
🔇 Additional comments (3)
spec/dummy/spec/helpers/react_on_rails_helper_spec.rb (3)
30-40
: LGTM! Global stub setup correctly addresses previous NameError issue.The global
stub_const
setup forReactOnRailsPro
modules properly prevents NameError when tests referenceReactOnRailsPro::Utils.pro_attribution_comment
. This addresses the concern from the previous review. The approach of defining the stub in the global before block (sincereact_on_rails_pro?
is true by default) ensures all tests can safely reference Pro modules.Based on past review comments (commit b63a704 addressed the missing stub_const issue).
648-682
: Comprehensive test coverage for the attribution comment helper.The tests properly verify both Pro and OSS attribution comment generation, including verification that
ReactOnRailsPro::Utils.pro_attribution_comment
is called when Pro is installed. The test structure is clear and properly isolated.
684-873
: Excellent test coverage for attribution comment behavior.The tests comprehensively verify that:
- Attribution comments appear in
react_component
,redux_store
, andreact_component_hash
outputs- Both Pro and OSS variants are tested
- Comments appear exactly once per page, even with multiple helper calls
- Mixed helper usage (component + store) maintains single attribution
The use of
scan
to count comment occurrences is appropriate for verifying the "only once" requirement.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🧹 Nitpick comments (2)
spec/dummy/spec/system/integration_spec.rb (2)
99-106
: Simplify constant stubbing; avoid clobbering parent moduleStubbing the parent constant is unnecessary and can shadow other nested constants (e.g., VERSION). Stub just the nested Utils, and use replace/transfer flags to be safe if it already exists.
- pro_module = Module.new utils_module = Module.new do def self.pro_attribution_comment "<!-- Powered by React on Rails Pro (c) ShakaCode | Licensed -->" end end - stub_const("ReactOnRailsPro", pro_module) - stub_const("ReactOnRailsPro::Utils", utils_module) + stub_const( + "ReactOnRailsPro::Utils", + utils_module, + transfer_nested_constants: true, + replace: true + )
97-104
: Tighten comment wording to reflect scope“This for all tests” and “by default” is misleading; this applies only within this shared_context.
- # Stub ReactOnRailsPro::Utils.pro_attribution_comment for all tests - # since react_on_rails_pro? is set to true by default + # Stub ReactOnRailsPro::Utils.pro_attribution_comment for tests in this context + # since react_on_rails_pro? is stubbed to true here
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
spec/dummy/spec/system/integration_spec.rb
(1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
{Gemfile,Rakefile,config.ru,**/*.{rb,rake,gemspec,ru}}
📄 CodeRabbit inference engine (CLAUDE.md)
{Gemfile,Rakefile,config.ru,**/*.{rb,rake,gemspec,ru}}
: All Ruby code must pass RuboCop with zero offenses before commit/push
RuboCop is the sole authority for Ruby file formatting; never manually format Ruby files
Files:
spec/dummy/spec/system/integration_spec.rb
🧠 Learnings (1)
📚 Learning: 2025-09-15T21:24:48.207Z
Learnt from: AbanoubGhadban
PR: shakacode/react_on_rails#1781
File: node_package/src/ClientSideRenderer.ts:82-95
Timestamp: 2025-09-15T21:24:48.207Z
Learning: In React on Rails, the force_load feature includes both explicit `data-force-load="true"` usage and the ability to hydrate components during the page loading state (`document.readyState === 'loading'`). Both capabilities require a Pro license, so the condition `!railsContext.rorPro && (isComponentForceLoaded || document.readyState === 'loading')` correctly gates both scenarios.
Applied to files:
spec/dummy/spec/system/integration_spec.rb
🧬 Code graph analysis (1)
spec/dummy/spec/system/integration_spec.rb (1)
spec/dummy/spec/helpers/react_on_rails_helper_spec.rb (1)
pro_attribution_comment
(34-36)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (10)
- GitHub Check: dummy-app-integration-tests (3.4, 22)
- GitHub Check: dummy-app-integration-tests (3.2, 20)
- GitHub Check: rspec-package-tests (3.4, minimum)
- GitHub Check: rspec-package-tests (3.4, latest)
- GitHub Check: rspec-package-tests (3.2, latest)
- GitHub Check: claude-review
- GitHub Check: markdown-link-check
- GitHub Check: rspec-package-tests (3.2, minimum)
- GitHub Check: build
- GitHub Check: build-and-test
…tribution for React on Rails
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 4
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
CHANGELOG.md
(2 hunks)react_on_rails_pro/CHANGELOG.md
(1 hunks)react_on_rails_pro/CI_SETUP.md
(1 hunks)react_on_rails_pro/LICENSE_SETUP.md
(1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{js,jsx,ts,tsx,css,scss,json,yml,yaml,md}
📄 CodeRabbit inference engine (CLAUDE.md)
Prettier is the sole authority for formatting all non-Ruby files; never manually format them
Files:
react_on_rails_pro/CHANGELOG.md
CHANGELOG.md
react_on_rails_pro/CI_SETUP.md
react_on_rails_pro/LICENSE_SETUP.md
🧠 Learnings (1)
📚 Learning: 2025-09-15T21:24:48.207Z
Learnt from: AbanoubGhadban
PR: shakacode/react_on_rails#1781
File: node_package/src/ClientSideRenderer.ts:82-95
Timestamp: 2025-09-15T21:24:48.207Z
Learning: In React on Rails, the force_load feature includes both explicit `data-force-load="true"` usage and the ability to hydrate components during the page loading state (`document.readyState === 'loading'`). Both capabilities require a Pro license, so the condition `!railsContext.rorPro && (isComponentForceLoaded || document.readyState === 'loading')` correctly gates both scenarios.
Applied to files:
CHANGELOG.md
🪛 LanguageTool
react_on_rails_pro/CI_SETUP.md
[grammar] ~19-~19: There might be a mistake here.
Context: ...## Option 1: Use a Team Member's License - Any developer's FREE license works for C...
(QB_NEW_EN)
[grammar] ~24-~24: There might be a mistake here.
Context: ... Option 2: Create a Dedicated CI License - Register with [email protected]
or si...
(QB_NEW_EN)
[grammar] ~38-~38: There might be a mistake here.
Context: ... Click "New repository secret" 4. Name: REACT_ON_RAILS_PRO_LICENSE
5. Value: Your complete JWT license token (...
(QB_NEW_EN)
[grammar] ~87-~87: There might be a mistake here.
Context: ...riables 3. Click "Add variable" 4. Key: REACT_ON_RAILS_PRO_LICENSE
5. Value: Your license token 6. ✅ Check "Pr...
(QB_NEW_EN)
[grammar] ~88-~88: There might be a mistake here.
Context: ...RO_LICENSE` 5. Value: Your license token 6. ✅ Check "Protect variable" (optional) 7....
(QB_NEW_EN)
[grammar] ~123-~123: There might be a mistake here.
Context: ...ick "Add Environment Variable" 4. Name: REACT_ON_RAILS_PRO_LICENSE
5. Value: Your license token **Step 2: Use...
(QB_NEW_EN)
[grammar] ~179-~179: There might be a mistake here.
Context: ...ttings → Environment Variables 3. Name: REACT_ON_RAILS_PRO_LICENSE
4. Value: Your license token 5. ✅ Check "Di...
(QB_NEW_EN)
[grammar] ~180-~180: There might be a mistake here.
Context: ...RO_LICENSE` 4. Value: Your license token 5. ✅ Check "Display value in build log": **...
(QB_NEW_EN)
[grammar] ~219-~219: There might be a mistake here.
Context: ...ecret text 4. Secret: Your license token 5. ID: REACT_ON_RAILS_PRO_LICENSE
6. Desc...
(QB_NEW_EN)
[grammar] ~220-~220: There might be a mistake here.
Context: ...xt 4. Secret: Your license token 5. ID: REACT_ON_RAILS_PRO_LICENSE
6. Description: "React on Rails Pro License...
(QB_NEW_EN)
[grammar] ~270-~270: There might be a mistake here.
Context: ...RO_LICENSE` 4. Value: Your license token 5. ✅ Check "Secured" (recommended) **Step ...
(QB_NEW_EN)
[grammar] ~370-~370: There might be a mistake here.
Context: ...oad['exp']).to_s " ``` Common issues: - License not set in CI environment variab...
(QB_NEW_EN)
[grammar] ~387-~387: There might be a mistake here.
Context: ...: "No license found" in CI Checklist: - ✅ License added to CI environment variab...
(QB_NEW_EN)
[grammar] ~388-~388: There might be a mistake here.
Context: ...icense added to CI environment variables - ✅ Variable name is exactly `REACT_ON_RAI...
(QB_NEW_EN)
[grammar] ~390-~390: There might be a mistake here.
Context: ...icense value is complete (not truncated) - ✅ License is accessible in the job/step ...
(QB_NEW_EN)
[grammar] ~405-~405: There might be a mistake here.
Context: ...rror: "License has expired" Solution: 1. Get a new FREE 3-month license from [sha...
(QB_NEW_EN)
[grammar] ~412-~412: There might be a mistake here.
Context: ...Locally But Fail in CI Common causes: - License not set in CI environment - Wron...
(QB_NEW_EN)
[grammar] ~413-~413: There might be a mistake here.
Context: ...s:** - License not set in CI environment - Wrong variable name - License truncated ...
(QB_NEW_EN)
[grammar] ~414-~414: There might be a mistake here.
Context: ... in CI environment - Wrong variable name - License truncated when copying **Soluti...
(QB_NEW_EN)
[grammar] ~417-~417: There might be a mistake here.
Context: ...se truncated when copying Solution: Compare local and CI environments: ```b...
(QB_NEW_EN)
[grammar] ~452-~452: There might be a mistake here.
Context: ...luation purposes - renew every 3 months) - Staging: Can use FREE evaluation licen...
(QB_NEW_EN)
[grammar] ~453-~453: There might be a mistake here.
Context: ...r non-production testing or paid license - Production: Paid license (required - f...
(QB_NEW_EN)
[grammar] ~462-~462: There might be a mistake here.
Context: ...ar reminder**: 2 weeks before expiration 2. CI notification: Tests will fail when ...
(QB_NEW_EN)
[grammar] ~463-~463: There might be a mistake here.
Context: ...fication**: Tests will fail when expired 3. Email: We'll send renewal reminders #...
(QB_NEW_EN)
[grammar] ~470-~470: There might be a mistake here.
Context: ...rails-pro) 2. Log in with your email 3. Get new FREE license (or upgrade to paid) 4...
(QB_NEW_EN)
[grammar] ~478-~478: There might be a mistake here.
Context: ... - Documentation: LICENSE_SETUP.md - Get FREE License: [shakacode.com/react...
(QB_NEW_EN)
[grammar] ~479-~479: There might be a mistake here.
Context: ...nse**: shakacode.com/react-on-rails-pro - Email Support: [email protected] -...
(QB_NEW_EN)
[grammar] ~480-~480: There might be a mistake here.
Context: ...Email Support: [email protected] - CI Issues: Include your CI provider na...
(QB_NEW_EN)
[grammar] ~499-~499: There might be a mistake here.
Context: ...-license-token" ``` --- Quick Links: - 🎁 [Get FREE License](https://shakacode....
(QB_NEW_EN)
[grammar] ~500-~500: There might be a mistake here.
Context: ... Quick Links: - 🎁 Get FREE License - 📚 General Setup -...
(QB_NEW_EN)
react_on_rails_pro/LICENSE_SETUP.md
[grammar] ~22-~22: There might be a mistake here.
Context: ...e.** ## License Types ### Free License - Duration: 3 months - Usage: Person...
(QB_NEW_EN)
[grammar] ~23-~23: There might be a mistake here.
Context: ...## Free License - Duration: 3 months - Usage: Personal, educational, and eval...
(QB_NEW_EN)
[grammar] ~24-~24: There might be a mistake here.
Context: ...valuation, CI/CD) - NOT for production - Cost: FREE - just register with your e...
(QB_NEW_EN)
[grammar] ~25-~25: There might be a mistake here.
Context: ...**: FREE - just register with your email - Renewal: Get a new free license or upg...
(QB_NEW_EN)
[grammar] ~28-~28: There might be a mistake here.
Context: ...nse or upgrade to paid ### Paid License - Duration: 1 year (or longer) - **Usage...
(QB_NEW_EN)
[grammar] ~29-~29: There might be a mistake here.
Context: ...cense - Duration: 1 year (or longer) - Usage: Production deployment - *Cost...
(QB_NEW_EN)
[grammar] ~30-~30: There might be a mistake here.
Context: ...nger) - Usage: Production deployment - Cost: Subscription-based - Support...
(QB_NEW_EN)
[grammar] ~31-~31: There might be a mistake here.
Context: ...eployment - Cost: Subscription-based - Support: Includes professional support...
(QB_NEW_EN)
[grammar] ~81-~81: There might be a mistake here.
Context: ...d at multiple points: 1. Ruby Gem: When Rails application starts 2. **Node Rend...
(QB_NEW_EN)
[grammar] ~81-~81: There might be a mistake here.
Context: ...uby Gem**: When Rails application starts 2. Node Renderer: When the Node renderer ...
(QB_NEW_EN)
[grammar] ~82-~82: There might be a mistake here.
Context: ...*: When the Node renderer process starts 3. Browser Package: Trusts server-side va...
(QB_NEW_EN)
[grammar] ~103-~103: There might be a mistake here.
Context: ...mediately (helps catch expiration early) Important: The grace period is designe...
(QB_NEW_EN)
[grammar] ~105-~105: There might be a mistake here.
Context: ...ployments time to renew, but you should: 1. Monitor your logs for license expiration...
(QB_NEW_EN)
[grammar] ~159-~159: There might be a mistake here.
Context: ... Error: "No license found" Solutions: 1. Verify environment variable: `echo $REAC...
(QB_NEW_EN)
[grammar] ~170-~170: There might be a mistake here.
Context: ...ust be complete JWT token) Solutions: 1. Ensure you copied the complete license (...
(QB_NEW_EN)
[grammar] ~177-~177: There might be a mistake here.
Context: ...: "License has expired" What happens: - Development/Test/CI: Application fails...
(QB_NEW_EN)
[grammar] ~181-~181: There might be a mistake here.
Context: ... logs, then fails to start Solutions: 1. Free License: Get a new 3-month FREE l...
(QB_NEW_EN)
[grammar] ~187-~187: There might be a mistake here.
Context: ... You have time to renew, but don't wait! - The warning shows how many days remain -...
(QB_NEW_EN)
[grammar] ~225-~225: There might be a mistake here.
Context: ...ation**: No internet connection required - Public key verification: Uses embedded...
(QB_NEW_EN)
[grammar] ~226-~226: There might be a mistake here.
Context: ...fication**: Uses embedded RSA public key - Tamper-proof: Any modification invalid...
(QB_NEW_EN)
[grammar] ~227-~227: There might be a mistake here.
Context: ...y modification invalidates the signature - No tracking: License validation happen...
(QB_NEW_EN)
[grammar] ~240-~240: There might be a mistake here.
Context: ...nse at shakacode.com/react-on-rails-pro 2. Documentation: Check [CI_SETUP.md](./C...
(QB_NEW_EN)
[grammar] ~242-~242: There might be a mistake here.
Context: ...tion 3. Email: [email protected] 4. License Management: [shakacode.com/rea...
(QB_NEW_EN)
[grammar] ~247-~247: There might be a mistake here.
Context: ...nfig/react_on_rails_pro_license.keyto
.gitignore` 2. ✅ **Use environment variables in product...
(QB_NEW_EN)
[grammar] ~265-~265: There might be a mistake here.
Context: ... happens when my free license expires?** A: Get a new 3-month FREE license, or up...
(QB_NEW_EN)
🪛 markdownlint-cli2 (0.18.1)
react_on_rails_pro/CI_SETUP.md
33-33: Emphasis used instead of a heading
(MD036, no-emphasis-as-heading)
41-41: Emphasis used instead of a heading
(MD036, no-emphasis-as-heading)
82-82: Emphasis used instead of a heading
(MD036, no-emphasis-as-heading)
92-92: Emphasis used instead of a heading
(MD036, no-emphasis-as-heading)
118-118: Emphasis used instead of a heading
(MD036, no-emphasis-as-heading)
126-126: Emphasis used instead of a heading
(MD036, no-emphasis-as-heading)
175-175: Emphasis used instead of a heading
(MD036, no-emphasis-as-heading)
183-183: Emphasis used instead of a heading
(MD036, no-emphasis-as-heading)
214-214: Emphasis used instead of a heading
(MD036, no-emphasis-as-heading)
223-223: Emphasis used instead of a heading
(MD036, no-emphasis-as-heading)
265-265: Emphasis used instead of a heading
(MD036, no-emphasis-as-heading)
273-273: Emphasis used instead of a heading
(MD036, no-emphasis-as-heading)
304-304: Emphasis used instead of a heading
(MD036, no-emphasis-as-heading)
310-310: Emphasis used instead of a heading
(MD036, no-emphasis-as-heading)
373-373: Bare URL used
(MD034, no-bare-urls)
480-480: Bare URL used
(MD034, no-bare-urls)
react_on_rails_pro/LICENSE_SETUP.md
242-242: Bare URL used
(MD034, no-bare-urls)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (10)
- GitHub Check: dummy-app-integration-tests (3.4, 22)
- GitHub Check: dummy-app-integration-tests (3.2, 20)
- GitHub Check: markdown-link-check
- GitHub Check: claude-review
- GitHub Check: rspec-package-tests (3.4, latest)
- GitHub Check: rspec-package-tests (3.2, latest)
- GitHub Check: build
- GitHub Check: rspec-package-tests (3.4, minimum)
- GitHub Check: rspec-package-tests (3.2, minimum)
- GitHub Check: build-and-test
|
||
License validation happens automatically when Rails starts. | ||
|
||
✅ **If your CI tests run, your license is valid** | ||
❌ **If license is invalid, Rails fails to start immediately** | ||
|
||
**No verification step needed** - the application won't start without a valid license. | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Clarify what actually happens on invalid licenses
This section promises CI will fail automatically, but in non-production environments the validator only logs warnings. Please update the doc so teams know tests may still run with an expired/missing license.
🤖 Prompt for AI Agents
react_on_rails_pro/CI_SETUP.md around lines 344 to 351: the current text asserts
that CI tests will fail on invalid licenses because Rails refuses to start, but
in non-production environments the license validator only emits warnings and
tests can still pass; update the wording to state this distinction (production:
Rails fails to start; development/test: validator logs warnings and tests may
run), and add a short recommendation to include an explicit license validation
step in CI (for example running the license check task or booting in production
mode) so teams know how to ensure CI actually fails on invalid or expired
licenses.
…update tests for consistency
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
♻️ Duplicate comments (4)
react_on_rails_pro/packages/node-renderer/src/shared/licenseValidator.ts (4)
84-108
: ESLint disable is a symptom of the process.exit issue.The
eslint-disable-next-line consistent-return
comment (line 84) exists becausehandleInvalidLicense()
never returns due toprocess.exit()
. Once the critical process.exit issue is addressed, this ESLint disable can be removed.After refactoring
handleInvalidLicense()
to not exit, update the function to explicitly return or throw:-// eslint-disable-next-line consistent-return function loadLicenseString(): string { // ... existing code ... - handleInvalidLicense(errorMsg); + handleInvalidLicense(errorMsg); + throw new Error(errorMsg); // Make control flow explicit }
190-227
: Another ESLint disable due to process.exit.Similar to
loadLicenseString()
, theeslint-disable-next-line consistent-return
(line 190) exists because error paths callhandleInvalidLicense()
which exits the process.The error handling logic itself is good—it distinguishes
JsonWebTokenError
from other errors and provides helpful messages. However, this is another symptom of the critical process.exit issue already flagged.After removing
process.exit()
fromhandleInvalidLicense()
, update error handling:-// eslint-disable-next-line consistent-return export function getValidatedLicenseData(): LicenseData { // ... existing code ... } catch (error: unknown) { if (error instanceof Error && error.name === 'JsonWebTokenError') { // ... build errorMsg ... handleInvalidLicense(errorMsg); + throw new Error(errorMsg); } else if (error instanceof Error) { // ... build errorMsg ... handleInvalidLicense(errorMsg); + throw error; } else { // ... build errorMsg ... handleInvalidLicense(errorMsg); + throw new Error('Unknown validation error'); } } }
32-37
: Critical: Process exit still prevents testing and violates separation of concerns.This is the same issue raised in previous reviews. The
handleInvalidLicense()
function unconditionally callsprocess.exit(1)
, which:
- Makes the validator untestable (tests crash instead of verifying error handling)
- Violates separation of concerns (libraries shouldn't control process lifecycle)
- Contradicts the PR description stating "master.ts handles environment-specific behavior"
The validator should return or throw validation state, letting
master.ts
decide whether to exit based on environment.Refactor to remove process control:
-function handleInvalidLicense(message: string): never { +function handleInvalidLicense(message: string): void { const fullMessage = `[React on Rails Pro] ${message}`; console.error(fullMessage); - // Validation errors should prevent the application from starting - process.exit(1); + // Let the caller decide whether to exit based on environment }Then update
master.ts
to handle exit logic:const licenseData = getValidatedLicenseData(); if (!licenseData && process.env.NODE_ENV === 'production') { process.exit(1); }
117-128
: Unsafe type cast without runtime validation.The result of
jwt.verify()
is cast toLicenseData
without validating the payload structure (line 125). While signature verification ensures authenticity, it doesn't guarantee the expected fields exist with correct types.Add runtime validation before the cast:
const decoded = jwt.verify(licenseString, PUBLIC_KEY, { // ... existing options ... }) as LicenseData; + + // Validate payload structure + if ( + typeof decoded !== 'object' || + decoded === null || + typeof decoded.exp !== 'number' + ) { + throw new Error('Invalid license payload: missing or malformed required fields'); + } - return decoded; + return decoded as LicenseData; }
🧹 Nitpick comments (1)
react_on_rails_pro/packages/node-renderer/src/shared/licenseValidator.ts (1)
43-63
: Consider adding timestamp conversion comment.The helper functions correctly implement grace period logic. However, the
/ 1000
conversion from milliseconds to seconds appears multiple times. Consider adding a comment explaining this, as suggested in past reviews.Add a comment at the first occurrence:
function isWithinGracePeriod(expTime: number): boolean { + // Date.now() returns milliseconds; convert to seconds for Unix timestamp comparison return Math.floor(Date.now() / 1000) <= expTime + GRACE_PERIOD_SECONDS; }
Alternatively, extract a helper function:
function getCurrentUnixTime(): number { return Math.floor(Date.now() / 1000); }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
react_on_rails_pro/packages/node-renderer/src/master.ts
(1 hunks)react_on_rails_pro/packages/node-renderer/src/shared/licenseValidator.ts
(1 hunks)react_on_rails_pro/packages/node-renderer/tests/licenseValidator.test.ts
(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
- react_on_rails_pro/packages/node-renderer/tests/licenseValidator.test.ts
- react_on_rails_pro/packages/node-renderer/src/master.ts
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{js,jsx,ts,tsx,css,scss,json,yml,yaml,md}
📄 CodeRabbit inference engine (CLAUDE.md)
Prettier is the sole authority for formatting all non-Ruby files; never manually format them
Files:
react_on_rails_pro/packages/node-renderer/src/shared/licenseValidator.ts
**/*.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Use ESLint for JS/TS code (lint via rake lint or yarn lint)
Files:
react_on_rails_pro/packages/node-renderer/src/shared/licenseValidator.ts
🧬 Code graph analysis (1)
react_on_rails_pro/packages/node-renderer/src/shared/licenseValidator.ts (1)
react_on_rails_pro/packages/node-renderer/src/shared/licensePublicKey.ts (1)
PUBLIC_KEY
(11-19)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
- GitHub Check: rspec-package-tests (3.2, latest)
- GitHub Check: rspec-package-tests (3.4, latest)
- GitHub Check: rspec-package-tests (3.4, minimum)
- GitHub Check: rspec-package-tests (3.2, minimum)
- GitHub Check: claude-review
- GitHub Check: build
- GitHub Check: markdown-link-check
- GitHub Check: build-and-test
🔇 Additional comments (4)
react_on_rails_pro/packages/node-renderer/src/shared/licenseValidator.ts (4)
6-19
: License payload structure looks good.The
LicenseData
interface correctly uses standard JWT claim names (sub
,iat
,exp
,iss
) and properly marksexp
as required. The index signature allows flexibility for additional claims while maintaining type safety for known fields.
234-238
: LGTM: Evaluation check is clear and safe.The function correctly determines evaluation status by checking if the plan is "paid" or starts with "paid_". The fallback to an empty string (line 236) safely handles missing plan fields.
245-251
: LGTM: Grace days retrieval is correct.The function properly ensures validation has occurred before returning cached grace days. The caching strategy is consistent with the rest of the module.
256-259
: LGTM: Reset function is necessary for testing.This function correctly clears all cached state, which is essential for test isolation given the module-level caching strategy. The documentation clearly indicates its purpose.
react_on_rails_pro/packages/node-renderer/src/shared/licenseValidator.ts
Show resolved
Hide resolved
… attribution comments
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
♻️ Duplicate comments (2)
react_on_rails_pro/lib/react_on_rails_pro/utils.rb (1)
168-184
: Fix grammatical error in attribution comment.The attribution comment uses "day(s)" for all cases, which is grammatically incorrect when
grace_days
equals 1. The message should read "1 day remaining" (singular) instead of "1 day(s) remaining".Apply this diff to handle singular/plural correctly:
def self.pro_attribution_comment base = "Powered by React on Rails Pro (c) ShakaCode" # Check if in grace period grace_days = ReactOnRailsPro::LicenseValidator.grace_days_remaining comment = if grace_days - "#{base} | Licensed (Expired - Grace Period: #{grace_days} day(s) remaining)" + day_word = grace_days == 1 ? "day" : "days" + "#{base} | Licensed (Expired - Grace Period: #{grace_days} #{day_word} remaining)" elsif ReactOnRailsPro::LicenseValidator.evaluation? "#{base} | Evaluation License" else "#{base} | Licensed" end "<!-- #{comment} -->" endBased on past review comments.
react_on_rails_pro/spec/react_on_rails_pro/utils_spec.rb (1)
247-257
: Update test expectation for singular grammar.Once the implementation is updated to use "1 day" (singular) instead of "1 day(s)", this test expectation should also be updated to match.
Apply this diff after fixing the implementation:
it "returns attribution comment with singular day" do result = described_class.pro_attribution_comment expected = "<!-- Powered by React on Rails Pro (c) ShakaCode | " \ - "Licensed (Expired - Grace Period: 1 day(s) remaining) -->" + "Licensed (Expired - Grace Period: 1 day remaining) -->" expect(result).to eq(expected) endBased on past review comments.
🧹 Nitpick comments (1)
react_on_rails_pro/spec/react_on_rails_pro/utils_spec.rb (1)
25-25
: LGTM!The consolidation of multiple
allow(...).to receive(...)
calls into singlereceive_messages
calls is a clean refactor that improves test readability.Also applies to: 44-45, 81-87
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
react_on_rails_pro/lib/react_on_rails_pro/utils.rb
(2 hunks)react_on_rails_pro/spec/react_on_rails_pro/utils_spec.rb
(5 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
{Gemfile,Rakefile,config.ru,**/*.{rb,rake,gemspec,ru}}
📄 CodeRabbit inference engine (CLAUDE.md)
{Gemfile,Rakefile,config.ru,**/*.{rb,rake,gemspec,ru}}
: All Ruby code must pass RuboCop with zero offenses before commit/push
RuboCop is the sole authority for Ruby file formatting; never manually format Ruby files
Files:
react_on_rails_pro/lib/react_on_rails_pro/utils.rb
react_on_rails_pro/spec/react_on_rails_pro/utils_spec.rb
🧬 Code graph analysis (2)
react_on_rails_pro/lib/react_on_rails_pro/utils.rb (1)
react_on_rails_pro/lib/react_on_rails_pro/license_validator.rb (4)
validated_license_data!
(6-187)validated_license_data!
(15-40)grace_days_remaining
(57-63)evaluation?
(49-53)
react_on_rails_pro/spec/react_on_rails_pro/utils_spec.rb (2)
lib/react_on_rails/utils.rb (1)
rsc_bundle_js_file_path
(149-154)react_on_rails_pro/lib/react_on_rails_pro/utils.rb (1)
pro_attribution_comment
(170-184)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (10)
- GitHub Check: dummy-app-integration-tests (3.2, 20)
- GitHub Check: dummy-app-integration-tests (3.4, 22)
- GitHub Check: rspec-package-tests (3.4, latest)
- GitHub Check: rspec-package-tests (3.2, minimum)
- GitHub Check: rspec-package-tests (3.2, latest)
- GitHub Check: rspec-package-tests (3.4, minimum)
- GitHub Check: build
- GitHub Check: markdown-link-check
- GitHub Check: claude-review
- GitHub Check: build-and-test
🔇 Additional comments (1)
react_on_rails_pro/spec/react_on_rails_pro/utils_spec.rb (1)
222-283
: LGTM!The test coverage for
pro_attribution_comment
is comprehensive, covering all the different license states: valid license, grace period (multiple days), grace period (1 day), evaluation license, and expired grace period (0 days). The use ofreceive_messages
for stubbing is clean and follows Ruby best practices.
# Validates the license and raises an exception if invalid. | ||
# | ||
# @return [Boolean] true if license is valid | ||
# @raise [ReactOnRailsPro::Error] if license is invalid | ||
def self.validated_license_data! | ||
LicenseValidator.validated_license_data! | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix the return type documentation.
The documentation states the method returns [Boolean] true
, but it actually returns a Hash containing the license data (delegated from LicenseValidator.validated_license_data!
).
Apply this diff to correct the documentation:
- # Validates the license and raises an exception if invalid.
- #
- # @return [Boolean] true if license is valid
- # @raise [ReactOnRailsPro::Error] if license is invalid
+ # Validates the license and returns the license data.
+ # Raises an exception if the license is invalid.
+ #
+ # @return [Hash] The validated license data
+ # @raise [ReactOnRailsPro::Error] if license is invalid
def self.validated_license_data!
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
# Validates the license and raises an exception if invalid. | |
# | |
# @return [Boolean] true if license is valid | |
# @raise [ReactOnRailsPro::Error] if license is invalid | |
def self.validated_license_data! | |
LicenseValidator.validated_license_data! | |
end | |
# Validates the license and returns the license data. | |
# Raises an exception if the license is invalid. | |
# | |
# @return [Hash] The validated license data | |
# @raise [ReactOnRailsPro::Error] if license is invalid | |
def self.validated_license_data! | |
LicenseValidator.validated_license_data! | |
end |
🤖 Prompt for AI Agents
In react_on_rails_pro/lib/react_on_rails_pro/utils.rb around lines 19 to 25, the
YARD comment incorrectly claims this method returns [Boolean] true while it
actually returns the Hash from LicenseValidator.validated_license_data!; update
the @return line to reflect the correct type and description (e.g. @return
[Hash] the validated license data) and keep the @raise line as-is to indicate
the method still raises ReactOnRailsPro::Error on invalid license.
PR Review: Offline JWT-Based License Validation SystemI've completed a comprehensive review of this PR. Overall, this is a well-implemented, security-conscious license validation system. The code quality is excellent with strong test coverage and clear documentation. ✅ Strengths1. Excellent Security ImplementationThe JWT validation correctly implements critical security best practices: Ruby (license_validator.rb:136-151):
TypeScript (licenseValidator.ts:117-125):
Public Key Management:
2. Robust Architecture
3. Comprehensive Test CoverageRuby Tests (license_validator_spec.rb):
TypeScript Tests (licenseValidator.test.ts):
4. Excellent Documentation
🔍 Issues & RecommendationsCritical IssuesNone! The implementation is solid. High Priority (Should Fix)1. Grace Period Calculation Edge CaseFiles: license_validator.rb:125-131, licenseValidator.ts:59-63 Issue: Using integer division means "0.9 days remaining" shows as "0 days remaining", which might confuse users who see "0 days" but still have hours left. Recommendation: Use ceiling to avoid "0 days" when time remains, or show hours if < 1 day. Medium Priority (Consider)2. Attribution Comment Exposes License StatusFiles: helper.rb:633, utils.rb:170-184 Issue: The HTML comment reveals license status including grace period to anyone viewing page source. Consider hiding detailed status in production while keeping it visible in development for debugging. 3. TypeScript: Production Grace Period Tests MissingRuby tests thoroughly cover production grace period behavior, but TypeScript tests don't have explicit production environment tests with grace period validation. Low Priority (Nice to Have)
🔒 Security AnalysisStrengths:
No Critical Security Issues Found 🎉📊 Performance & Test QualityPerformance: No Issues 👍
Test Quality: Excellent ⭐⭐⭐⭐⭐
✅ Final VerdictApproval: YES ✅This is a high-quality implementation with strong security awareness, comprehensive testing, excellent documentation, and thoughtful architecture. Required Before Merge:
Recommended Before Merge:
Checklist Summary
Great work, @AbanoubGhadban! This is a solid foundation for the Pro license system. 🎉 Review generated by Claude Code 🤖 |
Summary
This PR implements a comprehensive offline license validation system and attribution comments for React on Rails Pro.
Key Features
Motivation
React on Rails Pro needs a licensing system that:
Implementation Details
License Validation
API Design (Ruby & Node.js are aligned):
Both Ruby and Node.js implementations use the same API pattern:
getValidatedLicenseData()
/validated_license_data!
isEvaluation()
/evaluation?
,getGraceDaysRemaining()
/grace_days_remaining
reset()
/reset!
to clear cacheRuby Implementation (
react_on_rails_pro/lib/react_on_rails_pro/license_validator.rb
):Node.js Implementation (
packages/node-renderer/src/shared/licenseValidator.ts
):Grace Period Behavior:
The grace period applies ONLY in production environments and is 1 month for ALL licenses (free and paid):
Production Environment (
NODE_ENV=production
/Rails.env.production?
):Non-Production Environments (development, test, CI):
Why this design?
License Sources (checked in order):
REACT_ON_RAILS_PRO_LICENSE
config/react_on_rails_pro_license.key
JWT Structure:
License Duration:
exp
(expiration) fieldexp
< current timeAttribution Comments
HTML Comments in Rendered Output:
<!-- React on Rails Pro: Using React on Rails Pro with free evaluation license (expires in 45 days) -->
Implementation:
ReactOnRailsPro::Utils.pro_attribution_comment
generates commentTesting
Ruby Tests (
react_on_rails_pro/spec/react_on_rails_pro/license_validator_spec.rb
):evaluation?
method for different plansreset!
functionalityNode.js Tests (
packages/node-renderer/tests/licenseValidator.test.ts
):isEvaluation()
for free vs paid licensesreset()
clears cacheChanges by Component
Ruby Gem (
react_on_rails_pro/
)New Files:
lib/react_on_rails_pro/license_validator.rb
- Main validation logiclib/react_on_rails_pro/license_public_key.rb
- RSA public keyspec/react_on_rails_pro/license_validator_spec.rb
- Comprehensive testsModified Files:
lib/react_on_rails_pro/utils.rb
- Addedpro_attribution_comment
methodreact_on_rails_pro.gemspec
- Addedjwt
gem dependency (~> 2.7).gitignore
- Addedconfig/react_on_rails_pro_license.key
Node Renderer (
react_on_rails_pro/packages/node-renderer/
)New Files:
src/shared/licenseValidator.ts
- Validation module (functional pattern)src/shared/licensePublicKey.ts
- Public key for Nodetests/licenseValidator.test.ts
- Jest testsModified Files:
src/master.ts
- Added license validation on startuppackage.json
- Addedjsonwebtoken
dependency (^9.0.0)Documentation
New Files:
react_on_rails_pro/LICENSE_SETUP.md
- Complete license setup guidereact_on_rails_pro/CI_SETUP.md
- CI/CD configuration guideModified Files:
CHANGELOG.md
- Added attribution comment featurereact_on_rails_pro/CHANGELOG.md
- Added license validation system and attribution commentHow It Works
License Types
FREE Evaluation License
exp
field (typically 3 months from issue date)Paid License
exp
field based on subscription (e.g., 1 year, 2 years)Note: Grace period behavior is the same for both license types - only the initial duration (encoded in
exp
) differs.Security Features
Breaking Changes
None. This is a new feature for React on Rails Pro.
Migration Guide
For existing React on Rails Pro users:
See
react_on_rails_pro/LICENSE_SETUP.md
for detailed instructions.Test Plan
Files Changed
Added (9 files)
react_on_rails_pro/lib/react_on_rails_pro/license_validator.rb
react_on_rails_pro/lib/react_on_rails_pro/license_public_key.rb
react_on_rails_pro/spec/react_on_rails_pro/license_validator_spec.rb
react_on_rails_pro/packages/node-renderer/src/shared/licenseValidator.ts
react_on_rails_pro/packages/node-renderer/src/shared/licensePublicKey.ts
react_on_rails_pro/packages/node-renderer/tests/licenseValidator.test.ts
react_on_rails_pro/LICENSE_SETUP.md
react_on_rails_pro/CI_SETUP.md
Modified (8 files)
react_on_rails_pro/lib/react_on_rails_pro/utils.rb
- Added attribution commentreact_on_rails_pro/lib/react_on_rails_pro/engine.rb
- License validation integrationreact_on_rails_pro/packages/node-renderer/src/master.ts
- Startup validationreact_on_rails_pro/react_on_rails_pro.gemspec
- Added jwt dependencyreact_on_rails_pro/packages/node-renderer/package.json
- Added jsonwebtokenreact_on_rails_pro/.gitignore
- Added license file patternCHANGELOG.md
- Attribution comment entryreact_on_rails_pro/CHANGELOG.md
- License validation entriesNext Steps
FAQ
Q: Why do I need a license for development?
A: FREE evaluation licenses let us track usage and provide better support. Registration takes 30 seconds!
Q: What happens when my license expires in development?
A: The app fails to start immediately (no grace period in dev/test/CI). This helps you catch expiration early before it affects production.
Q: What happens when my license expires in production?
A: You get a 1-month grace period with ERROR logs showing days remaining. This gives you time to renew without downtime. Monitor your logs!
Q: Is the grace period different for free vs paid licenses?
A: No! Both get the same 1-month grace period in production. The difference is the initial license duration (encoded in the JWT's
exp
field).Q: Can I use free licenses in production?
A: Free licenses are intended for evaluation only (dev/test/CI). Production should use paid licenses, but technically both work the same way with the same grace period.
Q: Does validation require internet?
A: No! Everything is validated offline using cryptographic signatures.
Q: How long do licenses last?
A: License duration is set when the JWT is issued:
🤖 Generated with Claude Code
Co-Authored-By: Claude [email protected]
This change is