api_keys 0.5.1 → 0.7.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.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +69 -0
  3. data/README.md +402 -13
  4. data/SECURITY.md +7 -6
  5. data/app/controllers/api_keys/keys_controller.rb +73 -46
  6. data/app/views/api_keys/keys/_form.html.erb +56 -10
  7. data/app/views/api_keys/keys/_key_row.html.erb +4 -2
  8. data/app/views/api_keys/keys/_refusal_notice.html.erb +24 -0
  9. data/app/views/api_keys/keys/_show_token.html.erb +4 -7
  10. data/app/views/api_keys/keys/edit.html.erb +2 -0
  11. data/app/views/layouts/api_keys/application.html.erb +6 -0
  12. data/docs/reviews/2026-10-18-0-6-0/01-astra-review-of-0-6-0.md +36 -0
  13. data/docs/reviews/2026-10-18-0-6-0/02-astra-re-review-of-0-6-0.md +34 -0
  14. data/docs/reviews/2026-10-18-0-6-0/03-sol-re-check-of-0-6-0.md +41 -0
  15. data/lib/api_keys/authentication.rb +174 -7
  16. data/lib/api_keys/configuration.rb +82 -0
  17. data/lib/api_keys/device_flow.rb +146 -0
  18. data/lib/api_keys/engine.rb +9 -0
  19. data/lib/api_keys/errors.rb +11 -0
  20. data/lib/api_keys/form_builder_extensions.rb +3 -1
  21. data/lib/api_keys/helpers/token_session.rb +183 -23
  22. data/lib/api_keys/helpers/view_helpers.rb +34 -0
  23. data/lib/api_keys/idempotency.rb +155 -0
  24. data/lib/api_keys/jobs/update_stats_job.rb +45 -1
  25. data/lib/api_keys/models/api_key.rb +261 -3
  26. data/lib/api_keys/models/concerns/has_api_keys.rb +9 -1
  27. data/lib/api_keys/models/device_authorization.rb +333 -0
  28. data/lib/api_keys/models/idempotency_key.rb +184 -0
  29. data/lib/api_keys/version.rb +1 -1
  30. data/lib/api_keys.rb +13 -0
  31. data/lib/generators/api_keys/add_created_by_generator.rb +50 -0
  32. data/lib/generators/api_keys/add_device_authorizations_generator.rb +51 -0
  33. data/lib/generators/api_keys/add_idempotency_keys_generator.rb +52 -0
  34. data/lib/generators/api_keys/add_key_activity_generator.rb +59 -0
  35. data/lib/generators/api_keys/templates/add_created_by_to_api_keys.rb.erb +33 -0
  36. data/lib/generators/api_keys/templates/add_key_activity_to_api_keys.rb.erb +37 -0
  37. data/lib/generators/api_keys/templates/create_api_keys_device_authorizations.rb.erb +70 -0
  38. data/lib/generators/api_keys/templates/create_api_keys_idempotency_keys.rb.erb +52 -0
  39. data/lib/generators/api_keys/templates/create_api_keys_table.rb.erb +16 -0
  40. data/lib/generators/api_keys/templates/initializer.rb +35 -0
  41. metadata +17 -1
@@ -0,0 +1,52 @@
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 Idempotency-Keys table (api_keys 0.7.0), which
9
+ # ApiKeys::Idempotency needs: each write under an Idempotency-Key runs
10
+ # once per API key, and a retry answers the record as it is now. New and
11
+ # existing installations run it alike.
12
+ class AddIdempotencyKeysGenerator < Rails::Generators::Base
13
+ include ActiveRecord::Generators::Migration
14
+
15
+ source_root File.expand_path("templates", __dir__)
16
+
17
+ # Implement the required interface for Rails::Generators::Migration.
18
+ def self.next_migration_number(dirname)
19
+ next_migration_number = current_migration_number(dirname) + 1
20
+ ActiveRecord::Migration.next_migration_number(next_migration_number)
21
+ end
22
+
23
+ # Creates the migration file using the template.
24
+ def create_migration_file
25
+ migration_template "create_api_keys_idempotency_keys.rb.erb",
26
+ File.join(db_migrate_path, "create_api_keys_idempotency_keys.rb")
27
+ end
28
+
29
+ # Displays helpful information to the user after installation.
30
+ def display_post_install_message
31
+ say "\nšŸ” api_keys Idempotency-Keys migration created!", :green
32
+ say "\nNext steps:"
33
+ say " 1. Run `rails db:migrate` to create the api_keys_idempotency_keys table."
34
+ say "\n 2. In your API base controller, after authenticate_api_key!:"
35
+ say " include ApiKeys::Idempotency"
36
+ say " around_action :run_once_per_idempotency_key"
37
+ say "\n 3. Answer every write with render_resource(record, status: ...), and define"
38
+ say " resource_json(record, **options) to render a record and"
39
+ say " idempotent_replay_allowed?(record) to say whether current_api_key may read it."
40
+ say " Keep the around_action outside any transaction your app opens."
41
+ say "\n 4. Run ApiKeys::IdempotencyKey.prune! once a day."
42
+ say "\nSee the api_keys README for detailed usage and examples.", :cyan
43
+ end
44
+
45
+ private
46
+
47
+ def migration_version
48
+ "[#{ActiveRecord::VERSION::STRING.to_f}]"
49
+ end
50
+ end
51
+ end
52
+ end
@@ -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,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Migration to add the person behind each key to the api_keys table
4
+ # (api_keys 0.7.0): `created_by_type` and `created_by_id`, a polymorphic
5
+ # reference, for keys whose owner is not a person (an organization).
6
+ # `create_api_key!(created_by: current_user)` sets it, and
7
+ # `config.trusted_key_check` can refuse a key once that person no longer
8
+ # stands behind it.
9
+ #
10
+ # Run this migration if you're upgrading from a version of api_keys before
11
+ # 0.7.0. New installs get the columns from the install generator. Existing
12
+ # keys keep NULL. The migration is idempotent: columns that already exist are
13
+ # left alone.
14
+ class AddCreatedByToApiKeys < ActiveRecord::Migration<%= migration_version %>
15
+ def up
16
+ return if column_exists?(:api_keys, :created_by_id)
17
+
18
+ add_reference :api_keys, :created_by, polymorphic: true, null: true, type: foreign_key_type
19
+ end
20
+
21
+ def down
22
+ remove_reference :api_keys, :created_by, polymorphic: true if column_exists?(:api_keys, :created_by_id)
23
+ end
24
+
25
+ private
26
+
27
+ # The key type the install generator gives api_keys.owner_id: the app's
28
+ # `primary_key_type` generator setting, else :bigint.
29
+ def foreign_key_type
30
+ generators = Rails.application&.config&.generators
31
+ generators&.options&.dig(generators.orm, :primary_key_type) || :bigint
32
+ end
33
+ 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
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Migration to create the table of device authorizations (api_keys 0.7.0).
4
+ # Each row is a client's request for an API key on the OAuth 2.0 Device
5
+ # Authorization Grant (RFC 8628), which a person approves or denies in your
6
+ # app (ApiKeys::DeviceFlow, ApiKeys::DeviceAuthorization). Both codes are
7
+ # kept as SHA-256 digests, never as text, and the key's token is never
8
+ # stored.
9
+ #
10
+ # Only apps that use the device flow need this table.
11
+ class CreateApiKeysDeviceAuthorizations < ActiveRecord::Migration<%= migration_version %>
12
+ def change
13
+ primary_key_type, foreign_key_type = primary_and_foreign_key_types
14
+
15
+ create_table :api_keys_device_authorizations, id: primary_key_type do |t|
16
+ t.string :device_code_digest, null: false, index: { unique: true }
17
+ t.string :user_code_digest, null: false, index: { unique: true }
18
+ # The client names itself, and every poll repeats it (RFC 8628 §3.4).
19
+ t.string :client_id, null: false
20
+ t.string :device_name
21
+ t.string :requested_from
22
+ # No default here: MySQL refuses a literal default on a JSON column.
23
+ # The model defaults scopes to [].
24
+ t.send(json_column_type, :scopes, null: false)
25
+ # The type of the key the approval makes, when key types are configured.
26
+ t.string :key_type
27
+ t.string :state, default: "pending", null: false
28
+ t.integer :interval, default: 5, null: false
29
+ t.datetime :expires_at, null: false, index: true
30
+ t.datetime :last_polled_at
31
+ # Who the key is for, and who decided: set when the person decides.
32
+ t.references :owner, polymorphic: true, null: true, type: foreign_key_type
33
+ t.references :decided_by, polymorphic: true, null: true, type: foreign_key_type
34
+ t.datetime :decided_at
35
+ # The key that the first poll after the approval created.
36
+ t.references :api_key, null: true, type: api_key_id_type, foreign_key: { on_delete: :nullify }
37
+ t.timestamps
38
+ end
39
+ end
40
+
41
+ private
42
+
43
+ # The key types the install generator gives the api_keys table: the app's
44
+ # `primary_key_type` generator setting, else Rails' defaults.
45
+ def primary_and_foreign_key_types
46
+ generators = Rails.application&.config&.generators
47
+ setting = generators&.options&.dig(generators.orm, :primary_key_type)
48
+ [setting || :primary_key, setting || :bigint]
49
+ end
50
+
51
+ # The type of api_keys.id, so that the foreign key matches it exactly.
52
+ def api_key_id_type
53
+ column = connection.columns(:api_keys).find { |candidate| candidate.name == "id" }
54
+ return primary_and_foreign_key_types.last unless column
55
+
56
+ sql_type = column.sql_type.to_s.downcase
57
+ if sql_type.include?("uuid")
58
+ :uuid
59
+ elsif column.type == :integer
60
+ sql_type.include?("bigint") || column.limit == 8 ? :bigint : :integer
61
+ else
62
+ column.type
63
+ end
64
+ end
65
+
66
+ # :jsonb on PostgreSQL, :json elsewhere, as on the api_keys table.
67
+ def json_column_type
68
+ connection.adapter_name.downcase.include?("postg") ? :jsonb : :json
69
+ end
70
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Migration to create the table of Idempotency-Keys (api_keys 0.7.0). Each
4
+ # row is one write under one API key's Idempotency-Key header
5
+ # (ApiKeys::Idempotency, ApiKeys::IdempotencyKey): the request it stands for
6
+ # as a digest, and, committed with the write itself, its status and a
7
+ # reference to the record the write made or changed. A row never keeps a
8
+ # response body, so a secret shown once is never stored.
9
+ #
10
+ # Only apps that use ApiKeys::Idempotency need this table.
11
+ class CreateApiKeysIdempotencyKeys < ActiveRecord::Migration<%= migration_version %>
12
+ def change
13
+ primary_key_type, foreign_key_type = primary_and_foreign_key_types
14
+
15
+ create_table :api_keys_idempotency_keys, id: primary_key_type do |t|
16
+ # Each API key has its own keys. A deleted API key takes them with it.
17
+ t.references :api_key, null: false, type: api_key_id_type, foreign_key: { on_delete: :cascade }, index: false
18
+ t.string :key, null: false
19
+ t.string :request_digest
20
+ t.integer :response_status
21
+ t.references :resource, polymorphic: true, null: true, type: foreign_key_type, index: false
22
+ t.timestamps
23
+ t.index :created_at
24
+ t.index [:api_key_id, :key], unique: true
25
+ end
26
+ end
27
+
28
+ private
29
+
30
+ # The key types the install generator gives the api_keys table: the app's
31
+ # `primary_key_type` generator setting, else Rails' defaults.
32
+ def primary_and_foreign_key_types
33
+ generators = Rails.application&.config&.generators
34
+ setting = generators&.options&.dig(generators.orm, :primary_key_type)
35
+ [setting || :primary_key, setting || :bigint]
36
+ end
37
+
38
+ # The type of api_keys.id, so that the foreign key matches it exactly.
39
+ def api_key_id_type
40
+ column = connection.columns(:api_keys).find { |candidate| candidate.name == "id" }
41
+ return primary_and_foreign_key_types.last unless column
42
+
43
+ sql_type = column.sql_type.to_s.downcase
44
+ if sql_type.include?("uuid")
45
+ :uuid
46
+ elsif column.type == :integer
47
+ sql_type.include?("bigint") || column.limit == 8 ? :bigint : :integer
48
+ else
49
+ column.type
50
+ end
51
+ end
52
+ end
@@ -26,6 +26,10 @@ class CreateApiKeysTable < ActiveRecord::Migration<%= migration_version %>
26
26
  # Who owns this key? Can be null for ownerless keys.
27
27
  t.references :owner, polymorphic: true, null: true, type: foreign_key_type
28
28
 
29
+ # Who created this key: the person behind it, for keys whose owner is
30
+ # not a person (an organization). Optional.
31
+ t.references :created_by, polymorphic: true, null: true, type: foreign_key_type
32
+
29
33
  # Optional list of permissions granted to this key (array of strings)
30
34
  t.send(json_column_type, :scopes, default: [], null: false)
31
35
 
@@ -45,6 +49,18 @@ class CreateApiKeysTable < ActiveRecord::Migration<%= migration_version %>
45
49
  # Optional counter cache for total requests made with this key.
46
50
  t.bigint :requests_count, default: 0, null: false
47
51
 
52
+ # Refused-but-identified traffic: requests that presented this key's real
53
+ # token and were refused by a policy (origin/IP lock, environment, scope,
54
+ # revoked, expired...). Kept apart from last_used_at, which a refusal never moves.
55
+ t.datetime :last_refused_at
56
+ t.bigint :refusals_count, default: 0, null: false
57
+ t.string :last_refusal_code, limit: 64
58
+
59
+ # When this key's token was released through the one-time dashboard
60
+ # handoff. The first retrieval claims it with a conditional UPDATE, so a
61
+ # copied session can never show the token twice.
62
+ t.datetime :token_shown_at
63
+
48
64
  # Timestamp when the key was revoked (null means active)
49
65
  t.datetime :revoked_at
50
66
 
@@ -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
@@ -318,6 +327,24 @@ ApiKeys.configure do |config|
318
327
  # when your network ingress rejects requests that bypass that vendor:
319
328
  # config.client_ip_resolver = ->(request) { request.headers.fetch("CF-Connecting-IP") }
320
329
 
330
+ # ============================================================================
331
+ # THE PERSON BEHIND A KEY
332
+ # ============================================================================
333
+ #
334
+ # When keys belong to an organization, name the person who creates each one:
335
+ #
336
+ # current_organization.create_api_key!(name: "CI", created_by: current_user)
337
+ #
338
+ # Then the check below can stop a key once that person no longer stands
339
+ # behind it (they left, or lost the right to manage keys). It runs after the
340
+ # scope check, on every authenticated request. A false or nil answer refuses
341
+ # the request with 403 `untrusted_key`. Keys made before you set created_by
342
+ # have none: decide what the check does with them.
343
+ #
344
+ # Existing installs: rails generate api_keys:add_created_by && rails db:migrate
345
+ # Default: nil (every valid key is trusted)
346
+ # config.trusted_key_check = ->(api_key) { api_key.owner.managed_by?(api_key.created_by) }
347
+
321
348
  # ============================================================================
322
349
  # BACKGROUND JOBS & CALLBACKS
323
350
  # ============================================================================
@@ -344,9 +371,17 @@ ApiKeys.configure do |config|
344
371
 
345
372
  # Minimum interval between last_used_at jobs when exact request counting is
346
373
  # disabled. Set to 0 or nil to update on every successful authentication.
374
+ # The same interval debounces refusal stats (below).
347
375
  # Default: 1.minute
348
376
  # config.stats_update_interval = 1.minute
349
377
 
378
+ # Refused requests: when a request presents a key's real token and a policy
379
+ # refuses it (origin/IP lock, environment, missing scope, revoked, expired),
380
+ # the same stats job stamps last_refused_at and last_refusal_code (and
381
+ # refusals_count when track_requests_count is on). A refusal never moves
382
+ # last_used_at or requests_count. The dashboard shows it on the key's page.
383
+ # Existing installs: rails generate api_keys:add_key_activity && rails db:migrate
384
+
350
385
  # Queue names for background jobs.
351
386
  # Default: :default
352
387
  # 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.5.1
4
+ version: 0.7.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,21 +99,28 @@ 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
104
108
  - lib/api_keys/controller.rb
109
+ - lib/api_keys/device_flow.rb
105
110
  - lib/api_keys/engine.rb
106
111
  - lib/api_keys/errors.rb
107
112
  - lib/api_keys/form_builder_extensions.rb
108
113
  - lib/api_keys/helpers/expiration_options.rb
109
114
  - lib/api_keys/helpers/token_session.rb
110
115
  - lib/api_keys/helpers/view_helpers.rb
116
+ - lib/api_keys/idempotency.rb
111
117
  - lib/api_keys/jobs/callbacks_job.rb
112
118
  - lib/api_keys/jobs/update_stats_job.rb
113
119
  - lib/api_keys/logging.rb
114
120
  - lib/api_keys/models/api_key.rb
115
121
  - lib/api_keys/models/concerns/has_api_keys.rb
122
+ - lib/api_keys/models/device_authorization.rb
123
+ - lib/api_keys/models/idempotency_key.rb
116
124
  - lib/api_keys/restrictions.rb
117
125
  - lib/api_keys/services/authenticator.rb
118
126
  - lib/api_keys/services/digestor.rb
@@ -120,12 +128,20 @@ files:
120
128
  - lib/api_keys/tenant_resolution.rb
121
129
  - lib/api_keys/version.rb
122
130
  - lib/generators/api_keys/add_authentication_index_generator.rb
131
+ - lib/generators/api_keys/add_created_by_generator.rb
132
+ - lib/generators/api_keys/add_device_authorizations_generator.rb
133
+ - lib/generators/api_keys/add_idempotency_keys_generator.rb
134
+ - lib/generators/api_keys/add_key_activity_generator.rb
123
135
  - lib/generators/api_keys/add_key_types_generator.rb
124
136
  - lib/generators/api_keys/add_restrictions_generator.rb
125
137
  - lib/generators/api_keys/install_generator.rb
126
138
  - lib/generators/api_keys/templates/add_authentication_index_to_api_keys.rb.erb
139
+ - lib/generators/api_keys/templates/add_created_by_to_api_keys.rb.erb
140
+ - lib/generators/api_keys/templates/add_key_activity_to_api_keys.rb.erb
127
141
  - lib/generators/api_keys/templates/add_key_types_to_api_keys.rb.erb
128
142
  - lib/generators/api_keys/templates/add_restrictions_to_api_keys.rb.erb
143
+ - lib/generators/api_keys/templates/create_api_keys_device_authorizations.rb.erb
144
+ - lib/generators/api_keys/templates/create_api_keys_idempotency_keys.rb.erb
129
145
  - lib/generators/api_keys/templates/create_api_keys_table.rb.erb
130
146
  - lib/generators/api_keys/templates/initializer.rb
131
147
  homepage: https://github.com/rameerez/api_keys