api_keys 0.5.1 ā 0.6.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 +37 -0
- data/README.md +126 -12
- data/SECURITY.md +7 -6
- data/app/controllers/api_keys/keys_controller.rb +73 -46
- data/app/views/api_keys/keys/_form.html.erb +56 -10
- data/app/views/api_keys/keys/_key_row.html.erb +4 -2
- data/app/views/api_keys/keys/_refusal_notice.html.erb +24 -0
- data/app/views/api_keys/keys/_show_token.html.erb +4 -7
- data/app/views/api_keys/keys/edit.html.erb +2 -0
- data/app/views/layouts/api_keys/application.html.erb +6 -0
- data/docs/reviews/2026-10-18-0-6-0/01-astra-review-of-0-6-0.md +36 -0
- data/docs/reviews/2026-10-18-0-6-0/02-astra-re-review-of-0-6-0.md +34 -0
- data/docs/reviews/2026-10-18-0-6-0/03-sol-re-check-of-0-6-0.md +41 -0
- data/lib/api_keys/authentication.rb +123 -7
- data/lib/api_keys/configuration.rb +26 -0
- data/lib/api_keys/form_builder_extensions.rb +3 -1
- data/lib/api_keys/helpers/token_session.rb +183 -23
- data/lib/api_keys/helpers/view_helpers.rb +33 -0
- data/lib/api_keys/jobs/update_stats_job.rb +45 -1
- data/lib/api_keys/models/api_key.rb +231 -0
- data/lib/api_keys/version.rb +1 -1
- data/lib/api_keys.rb +7 -0
- data/lib/generators/api_keys/add_key_activity_generator.rb +59 -0
- data/lib/generators/api_keys/templates/add_key_activity_to_api_keys.rb.erb +37 -0
- data/lib/generators/api_keys/templates/create_api_keys_table.rb.erb +12 -0
- data/lib/generators/api_keys/templates/initializer.rb +17 -0
- metadata +7 -1
data/lib/api_keys.rb
CHANGED
|
@@ -42,6 +42,13 @@ module ApiKeys
|
|
|
42
42
|
def reset_configuration!
|
|
43
43
|
@configuration = ::ApiKeys::Configuration.new
|
|
44
44
|
end
|
|
45
|
+
|
|
46
|
+
# Add-only backfill after widening a key type's `permissions:` ceiling, for
|
|
47
|
+
# types that keep stored scopes. See ApiKeys::ApiKey.reconcile_scopes!.
|
|
48
|
+
# @return [Integer] How many keys gained scopes.
|
|
49
|
+
def reconcile_scopes!(key_type:)
|
|
50
|
+
::ApiKeys::ApiKey.reconcile_scopes!(key_type: key_type)
|
|
51
|
+
end
|
|
45
52
|
end
|
|
46
53
|
end
|
|
47
54
|
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rails/generators/base"
|
|
4
|
+
require "rails/generators/active_record"
|
|
5
|
+
|
|
6
|
+
module ApiKeys
|
|
7
|
+
module Generators
|
|
8
|
+
# Rails generator for the columns api_keys 0.6.0 adds to the api_keys
|
|
9
|
+
# table, in one migration:
|
|
10
|
+
#
|
|
11
|
+
# - `last_refused_at`, `refusals_count`, `last_refusal_code`: requests that
|
|
12
|
+
# presented a key's real token and were refused by a policy.
|
|
13
|
+
# - `token_shown_at`: when the key's token was released through the
|
|
14
|
+
# one-time dashboard handoff, which makes that handoff durable.
|
|
15
|
+
#
|
|
16
|
+
# This generator is for existing installations; new installs get the
|
|
17
|
+
# columns from the install generator, so they never need to run this.
|
|
18
|
+
class AddKeyActivityGenerator < Rails::Generators::Base
|
|
19
|
+
include ActiveRecord::Generators::Migration
|
|
20
|
+
|
|
21
|
+
source_root File.expand_path("templates", __dir__)
|
|
22
|
+
|
|
23
|
+
# Implement the required interface for Rails::Generators::Migration.
|
|
24
|
+
def self.next_migration_number(dirname)
|
|
25
|
+
next_migration_number = current_migration_number(dirname) + 1
|
|
26
|
+
ActiveRecord::Migration.next_migration_number(next_migration_number)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Creates the migration file using the template.
|
|
30
|
+
def create_migration_file
|
|
31
|
+
migration_template "add_key_activity_to_api_keys.rb.erb",
|
|
32
|
+
File.join(db_migrate_path, "add_key_activity_to_api_keys.rb")
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Displays helpful information to the user after installation.
|
|
36
|
+
def display_post_install_message
|
|
37
|
+
say "\nš¦ api_keys 0.6.0 key activity migration created!", :green
|
|
38
|
+
say "\nNext steps:"
|
|
39
|
+
say " 1. Run `rails db:migrate` to add last_refused_at, refusals_count,"
|
|
40
|
+
say " last_refusal_code and token_shown_at."
|
|
41
|
+
say "\n 2. That's it. Requests that present a key's real token and are refused"
|
|
42
|
+
say " (origin/IP restrictions, environment, missing scope, revoked, expired...)"
|
|
43
|
+
say " are now stamped on the key, through the same background stats job as"
|
|
44
|
+
say " last_used_at, and the dashboard shows them on the key's page."
|
|
45
|
+
say " A refusal never moves last_used_at or requests_count."
|
|
46
|
+
say " refusals_count, like requests_count, is only kept with `config.track_requests_count = true`."
|
|
47
|
+
say "\n The one-time token page now records the first retrieval in token_shown_at,"
|
|
48
|
+
say " so a copied session cannot show a token twice, even after a cache flush."
|
|
49
|
+
say "\nSee the api_keys README for detailed usage and examples.", :cyan
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
private
|
|
53
|
+
|
|
54
|
+
def migration_version
|
|
55
|
+
"[#{ActiveRecord::VERSION::STRING.to_f}]"
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Migration to add the api_keys 0.6.0 columns to the api_keys table.
|
|
4
|
+
#
|
|
5
|
+
# `last_refused_at`, `refusals_count`, and `last_refusal_code` record requests
|
|
6
|
+
# that presented a key's real token and were refused by a policy (origin or IP
|
|
7
|
+
# restrictions, environment isolation, missing scope, revoked, expired...), so
|
|
8
|
+
# the dashboard can say "requests are arriving but refused" instead of showing
|
|
9
|
+
# a key nobody ever tried. They never touch `last_used_at` or `requests_count`.
|
|
10
|
+
#
|
|
11
|
+
# `token_shown_at` records when the key's token was released through the
|
|
12
|
+
# one-time dashboard handoff. The first retrieval claims it with a single
|
|
13
|
+
# conditional UPDATE, so a copy of the session cannot show the token again,
|
|
14
|
+
# even after the cache is flushed. Existing keys keep NULL.
|
|
15
|
+
#
|
|
16
|
+
# Run this migration if you're upgrading from a version of api_keys before
|
|
17
|
+
# 0.6.0. New installs get the columns from the install generator. Until it
|
|
18
|
+
# runs, authentication works unchanged, refusals are simply not recorded, and
|
|
19
|
+
# the token handoff relies on the cache marker alone, as in 0.5.1. The
|
|
20
|
+
# migration is idempotent: columns that already exist are left alone.
|
|
21
|
+
class AddKeyActivityToApiKeys < ActiveRecord::Migration<%= migration_version %>
|
|
22
|
+
def up
|
|
23
|
+
add_column :api_keys, :last_refused_at, :datetime unless column_exists?(:api_keys, :last_refused_at)
|
|
24
|
+
unless column_exists?(:api_keys, :refusals_count)
|
|
25
|
+
add_column :api_keys, :refusals_count, :bigint, default: 0, null: false
|
|
26
|
+
end
|
|
27
|
+
add_column :api_keys, :last_refusal_code, :string, limit: 64 unless column_exists?(:api_keys, :last_refusal_code)
|
|
28
|
+
add_column :api_keys, :token_shown_at, :datetime unless column_exists?(:api_keys, :token_shown_at)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def down
|
|
32
|
+
remove_column :api_keys, :token_shown_at if column_exists?(:api_keys, :token_shown_at)
|
|
33
|
+
remove_column :api_keys, :last_refusal_code if column_exists?(:api_keys, :last_refusal_code)
|
|
34
|
+
remove_column :api_keys, :refusals_count if column_exists?(:api_keys, :refusals_count)
|
|
35
|
+
remove_column :api_keys, :last_refused_at if column_exists?(:api_keys, :last_refused_at)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -45,6 +45,18 @@ class CreateApiKeysTable < ActiveRecord::Migration<%= migration_version %>
|
|
|
45
45
|
# Optional counter cache for total requests made with this key.
|
|
46
46
|
t.bigint :requests_count, default: 0, null: false
|
|
47
47
|
|
|
48
|
+
# Refused-but-identified traffic: requests that presented this key's real
|
|
49
|
+
# token and were refused by a policy (origin/IP lock, environment, scope,
|
|
50
|
+
# revoked, expired...). Kept apart from last_used_at, which a refusal never moves.
|
|
51
|
+
t.datetime :last_refused_at
|
|
52
|
+
t.bigint :refusals_count, default: 0, null: false
|
|
53
|
+
t.string :last_refusal_code, limit: 64
|
|
54
|
+
|
|
55
|
+
# When this key's token was released through the one-time dashboard
|
|
56
|
+
# handoff. The first retrieval claims it with a conditional UPDATE, so a
|
|
57
|
+
# copied session can never show the token twice.
|
|
58
|
+
t.datetime :token_shown_at
|
|
59
|
+
|
|
48
60
|
# Timestamp when the key was revoked (null means active)
|
|
49
61
|
t.datetime :revoked_at
|
|
50
62
|
|
|
@@ -148,11 +148,20 @@ ApiKeys.configure do |config|
|
|
|
148
148
|
# any subset of [:origins, :ips]. Omitted = both allowed.
|
|
149
149
|
# `restrictions: []` forbids restrictions for this type.
|
|
150
150
|
# See "REQUEST RESTRICTIONS" below.
|
|
151
|
+
# - scopes: Where scope checks look. :stored (default) uses the scopes each
|
|
152
|
+
# key saved at creation, so WIDENING `permissions` LATER DOES NOT
|
|
153
|
+
# REACH EXISTING KEYS: they keep the old list and 403 new scopes.
|
|
154
|
+
# :ceiling makes the live `permissions` list the contract for
|
|
155
|
+
# every key of the type (widening and narrowing apply at once).
|
|
156
|
+
# Use it for types whose users do not pick scopes; it requires a
|
|
157
|
+
# finite, non-empty permissions list. For :stored types, backfill
|
|
158
|
+
# after widening with ApiKeys.reconcile_scopes!(key_type: :name).
|
|
151
159
|
#
|
|
152
160
|
# config.key_types = {
|
|
153
161
|
# publishable: {
|
|
154
162
|
# prefix: "pk", # ā pk_test_, pk_live_
|
|
155
163
|
# permissions: %w[read validate], # Can ONLY have these scopes
|
|
164
|
+
# scopes: :ceiling, # Every pk key has exactly these, even after edits
|
|
156
165
|
# public: true, # Store token for later viewing in dashboard
|
|
157
166
|
# limit: 1, # Only 1 publishable key per environment
|
|
158
167
|
# restrictions: [:origins] # Browser keys lock to domains, not IPs
|
|
@@ -344,9 +353,17 @@ ApiKeys.configure do |config|
|
|
|
344
353
|
|
|
345
354
|
# Minimum interval between last_used_at jobs when exact request counting is
|
|
346
355
|
# disabled. Set to 0 or nil to update on every successful authentication.
|
|
356
|
+
# The same interval debounces refusal stats (below).
|
|
347
357
|
# Default: 1.minute
|
|
348
358
|
# config.stats_update_interval = 1.minute
|
|
349
359
|
|
|
360
|
+
# Refused requests: when a request presents a key's real token and a policy
|
|
361
|
+
# refuses it (origin/IP lock, environment, missing scope, revoked, expired),
|
|
362
|
+
# the same stats job stamps last_refused_at and last_refusal_code (and
|
|
363
|
+
# refusals_count when track_requests_count is on). A refusal never moves
|
|
364
|
+
# last_used_at or requests_count. The dashboard shows it on the key's page.
|
|
365
|
+
# Existing installs: rails generate api_keys:add_key_activity && rails db:migrate
|
|
366
|
+
|
|
350
367
|
# Queue names for background jobs.
|
|
351
368
|
# Default: :default
|
|
352
369
|
# config.stats_job_queue = :default
|
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.6.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/_refusal_notice.html.erb
|
|
87
88
|
- app/views/api_keys/keys/_restriction_fields.html.erb
|
|
88
89
|
- app/views/api_keys/keys/_secret_keys.html.erb
|
|
89
90
|
- app/views/api_keys/keys/_show_token.html.erb
|
|
@@ -98,6 +99,9 @@ files:
|
|
|
98
99
|
- docs/reviews/2026-10-18-0-5-1/01-astra-review-of-0-5-0.md
|
|
99
100
|
- docs/reviews/2026-10-18-0-5-1/02-astra-re-review-of-0-5-1.md
|
|
100
101
|
- docs/reviews/2026-10-18-0-5-1/03-opus-review-of-0-5-1.md
|
|
102
|
+
- docs/reviews/2026-10-18-0-6-0/01-astra-review-of-0-6-0.md
|
|
103
|
+
- docs/reviews/2026-10-18-0-6-0/02-astra-re-review-of-0-6-0.md
|
|
104
|
+
- docs/reviews/2026-10-18-0-6-0/03-sol-re-check-of-0-6-0.md
|
|
101
105
|
- lib/api_keys.rb
|
|
102
106
|
- lib/api_keys/authentication.rb
|
|
103
107
|
- lib/api_keys/configuration.rb
|
|
@@ -120,10 +124,12 @@ files:
|
|
|
120
124
|
- lib/api_keys/tenant_resolution.rb
|
|
121
125
|
- lib/api_keys/version.rb
|
|
122
126
|
- lib/generators/api_keys/add_authentication_index_generator.rb
|
|
127
|
+
- lib/generators/api_keys/add_key_activity_generator.rb
|
|
123
128
|
- lib/generators/api_keys/add_key_types_generator.rb
|
|
124
129
|
- lib/generators/api_keys/add_restrictions_generator.rb
|
|
125
130
|
- lib/generators/api_keys/install_generator.rb
|
|
126
131
|
- lib/generators/api_keys/templates/add_authentication_index_to_api_keys.rb.erb
|
|
132
|
+
- lib/generators/api_keys/templates/add_key_activity_to_api_keys.rb.erb
|
|
127
133
|
- lib/generators/api_keys/templates/add_key_types_to_api_keys.rb.erb
|
|
128
134
|
- lib/generators/api_keys/templates/add_restrictions_to_api_keys.rb.erb
|
|
129
135
|
- lib/generators/api_keys/templates/create_api_keys_table.rb.erb
|