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 =
'91f54336c0d42a7c88642b8d7f8bacab3adba4a4d633a33002fc15f676bfd8f0'- RAI_PREFIX =
'NNCC-'- GRACE_PERIOD_DAYS =
14- OFFLINE_GRACE_DAYS =
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.
26 27 28 29 30 |
# File 'lib/rosett_ai/licensing/license_key.rb', line 26 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.
23 24 25 |
# File 'lib/rosett_ai/licensing/license_key.rb', line 23 def claims @claims end |
Instance Method Details
#decode ⇒ self
Decodes and verifies the Ed25519 JWT signature.
36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
# File 'lib/rosett_ai/licensing/license_key.rb', line 36 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.
72 73 74 75 76 77 78 79 80 |
# File 'lib/rosett_ai/licensing/license_key.rb', line 72 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.
101 102 103 104 105 106 107 108 109 110 111 112 113 |
# File 'lib/rosett_ai/licensing/license_key.rb', line 101 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.
118 119 120 121 122 123 124 125 126 |
# File 'lib/rosett_ai/licensing/license_key.rb', line 118 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).
85 86 87 88 |
# File 'lib/rosett_ai/licensing/license_key.rb', line 85 def perpetual? decode claims['perpetual'] == true end |
#subscriber? ⇒ Boolean
Whether the license tier is subscriber or enterprise.
93 94 95 96 |
# File 'lib/rosett_ai/licensing/license_key.rb', line 93 def subscriber? decode ['subscriber', 'enterprise'].include?(claims['tier']) end |
#tier ⇒ Tier
Returns the license tier.
64 65 66 67 |
# File 'lib/rosett_ai/licensing/license_key.rb', line 64 def tier decode Tier.new(claims['tier'] || 'community') end |
#valid? ⇒ Boolean
Whether the license is currently valid (not expired or within grace).
54 55 56 57 58 59 |
# File 'lib/rosett_ai/licensing/license_key.rb', line 54 def valid? decode !expired? || within_grace? rescue RosettAi::LicenseError false end |