Class: ApiKeys::ApiKey
- Inherits:
-
ActiveRecord::Base
- Object
- ActiveRecord::Base
- ApiKeys::ApiKey
- Defined in:
- lib/api_keys/models/api_key.rb
Overview
The core ActiveRecord model representing an API key.
Constant Summary collapse
- MAX_SCOPES =
100- MAX_SCOPE_BYTESIZE =
128- MAX_METADATA_BYTESIZE =
16_384- IMMUTABLE_IDENTITY_ATTRIBUTES =
%w[ token_digest digest_algorithm prefix last4 owner_type owner_id key_type environment ].freeze
Instance Attribute Summary collapse
-
#token ⇒ Object
readonly
Attributes & Serialization == Expose the plaintext token only immediately after creation.
Instance Method Summary collapse
- #active? ⇒ Boolean
-
#allows_permission?(required_permission) ⇒ Boolean
Check if this key has a specific permission (alias for allows_scope?).
-
#allows_scope?(required_scope) ⇒ Boolean
Basic scope check.
-
#destroy ⇒ Object
Override destroy to prevent destroying non-revocable keys.
- #destroy! ⇒ Object
-
#environment_config ⇒ Object
Returns the configuration hash for this key's environment.
- #expired? ⇒ Boolean
-
#inspect ⇒ Object
Keep credentials and credential-derived values out of logs and consoles.
-
#key_type_config ⇒ Object
Returns the configuration hash for this key's type.
-
#masked_token ⇒ Object
Provides a masked version of the token for display (e.g., ak_live_••••rj4p) Requires the plaintext token to be available (only right after creation).
-
#permissions ⇒ Array<String>
Alias for scopes - provides a more user-friendly API that matches the configuration DSL where key types use
permissionsfor scope ceiling. - #pretty_print(printer) ⇒ Object
-
#public_key_type? ⇒ Boolean
Returns true if this key type is configured as public AND non-revocable.
-
#reload ⇒ Object
The plaintext token is an ephemeral creation-time value.
-
#revocable? ⇒ Boolean
Returns true if this key can be revoked/destroyed Keys without a key_type (legacy) are always revocable Keys with a key_type check the configuration.
-
#revoke! ⇒ Object
Instance Methods ==.
- #revoked? ⇒ Boolean
-
#scopes=(value) ⇒ Object
Override scopes setter to auto-clean blank values.
-
#serializable_hash(options = nil) ⇒ Object
Rendering a model as JSON must never expose the verification digest or a public token stored in the reserved metadata field.
-
#viewable_token ⇒ String?
Returns the stored plaintext token for public, non-revocable keys.
Instance Attribute Details
#token ⇒ Object (readonly)
Attributes & Serialization ==
Expose the plaintext token only immediately after creation
28 29 30 |
# File 'lib/api_keys/models/api_key.rb', line 28 def token @token end |
Instance Method Details
#active? ⇒ Boolean
155 156 157 |
# File 'lib/api_keys/models/api_key.rb', line 155 def active? !revoked? && !expired? end |
#allows_permission?(required_permission) ⇒ Boolean
Check if this key has a specific permission (alias for allows_scope?)
289 290 291 |
# File 'lib/api_keys/models/api_key.rb', line 289 def () allows_scope?() end |
#allows_scope?(required_scope) ⇒ Boolean
Basic scope check. Assumes scopes are stored as an array of strings.
Behavior depends on whether key_types mode is enabled:
- Simple mode (no key_types): blank scopes means "unrestricted" (all scopes allowed). This preserves backwards compatibility for apps that don't use scopes at all.
- Key types mode: blank scopes means "no permissions". When you've configured key types with permission ceilings, an empty scope list should deny access, not silently bypass the entire permission system.
260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 |
# File 'lib/api_keys/models/api_key.rb', line 260 def allows_scope?(required_scope) return false unless respond_to?(:scopes) return false unless required_scope.present? return false unless scopes.is_a?(Array) if scopes.blank? return !scope_policy_enabled? end required = required_scope.to_s return false unless scopes.all? { |scope| valid_scope_value?(scope) } return false unless scopes.include?(required) ceiling = ceiling == :all || ceiling.include?(required) end |
#destroy ⇒ Object
Override destroy to prevent destroying non-revocable keys
242 243 244 245 |
# File 'lib/api_keys/models/api_key.rb', line 242 def destroy raise ApiKeys::Errors::KeyNotRevocableError unless revocable? super end |
#destroy! ⇒ Object
247 248 249 250 |
# File 'lib/api_keys/models/api_key.rb', line 247 def destroy! raise ApiKeys::Errors::KeyNotRevocableError unless revocable? super end |
#environment_config ⇒ Object
Returns the configuration hash for this key's environment
217 218 219 220 221 |
# File 'lib/api_keys/models/api_key.rb', line 217 def environment_config return nil if environment.blank? configured_pair = ApiKeys.configuration.environments&.find { |name, _settings| name.to_s == environment.to_s } configured_pair&.last end |
#expired? ⇒ Boolean
151 152 153 |
# File 'lib/api_keys/models/api_key.rb', line 151 def expired? expires_at? && expires_at <= Time.current end |
#inspect ⇒ Object
Keep credentials and credential-derived values out of logs and consoles.
167 168 169 170 171 172 173 174 |
# File 'lib/api_keys/models/api_key.rb', line 167 def inspect attributes = %w[id prefix last4 name owner_type owner_id key_type environment expires_at revoked_at] .select { |attribute_name| has_attribute?(attribute_name) } .map { |attribute_name| "#{attribute_name}: #{attribute_for_inspect(attribute_name)}" } "#<#{self.class.name} #{attributes.join(', ')}>" rescue StandardError "#<#{self.class.name}>" end |
#key_type_config ⇒ Object
Returns the configuration hash for this key's type
210 211 212 213 214 |
# File 'lib/api_keys/models/api_key.rb', line 210 def key_type_config return nil if key_type.blank? configured_pair = ApiKeys.configuration.key_types&.find { |type, _settings| type.to_s == key_type.to_s } configured_pair&.last end |
#masked_token ⇒ Object
Provides a masked version of the token for display (e.g., ak_live_••••rj4p) Requires the plaintext token to be available (only right after creation).
295 296 297 298 299 300 301 302 303 304 |
# File 'lib/api_keys/models/api_key.rb', line 295 def masked_token # return "[Token not available]" unless token # No longer needed # Show prefix, 4 bullets, last 4 chars of the random part # random_part = token.delete_prefix(prefix) # No longer needed # "#{prefix}••••#{random_part.last(4)}" # No longer needed # Use the stored prefix and last4 attributes return "[Invalid Key Data]" unless prefix.present? && last4.present? "#{prefix}••••#{last4}" end |
#permissions ⇒ Array<String>
Alias for scopes - provides a more user-friendly API that matches
the configuration DSL where key types use permissions for scope ceiling.
Note: We use a method instead of alias_method because scopes is defined
dynamically via the attribute API in the engine initializer.
282 283 284 |
# File 'lib/api_keys/models/api_key.rb', line 282 def scopes end |
#pretty_print(printer) ⇒ Object
176 177 178 |
# File 'lib/api_keys/models/api_key.rb', line 176 def pretty_print(printer) printer.text(inspect) end |
#public_key_type? ⇒ Boolean
Returns true if this key type is configured as public AND non-revocable. Only these keys have their plaintext token stored in metadata for later viewing. This is used for publishable keys that are designed to be embedded in distributed apps.
226 227 228 229 230 231 |
# File 'lib/api_keys/models/api_key.rb', line 226 def public_key_type? return false if key_type.blank? config = key_type_config return false if config.nil? config[:public] == true && config[:revocable] == false end |
#reload ⇒ Object
The plaintext token is an ephemeral creation-time value. Active Record's reload does not clear arbitrary instance variables, so clear it explicitly.
161 162 163 164 |
# File 'lib/api_keys/models/api_key.rb', line 161 def reload(...) @token = nil super end |
#revocable? ⇒ Boolean
Returns true if this key can be revoked/destroyed Keys without a key_type (legacy) are always revocable Keys with a key_type check the configuration
202 203 204 205 206 207 |
# File 'lib/api_keys/models/api_key.rb', line 202 def revocable? return true if key_type.blank? config = key_type_config return false if config.nil? config.fetch(:revocable, true) end |
#revoke! ⇒ Object
Instance Methods ==
142 143 144 145 |
# File 'lib/api_keys/models/api_key.rb', line 142 def revoke! raise ApiKeys::Errors::KeyNotRevocableError unless revocable? update!(revoked_at: Time.current) end |
#revoked? ⇒ Boolean
147 148 149 |
# File 'lib/api_keys/models/api_key.rb', line 147 def revoked? revoked_at.present? end |
#scopes=(value) ⇒ Object
Override scopes setter to auto-clean blank values. This handles the common case where form checkboxes submit empty strings. Works for both create and update operations.
36 37 38 39 40 41 42 43 44 45 46 |
# File 'lib/api_keys/models/api_key.rb', line 36 def scopes=(value) cleaned = if value.is_a?(Array) value .map { |scope| scope.is_a?(Symbol) ? scope.to_s : scope } .reject { |scope| scope.respond_to?(:blank?) && scope.blank? } .uniq else value end super(cleaned) end |
#serializable_hash(options = nil) ⇒ Object
Rendering a model as JSON must never expose the verification digest or a public token stored in the reserved metadata field. Call #viewable_token explicitly when an authenticated UI intentionally needs a public token.
183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 |
# File 'lib/api_keys/models/api_key.rb', line 183 def serializable_hash( = nil) serialized = super() serialized.delete("token_digest") serialized.delete(:token_digest) = serialized["metadata"] || serialized[:metadata] if .is_a?(Hash) = .dup .delete("token") .delete(:token) serialized[serialized.key?("metadata") ? "metadata" : :metadata] = end serialized end |
#viewable_token ⇒ String?
Returns the stored plaintext token for public, non-revocable keys. Returns nil for all other key types (the token is only available at creation time).
236 237 238 239 |
# File 'lib/api_keys/models/api_key.rb', line 236 def viewable_token return nil unless public_key_type? &.dig("token") end |