api_keys 0.4.3 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +22 -0
- data/README.md +154 -21
- data/app/controllers/api_keys/keys_controller.rb +39 -8
- data/app/views/api_keys/keys/_form.html.erb +45 -4
- data/app/views/api_keys/keys/_key_badges.html.erb +7 -0
- data/app/views/api_keys/keys/_restriction_fields.html.erb +32 -0
- data/app/views/layouts/api_keys/application.html.erb +9 -1
- data/lib/api_keys/authentication.rb +7 -1
- data/lib/api_keys/configuration.rb +49 -8
- data/lib/api_keys/engine.rb +6 -0
- data/lib/api_keys/errors.rb +11 -0
- data/lib/api_keys/models/api_key.rb +184 -10
- data/lib/api_keys/models/concerns/has_api_keys.rb +59 -10
- data/lib/api_keys/restrictions.rb +379 -0
- data/lib/api_keys/services/authenticator.rb +88 -12
- data/lib/api_keys/version.rb +1 -1
- data/lib/api_keys.rb +1 -0
- data/lib/generators/api_keys/add_key_types_generator.rb +1 -1
- data/lib/generators/api_keys/add_restrictions_generator.rb +57 -0
- data/lib/generators/api_keys/templates/add_restrictions_to_api_keys.rb.erb +54 -0
- data/lib/generators/api_keys/templates/create_api_keys_table.rb.erb +22 -0
- data/lib/generators/api_keys/templates/initializer.rb +41 -6
- metadata +5 -1
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Migration to add the `restrictions` column to the api_keys table.
|
|
4
|
+
# This enables request restrictions: locking a key to specific web origins
|
|
5
|
+
# (with `*.` subdomain wildcards) and/or to specific IP addresses and CIDR
|
|
6
|
+
# ranges.
|
|
7
|
+
#
|
|
8
|
+
# Run this migration if you're upgrading from a version of api_keys that
|
|
9
|
+
# didn't have request restrictions support. The column defaults to an empty
|
|
10
|
+
# object, which means "unrestricted", so existing keys are unaffected.
|
|
11
|
+
class AddRestrictionsToApiKeys < ActiveRecord::Migration<%= migration_version %>
|
|
12
|
+
CONSTRAINT_NAME = "api_keys_restrictions_is_object"
|
|
13
|
+
|
|
14
|
+
def up
|
|
15
|
+
add_column :api_keys, :restrictions, json_column_type, default: {}, null: false
|
|
16
|
+
add_restrictions_object_constraint
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def down
|
|
20
|
+
remove_column :api_keys, :restrictions
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
# Helper method to determine the appropriate JSON column type based on the database adapter.
|
|
26
|
+
# Uses :jsonb for PostgreSQL for better performance and indexing, :json otherwise.
|
|
27
|
+
def json_column_type
|
|
28
|
+
# Check connection availability for adapter name inspection
|
|
29
|
+
if ActiveRecord::Base.connection.adapter_name.downcase.include?('postgresql')
|
|
30
|
+
:jsonb
|
|
31
|
+
else
|
|
32
|
+
:json
|
|
33
|
+
end
|
|
34
|
+
rescue ActiveRecord::ConnectionNotEstablished
|
|
35
|
+
# Fallback during initial setup or if connection isn't available
|
|
36
|
+
:text
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# JSON columns can also hold arrays and scalars. The application requires an
|
|
40
|
+
# object so damaged or validation-bypassing writes cannot erase a policy.
|
|
41
|
+
def add_restrictions_object_constraint
|
|
42
|
+
return unless connection.supports_check_constraints?
|
|
43
|
+
|
|
44
|
+
expression = case connection.adapter_name.downcase
|
|
45
|
+
when /postgres/
|
|
46
|
+
"jsonb_typeof(restrictions) = 'object'"
|
|
47
|
+
when /sqlite/
|
|
48
|
+
"json_valid(restrictions) AND json_type(restrictions) = 'object'"
|
|
49
|
+
when /mysql|trilogy/
|
|
50
|
+
"JSON_TYPE(restrictions) = 'OBJECT'"
|
|
51
|
+
end
|
|
52
|
+
add_check_constraint :api_keys, expression, name: CONSTRAINT_NAME if expression
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
# Migration responsible for creating the core api_keys table.
|
|
4
4
|
class CreateApiKeysTable < ActiveRecord::Migration<%= migration_version %>
|
|
5
|
+
RESTRICTIONS_CONSTRAINT_NAME = "api_keys_restrictions_is_object"
|
|
6
|
+
|
|
5
7
|
def change
|
|
6
8
|
primary_key_type, foreign_key_type = primary_and_foreign_key_types
|
|
7
9
|
|
|
@@ -30,6 +32,10 @@ class CreateApiKeysTable < ActiveRecord::Migration<%= migration_version %>
|
|
|
30
32
|
# Optional freeform metadata for tagging
|
|
31
33
|
t.send(json_column_type, :metadata, default: {}, null: false)
|
|
32
34
|
|
|
35
|
+
# Optional request restrictions: which web origins and IP addresses may
|
|
36
|
+
# present this key. An empty object means the key is unrestricted.
|
|
37
|
+
t.send(json_column_type, :restrictions, default: {}, null: false)
|
|
38
|
+
|
|
33
39
|
# Optional auto-expiration timestamp
|
|
34
40
|
t.datetime :expires_at
|
|
35
41
|
|
|
@@ -67,6 +73,8 @@ class CreateApiKeysTable < ActiveRecord::Migration<%= migration_version %>
|
|
|
67
73
|
t.index :environment
|
|
68
74
|
t.index [:owner_type, :owner_id, :key_type, :environment], name: "index_api_keys_owner_type_env"
|
|
69
75
|
end
|
|
76
|
+
|
|
77
|
+
add_restrictions_object_constraint
|
|
70
78
|
end
|
|
71
79
|
|
|
72
80
|
private
|
|
@@ -94,4 +102,18 @@ class CreateApiKeysTable < ActiveRecord::Migration<%= migration_version %>
|
|
|
94
102
|
# Fallback during initial setup or if connection isn't available
|
|
95
103
|
:text
|
|
96
104
|
end
|
|
105
|
+
|
|
106
|
+
def add_restrictions_object_constraint
|
|
107
|
+
return unless connection.supports_check_constraints?
|
|
108
|
+
|
|
109
|
+
expression = case connection.adapter_name.downcase
|
|
110
|
+
when /postgres/
|
|
111
|
+
"jsonb_typeof(restrictions) = 'object'"
|
|
112
|
+
when /sqlite/
|
|
113
|
+
"json_valid(restrictions) AND json_type(restrictions) = 'object'"
|
|
114
|
+
when /mysql|trilogy/
|
|
115
|
+
"JSON_TYPE(restrictions) = 'OBJECT'"
|
|
116
|
+
end
|
|
117
|
+
add_check_constraint :api_keys, expression, name: RESTRICTIONS_CONSTRAINT_NAME if expression
|
|
118
|
+
end
|
|
97
119
|
end
|
|
@@ -139,23 +139,28 @@ ApiKeys.configure do |config|
|
|
|
139
139
|
# - permissions: Scope ceiling - array of allowed scopes, or :all for unrestricted
|
|
140
140
|
# - revocable: Whether keys of this type can be revoked/deleted (default: true)
|
|
141
141
|
# - limit: Max keys of this type per owner per environment (nil = unlimited)
|
|
142
|
-
# - public: If true
|
|
143
|
-
#
|
|
144
|
-
#
|
|
142
|
+
# - public: If true, stores plaintext token in metadata so it can be viewed
|
|
143
|
+
# again in the dashboard. Use ONLY for publishable keys designed
|
|
144
|
+
# to be embedded in distributed apps. Public types must
|
|
145
145
|
# use a finite, non-empty permissions array (never :all). (default: false)
|
|
146
146
|
# SECURITY: NEVER set public: true on secret keys!
|
|
147
|
+
# - restrictions: Which request-restriction kinds keys of this type may carry:
|
|
148
|
+
# any subset of [:origins, :ips]. Omitted = both allowed.
|
|
149
|
+
# `restrictions: []` forbids restrictions for this type.
|
|
150
|
+
# See "REQUEST RESTRICTIONS" below.
|
|
147
151
|
#
|
|
148
152
|
# config.key_types = {
|
|
149
153
|
# publishable: {
|
|
150
154
|
# prefix: "pk", # → pk_test_, pk_live_
|
|
151
155
|
# permissions: %w[read validate], # Can ONLY have these scopes
|
|
152
|
-
# revocable: false, # Cannot be revoked - protects deployed apps!
|
|
153
156
|
# public: true, # Store token for later viewing in dashboard
|
|
154
|
-
# limit: 1
|
|
157
|
+
# limit: 1, # Only 1 publishable key per environment
|
|
158
|
+
# restrictions: [:origins] # Browser keys lock to domains, not IPs
|
|
155
159
|
# },
|
|
156
160
|
# secret: {
|
|
157
161
|
# prefix: "sk", # → sk_test_, sk_live_
|
|
158
|
-
# permissions: :all
|
|
162
|
+
# permissions: :all, # No scope restrictions
|
|
163
|
+
# restrictions: [:ips] # Server keys lock to addresses, not domains
|
|
159
164
|
# # revocable: true (default)
|
|
160
165
|
# # public: false (default) - NEVER store secret keys!
|
|
161
166
|
# # limit: nil (default = unlimited)
|
|
@@ -275,6 +280,36 @@ ApiKeys.configure do |config|
|
|
|
275
280
|
# Default: true
|
|
276
281
|
# config.https_strict_mode = true
|
|
277
282
|
|
|
283
|
+
# ============================================================================
|
|
284
|
+
# REQUEST RESTRICTIONS (origin and IP allowlists)
|
|
285
|
+
# ============================================================================
|
|
286
|
+
#
|
|
287
|
+
# Any key can be locked to the places it may be used from:
|
|
288
|
+
#
|
|
289
|
+
# user.create_api_key!(name: "Widget key", allowed_origins: "example.com, *.example.com")
|
|
290
|
+
# key.allowed_ips = "203.0.113.7, 10.0.0.0/8"
|
|
291
|
+
#
|
|
292
|
+
# Origins are matched against the browser's Origin header (falling back to
|
|
293
|
+
# Referer); IPs are matched with CIDR support. Within a list any entry
|
|
294
|
+
# admits the request; every list that is set must pass. Keys with no
|
|
295
|
+
# restrictions work from anywhere, so nothing changes until you opt in.
|
|
296
|
+
# Policy mismatches answer 403 with `origin_not_allowed` / `ip_not_allowed`.
|
|
297
|
+
# Malformed stored policy also fails closed with `restriction_misconfigured`.
|
|
298
|
+
#
|
|
299
|
+
# Requires the restrictions column:
|
|
300
|
+
# rails generate api_keys:add_restrictions && rails db:migrate
|
|
301
|
+
# ============================================================================
|
|
302
|
+
|
|
303
|
+
# How the client IP is resolved for `allowed_ips` checks.
|
|
304
|
+
# The default trusts Rails' own resolution, which honors
|
|
305
|
+
# config.action_dispatch.trusted_proxies. Behind a CDN that terminates the
|
|
306
|
+
# connection, configure trusted_proxies whenever possible. Trust a vendor
|
|
307
|
+
# header directly only when your network ingress rejects requests that
|
|
308
|
+
# bypass that vendor; otherwise callers can spoof the address being checked.
|
|
309
|
+
# Default: ->(request) { request.remote_ip }
|
|
310
|
+
#
|
|
311
|
+
# config.client_ip_resolver = ->(request) { request.headers.fetch("CF-Connecting-IP") }
|
|
312
|
+
|
|
278
313
|
# ============================================================================
|
|
279
314
|
# BACKGROUND JOBS & CALLBACKS
|
|
280
315
|
# ============================================================================
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: api_keys
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.5.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- rameerez
|
|
@@ -84,6 +84,7 @@ files:
|
|
|
84
84
|
- app/views/api_keys/keys/_key_status.html.erb
|
|
85
85
|
- app/views/api_keys/keys/_keys_table.html.erb
|
|
86
86
|
- app/views/api_keys/keys/_publishable_keys.html.erb
|
|
87
|
+
- app/views/api_keys/keys/_restriction_fields.html.erb
|
|
87
88
|
- app/views/api_keys/keys/_secret_keys.html.erb
|
|
88
89
|
- app/views/api_keys/keys/_show_token.html.erb
|
|
89
90
|
- app/views/api_keys/keys/_token_display.html.erb
|
|
@@ -109,6 +110,7 @@ files:
|
|
|
109
110
|
- lib/api_keys/logging.rb
|
|
110
111
|
- lib/api_keys/models/api_key.rb
|
|
111
112
|
- lib/api_keys/models/concerns/has_api_keys.rb
|
|
113
|
+
- lib/api_keys/restrictions.rb
|
|
112
114
|
- lib/api_keys/services/authenticator.rb
|
|
113
115
|
- lib/api_keys/services/digestor.rb
|
|
114
116
|
- lib/api_keys/services/token_generator.rb
|
|
@@ -116,9 +118,11 @@ files:
|
|
|
116
118
|
- lib/api_keys/version.rb
|
|
117
119
|
- lib/generators/api_keys/add_authentication_index_generator.rb
|
|
118
120
|
- lib/generators/api_keys/add_key_types_generator.rb
|
|
121
|
+
- lib/generators/api_keys/add_restrictions_generator.rb
|
|
119
122
|
- lib/generators/api_keys/install_generator.rb
|
|
120
123
|
- lib/generators/api_keys/templates/add_authentication_index_to_api_keys.rb.erb
|
|
121
124
|
- lib/generators/api_keys/templates/add_key_types_to_api_keys.rb.erb
|
|
125
|
+
- lib/generators/api_keys/templates/add_restrictions_to_api_keys.rb.erb
|
|
122
126
|
- lib/generators/api_keys/templates/create_api_keys_table.rb.erb
|
|
123
127
|
- lib/generators/api_keys/templates/initializer.rb
|
|
124
128
|
homepage: https://github.com/rameerez/api_keys
|