Class: RosettAi::Licensing::LicenseKey
- Inherits:
-
Object
- Object
- RosettAi::Licensing::LicenseKey
- Defined in:
- lib/rosett_ai/licensing/license_key.rb
Overview
Decodes and validates Ed25519-signed JWT license keys.
The public verification key is safe to embed in source — it can only verify signatures, not forge them. The private signing key is held server-side and NEVER distributed.
Constant Summary collapse
- PUBLIC_KEY_HEX =
Returns Hex-encoded public key for license verification.
'91f54336c0d42a7c88642b8d7f8bacab3adba4a4d633a33002fc15f676bfd8f0'- RAI_PREFIX =
Returns Prefix for rosett-ai license key strings.
'NNCC-'- GRACE_PERIOD_DAYS =
Returns Days of grace period after license expiry.
14- OFFLINE_GRACE_DAYS =
Returns Days of offline operation before license revalidation.
30
Instance Attribute Summary collapse
-
#claims ⇒ Object
readonly
Returns the value of attribute claims.
Instance Method Summary collapse
-
#decode ⇒ self
Decodes and verifies the Ed25519 JWT signature.
-
#expired? ⇒ Boolean
Whether the license has passed its expiration timestamp.
-
#grace_remaining ⇒ Integer?
Days remaining in the post-expiry grace period.
-
#initialize(raw_key) ⇒ LicenseKey
constructor
A new instance of LicenseKey.
-
#offline_valid? ⇒ Boolean
Whether the license is valid for offline use.
-
#perpetual? ⇒ Boolean
Whether the license is perpetual (never expires).
-
#subscriber? ⇒ Boolean
Whether the license tier is subscriber or enterprise.
-
#tier ⇒ Tier
Returns the license tier.
-
#valid? ⇒ Boolean
Whether the license is currently valid (not expired or within grace).
Constructor Details
#initialize(raw_key) ⇒ LicenseKey
Returns a new instance of LicenseKey.
30 31 32 33 34 |
# File 'lib/rosett_ai/licensing/license_key.rb', line 30 def initialize(raw_key) @raw_key = raw_key @claims = nil @decoded = false end |
Instance Attribute Details
#claims ⇒ Object (readonly)
Returns the value of attribute claims.
27 28 29 |
# File 'lib/rosett_ai/licensing/license_key.rb', line 27 def claims @claims end |
Instance Method Details
#decode ⇒ self
Decodes and verifies the Ed25519 JWT signature.
40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
# File 'lib/rosett_ai/licensing/license_key.rb', line 40 def decode return self if @decoded stripped = strip_prefix(@raw_key) verify_key = Ed25519::VerifyKey.new([PUBLIC_KEY_HEX].pack('H*')) decoded = JWT.decode(stripped, verify_key, true, algorithms: ['EdDSA'], verify_expiration: false) @claims = decoded.first @decoded = true self rescue Ed25519::VerifyError, JWT::DecodeError, JWT::VerificationError => e raise RosettAi::LicenseError, "signature verification failed: #{e.}" rescue ArgumentError, TypeError => e raise RosettAi::LicenseError, "invalid key format: #{e.}" end |
#expired? ⇒ Boolean
Whether the license has passed its expiration timestamp.
76 77 78 79 80 81 82 83 84 |
# File 'lib/rosett_ai/licensing/license_key.rb', line 76 def expired? decode return false if perpetual? exp = claims['exp'] return false unless exp Time.now.to_i > exp end |
#grace_remaining ⇒ Integer?
Days remaining in the post-expiry grace period.
105 106 107 108 109 110 111 112 113 114 115 116 117 |
# File 'lib/rosett_ai/licensing/license_key.rb', line 105 def grace_remaining decode return nil if perpetual? exp = claims['exp'] return nil unless exp elapsed = (Time.now.to_i - exp) / 86_400 return GRACE_PERIOD_DAYS if elapsed.negative? remaining = GRACE_PERIOD_DAYS - elapsed [remaining, 0].max end |
#offline_valid? ⇒ Boolean
Whether the license is valid for offline use.
122 123 124 125 126 127 128 129 130 |
# File 'lib/rosett_ai/licensing/license_key.rb', line 122 def offline_valid? decode return true if perpetual? exp = claims['exp'] return true unless exp (Time.now.to_i - exp) < (OFFLINE_GRACE_DAYS * 86_400) end |
#perpetual? ⇒ Boolean
Whether the license is perpetual (never expires).
89 90 91 92 |
# File 'lib/rosett_ai/licensing/license_key.rb', line 89 def perpetual? decode claims['perpetual'] == true end |
#subscriber? ⇒ Boolean
Whether the license tier is subscriber or enterprise.
97 98 99 100 |
# File 'lib/rosett_ai/licensing/license_key.rb', line 97 def subscriber? decode ['subscriber', 'enterprise'].include?(claims['tier']) end |
#tier ⇒ Tier
Returns the license tier.
68 69 70 71 |
# File 'lib/rosett_ai/licensing/license_key.rb', line 68 def tier decode Tier.new(claims['tier'] || 'community') end |
#valid? ⇒ Boolean
Whether the license is currently valid (not expired or within grace).
58 59 60 61 62 63 |
# File 'lib/rosett_ai/licensing/license_key.rb', line 58 def valid? decode !expired? || within_grace? rescue RosettAi::LicenseError false end |