rails_api_keys 0.1.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 +7 -0
- data/CHANGELOG.md +20 -0
- data/LICENSE +21 -0
- data/README.md +144 -0
- data/app/models/rails_api_keys/api_key.rb +110 -0
- data/app/models/rails_api_keys/application_record.rb +5 -0
- data/config/routes.rb +5 -0
- data/db/migrate/20260810120000_create_rails_api_keys_api_keys.rb +19 -0
- data/lib/generators/rails_api_keys/install/install_generator.rb +37 -0
- data/lib/generators/rails_api_keys/install/templates/create_rails_api_keys_api_keys.rb.tt +19 -0
- data/lib/generators/rails_api_keys/install/templates/initializer.rb.tt +9 -0
- data/lib/rails_api_keys/authentication.rb +63 -0
- data/lib/rails_api_keys/configuration.rb +38 -0
- data/lib/rails_api_keys/engine.rb +7 -0
- data/lib/rails_api_keys/version.rb +5 -0
- data/lib/rails_api_keys.rb +28 -0
- metadata +82 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: f6a89bf64215fd1c3fcad121b300e716eeff78787be8f438e63dca2c399b6bf7
|
|
4
|
+
data.tar.gz: 2d4ec14c89eb7e8675d0036ff18d879532b74b93ff1311320b0d5f5a75531a50
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 9552c48d8976f5b1df454460273d9616a9203d2bb9a6987b643981b99d0ac3b93b2cd21af5c948afdaf4b414a48ec091cc2eb8f3699b77240a371a42f16c02b7
|
|
7
|
+
data.tar.gz: f245f531718d6e288d1d108de0aee915f0f18a78dcbd743c0f44a1e7a86511446856719ef15b38370f7545c5e30104acbb7eab45fa65b4c4048d479f533f7ecf
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
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.html).
|
|
7
|
+
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
## [0.1.0] - 2026-08-10
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- `RailsApiKeys::ApiKey` — create (raw token once), SHA-256 digest storage, soft revoke, `read` / `read_write` permissions
|
|
15
|
+
- `RailsApiKeys::Authentication` — Bearer token auth helpers for `ActionController::API`
|
|
16
|
+
- `RailsApiKeys.configure` — optional `owner_class`, `token_prefix`, and `owner_active`
|
|
17
|
+
- Install generator (`rails_api_keys:install`) for migration and initializer
|
|
18
|
+
|
|
19
|
+
[Unreleased]: https://github.com/rubyroidlabs/rails_api_keys/compare/v0.1.0...HEAD
|
|
20
|
+
[0.1.0]: https://github.com/rubyroidlabs/rails_api_keys/releases/tag/v0.1.0
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Rubyroid Labs
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
# rails_api_keys
|
|
2
|
+
|
|
3
|
+
[](https://github.com/rubyroidlabs/rails_api_keys/actions/workflows/ci.yml)
|
|
4
|
+
|
|
5
|
+
Reusable Rails engine for personal API keys: create (reveal once), hash at rest, revoke, and authenticate via `Authorization: Bearer <token>`.
|
|
6
|
+
|
|
7
|
+
Host apps own UI, routes, and domain APIs. This gem stays thin on purpose.
|
|
8
|
+
|
|
9
|
+
## What this gem includes
|
|
10
|
+
|
|
11
|
+
- `RailsApiKeys::ApiKey` — issue, authenticate, revoke
|
|
12
|
+
- `RailsApiKeys::Authentication` — controller concern for Bearer tokens
|
|
13
|
+
- Install generator (migration + initializer)
|
|
14
|
+
|
|
15
|
+
No mailers, jobs, views, assets, or mounted domain routes.
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
Add the gem and install:
|
|
20
|
+
|
|
21
|
+
```ruby
|
|
22
|
+
# Gemfile
|
|
23
|
+
gem "rails_api_keys"
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
bundle install
|
|
28
|
+
bin/rails generate rails_api_keys:install
|
|
29
|
+
bin/rails db:migrate
|
|
30
|
+
```
|
|
31
|
+
|
|
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
|
+
## Host setup
|
|
40
|
+
|
|
41
|
+
On the owner model (defaults to `User`):
|
|
42
|
+
|
|
43
|
+
```ruby
|
|
44
|
+
class User < ApplicationRecord
|
|
45
|
+
has_many :api_keys, as: :owner, class_name: "RailsApiKeys::ApiKey", dependent: :destroy
|
|
46
|
+
end
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Build your own controllers/UI to create keys (show the raw token once), list them, and revoke.
|
|
50
|
+
|
|
51
|
+
## Configuration
|
|
52
|
+
|
|
53
|
+
Defaults suit a typical Devise `User` host:
|
|
54
|
+
|
|
55
|
+
| Option | Default | Purpose |
|
|
56
|
+
| --- | --- | --- |
|
|
57
|
+
| `owner_class` | `"User"` | Expected owner class name |
|
|
58
|
+
| `token_prefix` | `"#{AppName.downcase}_ak_"` | Prefix on generated raw tokens |
|
|
59
|
+
| `owner_active` | `active_for_authentication?` when present | Reject keys whose owner is inactive |
|
|
60
|
+
|
|
61
|
+
Override only what you need:
|
|
62
|
+
|
|
63
|
+
```ruby
|
|
64
|
+
# config/initializers/rails_api_keys.rb
|
|
65
|
+
RailsApiKeys.configure do |config|
|
|
66
|
+
# config.token_prefix = "myapp_ak_"
|
|
67
|
+
# config.owner_class = "Admin"
|
|
68
|
+
# config.owner_active = ->(owner) { owner.active? }
|
|
69
|
+
end
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Usage
|
|
73
|
+
|
|
74
|
+
```ruby
|
|
75
|
+
key, raw = RailsApiKeys::ApiKey.generate_for!(
|
|
76
|
+
owner: current_user,
|
|
77
|
+
name: "Zapier",
|
|
78
|
+
permission: :read
|
|
79
|
+
)
|
|
80
|
+
# Show `raw` once — it cannot be recovered later.
|
|
81
|
+
|
|
82
|
+
RailsApiKeys::ApiKey.authenticate(raw) # => key or nil
|
|
83
|
+
key.revoke!
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
```ruby
|
|
87
|
+
class Api::V1::BaseController < ActionController::API
|
|
88
|
+
include RailsApiKeys::Authentication
|
|
89
|
+
|
|
90
|
+
before_action :authenticate_api_key!
|
|
91
|
+
before_action -> { require_api_permission!(:read) }
|
|
92
|
+
|
|
93
|
+
# current_api_key / current_api_owner are available after authenticate
|
|
94
|
+
end
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Clients send:
|
|
98
|
+
|
|
99
|
+
```http
|
|
100
|
+
Authorization: Bearer <raw_token>
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Permissions
|
|
104
|
+
|
|
105
|
+
| Permission | `allows_read?` | `allows_write?` |
|
|
106
|
+
| --- | --- | --- |
|
|
107
|
+
| `read` | yes | no |
|
|
108
|
+
| `read_write` | yes | yes |
|
|
109
|
+
|
|
110
|
+
Permissions are immutable after create. Revoke with `revoke!` (sets `revoked_at`).
|
|
111
|
+
|
|
112
|
+
## Security notes
|
|
113
|
+
|
|
114
|
+
- Raw tokens are returned only from `generate_for!` and never stored
|
|
115
|
+
- Digests use SHA-256 (`token_digest`); UI can show `token_display_prefix`
|
|
116
|
+
- Soft revoke via `revoked_at`; authentication ignores revoked keys
|
|
117
|
+
|
|
118
|
+
## Development
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
bundle install
|
|
122
|
+
bundle exec rspec
|
|
123
|
+
bin/rubocop
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
See [CHANGELOG.md](./CHANGELOG.md) for release notes. Agent-oriented notes live in [`AGENTS.md`](./AGENTS.md).
|
|
127
|
+
|
|
128
|
+
## License
|
|
129
|
+
|
|
130
|
+
This project is licensed under the [MIT License](./LICENSE).
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
<p align="center">
|
|
135
|
+
<a href="https://rubyroidlabs.com">
|
|
136
|
+
<img src="./docs/logo.svg" alt="Rubyroid Labs" width="100%" />
|
|
137
|
+
</a>
|
|
138
|
+
</p>
|
|
139
|
+
|
|
140
|
+
**[Rubyroid Labs](https://rubyroidlabs.com)** — full-cycle software development company for businesses delivering scalable web and mobile apps, dedicated developers, and full-cycle teams in Ruby on Rails, React Native, and UX/UI. 98% on-time delivery.
|
|
141
|
+
|
|
142
|
+
- Website: [rubyroidlabs.com](https://rubyroidlabs.com)
|
|
143
|
+
- Email: [hi@rubyroidlabs.com](mailto:hi@rubyroidlabs.com)
|
|
144
|
+
- References: [Clutch](https://clutch.co/profile/rubyroid-labs)
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "digest"
|
|
4
|
+
require "securerandom"
|
|
5
|
+
|
|
6
|
+
module RailsApiKeys
|
|
7
|
+
# Persists personal API keys for a polymorphic owner.
|
|
8
|
+
#
|
|
9
|
+
# Raw tokens are returned only from {.generate_for!} and are never stored.
|
|
10
|
+
# Authentication matches against a SHA-256 digest and refuses revoked or
|
|
11
|
+
# inactive-owner keys.
|
|
12
|
+
class ApiKey < ApplicationRecord
|
|
13
|
+
self.table_name = "rails_api_keys_api_keys"
|
|
14
|
+
|
|
15
|
+
DISPLAY_PREFIX_LENGTH = 12
|
|
16
|
+
TOKEN_BYTES = 32
|
|
17
|
+
|
|
18
|
+
belongs_to :owner, polymorphic: true
|
|
19
|
+
|
|
20
|
+
enum :permission, { read: "read", read_write: "read_write" }, validate: true
|
|
21
|
+
|
|
22
|
+
validates :name, :token_digest, :token_display_prefix, :permission, presence: true
|
|
23
|
+
validates :token_digest, uniqueness: true
|
|
24
|
+
validate :owner_matches_configured_class
|
|
25
|
+
validate :permission_immutable, on: :update
|
|
26
|
+
|
|
27
|
+
scope :active, -> { where(revoked_at: nil) }
|
|
28
|
+
|
|
29
|
+
class << self
|
|
30
|
+
# Creates a key and returns +[record, raw_token]+. Show +raw_token+ once.
|
|
31
|
+
def generate_for!(owner:, name:, permission:)
|
|
32
|
+
raw_token = build_raw_token
|
|
33
|
+
record = create!(
|
|
34
|
+
owner: owner,
|
|
35
|
+
name: name,
|
|
36
|
+
permission: permission,
|
|
37
|
+
token_digest: digest(raw_token),
|
|
38
|
+
token_display_prefix: display_prefix_for(raw_token)
|
|
39
|
+
)
|
|
40
|
+
[ record, raw_token ]
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Returns an active key for +raw_token+, or +nil+. Touches +last_used_at+.
|
|
44
|
+
def authenticate(raw_token)
|
|
45
|
+
return if raw_token.blank?
|
|
46
|
+
|
|
47
|
+
key = active.find_by(token_digest: digest(raw_token))
|
|
48
|
+
return unless key
|
|
49
|
+
return unless RailsApiKeys.configuration.owner_active?(key.owner)
|
|
50
|
+
|
|
51
|
+
key.touch_last_used!
|
|
52
|
+
key
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def digest(raw_token)
|
|
56
|
+
Digest::SHA256.hexdigest(raw_token.to_s)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def build_raw_token
|
|
60
|
+
"#{RailsApiKeys.configuration.resolved_token_prefix}#{SecureRandom.urlsafe_base64(TOKEN_BYTES)}"
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def display_prefix_for(raw_token)
|
|
64
|
+
raw_token.to_s[0, DISPLAY_PREFIX_LENGTH]
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Soft-revokes the key so {.authenticate} no longer accepts it.
|
|
69
|
+
def revoke!
|
|
70
|
+
update!(revoked_at: Time.current)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def revoked?
|
|
74
|
+
revoked_at.present?
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def active?
|
|
78
|
+
!revoked?
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def allows_read?
|
|
82
|
+
read? || read_write?
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def allows_write?
|
|
86
|
+
read_write?
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def touch_last_used!
|
|
90
|
+
update_column(:last_used_at, Time.current)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
private
|
|
94
|
+
|
|
95
|
+
def owner_matches_configured_class
|
|
96
|
+
return if owner.blank?
|
|
97
|
+
|
|
98
|
+
expected = RailsApiKeys.configuration.owner_class_name
|
|
99
|
+
return if owner.class.name == expected
|
|
100
|
+
|
|
101
|
+
errors.add(:owner, "must be a #{expected}")
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def permission_immutable
|
|
105
|
+
return unless permission_changed? && persisted?
|
|
106
|
+
|
|
107
|
+
errors.add(:permission, "cannot be changed after creation")
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
data/config/routes.rb
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class CreateRailsApiKeysApiKeys < ActiveRecord::Migration[7.0]
|
|
4
|
+
def change
|
|
5
|
+
create_table :rails_api_keys_api_keys do |t|
|
|
6
|
+
t.string :name, null: false
|
|
7
|
+
t.string :token_digest, null: false
|
|
8
|
+
t.string :token_display_prefix, null: false
|
|
9
|
+
t.string :permission, null: false, default: "read"
|
|
10
|
+
t.references :owner, polymorphic: true, null: false, index: true
|
|
11
|
+
t.datetime :revoked_at
|
|
12
|
+
t.datetime :last_used_at
|
|
13
|
+
t.timestamps
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
add_index :rails_api_keys_api_keys, :token_digest, unique: true
|
|
17
|
+
add_index :rails_api_keys_api_keys, :revoked_at
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rails/generators"
|
|
4
|
+
require "rails/generators/migration"
|
|
5
|
+
|
|
6
|
+
module RailsApiKeys
|
|
7
|
+
module Generators
|
|
8
|
+
class InstallGenerator < Rails::Generators::Base
|
|
9
|
+
include Rails::Generators::Migration
|
|
10
|
+
|
|
11
|
+
source_root File.expand_path("templates", __dir__)
|
|
12
|
+
|
|
13
|
+
desc "Install RailsApiKeys migrations and initializer"
|
|
14
|
+
|
|
15
|
+
def self.next_migration_number(dirname)
|
|
16
|
+
next_number = Time.now.utc.strftime("%Y%m%d%H%M%S")
|
|
17
|
+
if Dir.exist?(dirname)
|
|
18
|
+
while Dir.children(dirname).any? { |f| f.start_with?(next_number) }
|
|
19
|
+
next_number = (next_number.to_i + 1).to_s
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
next_number
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def copy_migration
|
|
26
|
+
migration_template(
|
|
27
|
+
"create_rails_api_keys_api_keys.rb.tt",
|
|
28
|
+
"db/migrate/create_rails_api_keys_api_keys.rb"
|
|
29
|
+
)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def copy_initializer
|
|
33
|
+
template "initializer.rb.tt", "config/initializers/rails_api_keys.rb"
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class CreateRailsApiKeysApiKeys < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version %>]
|
|
4
|
+
def change
|
|
5
|
+
create_table :rails_api_keys_api_keys do |t|
|
|
6
|
+
t.string :name, null: false
|
|
7
|
+
t.string :token_digest, null: false
|
|
8
|
+
t.string :token_display_prefix, null: false
|
|
9
|
+
t.string :permission, null: false, default: "read"
|
|
10
|
+
t.references :owner, polymorphic: true, null: false, index: true
|
|
11
|
+
t.datetime :revoked_at
|
|
12
|
+
t.datetime :last_used_at
|
|
13
|
+
t.timestamps
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
add_index :rails_api_keys_api_keys, :token_digest, unique: true
|
|
17
|
+
add_index :rails_api_keys_api_keys, :revoked_at
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Defaults: owner_class "User", token_prefix "<AppName>_ak_",
|
|
4
|
+
# owner_active via active_for_authentication? when available.
|
|
5
|
+
# Uncomment to override:
|
|
6
|
+
#
|
|
7
|
+
# RailsApiKeys.configure do |config|
|
|
8
|
+
# config.token_prefix = "myapp_ak_"
|
|
9
|
+
# end
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "active_support/concern"
|
|
4
|
+
|
|
5
|
+
module RailsApiKeys
|
|
6
|
+
# Controller concern for +ActionController::API+.
|
|
7
|
+
#
|
|
8
|
+
# Include and call {#authenticate_api_key!} / {#require_api_permission!} from
|
|
9
|
+
# +before_action+. After a successful authenticate, +current_api_key+ and
|
|
10
|
+
# +current_api_owner+ are set.
|
|
11
|
+
#
|
|
12
|
+
# Expects +Authorization: Bearer <raw_token>+.
|
|
13
|
+
module Authentication
|
|
14
|
+
extend ActiveSupport::Concern
|
|
15
|
+
|
|
16
|
+
included do
|
|
17
|
+
attr_reader :current_api_key, :current_api_owner
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
private
|
|
21
|
+
|
|
22
|
+
# Authenticates the Bearer token. Renders 401 and returns +false+ on failure.
|
|
23
|
+
def authenticate_api_key!
|
|
24
|
+
token = bearer_token
|
|
25
|
+
key = RailsApiKeys::ApiKey.authenticate(token)
|
|
26
|
+
|
|
27
|
+
unless key
|
|
28
|
+
render_api_unauthorized
|
|
29
|
+
return false
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
@current_api_key = key
|
|
33
|
+
@current_api_owner = key.owner
|
|
34
|
+
true
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Requires +:read+ or +:write+. +read_write+ keys satisfy both. Renders 403 on failure.
|
|
38
|
+
def require_api_permission!(level)
|
|
39
|
+
allowed =
|
|
40
|
+
case level.to_sym
|
|
41
|
+
when :read then current_api_key&.allows_read?
|
|
42
|
+
when :write then current_api_key&.allows_write?
|
|
43
|
+
else false
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
return true if allowed
|
|
47
|
+
|
|
48
|
+
render json: { error: "Forbidden" }, status: :forbidden
|
|
49
|
+
false
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def bearer_token
|
|
53
|
+
header = request.authorization.to_s
|
|
54
|
+
return unless header.match?(/\ABearer\s+/i)
|
|
55
|
+
|
|
56
|
+
header.split(/\s+/, 2).last.presence
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def render_api_unauthorized
|
|
60
|
+
render json: { error: "Unauthorized" }, status: :unauthorized
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RailsApiKeys
|
|
4
|
+
# Optional host overrides. Set via {RailsApiKeys.configure}.
|
|
5
|
+
#
|
|
6
|
+
# Defaults: +owner_class+ is +"User"+, +token_prefix+ derives from the app
|
|
7
|
+
# name, and +owner_active+ uses Devise-style +active_for_authentication?+
|
|
8
|
+
# when available.
|
|
9
|
+
class Configuration
|
|
10
|
+
attr_accessor :owner_class, :token_prefix, :owner_active
|
|
11
|
+
|
|
12
|
+
def initialize
|
|
13
|
+
@owner_class = "User"
|
|
14
|
+
@token_prefix = nil
|
|
15
|
+
@owner_active = ->(owner) {
|
|
16
|
+
if owner.respond_to?(:active_for_authentication?)
|
|
17
|
+
owner.active_for_authentication?
|
|
18
|
+
else
|
|
19
|
+
true
|
|
20
|
+
end
|
|
21
|
+
}
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def resolved_token_prefix
|
|
25
|
+
return token_prefix.to_s if token_prefix.present?
|
|
26
|
+
|
|
27
|
+
"#{Rails.application.class.module_parent_name.downcase}_ak_"
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def owner_class_name
|
|
31
|
+
owner_class.to_s
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def owner_active?(owner)
|
|
35
|
+
owner_active.call(owner)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rails_api_keys/version"
|
|
4
|
+
require "rails_api_keys/configuration"
|
|
5
|
+
require "rails_api_keys/authentication"
|
|
6
|
+
require "rails_api_keys/engine"
|
|
7
|
+
|
|
8
|
+
# Thin Rails engine for personal API key authentication.
|
|
9
|
+
#
|
|
10
|
+
# @see file:README.md
|
|
11
|
+
module RailsApiKeys
|
|
12
|
+
class << self
|
|
13
|
+
# @return [RailsApiKeys::Configuration]
|
|
14
|
+
def configuration
|
|
15
|
+
@configuration ||= Configuration.new
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Yields {configuration} for host overrides in an initializer.
|
|
19
|
+
def configure
|
|
20
|
+
yield configuration
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Resets configuration to defaults (primarily for tests).
|
|
24
|
+
def reset_configuration!
|
|
25
|
+
@configuration = Configuration.new
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: rails_api_keys
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Pavel Pershko
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: rails
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '7.0'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '7.0'
|
|
26
|
+
description: |
|
|
27
|
+
Thin Rails engine for personal API keys: issue a raw token once, store a
|
|
28
|
+
SHA-256 digest, soft-revoke, and authenticate ActionController::API
|
|
29
|
+
requests with Authorization: Bearer. Host apps own UI, routes, and domain APIs.
|
|
30
|
+
email:
|
|
31
|
+
- pavel.pershko@rubyroidlabs.com
|
|
32
|
+
executables: []
|
|
33
|
+
extensions: []
|
|
34
|
+
extra_rdoc_files:
|
|
35
|
+
- CHANGELOG.md
|
|
36
|
+
- LICENSE
|
|
37
|
+
- README.md
|
|
38
|
+
files:
|
|
39
|
+
- CHANGELOG.md
|
|
40
|
+
- LICENSE
|
|
41
|
+
- README.md
|
|
42
|
+
- app/models/rails_api_keys/api_key.rb
|
|
43
|
+
- app/models/rails_api_keys/application_record.rb
|
|
44
|
+
- config/routes.rb
|
|
45
|
+
- db/migrate/20260810120000_create_rails_api_keys_api_keys.rb
|
|
46
|
+
- lib/generators/rails_api_keys/install/install_generator.rb
|
|
47
|
+
- lib/generators/rails_api_keys/install/templates/create_rails_api_keys_api_keys.rb.tt
|
|
48
|
+
- lib/generators/rails_api_keys/install/templates/initializer.rb.tt
|
|
49
|
+
- lib/rails_api_keys.rb
|
|
50
|
+
- lib/rails_api_keys/authentication.rb
|
|
51
|
+
- lib/rails_api_keys/configuration.rb
|
|
52
|
+
- lib/rails_api_keys/engine.rb
|
|
53
|
+
- lib/rails_api_keys/version.rb
|
|
54
|
+
homepage: https://github.com/rubyroidlabs/rails_api_keys
|
|
55
|
+
licenses:
|
|
56
|
+
- MIT
|
|
57
|
+
metadata:
|
|
58
|
+
homepage_uri: https://github.com/rubyroidlabs/rails_api_keys
|
|
59
|
+
source_code_uri: https://github.com/rubyroidlabs/rails_api_keys/tree/v0.1.0
|
|
60
|
+
changelog_uri: https://github.com/rubyroidlabs/rails_api_keys/blob/v0.1.0/CHANGELOG.md
|
|
61
|
+
bug_tracker_uri: https://github.com/rubyroidlabs/rails_api_keys/issues
|
|
62
|
+
documentation_uri: https://www.rubydoc.info/gems/rails_api_keys/0.1.0
|
|
63
|
+
rubygems_mfa_required: 'true'
|
|
64
|
+
allowed_push_host: https://rubygems.org
|
|
65
|
+
rdoc_options: []
|
|
66
|
+
require_paths:
|
|
67
|
+
- lib
|
|
68
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
69
|
+
requirements:
|
|
70
|
+
- - ">="
|
|
71
|
+
- !ruby/object:Gem::Version
|
|
72
|
+
version: 3.2.0
|
|
73
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
74
|
+
requirements:
|
|
75
|
+
- - ">="
|
|
76
|
+
- !ruby/object:Gem::Version
|
|
77
|
+
version: '0'
|
|
78
|
+
requirements: []
|
|
79
|
+
rubygems_version: 4.0.18
|
|
80
|
+
specification_version: 4
|
|
81
|
+
summary: Reusable Rails engine for personal API key authentication.
|
|
82
|
+
test_files: []
|