tokenzen 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/README.md +210 -0
- data/lib/tokenzen/authenticatable.rb +93 -0
- data/lib/tokenzen/configuration.rb +52 -0
- data/lib/tokenzen/model.rb +46 -0
- data/lib/tokenzen/railtie.rb +9 -0
- data/lib/tokenzen/token_store.rb +102 -0
- data/lib/tokenzen/version.rb +5 -0
- data/lib/tokenzen.rb +26 -0
- metadata +89 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 2f4dde7ef56bd6c8ed1d4b23b43f4256b17dfce96f9a0b8a0272e294db5fbf30
|
|
4
|
+
data.tar.gz: c8fbddcc5c83663e8635063fc67dd0d48b085166ce1c8b8f13f3f5c01266bbe5
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 13f223728ffc56a0609a1834b09c1e1bb4b5dc0d6d4a7a426a221d9d42bd316a52cea0135ef11d9430943057ae43ec67c2b5e8c98a624a624e3209e9b389100e
|
|
7
|
+
data.tar.gz: 4b38a201f3a87dd92e0c8c68d4eed112ff497f960c10cab5ebea806843e430b21d1c93f85247974649956bd8915701d1971c96ad442e9d461af19b928a367adb
|
data/README.md
ADDED
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
Tokenzen
|
|
2
|
+
=======
|
|
3
|
+
|
|
4
|
+
Tokenzen is a lightweight, model-agnostic token authentication toolkit for Rails.
|
|
5
|
+
|
|
6
|
+
It provides secure, polymorphic access token management for any ActiveRecord model — not just User.
|
|
7
|
+
|
|
8
|
+
Tokenzen is designed to be:
|
|
9
|
+
- Model agnostic
|
|
10
|
+
- Multi-model compatible
|
|
11
|
+
- Cache-backed and scalable
|
|
12
|
+
- Configurable
|
|
13
|
+
- Lightweight
|
|
14
|
+
- Easy to integrate
|
|
15
|
+
|
|
16
|
+
========================================
|
|
17
|
+
FEATURES
|
|
18
|
+
=========
|
|
19
|
+
|
|
20
|
+
- Works with any model (Admin, Customer, Account, etc.)
|
|
21
|
+
- Access and refresh token generation
|
|
22
|
+
- Token revocation (logout)
|
|
23
|
+
- Multi-device support
|
|
24
|
+
- Automatic token rotation on refresh token change
|
|
25
|
+
- Token validation
|
|
26
|
+
- Refresh access token using refresh token
|
|
27
|
+
- Configurable expiration
|
|
28
|
+
- AES-256 encryption of tokens
|
|
29
|
+
- Rails auto-loading via Railtie
|
|
30
|
+
|
|
31
|
+
========================================
|
|
32
|
+
INSTALLATION
|
|
33
|
+
============
|
|
34
|
+
|
|
35
|
+
Add this to your application's Gemfile:
|
|
36
|
+
|
|
37
|
+
gem "tokenzen"
|
|
38
|
+
|
|
39
|
+
Then run:
|
|
40
|
+
|
|
41
|
+
bundle install
|
|
42
|
+
|
|
43
|
+
Or install manually:
|
|
44
|
+
|
|
45
|
+
gem install tokenzen
|
|
46
|
+
|
|
47
|
+
========================================
|
|
48
|
+
BASIC USAGE
|
|
49
|
+
===========
|
|
50
|
+
|
|
51
|
+
Include Tokenzen in any ActiveRecord model.
|
|
52
|
+
|
|
53
|
+
Example:
|
|
54
|
+
|
|
55
|
+
class Admin < ApplicationRecord
|
|
56
|
+
include Tokenzen::Authenticatable
|
|
57
|
+
tokenzen
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
You can use Tokenzen in multiple models at the same time:
|
|
61
|
+
|
|
62
|
+
class Customer < ApplicationRecord
|
|
63
|
+
include Tokenzen::Authenticatable
|
|
64
|
+
tokenzen
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
========================================
|
|
68
|
+
GENERATE ACCESS + REFRESH TOKEN
|
|
69
|
+
===============================
|
|
70
|
+
|
|
71
|
+
admin = Admin.first
|
|
72
|
+
tokens = admin.generate_tokens
|
|
73
|
+
# tokens => { access_token: "...", refresh_token: "..." }
|
|
74
|
+
|
|
75
|
+
This generates secure encrypted tokens and stores them in cache with separate expiries.
|
|
76
|
+
|
|
77
|
+
========================================
|
|
78
|
+
LOGIN / VALIDATE TOKENS
|
|
79
|
+
=======================
|
|
80
|
+
|
|
81
|
+
Use the class-level `login` method to validate a token pair:
|
|
82
|
+
|
|
83
|
+
record = Admin.login(tokens[:access_token], tokens[:refresh_token])
|
|
84
|
+
# returns Admin instance if valid, nil if invalid
|
|
85
|
+
|
|
86
|
+
You can also validate token pair from an instance:
|
|
87
|
+
|
|
88
|
+
valid = admin.validate_tokens(tokens[:access_token])
|
|
89
|
+
# => ActiveRecord or nil
|
|
90
|
+
|
|
91
|
+
========================================
|
|
92
|
+
REFRESH ACCESS TOKEN
|
|
93
|
+
====================
|
|
94
|
+
|
|
95
|
+
Use the refresh token to generate a new access token:
|
|
96
|
+
|
|
97
|
+
new_tokens = Admin.refresh_access(tokens[:refresh_token])
|
|
98
|
+
# returns { access_token: "...", refresh_token: "..." }
|
|
99
|
+
|
|
100
|
+
========================================
|
|
101
|
+
LOGOUT / CLEAR ALL TOKENS
|
|
102
|
+
=========================
|
|
103
|
+
|
|
104
|
+
admin.logout
|
|
105
|
+
# clears all access and refresh tokens for this record
|
|
106
|
+
|
|
107
|
+
========================================
|
|
108
|
+
CONFIGURATION
|
|
109
|
+
=============
|
|
110
|
+
|
|
111
|
+
Create an initializer:
|
|
112
|
+
|
|
113
|
+
config/initializers/tokenzen.rb
|
|
114
|
+
|
|
115
|
+
Tokenzen.configure do |config|
|
|
116
|
+
config.access_token_expiry = 2.days
|
|
117
|
+
config.refresh_token_expiry = 2.months
|
|
118
|
+
config.secret_key = ENV["TOKENZEN_SECRET_KEY"] || Rails.application.secret_key_base
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
The gem automatically encrypts all tokens using AES-256 with this secret key.
|
|
122
|
+
|
|
123
|
+
========================================
|
|
124
|
+
HOW IT WORKS
|
|
125
|
+
=============
|
|
126
|
+
|
|
127
|
+
When a token is issued:
|
|
128
|
+
|
|
129
|
+
- A secure random key is generated for access and refresh tokens.
|
|
130
|
+
- Tokens are stored in cache along with model name and record ID.
|
|
131
|
+
- Tokens are encrypted using AES-256.
|
|
132
|
+
- Tokens are tracked per record for easy revocation.
|
|
133
|
+
|
|
134
|
+
Stored payload example:
|
|
135
|
+
|
|
136
|
+
{
|
|
137
|
+
"model" => "Admin",
|
|
138
|
+
"id" => 1,
|
|
139
|
+
"type" => "access" # or "refresh"
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
This allows Tokenzen to work with any ActiveRecord model automatically.
|
|
143
|
+
|
|
144
|
+
========================================
|
|
145
|
+
TOKEN ROTATION
|
|
146
|
+
===============
|
|
147
|
+
|
|
148
|
+
If your model includes:
|
|
149
|
+
|
|
150
|
+
- refresh_token
|
|
151
|
+
- password_digest
|
|
152
|
+
|
|
153
|
+
Tokenzen will automatically:
|
|
154
|
+
|
|
155
|
+
- Rotate the refresh token when password changes
|
|
156
|
+
- Clear all access tokens when refresh token changes
|
|
157
|
+
|
|
158
|
+
This behavior is optional and safely guarded.
|
|
159
|
+
|
|
160
|
+
========================================
|
|
161
|
+
PRODUCTION RECOMMENDATION
|
|
162
|
+
==========================
|
|
163
|
+
|
|
164
|
+
Use Redis as your cache store for production environments:
|
|
165
|
+
|
|
166
|
+
config.cache_store = :redis_cache_store, { url: ENV["REDIS_URL"] }
|
|
167
|
+
|
|
168
|
+
Redis provides better performance and scalability for token storage.
|
|
169
|
+
|
|
170
|
+
========================================
|
|
171
|
+
REQUIREMENTS
|
|
172
|
+
=============
|
|
173
|
+
|
|
174
|
+
- Ruby >= 3.0.0
|
|
175
|
+
- Rails >= 6.0
|
|
176
|
+
- ActiveRecord-backed models
|
|
177
|
+
|
|
178
|
+
========================================
|
|
179
|
+
SECURITY NOTES
|
|
180
|
+
================
|
|
181
|
+
|
|
182
|
+
- Tokens are encrypted using AES-256
|
|
183
|
+
- Tokens are stored in cache (Redis recommended)
|
|
184
|
+
- Tokens are namespaced per model
|
|
185
|
+
- Clearing tokens invalidates all sessions instantly
|
|
186
|
+
|
|
187
|
+
========================================
|
|
188
|
+
ROADMAP
|
|
189
|
+
========
|
|
190
|
+
|
|
191
|
+
- Controller helpers (current_resource)
|
|
192
|
+
- Rack middleware
|
|
193
|
+
- Stateless JWT mode
|
|
194
|
+
- Device/session tracking
|
|
195
|
+
- Revokable single-session tokens
|
|
196
|
+
- OAuth support
|
|
197
|
+
|
|
198
|
+
========================================
|
|
199
|
+
CONTRIBUTING
|
|
200
|
+
==============
|
|
201
|
+
|
|
202
|
+
Bug reports and pull requests are welcome at:
|
|
203
|
+
|
|
204
|
+
https://github.com/stndrk/tokenzen
|
|
205
|
+
|
|
206
|
+
========================================
|
|
207
|
+
LICENSE
|
|
208
|
+
========
|
|
209
|
+
|
|
210
|
+
Tokenzen is released under the MIT License.
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Tokenzen
|
|
4
|
+
module Authenticatable
|
|
5
|
+
def self.included(base)
|
|
6
|
+
base.extend(ClassMethods)
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
module ClassMethods
|
|
10
|
+
def tokenzen(options = {})
|
|
11
|
+
include Tokenzen::Authenticatable::InstanceMethods
|
|
12
|
+
|
|
13
|
+
class_attribute :tokenzen_options
|
|
14
|
+
self.tokenzen_options = options
|
|
15
|
+
|
|
16
|
+
before_save :_tokenzen_rotate_refresh_token
|
|
17
|
+
around_save :_tokenzen_handle_refresh_rotation
|
|
18
|
+
after_destroy :_tokenzen_clear_tokens
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
module InstanceMethods
|
|
23
|
+
attr_reader :current_access_token, :current_refresh_token
|
|
24
|
+
|
|
25
|
+
# Generate access + refresh token
|
|
26
|
+
def generate_tokens(access_expiry: nil, refresh_expiry: nil)
|
|
27
|
+
tokens = TokenStore.issue_tokens(self, access_expiry: access_expiry, refresh_expiry: refresh_expiry)
|
|
28
|
+
@current_access_token = tokens[:access_token]
|
|
29
|
+
@current_refresh_token = tokens[:refresh_token]
|
|
30
|
+
tokens
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Logout: clear all tokens
|
|
34
|
+
def logout
|
|
35
|
+
clear_all_access_tokens
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Clear all tokens
|
|
39
|
+
def clear_all_access_tokens
|
|
40
|
+
TokenStore.clear_tokens(self)
|
|
41
|
+
@current_access_token = nil
|
|
42
|
+
@current_refresh_token = nil
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Validate tokens for this record
|
|
46
|
+
def validate_tokens(access_token)
|
|
47
|
+
TokenStore.fetch_record_from_access(access_token)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# =============================
|
|
51
|
+
# Class-level login using token pair
|
|
52
|
+
# =============================
|
|
53
|
+
#
|
|
54
|
+
# Returns the record if tokens valid, else nil
|
|
55
|
+
#
|
|
56
|
+
def self.login(access_token, refresh_token = nil)
|
|
57
|
+
record = TokenStore.fetch_record_from_access(access_token)
|
|
58
|
+
return nil unless record
|
|
59
|
+
|
|
60
|
+
if refresh_token
|
|
61
|
+
refresh_record = TokenStore.fetch_record_from_refresh(refresh_token)
|
|
62
|
+
return nil unless refresh_record == record
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
record
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Refresh access token using refresh token
|
|
69
|
+
def self.refresh_access(refresh_token)
|
|
70
|
+
TokenStore.refresh_access_token(refresh_token)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
private
|
|
74
|
+
|
|
75
|
+
def _tokenzen_rotate_refresh_token
|
|
76
|
+
return unless respond_to?(:password_digest_changed?) && password_digest_changed?
|
|
77
|
+
self.refresh_token = JwtProcessor.encode(model: self.class.name, id: id) if respond_to?(:refresh_token=)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def _tokenzen_handle_refresh_rotation
|
|
81
|
+
changed = respond_to?(:refresh_token_changed?) && refresh_token_changed?
|
|
82
|
+
yield
|
|
83
|
+
return unless changed
|
|
84
|
+
clear_all_access_tokens
|
|
85
|
+
generate_tokens
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def _tokenzen_clear_tokens
|
|
89
|
+
clear_all_access_tokens
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "active_support"
|
|
4
|
+
require "active_support/message_encryptor"
|
|
5
|
+
require "active_support/key_generator"
|
|
6
|
+
|
|
7
|
+
module Tokenzen
|
|
8
|
+
class Configuration
|
|
9
|
+
attr_accessor :cache_store,
|
|
10
|
+
:access_token_expiry,
|
|
11
|
+
:refresh_token_expiry,
|
|
12
|
+
:secret_key
|
|
13
|
+
|
|
14
|
+
def initialize
|
|
15
|
+
@cache_store = Rails.cache
|
|
16
|
+
@access_token_expiry = 2.days
|
|
17
|
+
@refresh_token_expiry = 2.months
|
|
18
|
+
|
|
19
|
+
@secret_key = default_secret_key
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Public encrypt method
|
|
23
|
+
def encrypt(value)
|
|
24
|
+
message_encryptor.encrypt_and_sign(value)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Public decrypt method
|
|
28
|
+
def decrypt(value)
|
|
29
|
+
message_encryptor.decrypt_and_verify(value)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
|
|
34
|
+
def message_encryptor
|
|
35
|
+
@message_encryptor ||= begin
|
|
36
|
+
key_len = ActiveSupport::MessageEncryptor.key_len
|
|
37
|
+
generator = ActiveSupport::KeyGenerator.new(@secret_key)
|
|
38
|
+
key = generator.generate_key("tokenzen", key_len)
|
|
39
|
+
|
|
40
|
+
ActiveSupport::MessageEncryptor.new(key)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def default_secret_key
|
|
45
|
+
if defined?(Rails) && Rails.application.respond_to?(:secret_key_base)
|
|
46
|
+
Rails.application.secret_key_base
|
|
47
|
+
else
|
|
48
|
+
ENV["TOKENZEN_SECRET_KEY"] || raise("TOKENZEN_SECRET_KEY not set in ENV and Rails.secret_key_base not found")
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Tokenzen
|
|
4
|
+
module Model
|
|
5
|
+
extend ActiveSupport::Concern
|
|
6
|
+
|
|
7
|
+
included do
|
|
8
|
+
attr_reader :current_access_token
|
|
9
|
+
|
|
10
|
+
before_save :renew_refresh_token_on_password_change
|
|
11
|
+
around_save :handle_refresh_token_rotation
|
|
12
|
+
after_destroy :clear_all_access_tokens
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# PUBLIC API
|
|
16
|
+
def generate_access_token
|
|
17
|
+
token = Tokenzen::TokenStore.write(id)
|
|
18
|
+
return unless token
|
|
19
|
+
|
|
20
|
+
Tokenzen::TokenStore.store_user_token(id, token)
|
|
21
|
+
@current_access_token = token
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def clear_all_access_tokens
|
|
25
|
+
Tokenzen::TokenStore.clear_user_tokens(id)
|
|
26
|
+
@current_access_token = nil
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def renew_refresh_token_on_password_change
|
|
32
|
+
return unless respond_to?(:password_digest_changed?) && password_digest_changed?
|
|
33
|
+
|
|
34
|
+
self.refresh_token = JwtProcessor.encode(id: id)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def handle_refresh_token_rotation
|
|
38
|
+
changed = respond_to?(:refresh_token_changed?) && refresh_token_changed?
|
|
39
|
+
yield
|
|
40
|
+
return unless changed
|
|
41
|
+
|
|
42
|
+
clear_all_access_tokens
|
|
43
|
+
generate_access_token
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Tokenzen
|
|
4
|
+
class TokenStore
|
|
5
|
+
class << self
|
|
6
|
+
|
|
7
|
+
# Generate both tokens and store in cache
|
|
8
|
+
def issue_tokens(record, access_expiry: nil, refresh_expiry: nil)
|
|
9
|
+
access_expiry ||= Tokenzen.configuration.access_token_expiry
|
|
10
|
+
refresh_expiry ||= Tokenzen.configuration.refresh_token_expiry
|
|
11
|
+
|
|
12
|
+
access_key = SecureRandom.hex(32)
|
|
13
|
+
refresh_key = SecureRandom.hex(32)
|
|
14
|
+
|
|
15
|
+
access_payload = {
|
|
16
|
+
"model" => record.class.name,
|
|
17
|
+
"id" => record.id,
|
|
18
|
+
"type" => "access"
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
refresh_payload = {
|
|
22
|
+
"model" => record.class.name,
|
|
23
|
+
"id" => record.id,
|
|
24
|
+
"type" => "refresh"
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
cache.write(access_key, access_payload, expires_in: access_expiry)
|
|
28
|
+
cache.write(refresh_key, refresh_payload, expires_in: refresh_expiry)
|
|
29
|
+
|
|
30
|
+
register_token(record, access_key)
|
|
31
|
+
register_token(record, refresh_key)
|
|
32
|
+
|
|
33
|
+
{
|
|
34
|
+
access_token: encrypt(access_key),
|
|
35
|
+
refresh_token: encrypt(refresh_key)
|
|
36
|
+
}
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Fetch record using access token
|
|
40
|
+
def fetch_record_from_access(token)
|
|
41
|
+
raw_key = decrypt(token)
|
|
42
|
+
payload = cache.read(raw_key)
|
|
43
|
+
return nil unless payload && payload["type"] == "access"
|
|
44
|
+
|
|
45
|
+
payload["model"].constantize.find_by(id: payload["id"])
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Fetch record using refresh token
|
|
49
|
+
def fetch_record_from_refresh(token)
|
|
50
|
+
raw_key = decrypt(token)
|
|
51
|
+
payload = cache.read(raw_key)
|
|
52
|
+
return nil unless payload && payload["type"] == "refresh"
|
|
53
|
+
|
|
54
|
+
payload["model"].constantize.find_by(id: payload["id"])
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Refresh access token using refresh token
|
|
58
|
+
def refresh_access_token(refresh_token)
|
|
59
|
+
record = fetch_record_from_refresh(refresh_token)
|
|
60
|
+
return unless record
|
|
61
|
+
|
|
62
|
+
tokens = issue_tokens(record)
|
|
63
|
+
clear_tokens(record) # clear old tokens
|
|
64
|
+
tokens
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Clear all tokens for a record
|
|
68
|
+
def clear_tokens(record)
|
|
69
|
+
tokens_for(record).each { |key| cache.delete(key) }
|
|
70
|
+
cache.write(register_key(record), [], expires_in: Tokenzen.configuration.refresh_token_expiry)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
private
|
|
74
|
+
|
|
75
|
+
def register_token(record, raw_key)
|
|
76
|
+
list = tokens_for(record)
|
|
77
|
+
list << raw_key
|
|
78
|
+
cache.write(register_key(record), list, expires_in: Tokenzen.configuration.refresh_token_expiry)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def tokens_for(record)
|
|
82
|
+
cache.read(register_key(record)) || []
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def register_key(record)
|
|
86
|
+
"tokenzen:#{record.class.name}:#{record.id}:tokens"
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def cache
|
|
90
|
+
Tokenzen.configuration.cache_store
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def encrypt(value)
|
|
94
|
+
Tokenzen.configuration.encrypt(value)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def decrypt(value)
|
|
98
|
+
Tokenzen.configuration.decrypt(value)
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
data/lib/tokenzen.rb
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "securerandom"
|
|
4
|
+
|
|
5
|
+
require_relative "tokenzen/version"
|
|
6
|
+
require_relative "tokenzen/configuration"
|
|
7
|
+
require_relative "tokenzen/token_store"
|
|
8
|
+
require_relative "tokenzen/authenticatable"
|
|
9
|
+
require_relative "tokenzen/railtie"
|
|
10
|
+
|
|
11
|
+
module Tokenzen
|
|
12
|
+
class Error < StandardError; end
|
|
13
|
+
|
|
14
|
+
class << self
|
|
15
|
+
attr_accessor :configuration
|
|
16
|
+
|
|
17
|
+
def configure
|
|
18
|
+
self.configuration ||= Configuration.new
|
|
19
|
+
yield(configuration)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def configuration
|
|
23
|
+
@configuration ||= Configuration.new
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: tokenzen
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Satendra
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: exe
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2026-02-16 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: rails
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - "~>"
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '6.0'
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - "~>"
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '6.0'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: activesupport
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - "~>"
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '6.0'
|
|
34
|
+
type: :runtime
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - "~>"
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '6.0'
|
|
41
|
+
description: Tokenzen is a lightweight, model-agnostic authentication toolkit for
|
|
42
|
+
Rails. It provides secure, polymorphic access token management for any ActiveRecord
|
|
43
|
+
model with configurable expiration, AES-256 encryption, login, logout, and refresh
|
|
44
|
+
token rotation.
|
|
45
|
+
email:
|
|
46
|
+
- satendra.km@alumni.iitd.ac.in
|
|
47
|
+
executables: []
|
|
48
|
+
extensions: []
|
|
49
|
+
extra_rdoc_files: []
|
|
50
|
+
files:
|
|
51
|
+
- README.md
|
|
52
|
+
- lib/tokenzen.rb
|
|
53
|
+
- lib/tokenzen/authenticatable.rb
|
|
54
|
+
- lib/tokenzen/configuration.rb
|
|
55
|
+
- lib/tokenzen/model.rb
|
|
56
|
+
- lib/tokenzen/railtie.rb
|
|
57
|
+
- lib/tokenzen/token_store.rb
|
|
58
|
+
- lib/tokenzen/version.rb
|
|
59
|
+
homepage: https://github.com/stndrk/tokenzen
|
|
60
|
+
licenses:
|
|
61
|
+
- MIT
|
|
62
|
+
metadata:
|
|
63
|
+
rubygems_mfa_required: 'true'
|
|
64
|
+
allowed_push_host: https://rubygems.org
|
|
65
|
+
homepage_uri: https://github.com/stndrk/tokenzen
|
|
66
|
+
source_code_uri: https://github.com/stndrk/tokenzen
|
|
67
|
+
changelog_uri: https://github.com/stndrk/tokenzen/blob/main/CHANGELOG.md
|
|
68
|
+
bug_tracker_uri: https://github.com/stndrk/tokenzen/issues
|
|
69
|
+
documentation_uri: https://github.com/stndrk/tokenzen#readme
|
|
70
|
+
post_install_message:
|
|
71
|
+
rdoc_options: []
|
|
72
|
+
require_paths:
|
|
73
|
+
- lib
|
|
74
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
75
|
+
requirements:
|
|
76
|
+
- - ">="
|
|
77
|
+
- !ruby/object:Gem::Version
|
|
78
|
+
version: 3.0.0
|
|
79
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
80
|
+
requirements:
|
|
81
|
+
- - ">="
|
|
82
|
+
- !ruby/object:Gem::Version
|
|
83
|
+
version: '0'
|
|
84
|
+
requirements: []
|
|
85
|
+
rubygems_version: 3.5.21
|
|
86
|
+
signing_key:
|
|
87
|
+
specification_version: 4
|
|
88
|
+
summary: Model-agnostic token authentication for Rails
|
|
89
|
+
test_files: []
|