veri 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 +3 -0
- data/LICENSE.txt +21 -0
- data/README.md +315 -0
- data/lib/generators/veri/authentication_generator.rb +21 -0
- data/lib/generators/veri/templates/add_veri_authentication.rb.erb +16 -0
- data/lib/veri/configuration.rb +40 -0
- data/lib/veri/controllers/concerns/authentication.rb +73 -0
- data/lib/veri/inputs.rb +27 -0
- data/lib/veri/models/concerns/authenticatable.rb +25 -0
- data/lib/veri/models/session.rb +82 -0
- data/lib/veri/password/argon2.rb +17 -0
- data/lib/veri/password/bcrypt.rb +17 -0
- data/lib/veri/password/scrypt.rb +17 -0
- data/lib/veri/railtie.rb +12 -0
- data/lib/veri/version.rb +3 -0
- data/lib/veri.rb +26 -0
- data/veri.gemspec +29 -0
- metadata +165 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: ac32f0c6d36a07d566aec8905648f0e27811876d52929ab1753f3109af5648b7
|
4
|
+
data.tar.gz: eaaec22fa0215df1896cf3baadf7b8ce5e7ff4ebcb61094443a020e54c63023c
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: d75c20d2484660914c53ead2394f376bc915c45833bfe8b643b7e4964dc104c94d64747ce8be312bae24930e27216296060f1971a8356c9ec4c347372106c080
|
7
|
+
data.tar.gz: 4166c688264ac0cfa34866487a2ac572deb70972656c59fd724608841ea50d8b5436e64d70c7a45990302e4a8111cf0e47b103aff78075f0d792d05953c5a2b7
|
data/CHANGELOG.md
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2025 enjaku4 (https://github.com/enjaku4)
|
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
|
13
|
+
all 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
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,315 @@
|
|
1
|
+
# Veri: Minimal Authentication Framework for Rails
|
2
|
+
|
3
|
+
[](http://badge.fury.io/rb/veri)
|
4
|
+
[](https://github.com/brownboxdev/veri/actions/workflows/ci.yml)
|
5
|
+
|
6
|
+
Veri is a cookie-based authentication library for Ruby on Rails that provides essential authentication building blocks without imposing business logic. Unlike full-featured solutions, Veri gives you complete control over your authentication flow while handling the complex underlying mechanics of secure password storage and session management.
|
7
|
+
|
8
|
+
**Key Features:**
|
9
|
+
|
10
|
+
- Cookie-based authentication with database-stored sessions
|
11
|
+
- Supports multiple password hashing algorithms (argon2, bcrypt, scrypt)
|
12
|
+
- Granular session management and control
|
13
|
+
- Flexible authentication callbacks
|
14
|
+
- No pre-defined business logic, no views, controllers, or mailers — just the essential methods
|
15
|
+
- Built-in return path handling
|
16
|
+
|
17
|
+
> ⚠️ **Development Notice**<br>
|
18
|
+
> Veri is functional but in early development. Breaking changes may occur in minor releases until v1.0!
|
19
|
+
|
20
|
+
## Table of Contents
|
21
|
+
|
22
|
+
**Gem Usage:**
|
23
|
+
- [Installation](#installation)
|
24
|
+
- [Configuration](#configuration)
|
25
|
+
- [Password Management](#password-management)
|
26
|
+
- [Controller Integration](#controller-integration)
|
27
|
+
- [Authentication Sessions](#authentication-sessions)
|
28
|
+
- [View Helpers](#view-helpers)
|
29
|
+
- [Testing](#testing)
|
30
|
+
|
31
|
+
**Community Resources:**
|
32
|
+
- [Contributing](#contributing)
|
33
|
+
- [License](#license)
|
34
|
+
- [Code of Conduct](#code-of-conduct)
|
35
|
+
|
36
|
+
## Installation
|
37
|
+
|
38
|
+
Add Veri to your Gemfile:
|
39
|
+
|
40
|
+
```rb
|
41
|
+
gem "veri"
|
42
|
+
```
|
43
|
+
|
44
|
+
Install the gem:
|
45
|
+
|
46
|
+
```bash
|
47
|
+
bundle install
|
48
|
+
```
|
49
|
+
|
50
|
+
Generate the migration for your user model (replace `users` with your user table name if different):
|
51
|
+
|
52
|
+
```shell
|
53
|
+
# For standard integer IDs
|
54
|
+
rails generate veri:authentication users
|
55
|
+
|
56
|
+
# For UUID primary keys
|
57
|
+
rails generate veri:authentication users --uuid
|
58
|
+
```
|
59
|
+
|
60
|
+
Run the migration:
|
61
|
+
|
62
|
+
```shell
|
63
|
+
rails db:migrate
|
64
|
+
```
|
65
|
+
|
66
|
+
## Configuration
|
67
|
+
|
68
|
+
If customization is required, configure Veri in an initializer:
|
69
|
+
|
70
|
+
```rb
|
71
|
+
# These are the default values; you can change them as needed
|
72
|
+
Veri.configure do |config|
|
73
|
+
config.hashing_algorithm = :argon2 # Password hashing algorithm (:argon2, :bcrypt, or :scrypt)
|
74
|
+
config.inactive_session_lifetime = nil # Session inactivity timeout (nil means sessions never expire due to inactivity)
|
75
|
+
config.total_session_lifetime = 14.days # Maximum session duration regardless of activity
|
76
|
+
config.user_model_name = "User" # Your user model name
|
77
|
+
end
|
78
|
+
```
|
79
|
+
|
80
|
+
## Password Management
|
81
|
+
|
82
|
+
Your user model is automatically extended with password management methods:
|
83
|
+
|
84
|
+
```rb
|
85
|
+
# Set or update a password
|
86
|
+
user.update_password("new_password")
|
87
|
+
|
88
|
+
# Verify a password
|
89
|
+
user.verify_password("submitted_password")
|
90
|
+
```
|
91
|
+
|
92
|
+
## Controller Integration
|
93
|
+
|
94
|
+
### Basic Setup
|
95
|
+
|
96
|
+
Include the authentication module and configure protection:
|
97
|
+
|
98
|
+
```rb
|
99
|
+
class ApplicationController < ActionController::Base
|
100
|
+
include Veri::Authentication
|
101
|
+
|
102
|
+
with_authentication # Require authentication by default
|
103
|
+
end
|
104
|
+
|
105
|
+
class PicturesController < ApplicationController
|
106
|
+
skip_authentication only: [:index, :show] # Allow public access to index and show actions
|
107
|
+
end
|
108
|
+
```
|
109
|
+
|
110
|
+
### Authentication Methods
|
111
|
+
|
112
|
+
This is a simplified example of how to use Veri's authentication methods in your controllers:
|
113
|
+
|
114
|
+
```rb
|
115
|
+
class SessionsController < ApplicationController
|
116
|
+
skip_authentication except: [:destroy]
|
117
|
+
|
118
|
+
def create
|
119
|
+
user = User.find_by(email: params[:email])
|
120
|
+
|
121
|
+
if user&.verify_password(params[:password])
|
122
|
+
log_in(user)
|
123
|
+
redirect_to return_path || dashboard_path
|
124
|
+
else
|
125
|
+
flash.now[:alert] = "Invalid credentials"
|
126
|
+
render :new, status: :unprocessable_entity
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def destroy
|
131
|
+
log_out
|
132
|
+
redirect_to root_path
|
133
|
+
end
|
134
|
+
end
|
135
|
+
```
|
136
|
+
|
137
|
+
Available methods:
|
138
|
+
|
139
|
+
- `current_user` - returns the authenticated user or `nil`
|
140
|
+
- `logged_in?` - returns `true` if user is authenticated
|
141
|
+
- `log_in(user)` - authenticates the user and creates a session
|
142
|
+
- `log_out` - terminates the current session
|
143
|
+
- `return_path` - returns the path the user was trying to access before authentication
|
144
|
+
- `current_session` - returns the current authentication session
|
145
|
+
|
146
|
+
### Authentication Callbacks
|
147
|
+
|
148
|
+
Override these private methods to customize authentication behavior:
|
149
|
+
|
150
|
+
```rb
|
151
|
+
class ApplicationController < ActionController::Base
|
152
|
+
include Veri::Authentication
|
153
|
+
|
154
|
+
with_authentication
|
155
|
+
|
156
|
+
# ...
|
157
|
+
|
158
|
+
private
|
159
|
+
|
160
|
+
# Called after successful login
|
161
|
+
def after_login(user)
|
162
|
+
Rails.logger.info "User #{user.id} logged in"
|
163
|
+
# Custom redirect logic, analytics, etc.
|
164
|
+
end
|
165
|
+
|
166
|
+
# Called after logout
|
167
|
+
def after_logout
|
168
|
+
Rails.logger.info "User logged out"
|
169
|
+
# Cleanup, analytics, etc.
|
170
|
+
end
|
171
|
+
|
172
|
+
# Customize unauthenticated user handling
|
173
|
+
def when_unauthenticated
|
174
|
+
# By default redirects back with a fallback to the root path if the request format is HTML,
|
175
|
+
# otherwise responds with 401 Unauthorized
|
176
|
+
redirect_to login_path
|
177
|
+
end
|
178
|
+
end
|
179
|
+
```
|
180
|
+
|
181
|
+
## Authentication Sessions
|
182
|
+
|
183
|
+
Veri stores authentication sessions in the database, enabling powerful session management:
|
184
|
+
|
185
|
+
### Session Access
|
186
|
+
|
187
|
+
```rb
|
188
|
+
# Get all sessions for a user
|
189
|
+
user.veri_sessions
|
190
|
+
|
191
|
+
# Get current session in controller
|
192
|
+
current_session
|
193
|
+
```
|
194
|
+
|
195
|
+
### Session Information
|
196
|
+
|
197
|
+
```rb
|
198
|
+
session.info
|
199
|
+
# => {
|
200
|
+
# device: "Desktop",
|
201
|
+
# os: "macOS",
|
202
|
+
# browser: "Chrome",
|
203
|
+
# ip_address: "1.2.3.4",
|
204
|
+
# last_activity: "2023-10-01 12:00:00"
|
205
|
+
# }
|
206
|
+
```
|
207
|
+
|
208
|
+
### Session Status
|
209
|
+
|
210
|
+
```rb
|
211
|
+
session.active? # Session is active (neither expired nor inactive)
|
212
|
+
session.inactive? # Session exceeded inactivity timeout
|
213
|
+
session.expired? # Session exceeded maximum lifetime
|
214
|
+
```
|
215
|
+
|
216
|
+
### Session Management
|
217
|
+
|
218
|
+
```rb
|
219
|
+
# Terminate a specific session
|
220
|
+
session.terminate
|
221
|
+
|
222
|
+
# Terminate all sessions for a user
|
223
|
+
Veri::Session.terminate_all(user)
|
224
|
+
|
225
|
+
# Clean up expired/inactive sessions
|
226
|
+
Veri::Session.prune # All sessions
|
227
|
+
Veri::Session.prune(user) # Specific user's sessions
|
228
|
+
```
|
229
|
+
|
230
|
+
## View Helpers
|
231
|
+
|
232
|
+
Access authentication state in your views:
|
233
|
+
|
234
|
+
```erb
|
235
|
+
<% if logged_in? %>
|
236
|
+
<p>Welcome, <%= current_user.name %>!</p>
|
237
|
+
<%= link_to "Logout", logout_path, method: :delete %>
|
238
|
+
<% else %>
|
239
|
+
<%= link_to "Login", login_path %>
|
240
|
+
<% end %>
|
241
|
+
```
|
242
|
+
|
243
|
+
## Testing
|
244
|
+
|
245
|
+
Veri doesn't provide test helpers, but you can easily create your own:
|
246
|
+
|
247
|
+
### Request Specs (Recommended)
|
248
|
+
|
249
|
+
```rb
|
250
|
+
module AuthenticationHelpers
|
251
|
+
def log_in(user)
|
252
|
+
password = "test_password"
|
253
|
+
user.update_password(password)
|
254
|
+
post login_path, params: { email: user.email, password: }
|
255
|
+
end
|
256
|
+
|
257
|
+
def log_out
|
258
|
+
delete logout_path
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
# In your spec_helper.rb
|
263
|
+
RSpec.configure do |config|
|
264
|
+
config.include AuthenticationHelpers, type: :request
|
265
|
+
end
|
266
|
+
```
|
267
|
+
|
268
|
+
### Controller Specs (Legacy)
|
269
|
+
|
270
|
+
```rb
|
271
|
+
module AuthenticationHelpers
|
272
|
+
def log_in(user)
|
273
|
+
controller.log_in(user)
|
274
|
+
end
|
275
|
+
|
276
|
+
def log_out
|
277
|
+
controller.log_out
|
278
|
+
end
|
279
|
+
end
|
280
|
+
|
281
|
+
# In your spec_helper.rb
|
282
|
+
RSpec.configure do |config|
|
283
|
+
config.include AuthenticationHelpers, type: :controller
|
284
|
+
end
|
285
|
+
```
|
286
|
+
|
287
|
+
## Contributing
|
288
|
+
|
289
|
+
### Getting Help
|
290
|
+
Have a question or need assistance? Open a discussion in our [discussions section](https://github.com/brownboxdev/veri/discussions) for:
|
291
|
+
- Usage questions
|
292
|
+
- Implementation guidance
|
293
|
+
- Feature suggestions
|
294
|
+
|
295
|
+
### Reporting Issues
|
296
|
+
Found a bug? Please [create an issue](https://github.com/brownboxdev/veri/issues) with:
|
297
|
+
- A clear description of the problem
|
298
|
+
- Steps to reproduce the issue
|
299
|
+
- Your environment details (Rails version, Ruby version, etc.)
|
300
|
+
|
301
|
+
### Contributing Code
|
302
|
+
Ready to contribute? You can:
|
303
|
+
- Fix bugs by submitting pull requests
|
304
|
+
- Improve documentation
|
305
|
+
- Add new features (please discuss first in our [discussions section](https://github.com/brownboxdev/veri/discussions))
|
306
|
+
|
307
|
+
Before contributing, please read the [contributing guidelines](https://github.com/brownboxdev/veri/blob/master/CONTRIBUTING.md)
|
308
|
+
|
309
|
+
## License
|
310
|
+
|
311
|
+
The gem is available as open source under the terms of the [MIT License](https://github.com/brownboxdev/veri/blob/main/LICENSE.txt).
|
312
|
+
|
313
|
+
## Code of Conduct
|
314
|
+
|
315
|
+
Everyone interacting in the Veri project is expected to follow the [code of conduct](https://github.com/brownboxdev/veri/blob/main/CODE_OF_CONDUCT.md).
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require "rails/generators/migration"
|
2
|
+
|
3
|
+
module Veri
|
4
|
+
class AuthenticationGenerator < Rails::Generators::Base
|
5
|
+
include Rails::Generators::Migration
|
6
|
+
|
7
|
+
source_root File.expand_path("templates", __dir__)
|
8
|
+
|
9
|
+
argument :table_name, type: :string, required: true
|
10
|
+
|
11
|
+
class_option :uuid, type: :boolean, default: false, desc: "Use UUIDs as primary keys"
|
12
|
+
|
13
|
+
def create_migrations
|
14
|
+
migration_template "add_veri_authentication.rb.erb", "db/migrate/add_veri_authentication.rb"
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.next_migration_number(_path)
|
18
|
+
Time.now.utc.strftime("%Y%m%d%H%M%S")
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
class AddVeriAuthentication < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version.to_s %>]
|
2
|
+
def change
|
3
|
+
add_column <%= table_name.to_sym.inspect %>, :hashed_password, :text
|
4
|
+
|
5
|
+
create_table :veri_sessions<%= ", id: :uuid" if options[:uuid] %> do |t|
|
6
|
+
t.string :hashed_token, null: false, index: { unique: true }
|
7
|
+
t.datetime :expires_at, null: false
|
8
|
+
t.belongs_to :authenticatable, null: false, foreign_key: { to_table: <%= table_name.to_sym.inspect %> }, index: true<%= ", type: :uuid" if options[:uuid] %>
|
9
|
+
t.datetime :last_seen_at, null: false
|
10
|
+
t.string :ip_address
|
11
|
+
t.string :user_agent
|
12
|
+
|
13
|
+
t.timestamps
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require "active_support/core_ext/numeric/time"
|
2
|
+
require "dry-configurable"
|
3
|
+
|
4
|
+
module Veri
|
5
|
+
module Configuration
|
6
|
+
extend Dry::Configurable
|
7
|
+
|
8
|
+
module_function
|
9
|
+
|
10
|
+
setting :hashing_algorithm,
|
11
|
+
default: :argon2,
|
12
|
+
reader: true,
|
13
|
+
constructor: -> (value) { Veri::Inputs.process(value, as: :hashing_algorithm, error: Veri::ConfigurationError) }
|
14
|
+
setting :inactive_session_lifetime,
|
15
|
+
default: nil,
|
16
|
+
reader: true,
|
17
|
+
constructor: -> (value) { Veri::Inputs.process(value, as: :duration, optional: true, error: Veri::ConfigurationError) }
|
18
|
+
setting :total_session_lifetime,
|
19
|
+
default: 14.days,
|
20
|
+
reader: true,
|
21
|
+
constructor: -> (value) { Veri::Inputs.process(value, as: :duration, error: Veri::ConfigurationError) }
|
22
|
+
setting :user_model_name,
|
23
|
+
default: "User",
|
24
|
+
reader: true,
|
25
|
+
constructor: -> (value) { Veri::Inputs.process(value, as: :string, error: Veri::ConfigurationError) }
|
26
|
+
|
27
|
+
def hasher
|
28
|
+
case hashing_algorithm
|
29
|
+
when :argon2 then Veri::Password::Argon2
|
30
|
+
when :bcrypt then Veri::Password::BCrypt
|
31
|
+
when :scrypt then Veri::Password::SCrypt
|
32
|
+
else raise Veri::Error, "Invalid hashing algorithm: #{hashing_algorithm}"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def user_model
|
37
|
+
Veri::Inputs.process(user_model_name, as: :model, error: Veri::ConfigurationError)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
module Veri
|
2
|
+
module Authentication
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
include ActionController::Cookies unless self < ActionController::Cookies
|
7
|
+
|
8
|
+
helper_method(:current_user, :logged_in?) if respond_to?(:helper_method)
|
9
|
+
end
|
10
|
+
|
11
|
+
class_methods do
|
12
|
+
def with_authentication(options = {})
|
13
|
+
before_action :with_authentication, **options
|
14
|
+
rescue ArgumentError => e
|
15
|
+
raise Veri::InvalidArgumentError, e.message
|
16
|
+
end
|
17
|
+
|
18
|
+
def skip_authentication(options = {})
|
19
|
+
skip_before_action :with_authentication, **options
|
20
|
+
rescue ArgumentError => e
|
21
|
+
raise Veri::InvalidArgumentError, e.message
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def current_user
|
26
|
+
@current_user ||= current_session&.authenticatable
|
27
|
+
end
|
28
|
+
|
29
|
+
def current_session
|
30
|
+
token = cookies.encrypted[:veri_token]
|
31
|
+
@current_session ||= token ? Session.find_by(hashed_token: Digest::SHA256.hexdigest(token)) : nil
|
32
|
+
end
|
33
|
+
|
34
|
+
def log_in(authenticatable)
|
35
|
+
token = Veri::Session.establish(Veri::Inputs.process(authenticatable, as: :authenticatable), request)
|
36
|
+
cookies.encrypted.permanent[:veri_token] = { value: token, httponly: true }
|
37
|
+
after_login
|
38
|
+
end
|
39
|
+
|
40
|
+
def log_out
|
41
|
+
current_session&.terminate
|
42
|
+
cookies.delete(:veri_token)
|
43
|
+
after_logout
|
44
|
+
end
|
45
|
+
|
46
|
+
def logged_in?
|
47
|
+
current_user.present?
|
48
|
+
end
|
49
|
+
|
50
|
+
def return_path
|
51
|
+
cookies.signed[:veri_return_path]
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
def with_authentication
|
57
|
+
current_session.update_info(request) and return if logged_in? && current_session.active?
|
58
|
+
|
59
|
+
current_session&.terminate
|
60
|
+
|
61
|
+
cookies.signed[:veri_return_path] = { value: request.fullpath, expires: 15.minutes.from_now } if request.get? && request.format.html?
|
62
|
+
|
63
|
+
when_unauthenticated
|
64
|
+
end
|
65
|
+
|
66
|
+
def when_unauthenticated
|
67
|
+
request.format.html? ? redirect_back(fallback_location: root_path) : head(:unauthorized)
|
68
|
+
end
|
69
|
+
|
70
|
+
def after_login = nil
|
71
|
+
def after_logout = nil
|
72
|
+
end
|
73
|
+
end
|
data/lib/veri/inputs.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
require "dry-types"
|
2
|
+
|
3
|
+
module Veri
|
4
|
+
module Inputs
|
5
|
+
extend self
|
6
|
+
|
7
|
+
include Dry.Types()
|
8
|
+
|
9
|
+
def process(value, as:, optional: false, error: Veri::InvalidArgumentError)
|
10
|
+
checker = send(as)
|
11
|
+
checker = checker.optional if optional
|
12
|
+
|
13
|
+
checker[value]
|
14
|
+
rescue Dry::Types::CoercionError => e
|
15
|
+
raise error, e.message
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def hashing_algorithm = self::Strict::Symbol.enum(:argon2, :bcrypt, :scrypt)
|
21
|
+
def duration = self::Instance(ActiveSupport::Duration)
|
22
|
+
def string = self::Strict::String
|
23
|
+
def model = self::Strict::Class.constructor { _1.try(:safe_constantize) || _1 }.constrained(lt: ActiveRecord::Base)
|
24
|
+
def authenticatable = self::Instance(Veri::Configuration.user_model)
|
25
|
+
def request = self::Instance(ActionDispatch::Request)
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Veri
|
2
|
+
module Authenticatable
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
raise Veri::Error, "Veri::Authenticatable can only be included once" if defined?(@@included) && @@included != name
|
7
|
+
|
8
|
+
@@included = name
|
9
|
+
|
10
|
+
has_many :veri_sessions, class_name: "Veri::Session", foreign_key: :authenticatable_id, dependent: :destroy
|
11
|
+
end
|
12
|
+
|
13
|
+
def update_password(password)
|
14
|
+
update!(hashed_password: hasher.create(Veri::Inputs.process(password, as: :string)))
|
15
|
+
end
|
16
|
+
|
17
|
+
def verify_password(password)
|
18
|
+
hasher.verify(Veri::Inputs.process(password, as: :string), hashed_password)
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def hasher = Veri::Configuration.hasher
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
require "user_agent_parser"
|
2
|
+
|
3
|
+
module Veri
|
4
|
+
class Session < ActiveRecord::Base
|
5
|
+
self.table_name = "veri_sessions"
|
6
|
+
|
7
|
+
belongs_to :authenticatable, class_name: Veri::Configuration.user_model_name # rubocop:disable Rails/ReflectionClassName
|
8
|
+
|
9
|
+
def active?
|
10
|
+
!expired? && !inactive?
|
11
|
+
end
|
12
|
+
|
13
|
+
def expired?
|
14
|
+
expires_at < Time.current
|
15
|
+
end
|
16
|
+
|
17
|
+
def inactive?
|
18
|
+
inactive_session_lifetime = Veri::Configuration.inactive_session_lifetime
|
19
|
+
|
20
|
+
return false unless inactive_session_lifetime
|
21
|
+
|
22
|
+
last_seen_at < Time.current - inactive_session_lifetime
|
23
|
+
end
|
24
|
+
|
25
|
+
alias terminate delete
|
26
|
+
|
27
|
+
def update_info(request)
|
28
|
+
processed_request = Veri::Inputs.process(request, as: :request, error: Veri::Error)
|
29
|
+
|
30
|
+
update!(
|
31
|
+
last_seen_at: Time.current,
|
32
|
+
ip_address: processed_request.remote_ip,
|
33
|
+
user_agent: processed_request.user_agent
|
34
|
+
)
|
35
|
+
end
|
36
|
+
|
37
|
+
def info
|
38
|
+
agent = UserAgentParser.parse(user_agent)
|
39
|
+
|
40
|
+
{
|
41
|
+
device: agent.device.to_s,
|
42
|
+
os: agent.os.to_s,
|
43
|
+
browser: agent.to_s,
|
44
|
+
ip_address:,
|
45
|
+
last_seen_at:
|
46
|
+
}
|
47
|
+
end
|
48
|
+
|
49
|
+
class << self
|
50
|
+
def establish(authenticatable, request)
|
51
|
+
token = SecureRandom.hex(32)
|
52
|
+
expires_at = Time.current + Veri::Configuration.total_session_lifetime
|
53
|
+
|
54
|
+
new(
|
55
|
+
hashed_token: Digest::SHA256.hexdigest(token),
|
56
|
+
expires_at:,
|
57
|
+
authenticatable: Veri::Inputs.process(authenticatable, as: :authenticatable, error: Veri::Error)
|
58
|
+
).update_info(
|
59
|
+
Veri::Inputs.process(request, as: :request, error: Veri::Error)
|
60
|
+
)
|
61
|
+
|
62
|
+
token
|
63
|
+
end
|
64
|
+
|
65
|
+
def prune(authenticatable = nil)
|
66
|
+
processed_authenticatable = Veri::Inputs.process(authenticatable, as: :authenticatable, optional: true)
|
67
|
+
scope = processed_authenticatable ? where(authenticatable: processed_authenticatable) : all
|
68
|
+
to_be_pruned = scope.where(expires_at: ...Time.current)
|
69
|
+
if Veri::Configuration.inactive_session_lifetime
|
70
|
+
to_be_pruned = to_be_pruned.or(
|
71
|
+
scope.where(last_seen_at: ...(Time.current - Veri::Configuration.inactive_session_lifetime))
|
72
|
+
)
|
73
|
+
end
|
74
|
+
to_be_pruned.delete_all
|
75
|
+
end
|
76
|
+
|
77
|
+
def terminate_all(authenticatable)
|
78
|
+
Veri::Inputs.process(authenticatable, as: :authenticatable).veri_sessions.delete_all
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require "argon2"
|
2
|
+
|
3
|
+
module Veri
|
4
|
+
module Password
|
5
|
+
module Argon2
|
6
|
+
module_function
|
7
|
+
|
8
|
+
def create(password)
|
9
|
+
::Argon2::Password.create(password)
|
10
|
+
end
|
11
|
+
|
12
|
+
def verify(password, hashed_password)
|
13
|
+
::Argon2::Password.verify_password(password, hashed_password)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require "bcrypt"
|
2
|
+
|
3
|
+
module Veri
|
4
|
+
module Password
|
5
|
+
module BCrypt
|
6
|
+
module_function
|
7
|
+
|
8
|
+
def create(password)
|
9
|
+
::BCrypt::Password.create(password)
|
10
|
+
end
|
11
|
+
|
12
|
+
def verify(password, hashed_password)
|
13
|
+
::BCrypt::Password.new(hashed_password) == password
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require "scrypt"
|
2
|
+
|
3
|
+
module Veri
|
4
|
+
module Password
|
5
|
+
module SCrypt
|
6
|
+
module_function
|
7
|
+
|
8
|
+
def create(password)
|
9
|
+
::SCrypt::Password.create(password)
|
10
|
+
end
|
11
|
+
|
12
|
+
def verify(password, hashed_password)
|
13
|
+
::SCrypt::Password.new(hashed_password) == password
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
data/lib/veri/railtie.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
require "rails/railtie"
|
2
|
+
|
3
|
+
module Veri
|
4
|
+
class Railtie < Rails::Railtie
|
5
|
+
initializer "veri.to_prepare" do |app|
|
6
|
+
app.config.to_prepare do
|
7
|
+
user_model = Veri::Configuration.user_model
|
8
|
+
user_model.include Veri::Authenticatable unless user_model < Veri::Authenticatable
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
data/lib/veri/version.rb
ADDED
data/lib/veri.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
require_relative "veri/version"
|
2
|
+
|
3
|
+
require "active_record"
|
4
|
+
require "active_support"
|
5
|
+
|
6
|
+
require_relative "veri/password/argon2"
|
7
|
+
require_relative "veri/password/bcrypt"
|
8
|
+
require_relative "veri/password/scrypt"
|
9
|
+
|
10
|
+
require_relative "veri/inputs"
|
11
|
+
require_relative "veri/configuration"
|
12
|
+
|
13
|
+
module Veri
|
14
|
+
class Error < StandardError; end
|
15
|
+
class ConfigurationError < Veri::Error; end
|
16
|
+
class InvalidArgumentError < Veri::Error; end
|
17
|
+
|
18
|
+
delegate :configure, to: Veri::Configuration
|
19
|
+
module_function :configure
|
20
|
+
end
|
21
|
+
|
22
|
+
require_relative "veri/models/session"
|
23
|
+
require_relative "veri/controllers/concerns/authentication"
|
24
|
+
require_relative "veri/models/concerns/authenticatable"
|
25
|
+
|
26
|
+
require_relative "veri/railtie"
|
data/veri.gemspec
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
require_relative "lib/veri/version"
|
2
|
+
|
3
|
+
Gem::Specification.new do |spec|
|
4
|
+
spec.name = "veri"
|
5
|
+
spec.version = Veri::VERSION
|
6
|
+
spec.authors = ["enjaku4"]
|
7
|
+
spec.homepage = "https://github.com/brownboxdev/veri"
|
8
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
9
|
+
spec.metadata["source_code_uri"] = spec.homepage
|
10
|
+
spec.metadata["changelog_uri"] = "#{spec.homepage}/blob/main/CHANGELOG.md"
|
11
|
+
spec.metadata["rubygems_mfa_required"] = "true"
|
12
|
+
spec.summary = "Minimal cookie-based authentication library for Ruby on Rails"
|
13
|
+
spec.license = "MIT"
|
14
|
+
spec.required_ruby_version = ">= 3.2", "< 3.5"
|
15
|
+
|
16
|
+
spec.files = [
|
17
|
+
"veri.gemspec", "README.md", "CHANGELOG.md", "LICENSE.txt"
|
18
|
+
] + Dir.glob("lib/**/*")
|
19
|
+
|
20
|
+
spec.require_paths = ["lib"]
|
21
|
+
|
22
|
+
spec.add_dependency "argon2", "~> 2.0"
|
23
|
+
spec.add_dependency "bcrypt", "~> 3.0"
|
24
|
+
spec.add_dependency "dry-configurable", "~> 1.3"
|
25
|
+
spec.add_dependency "dry-types", "~> 1.8"
|
26
|
+
spec.add_dependency "rails", ">= 7.1", "< 8.1"
|
27
|
+
spec.add_dependency "scrypt", "~> 3.0"
|
28
|
+
spec.add_dependency "user_agent_parser", "~> 2.0"
|
29
|
+
end
|
metadata
ADDED
@@ -0,0 +1,165 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: veri
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- enjaku4
|
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: argon2
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
15
|
+
requirements:
|
16
|
+
- - "~>"
|
17
|
+
- !ruby/object:Gem::Version
|
18
|
+
version: '2.0'
|
19
|
+
type: :runtime
|
20
|
+
prerelease: false
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
22
|
+
requirements:
|
23
|
+
- - "~>"
|
24
|
+
- !ruby/object:Gem::Version
|
25
|
+
version: '2.0'
|
26
|
+
- !ruby/object:Gem::Dependency
|
27
|
+
name: bcrypt
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
29
|
+
requirements:
|
30
|
+
- - "~>"
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '3.0'
|
33
|
+
type: :runtime
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - "~>"
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '3.0'
|
40
|
+
- !ruby/object:Gem::Dependency
|
41
|
+
name: dry-configurable
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - "~>"
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '1.3'
|
47
|
+
type: :runtime
|
48
|
+
prerelease: false
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - "~>"
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '1.3'
|
54
|
+
- !ruby/object:Gem::Dependency
|
55
|
+
name: dry-types
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - "~>"
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: '1.8'
|
61
|
+
type: :runtime
|
62
|
+
prerelease: false
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
64
|
+
requirements:
|
65
|
+
- - "~>"
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: '1.8'
|
68
|
+
- !ruby/object:Gem::Dependency
|
69
|
+
name: rails
|
70
|
+
requirement: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - ">="
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: '7.1'
|
75
|
+
- - "<"
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '8.1'
|
78
|
+
type: :runtime
|
79
|
+
prerelease: false
|
80
|
+
version_requirements: !ruby/object:Gem::Requirement
|
81
|
+
requirements:
|
82
|
+
- - ">="
|
83
|
+
- !ruby/object:Gem::Version
|
84
|
+
version: '7.1'
|
85
|
+
- - "<"
|
86
|
+
- !ruby/object:Gem::Version
|
87
|
+
version: '8.1'
|
88
|
+
- !ruby/object:Gem::Dependency
|
89
|
+
name: scrypt
|
90
|
+
requirement: !ruby/object:Gem::Requirement
|
91
|
+
requirements:
|
92
|
+
- - "~>"
|
93
|
+
- !ruby/object:Gem::Version
|
94
|
+
version: '3.0'
|
95
|
+
type: :runtime
|
96
|
+
prerelease: false
|
97
|
+
version_requirements: !ruby/object:Gem::Requirement
|
98
|
+
requirements:
|
99
|
+
- - "~>"
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: '3.0'
|
102
|
+
- !ruby/object:Gem::Dependency
|
103
|
+
name: user_agent_parser
|
104
|
+
requirement: !ruby/object:Gem::Requirement
|
105
|
+
requirements:
|
106
|
+
- - "~>"
|
107
|
+
- !ruby/object:Gem::Version
|
108
|
+
version: '2.0'
|
109
|
+
type: :runtime
|
110
|
+
prerelease: false
|
111
|
+
version_requirements: !ruby/object:Gem::Requirement
|
112
|
+
requirements:
|
113
|
+
- - "~>"
|
114
|
+
- !ruby/object:Gem::Version
|
115
|
+
version: '2.0'
|
116
|
+
executables: []
|
117
|
+
extensions: []
|
118
|
+
extra_rdoc_files: []
|
119
|
+
files:
|
120
|
+
- CHANGELOG.md
|
121
|
+
- LICENSE.txt
|
122
|
+
- README.md
|
123
|
+
- lib/generators/veri/authentication_generator.rb
|
124
|
+
- lib/generators/veri/templates/add_veri_authentication.rb.erb
|
125
|
+
- lib/veri.rb
|
126
|
+
- lib/veri/configuration.rb
|
127
|
+
- lib/veri/controllers/concerns/authentication.rb
|
128
|
+
- lib/veri/inputs.rb
|
129
|
+
- lib/veri/models/concerns/authenticatable.rb
|
130
|
+
- lib/veri/models/session.rb
|
131
|
+
- lib/veri/password/argon2.rb
|
132
|
+
- lib/veri/password/bcrypt.rb
|
133
|
+
- lib/veri/password/scrypt.rb
|
134
|
+
- lib/veri/railtie.rb
|
135
|
+
- lib/veri/version.rb
|
136
|
+
- veri.gemspec
|
137
|
+
homepage: https://github.com/brownboxdev/veri
|
138
|
+
licenses:
|
139
|
+
- MIT
|
140
|
+
metadata:
|
141
|
+
homepage_uri: https://github.com/brownboxdev/veri
|
142
|
+
source_code_uri: https://github.com/brownboxdev/veri
|
143
|
+
changelog_uri: https://github.com/brownboxdev/veri/blob/main/CHANGELOG.md
|
144
|
+
rubygems_mfa_required: 'true'
|
145
|
+
rdoc_options: []
|
146
|
+
require_paths:
|
147
|
+
- lib
|
148
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
149
|
+
requirements:
|
150
|
+
- - ">="
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: '3.2'
|
153
|
+
- - "<"
|
154
|
+
- !ruby/object:Gem::Version
|
155
|
+
version: '3.5'
|
156
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
157
|
+
requirements:
|
158
|
+
- - ">="
|
159
|
+
- !ruby/object:Gem::Version
|
160
|
+
version: '0'
|
161
|
+
requirements: []
|
162
|
+
rubygems_version: 3.6.7
|
163
|
+
specification_version: 4
|
164
|
+
summary: Minimal cookie-based authentication library for Ruby on Rails
|
165
|
+
test_files: []
|