rails_api_keys 0.1.0 → 0.2.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 +19 -2
- data/README.md +16 -23
- data/app/models/rails_api_keys/api_key.rb +4 -6
- data/lib/generators/rails_api_keys/install/templates/initializer.rb.tt +2 -1
- data/lib/rails_api_keys/authentication.rb +12 -11
- data/lib/rails_api_keys/configuration.rb +4 -9
- data/lib/rails_api_keys/engine.rb +6 -0
- data/lib/rails_api_keys/owner.rb +26 -0
- data/lib/rails_api_keys/version.rb +1 -1
- data/lib/rails_api_keys.rb +19 -0
- metadata +5 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 5efd1a62dc4d875f9411e190b6cdeb33129d09c85790ea2f2c61cf12381b8123
|
|
4
|
+
data.tar.gz: 0bd1d07535c449f62b8a59517057a6f990b2524d57e4c5d6ab457e4b0636a850
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 2a106e93e5e874e58754bc2502845a6ef8f19ba78cba31806cbda95c52ab6721fc6600b78ad90c678c95d212675b070f0e1dbc4902f222dd2ee1d5c65d345872
|
|
7
|
+
data.tar.gz: 2d6ece664815bd002f8543789317e36f6dbe52490087fdd8a31d072ab1440eee975d1df94e7f8b209beab92d18515d30085a7426c460b66b1256dc24383c5047
|
data/CHANGELOG.md
CHANGED
|
@@ -3,10 +3,26 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
-
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0/).
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.2.0] - 2026-08-10
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- `has_api_keys` — Active Record macro for owner models (`User`, `Company`, etc.); registers allowed polymorphic owners
|
|
15
|
+
- `create_api_key!` — instance helper on owners that call `has_api_keys` (wraps `ApiKey.generate_for!`)
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
|
|
19
|
+
- `authenticate_api_key!` now enforces permissions from the HTTP method: GET/HEAD require read; other methods require write
|
|
20
|
+
|
|
21
|
+
### Removed
|
|
22
|
+
|
|
23
|
+
- `require_api_permission!` — hosts no longer call this separately
|
|
24
|
+
- `config.owner_class` — replaced by opt-in `has_api_keys` on each owner model
|
|
25
|
+
|
|
10
26
|
## [0.1.0] - 2026-08-10
|
|
11
27
|
|
|
12
28
|
### Added
|
|
@@ -16,5 +32,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
16
32
|
- `RailsApiKeys.configure` — optional `owner_class`, `token_prefix`, and `owner_active`
|
|
17
33
|
- Install generator (`rails_api_keys:install`) for migration and initializer
|
|
18
34
|
|
|
19
|
-
[Unreleased]: https://github.com/rubyroidlabs/rails_api_keys/compare/v0.
|
|
35
|
+
[Unreleased]: https://github.com/rubyroidlabs/rails_api_keys/compare/v0.2.0...HEAD
|
|
36
|
+
[0.2.0]: https://github.com/rubyroidlabs/rails_api_keys/compare/v0.1.0...v0.2.0
|
|
20
37
|
[0.1.0]: https://github.com/rubyroidlabs/rails_api_keys/releases/tag/v0.1.0
|
data/README.md
CHANGED
|
@@ -9,6 +9,7 @@ Host apps own UI, routes, and domain APIs. This gem stays thin on purpose.
|
|
|
9
9
|
## What this gem includes
|
|
10
10
|
|
|
11
11
|
- `RailsApiKeys::ApiKey` — issue, authenticate, revoke
|
|
12
|
+
- `has_api_keys` — opt-in owner macro with `create_api_key!` (supports multiple models)
|
|
12
13
|
- `RailsApiKeys::Authentication` — controller concern for Bearer tokens
|
|
13
14
|
- Install generator (migration + initializer)
|
|
14
15
|
|
|
@@ -29,32 +30,28 @@ bin/rails generate rails_api_keys:install
|
|
|
29
30
|
bin/rails db:migrate
|
|
30
31
|
```
|
|
31
32
|
|
|
32
|
-
Until the gem is published on RubyGems, you can use git or a local path:
|
|
33
|
-
|
|
34
|
-
```ruby
|
|
35
|
-
gem "rails_api_keys", git: "https://github.com/rubyroidlabs/rails_api_keys.git"
|
|
36
|
-
# or: gem "rails_api_keys", path: "../rails_api_keys"
|
|
37
|
-
```
|
|
38
|
-
|
|
39
33
|
## Host setup
|
|
40
34
|
|
|
41
|
-
|
|
35
|
+
Opt in on each owner model with `has_api_keys` (any number of models):
|
|
42
36
|
|
|
43
37
|
```ruby
|
|
44
38
|
class User < ApplicationRecord
|
|
45
|
-
|
|
39
|
+
has_api_keys
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
class Company < ApplicationRecord
|
|
43
|
+
has_api_keys
|
|
46
44
|
end
|
|
47
45
|
```
|
|
48
46
|
|
|
49
|
-
Build your own controllers/UI to create keys (show the raw token once), list them, and revoke.
|
|
47
|
+
That declares `has_many :api_keys` and allows the model to own keys. Build your own controllers/UI to create keys (show the raw token once), list them, and revoke.
|
|
50
48
|
|
|
51
49
|
## Configuration
|
|
52
50
|
|
|
53
|
-
Defaults suit a typical Devise `User` host
|
|
51
|
+
Defaults suit a typical Devise `User` host; multiple owners are supported via `has_api_keys`:
|
|
54
52
|
|
|
55
53
|
| Option | Default | Purpose |
|
|
56
54
|
| --- | --- | --- |
|
|
57
|
-
| `owner_class` | `"User"` | Expected owner class name |
|
|
58
55
|
| `token_prefix` | `"#{AppName.downcase}_ak_"` | Prefix on generated raw tokens |
|
|
59
56
|
| `owner_active` | `active_for_authentication?` when present | Reject keys whose owner is inactive |
|
|
60
57
|
|
|
@@ -64,7 +61,6 @@ Override only what you need:
|
|
|
64
61
|
# config/initializers/rails_api_keys.rb
|
|
65
62
|
RailsApiKeys.configure do |config|
|
|
66
63
|
# config.token_prefix = "myapp_ak_"
|
|
67
|
-
# config.owner_class = "Admin"
|
|
68
64
|
# config.owner_active = ->(owner) { owner.active? }
|
|
69
65
|
end
|
|
70
66
|
```
|
|
@@ -72,11 +68,7 @@ end
|
|
|
72
68
|
## Usage
|
|
73
69
|
|
|
74
70
|
```ruby
|
|
75
|
-
key, raw =
|
|
76
|
-
owner: current_user,
|
|
77
|
-
name: "Zapier",
|
|
78
|
-
permission: :read
|
|
79
|
-
)
|
|
71
|
+
key, raw = current_user.create_api_key!(name: "Zapier", permission: :read)
|
|
80
72
|
# Show `raw` once — it cannot be recovered later.
|
|
81
73
|
|
|
82
74
|
RailsApiKeys::ApiKey.authenticate(raw) # => key or nil
|
|
@@ -88,7 +80,6 @@ class Api::V1::BaseController < ActionController::API
|
|
|
88
80
|
include RailsApiKeys::Authentication
|
|
89
81
|
|
|
90
82
|
before_action :authenticate_api_key!
|
|
91
|
-
before_action -> { require_api_permission!(:read) }
|
|
92
83
|
|
|
93
84
|
# current_api_key / current_api_owner are available after authenticate
|
|
94
85
|
end
|
|
@@ -100,18 +91,20 @@ Clients send:
|
|
|
100
91
|
Authorization: Bearer <raw_token>
|
|
101
92
|
```
|
|
102
93
|
|
|
94
|
+
`authenticate_api_key!` rejects missing/invalid tokens with 401, then enforces permission from the HTTP method: GET/HEAD require read; other methods require write (403 if the key lacks it).
|
|
95
|
+
|
|
103
96
|
## Permissions
|
|
104
97
|
|
|
105
|
-
| Permission |
|
|
98
|
+
| Permission | GET/HEAD | Other methods |
|
|
106
99
|
| --- | --- | --- |
|
|
107
|
-
| `read` |
|
|
108
|
-
| `read_write` |
|
|
100
|
+
| `read` | allowed | forbidden |
|
|
101
|
+
| `read_write` | allowed | allowed |
|
|
109
102
|
|
|
110
103
|
Permissions are immutable after create. Revoke with `revoke!` (sets `revoked_at`).
|
|
111
104
|
|
|
112
105
|
## Security notes
|
|
113
106
|
|
|
114
|
-
- Raw tokens are returned only from `generate_for!` and never stored
|
|
107
|
+
- Raw tokens are returned only from `create_api_key!` / `generate_for!` and never stored
|
|
115
108
|
- Digests use SHA-256 (`token_digest`); UI can show `token_display_prefix`
|
|
116
109
|
- Soft revoke via `revoked_at`; authentication ignores revoked keys
|
|
117
110
|
|
|
@@ -21,7 +21,7 @@ module RailsApiKeys
|
|
|
21
21
|
|
|
22
22
|
validates :name, :token_digest, :token_display_prefix, :permission, presence: true
|
|
23
23
|
validates :token_digest, uniqueness: true
|
|
24
|
-
validate :
|
|
24
|
+
validate :owner_declares_has_api_keys
|
|
25
25
|
validate :permission_immutable, on: :update
|
|
26
26
|
|
|
27
27
|
scope :active, -> { where(revoked_at: nil) }
|
|
@@ -92,13 +92,11 @@ module RailsApiKeys
|
|
|
92
92
|
|
|
93
93
|
private
|
|
94
94
|
|
|
95
|
-
def
|
|
95
|
+
def owner_declares_has_api_keys
|
|
96
96
|
return if owner.blank?
|
|
97
|
+
return if RailsApiKeys.owner_class?(owner.class)
|
|
97
98
|
|
|
98
|
-
|
|
99
|
-
return if owner.class.name == expected
|
|
100
|
-
|
|
101
|
-
errors.add(:owner, "must be a #{expected}")
|
|
99
|
+
errors.add(:owner, "must declare has_api_keys")
|
|
102
100
|
end
|
|
103
101
|
|
|
104
102
|
def permission_immutable
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
# Defaults:
|
|
3
|
+
# Defaults: token_prefix "<AppName>_ak_",
|
|
4
4
|
# owner_active via active_for_authentication? when available.
|
|
5
|
+
# Owner models opt in with +has_api_keys+.
|
|
5
6
|
# Uncomment to override:
|
|
6
7
|
#
|
|
7
8
|
# RailsApiKeys.configure do |config|
|
|
@@ -5,9 +5,10 @@ require "active_support/concern"
|
|
|
5
5
|
module RailsApiKeys
|
|
6
6
|
# Controller concern for +ActionController::API+.
|
|
7
7
|
#
|
|
8
|
-
# Include and call {#authenticate_api_key!}
|
|
9
|
-
#
|
|
10
|
-
#
|
|
8
|
+
# Include and call {#authenticate_api_key!} from +before_action+. After a
|
|
9
|
+
# successful authenticate, +current_api_key+ and +current_api_owner+ are set.
|
|
10
|
+
# Permission is enforced from the HTTP method: GET/HEAD require read; other
|
|
11
|
+
# methods require write.
|
|
11
12
|
#
|
|
12
13
|
# Expects +Authorization: Bearer <raw_token>+.
|
|
13
14
|
module Authentication
|
|
@@ -19,7 +20,8 @@ module RailsApiKeys
|
|
|
19
20
|
|
|
20
21
|
private
|
|
21
22
|
|
|
22
|
-
# Authenticates the Bearer token
|
|
23
|
+
# Authenticates the Bearer token and enforces method-based permission.
|
|
24
|
+
# Renders 401 on auth failure, 403 when the key lacks the required level.
|
|
23
25
|
def authenticate_api_key!
|
|
24
26
|
token = bearer_token
|
|
25
27
|
key = RailsApiKeys::ApiKey.authenticate(token)
|
|
@@ -31,16 +33,15 @@ module RailsApiKeys
|
|
|
31
33
|
|
|
32
34
|
@current_api_key = key
|
|
33
35
|
@current_api_owner = key.owner
|
|
34
|
-
|
|
36
|
+
enforce_api_permission!
|
|
35
37
|
end
|
|
36
38
|
|
|
37
|
-
|
|
38
|
-
def require_api_permission!(level)
|
|
39
|
+
def enforce_api_permission!
|
|
39
40
|
allowed =
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
41
|
+
if request.get? || request.head?
|
|
42
|
+
current_api_key.allows_read?
|
|
43
|
+
else
|
|
44
|
+
current_api_key.allows_write?
|
|
44
45
|
end
|
|
45
46
|
|
|
46
47
|
return true if allowed
|
|
@@ -3,14 +3,13 @@
|
|
|
3
3
|
module RailsApiKeys
|
|
4
4
|
# Optional host overrides. Set via {RailsApiKeys.configure}.
|
|
5
5
|
#
|
|
6
|
-
# Defaults: +
|
|
7
|
-
#
|
|
8
|
-
#
|
|
6
|
+
# Defaults: +token_prefix+ derives from the app name, and +owner_active+ uses
|
|
7
|
+
# Devise-style +active_for_authentication?+ when available.
|
|
8
|
+
# Owner models opt in with {Owner.has_api_keys}.
|
|
9
9
|
class Configuration
|
|
10
|
-
attr_accessor :
|
|
10
|
+
attr_accessor :token_prefix, :owner_active
|
|
11
11
|
|
|
12
12
|
def initialize
|
|
13
|
-
@owner_class = "User"
|
|
14
13
|
@token_prefix = nil
|
|
15
14
|
@owner_active = ->(owner) {
|
|
16
15
|
if owner.respond_to?(:active_for_authentication?)
|
|
@@ -27,10 +26,6 @@ module RailsApiKeys
|
|
|
27
26
|
"#{Rails.application.class.module_parent_name.downcase}_ak_"
|
|
28
27
|
end
|
|
29
28
|
|
|
30
|
-
def owner_class_name
|
|
31
|
-
owner_class.to_s
|
|
32
|
-
end
|
|
33
|
-
|
|
34
29
|
def owner_active?(owner)
|
|
35
30
|
owner_active.call(owner)
|
|
36
31
|
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RailsApiKeys
|
|
4
|
+
# Mixes {.has_api_keys} into Active Record so host models can opt in as key owners.
|
|
5
|
+
module Owner
|
|
6
|
+
extend ActiveSupport::Concern
|
|
7
|
+
|
|
8
|
+
class_methods do
|
|
9
|
+
# Declares a polymorphic +has_many :api_keys+ and registers this class as an
|
|
10
|
+
# allowed owner for {RailsApiKeys::ApiKey}.
|
|
11
|
+
def has_api_keys(dependent: :destroy)
|
|
12
|
+
has_many :api_keys,
|
|
13
|
+
as: :owner,
|
|
14
|
+
class_name: "RailsApiKeys::ApiKey",
|
|
15
|
+
dependent: dependent
|
|
16
|
+
|
|
17
|
+
RailsApiKeys.register_owner_class!(self)
|
|
18
|
+
|
|
19
|
+
# Creates a key for this owner and returns +[record, raw_token]+.
|
|
20
|
+
define_method(:create_api_key!) do |name:, permission:|
|
|
21
|
+
RailsApiKeys::ApiKey.generate_for!(owner: self, name: name, permission: permission)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
data/lib/rails_api_keys.rb
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "set"
|
|
3
4
|
require "rails_api_keys/version"
|
|
4
5
|
require "rails_api_keys/configuration"
|
|
5
6
|
require "rails_api_keys/authentication"
|
|
7
|
+
require "rails_api_keys/owner"
|
|
6
8
|
require "rails_api_keys/engine"
|
|
7
9
|
|
|
8
10
|
# Thin Rails engine for personal API key authentication.
|
|
@@ -21,8 +23,25 @@ module RailsApiKeys
|
|
|
21
23
|
end
|
|
22
24
|
|
|
23
25
|
# Resets configuration to defaults (primarily for tests).
|
|
26
|
+
# Does not clear {.owner_class_names}; those are registered at model load via {Owner#has_api_keys}.
|
|
24
27
|
def reset_configuration!
|
|
25
28
|
@configuration = Configuration.new
|
|
26
29
|
end
|
|
30
|
+
|
|
31
|
+
# Records +klass+ as an allowed API key owner (idempotent).
|
|
32
|
+
def register_owner_class!(klass)
|
|
33
|
+
owner_class_names << klass.name
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# @return [Boolean] whether +klass+ (or its name) called {Owner#has_api_keys}
|
|
37
|
+
def owner_class?(klass)
|
|
38
|
+
name = klass.is_a?(Module) ? klass.name : klass.to_s
|
|
39
|
+
owner_class_names.include?(name)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# @return [Set<String>] registered owner class names
|
|
43
|
+
def owner_class_names
|
|
44
|
+
@owner_class_names ||= Set.new
|
|
45
|
+
end
|
|
27
46
|
end
|
|
28
47
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rails_api_keys
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Pavel Pershko
|
|
@@ -50,16 +50,17 @@ files:
|
|
|
50
50
|
- lib/rails_api_keys/authentication.rb
|
|
51
51
|
- lib/rails_api_keys/configuration.rb
|
|
52
52
|
- lib/rails_api_keys/engine.rb
|
|
53
|
+
- lib/rails_api_keys/owner.rb
|
|
53
54
|
- lib/rails_api_keys/version.rb
|
|
54
55
|
homepage: https://github.com/rubyroidlabs/rails_api_keys
|
|
55
56
|
licenses:
|
|
56
57
|
- MIT
|
|
57
58
|
metadata:
|
|
58
59
|
homepage_uri: https://github.com/rubyroidlabs/rails_api_keys
|
|
59
|
-
source_code_uri: https://github.com/rubyroidlabs/rails_api_keys/tree/v0.
|
|
60
|
-
changelog_uri: https://github.com/rubyroidlabs/rails_api_keys/blob/v0.
|
|
60
|
+
source_code_uri: https://github.com/rubyroidlabs/rails_api_keys/tree/v0.2.0
|
|
61
|
+
changelog_uri: https://github.com/rubyroidlabs/rails_api_keys/blob/v0.2.0/CHANGELOG.md
|
|
61
62
|
bug_tracker_uri: https://github.com/rubyroidlabs/rails_api_keys/issues
|
|
62
|
-
documentation_uri: https://www.rubydoc.info/gems/rails_api_keys/0.
|
|
63
|
+
documentation_uri: https://www.rubydoc.info/gems/rails_api_keys/0.2.0
|
|
63
64
|
rubygems_mfa_required: 'true'
|
|
64
65
|
allowed_push_host: https://rubygems.org
|
|
65
66
|
rdoc_options: []
|