tag_auth 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.md +5 -0
- data/README.md +75 -0
- data/lib/generators/tag_auth/controller_generator.rb +34 -0
- data/lib/generators/tag_auth/tag_auth_generator.rb +29 -0
- data/lib/generators/tag_auth/tag_authenticable_generator.rb +24 -0
- data/lib/generators/tag_auth/templates/add_auth_tag_and_token_migration.rb.erb +32 -0
- data/lib/generators/tag_auth/templates/tag_auth_controller.rb.erb +74 -0
- data/lib/generators/tag_auth/templates/tag_auth_initializer.rb +13 -0
- data/lib/generators/tag_auth/templates/tag_authenticable.rb.erb +27 -0
- data/lib/tag_auth/configuration.rb +13 -0
- data/lib/tag_auth/encryption_helper.rb +54 -0
- data/lib/tag_auth/tag_auth_token_authentication_handler.rb +13 -0
- data/lib/tag_auth/token_assigner.rb +30 -0
- data/lib/tag_auth/version.rb +5 -0
- data/lib/tag_auth.rb +20 -0
- data/spec/dummy_app_files/routes.rb +4 -0
- data/spec/lib/generators/tag_auth/controller_generator_spec.rb +52 -0
- data/spec/lib/generators/tag_auth/tag_auth_generator_spec.rb +42 -0
- data/spec/lib/generators/tag_auth/tag_authenticable_generator_spec.rb +27 -0
- data/spec/lib/tag_auth/encryption_helper_spec.rb +73 -0
- data/spec/lib/tag_auth/token_assigner_spec.rb +44 -0
- data/spec/spec_helper.rb +17 -0
- data/spec/support/mock_model.rb +9 -0
- metadata +176 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 7c3ca5e97dcc7a1d2471e08f61a4e0d1b74205e9c4ed5372b3a552f78c502838
|
4
|
+
data.tar.gz: f3747cbe921547000af1c96dd98fbd334ae2f2b09f75d3fe74ef7f24d84ad6c5
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 4724e36fbe82a42cbfb4361735475adbd1ad33f9f4c9595f3640c9db140f4bb3ee35a3748ced569e856143639f00604cf4b345b71ecedad134a85e38077406be
|
7
|
+
data.tar.gz: 36e3afacddd03e57d5a672d637a2ec5f030ef4521b48f2b517097869092c2b1c7f6d79ac91576d3a5fdcc86d8756e6950ded002811eec6275d024b7b46b5f2eb
|
data/CHANGELOG.md
ADDED
data/README.md
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
# TagAuth
|
2
|
+
|
3
|
+
TagAuth is a Ruby gem designed to integrate tag-based authentication into Rails applications using Devise. This gem is particularly suitable for web applications deployed on kiosks.
|
4
|
+
|
5
|
+
TagAuth supports two methods of tag-based authentication:
|
6
|
+
|
7
|
+
1. **Direct Input (Ideal for Keyboard-Emulating Readers)**:
|
8
|
+
- For kiosks with readers acting as keyboards, tags can be directly input into a designated field (to be implemented by the user of this gem).
|
9
|
+
- This method utilizes the custom Devise strategy generated by TagAuth.
|
10
|
+
|
11
|
+
|
12
|
+
2. **External Reader Endpoint (For Serial Readers)**:
|
13
|
+
- For kiosks where the reader cannot directly communicate with the web application, TagAuth provides an endpoint in the generated controller.
|
14
|
+
- An external client or script operating the reader can access this endpoint to receive a one-time authentication token linked to a user's tag.
|
15
|
+
- The client can then open a browser with a URL in the format `www.some-web-app.com/tag_auth_tokens?token=received_token` to authenticate the user.
|
16
|
+
- TagAuth includes a sample Powershell script for Windows kiosks, demonstrating how to listen to a reader communicating via a serial port.
|
17
|
+
|
18
|
+
|
19
|
+
### Installation
|
20
|
+
|
21
|
+
Add this line to your application's Gemfile:
|
22
|
+
|
23
|
+
```ruby
|
24
|
+
gem 'tag_auth'
|
25
|
+
```
|
26
|
+
|
27
|
+
And then execute:
|
28
|
+
|
29
|
+
```bash
|
30
|
+
bundle install
|
31
|
+
```
|
32
|
+
|
33
|
+
### Usage
|
34
|
+
|
35
|
+
TagAuth provides generators to set up the necessary components for tag-based authentication. Ensure you have Devise installed and configured in your Rails application before proceeding.
|
36
|
+
|
37
|
+
1. **TagAuth Generator**: Generates a migration to add `auth_tag`, `authentication_token`, and `authentication_token_valid_to` columns to your model, along with necessary indexes.
|
38
|
+
|
39
|
+
```bash
|
40
|
+
rails generate tag_auth:tag_auth [MODEL_NAME]
|
41
|
+
```
|
42
|
+
|
43
|
+
Replace `[MODEL_NAME]` with the name of your model (e.g., `User`) you wish to authenticate with a tag..
|
44
|
+
|
45
|
+
|
46
|
+
2. **TagAuthenticable Generator**: Generates a custom Devise strategy for tag-based authentication. It is used for the direct inputs readers described above.
|
47
|
+
|
48
|
+
```bash
|
49
|
+
rails generate tag_auth:tag_authenticable [MODEL_NAME]
|
50
|
+
```
|
51
|
+
|
52
|
+
Again, replace `[MODEL_NAME]` with the model name.
|
53
|
+
|
54
|
+
|
55
|
+
3. **Controller Generator**: Generates a controller and initializer to handle tag-based authentication. It is the center point handling the authentication. To integrate with external readers, it uses the `simple_token_authentication` gem. It enhances token validation of the gem to include time-based validity checks.
|
56
|
+
|
57
|
+
```bash
|
58
|
+
rails generate tag_auth:controller [MODEL_NAME]
|
59
|
+
```
|
60
|
+
|
61
|
+
Replace `[MODEL_NAME]` with the model name. Make sure you call it on the same model each time.
|
62
|
+
|
63
|
+
### Authentication middleware
|
64
|
+
In the `scripts` directory, there is an example Powershell script that demonstrates how to connect the reader with the web application.
|
65
|
+
In the script, replace the example URI with your own. Alternatively, adjust the reader properties to fit your usage.
|
66
|
+
This script can be inserted into Windows Task that starts running after the user login. To register the task, open the Task scheduler and create a new task.
|
67
|
+
Set up the trigger for the task ("At user log on" is recommended) and add new Action that will run the Powershell script. In the Program/Script option, insert `PowerShell -File "path-to-your-ps-script"`.
|
68
|
+
|
69
|
+
### Contributing
|
70
|
+
|
71
|
+
Contributions to TagAuth are welcome! Please follow the standard procedures for contributing to open-source projects.
|
72
|
+
|
73
|
+
### License
|
74
|
+
|
75
|
+
TagAuth is released under [MIT License](LICENSE.txt).
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'rails/generators/active_record'
|
2
|
+
|
3
|
+
module TagAuth
|
4
|
+
module Generators
|
5
|
+
class ControllerGenerator < Rails::Generators::Base
|
6
|
+
source_root File.expand_path('templates', __dir__)
|
7
|
+
|
8
|
+
desc 'Generates a controller responsible for creating one time sign in tokens' \
|
9
|
+
"based on model's authentication tag"
|
10
|
+
|
11
|
+
argument :scope, required: true,
|
12
|
+
desc: 'The scope in which controller will be created, e.g. users.' \
|
13
|
+
'It should be compatible with the model provided in other generators'
|
14
|
+
|
15
|
+
def create_controller
|
16
|
+
@model = scope.camelize.singularize
|
17
|
+
@instance = scope.downcase.singularize
|
18
|
+
@scope = scope.blank? ? 'Users' : scope.camelize
|
19
|
+
|
20
|
+
template 'tag_auth_controller.rb.erb',
|
21
|
+
'app/controllers/tag_auth_tokens_controller.rb'
|
22
|
+
end
|
23
|
+
|
24
|
+
def add_routes
|
25
|
+
route 'resources :tag_auth_tokens, only: [:index, :create]'
|
26
|
+
end
|
27
|
+
|
28
|
+
def create_initializer
|
29
|
+
template 'tag_auth_initializer.rb',
|
30
|
+
'config/initializers/tag_auth_initializer.rb'
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'rails/generators/active_record'
|
2
|
+
require 'rails/version'
|
3
|
+
|
4
|
+
module TagAuth
|
5
|
+
module Generators
|
6
|
+
class TagAuthGenerator < ActiveRecord::Generators::Base
|
7
|
+
source_root File.expand_path('templates', __dir__)
|
8
|
+
|
9
|
+
desc 'Generates a migration modifying a table with the given NAME which ' \
|
10
|
+
"adds two new columns for storing user's tag value and a one time authentication token."
|
11
|
+
|
12
|
+
def copy_tag_auth_migration
|
13
|
+
migration_template 'add_auth_tag_and_token_migration.rb.erb',
|
14
|
+
"#{migration_path}/add_auth_tag_and_token_to_#{file_path.pluralize}.rb",
|
15
|
+
migration_version: migration_version
|
16
|
+
end
|
17
|
+
|
18
|
+
def migration_path
|
19
|
+
File.join('db', 'migrate')
|
20
|
+
end
|
21
|
+
|
22
|
+
def migration_version
|
23
|
+
return unless Rails::VERSION::MAJOR >= 5
|
24
|
+
|
25
|
+
"[#{Rails::VERSION::MAJOR}.#{Rails::VERSION::MINOR}]"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'rails/generators/active_record'
|
2
|
+
|
3
|
+
module TagAuth
|
4
|
+
module Generators
|
5
|
+
class TagAuthenticableGenerator < Rails::Generators::Base
|
6
|
+
source_root File.expand_path('templates', __dir__)
|
7
|
+
|
8
|
+
desc 'Generates a Devise strategy for authenticating a user by tag'
|
9
|
+
|
10
|
+
argument :scope, required: true,
|
11
|
+
desc: 'The scope in which strategy will be created, e.g. users.' \
|
12
|
+
'It should be compatible with the model provided in other generators'
|
13
|
+
|
14
|
+
def create_controller
|
15
|
+
@model = scope.camelize.singularize
|
16
|
+
@instance = scope.downcase.singularize
|
17
|
+
@scope = scope.blank? ? 'Users' : scope.camelize
|
18
|
+
|
19
|
+
template 'tag_authenticable.rb.erb',
|
20
|
+
'config/initializers/tag_authenticable.rb'
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
class AddAuthTagAndTokenTo<%= table_name.camelize %> < ActiveRecord::Migration<%= migration_version %>
|
2
|
+
disable_ddl_transaction!
|
3
|
+
|
4
|
+
def up
|
5
|
+
unless column_exists?(:<%= table_name %>, :auth_tag)
|
6
|
+
add_column :<%= table_name %>, :auth_tag, :string
|
7
|
+
add_index :<%= table_name %>, :auth_tag, algorithm: :concurrently, unique: true
|
8
|
+
end
|
9
|
+
|
10
|
+
unless column_exists?(:<%= table_name %>, :authentication_token)
|
11
|
+
add_column :<%= table_name %>, :authentication_token, :string, limit: 30
|
12
|
+
add_index :<%= table_name %>, :authentication_token, algorithm: :concurrently, unique: true
|
13
|
+
end
|
14
|
+
|
15
|
+
unless column_exists?(:<%= table_name %>, :authentication_token_valid_to)
|
16
|
+
add_column :<%= table_name %>, :authentication_token_valid_to, :datetime
|
17
|
+
add_index :<%= table_name %>, :authentication_token_valid_to, algorithm: :concurrently, unique: true
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def down
|
22
|
+
safety_assured do
|
23
|
+
remove_column :<%= table_name %>, :auth_tag
|
24
|
+
|
25
|
+
remove_column :<%= table_name %>, :authentication_token
|
26
|
+
remove_index :<%= table_name %>, :authentication_token if index_exists? :<%= table_name %>, :authentication_token
|
27
|
+
|
28
|
+
remove_column :<%= table_name %>, :authentication_token_valid_to
|
29
|
+
remove_index :<%= table_name %>, :authentication_token_valid_to if index_exists? :<%= table_name %>, :authentication_token_valid_to
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
require 'tag_auth/tag_auth_token_authentication_handler'
|
2
|
+
require 'tag_auth/token_assigner'
|
3
|
+
require 'tag_auth/encryption_helper'
|
4
|
+
|
5
|
+
class TagAuthTokensController < ApplicationController
|
6
|
+
# skip_before_action :verify_authenticity_token
|
7
|
+
# skip_before_action :authenticate_<%= @instance %>!
|
8
|
+
|
9
|
+
# before_action :set_encryption_helper
|
10
|
+
# before_action :decrypt_params, only: [:index]
|
11
|
+
# before_action :register_as_tag_auth_attempt, only: [:index]
|
12
|
+
|
13
|
+
# prepend TagAuthTokenAuthenticationHandler
|
14
|
+
# acts_as_token_authentication_handler_for <%= @instance %>, fallback: :none
|
15
|
+
|
16
|
+
# # GET /tag_auth_tokens
|
17
|
+
# def index
|
18
|
+
# # TODO: insert the logic for fallback when user is not authenticated
|
19
|
+
# redirect_to root_path
|
20
|
+
# end
|
21
|
+
|
22
|
+
# # POST /tag_auth_tokens
|
23
|
+
# def create
|
24
|
+
# # TODO: update to conform your needs
|
25
|
+
# if <%= @instance %>_params[:auth_tag].present?
|
26
|
+
# @<%= @instance %> = <%= @model %>.find_by(auth_tag: <%= @instance %>_params[:auth_tag])
|
27
|
+
# end
|
28
|
+
|
29
|
+
# if @<%= @instance %>
|
30
|
+
# token_assigner = TagAuth::TokenAssigner.new(@<%= @instance %>)
|
31
|
+
# token = token_assigner.assign_token
|
32
|
+
|
33
|
+
# response_string = { token: token, email: @<%= @instance %>.email }.to_json
|
34
|
+
|
35
|
+
# encrypted_token = @encryption_helper.encrypt(response_string)
|
36
|
+
# uri_token = URI.encode_www_form_component(encrypted_token)
|
37
|
+
|
38
|
+
# render json: { token: uri_token }, status: :ok
|
39
|
+
# else
|
40
|
+
# render json: { error: 'Not found' }, status: :not_found
|
41
|
+
# end
|
42
|
+
# end
|
43
|
+
|
44
|
+
# private
|
45
|
+
|
46
|
+
# def <%= @instance %>_params
|
47
|
+
# params.require(:<%= @instance %>).permit(:auth_tag)
|
48
|
+
# end
|
49
|
+
|
50
|
+
# def after_successful_token_authentication
|
51
|
+
# renew_authentication_token!
|
52
|
+
# redirect_to after_sign_in_path_for(<%= @model %>)
|
53
|
+
# end
|
54
|
+
|
55
|
+
# def renew_authentication_token!
|
56
|
+
# @<%= @instance %>.update(authentication_token: nil, authentication_token_valid_to: nil)
|
57
|
+
# end
|
58
|
+
|
59
|
+
# def decrypt_params
|
60
|
+
# decrypted_params_string = @encryption_helper.decrypt(params[:token])
|
61
|
+
# decrypted_params = JSON.parse(decrypted_params_string).with_indifferent_access
|
62
|
+
# params[:<%= @instance %>_token] = decrypted_params[:token]
|
63
|
+
# params[:<%= @instance %>_email] = decrypted_params[:email]
|
64
|
+
# end
|
65
|
+
|
66
|
+
# def register_as_tag_auth_attempt
|
67
|
+
# # To register this authentication attempt as tag authentication, e.g. for AuthTrail
|
68
|
+
# request.env['warden'].winning_strategy = Devise::Strategies::TagAuthenticable.new(request.env['warden'].env, :<%= @instance %>)
|
69
|
+
# end
|
70
|
+
|
71
|
+
# def set_encryption_helper
|
72
|
+
# @encryption_helper = TagAuth::EncryptionHelper.new(Rails.application.config.tag_auth_encryption_algorithm, Rails.application.config.tag_auth_encryption_key)
|
73
|
+
# end
|
74
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'openssl'
|
2
|
+
|
3
|
+
Rails.application.config.tag_auth_encryption_algorithm = 'AES-256-CBC'
|
4
|
+
|
5
|
+
# TODO: change for ENV stored key if you are running multiple instances of your app because of load balancing
|
6
|
+
# Generate the key using the OpenSSL::Cipher.new with your chosen algorithm and use Base64.strict_encode64(your_key) to
|
7
|
+
# store it in ENV. Then uncomment the row below and delete the existing initialization of the tag_auth_encryption_key.
|
8
|
+
# Rails.application.config.tag_auth_encryption_key = Base64.strict_decode64(ENV['TAG_AUTH_KEY'])
|
9
|
+
Rails.application.config.tag_auth_encryption_key = OpenSSL::Cipher.new(Rails.application.config.tag_auth_encryption_algorithm).random_key
|
10
|
+
|
11
|
+
TagAuth.configure do |config|
|
12
|
+
config.token_validity_duration = 5.minutes
|
13
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Devise
|
2
|
+
module Strategies
|
3
|
+
class TagAuthenticable < Authenticatable
|
4
|
+
def valid?
|
5
|
+
params[:<%= @instance %>].present? && params[:<%= @instance %>][:auth_tag].present?
|
6
|
+
end
|
7
|
+
|
8
|
+
def authenticate!
|
9
|
+
<%= @instance %> = <%= @model %>.unscoped.find_by(auth_tag: params[:<%= @instance %>][:auth_tag].to_s.downcase.strip)
|
10
|
+
|
11
|
+
if <%= @instance %>.present?
|
12
|
+
remember_me(<%= @instance %>)
|
13
|
+
|
14
|
+
return success!(<%= @instance %>)
|
15
|
+
end
|
16
|
+
|
17
|
+
fail!
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
Warden::Strategies.add(:tag_authenticable, Devise::Strategies::TagAuthenticable)
|
24
|
+
|
25
|
+
Warden::Manager.before_logout do |record, _warden, _options|
|
26
|
+
record.invalidate_session! if record.respond_to?(:invalidate_session!)
|
27
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module TagAuth
|
2
|
+
class EncryptionHelper
|
3
|
+
require 'openssl'
|
4
|
+
require 'base64'
|
5
|
+
|
6
|
+
def initialize(algorithm, key)
|
7
|
+
raise ArgumentError, 'Invalid algorithm' unless valid_algorithm?(algorithm.downcase)
|
8
|
+
raise ArgumentError, 'Invalid key length' unless valid_key?(key, algorithm)
|
9
|
+
|
10
|
+
@algorithm = algorithm.downcase
|
11
|
+
@key = key
|
12
|
+
end
|
13
|
+
|
14
|
+
def encrypt(data)
|
15
|
+
cipher = OpenSSL::Cipher.new(@algorithm)
|
16
|
+
cipher.encrypt
|
17
|
+
cipher.key = @key
|
18
|
+
iv = cipher.random_iv
|
19
|
+
|
20
|
+
encrypted_data = cipher.update(data) + cipher.final
|
21
|
+
|
22
|
+
Base64.encode64(encrypted_data + iv)
|
23
|
+
end
|
24
|
+
|
25
|
+
def decrypt(encrypted_string)
|
26
|
+
decipher = OpenSSL::Cipher.new(@algorithm)
|
27
|
+
decipher.decrypt
|
28
|
+
decipher.key = @key
|
29
|
+
|
30
|
+
decoded_data = Base64.decode64(encrypted_string)
|
31
|
+
|
32
|
+
iv_len = decipher.iv_len
|
33
|
+
data_len = decoded_data.length - iv_len
|
34
|
+
|
35
|
+
encrypted_data = decoded_data[0, data_len]
|
36
|
+
iv = decoded_data[data_len, iv_len]
|
37
|
+
|
38
|
+
decipher.iv = iv
|
39
|
+
decipher.update(encrypted_data) + decipher.final
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def valid_algorithm?(algorithm)
|
45
|
+
OpenSSL::Cipher.ciphers.include?(algorithm)
|
46
|
+
end
|
47
|
+
|
48
|
+
def valid_key?(key, algorithm)
|
49
|
+
cipher = OpenSSL::Cipher.new(algorithm)
|
50
|
+
key_length = key.bytesize * 8 # Convert byte length to bit length
|
51
|
+
key_length == cipher.key_len * 8
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module TagAuthTokenAuthenticationHandler
|
2
|
+
private
|
3
|
+
|
4
|
+
def token_correct?(record, entity, token_comparator)
|
5
|
+
token_not_expired?(record) && super
|
6
|
+
end
|
7
|
+
|
8
|
+
def token_not_expired?(record)
|
9
|
+
return false unless record&.authentication_token_valid_to
|
10
|
+
|
11
|
+
Time.now <= record.authentication_token_valid_to
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'devise'
|
2
|
+
|
3
|
+
module TagAuth
|
4
|
+
class TokenAssigner
|
5
|
+
def initialize(model_instance)
|
6
|
+
@model_instance = model_instance
|
7
|
+
end
|
8
|
+
|
9
|
+
def assign_token
|
10
|
+
token = generate_token
|
11
|
+
@model_instance.update(authentication_token: token,
|
12
|
+
authentication_token_valid_to: DateTime.current + TagAuth.configuration.token_validity_duration)
|
13
|
+
|
14
|
+
token
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def generate_token
|
20
|
+
loop do
|
21
|
+
token = Devise.friendly_token
|
22
|
+
break token if token_suitable?(token)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def token_suitable?(token)
|
27
|
+
@model_instance.class.where(authentication_token: token).empty?
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
data/lib/tag_auth.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'tag_auth/version'
|
4
|
+
require 'tag_auth/encryption_helper'
|
5
|
+
require 'tag_auth/token_assigner'
|
6
|
+
require 'tag_auth/configuration'
|
7
|
+
|
8
|
+
module TagAuth
|
9
|
+
class << self
|
10
|
+
attr_writer :configuration
|
11
|
+
|
12
|
+
def configuration
|
13
|
+
@configuration ||= Configuration.new
|
14
|
+
end
|
15
|
+
|
16
|
+
def configure
|
17
|
+
yield(configuration)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'generator_spec'
|
3
|
+
require 'generators/tag_auth/controller_generator'
|
4
|
+
|
5
|
+
RSpec.describe TagAuth::Generators::ControllerGenerator, type: :generator do
|
6
|
+
destination File.expand_path('../../../tmp', __dir__)
|
7
|
+
|
8
|
+
let(:controller_file) { 'app/controllers/tag_auth_tokens_controller.rb' }
|
9
|
+
let(:initializer_file) { 'config/initializers/tag_auth_initializer.rb' }
|
10
|
+
let(:routes_file) { 'config/routes.rb' }
|
11
|
+
|
12
|
+
before do
|
13
|
+
prepare_destination
|
14
|
+
copy_routes
|
15
|
+
run_generator ['users']
|
16
|
+
end
|
17
|
+
|
18
|
+
after do
|
19
|
+
FileUtils.rm_rf(destination_root)
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'creates a controller file' do
|
23
|
+
expected_file = File.join(destination_root, controller_file)
|
24
|
+
expect(File.exist?(expected_file)).to be true
|
25
|
+
|
26
|
+
content = File.read(expected_file)
|
27
|
+
expect(content).to include('class TagAuthTokensController < ApplicationController')
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'creates an initializer file' do
|
31
|
+
expected_file = File.join(destination_root, initializer_file)
|
32
|
+
expect(File.exist?(expected_file)).to be true
|
33
|
+
|
34
|
+
# Add content checks for initializer if necessary
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'adds routes to the routes file' do
|
38
|
+
expected_file = File.join(destination_root, routes_file)
|
39
|
+
expect(File.exist?(expected_file)).to be true
|
40
|
+
|
41
|
+
content = File.read(expected_file)
|
42
|
+
expect(content).to include('resources :tag_auth_tokens, only: [:index, :create]')
|
43
|
+
end
|
44
|
+
|
45
|
+
def copy_routes
|
46
|
+
routes = File.expand_path('../../../dummy_app_files/routes.rb', __dir__)
|
47
|
+
destination = File.join(destination_root, 'config')
|
48
|
+
|
49
|
+
FileUtils.mkdir_p(destination)
|
50
|
+
FileUtils.cp routes, destination
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'generator_spec'
|
3
|
+
require 'generators/tag_auth/tag_auth_generator'
|
4
|
+
|
5
|
+
RSpec.describe TagAuth::Generators::TagAuthGenerator, type: :generator do
|
6
|
+
destination File.expand_path('../../../tmp', __dir__)
|
7
|
+
|
8
|
+
let(:timestamp_regex) { /\d{14}/ }
|
9
|
+
let(:migration_name) { "add_auth_tag_and_token_to_#{@table_name}" }
|
10
|
+
|
11
|
+
before(:all) do
|
12
|
+
prepare_destination
|
13
|
+
|
14
|
+
model_name = 'User'
|
15
|
+
@table_name = model_name.downcase.pluralize
|
16
|
+
|
17
|
+
run_generator [model_name]
|
18
|
+
end
|
19
|
+
|
20
|
+
after(:all) do
|
21
|
+
FileUtils.rm_rf(destination_root)
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'generates a migration file' do
|
25
|
+
generated_migration_files = Dir.glob(File.join(destination_root, 'db/migrate/*.rb'))
|
26
|
+
|
27
|
+
generated_migration_file = generated_migration_files.find do |file|
|
28
|
+
File.basename(file) =~ /^#{timestamp_regex}_#{migration_name}\.rb$/
|
29
|
+
end
|
30
|
+
|
31
|
+
expect(generated_migration_file).not_to be_nil
|
32
|
+
|
33
|
+
migration_content = File.read(generated_migration_file)
|
34
|
+
expect(migration_content).to include("class #{migration_name.camelize}")
|
35
|
+
|
36
|
+
expect(migration_content).to include('def up')
|
37
|
+
expect(migration_content).to include("add_column :#{@table_name}, :auth_tag, :string")
|
38
|
+
|
39
|
+
expect(migration_content).to include('def down')
|
40
|
+
expect(migration_content).to include("remove_column :#{@table_name}, :auth_tag")
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'generator_spec'
|
3
|
+
require 'generators/tag_auth/tag_authenticable_generator'
|
4
|
+
|
5
|
+
RSpec.describe TagAuth::Generators::TagAuthenticableGenerator, type: :generator do
|
6
|
+
destination File.expand_path('../../../tmp', __dir__)
|
7
|
+
|
8
|
+
let(:initializer_file) { 'config/initializers/tag_authenticable.rb' }
|
9
|
+
|
10
|
+
before do
|
11
|
+
prepare_destination
|
12
|
+
run_generator ['users']
|
13
|
+
end
|
14
|
+
|
15
|
+
after do
|
16
|
+
FileUtils.rm_rf(destination_root)
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'creates a tag authenticable initializer file' do
|
20
|
+
expected_file = File.join(destination_root, initializer_file)
|
21
|
+
expect(File.exist?(expected_file)).to be true
|
22
|
+
|
23
|
+
content = File.read(expected_file)
|
24
|
+
expect(content).to include('module Devise')
|
25
|
+
expect(content).to include('class TagAuthenticable')
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
RSpec.describe TagAuth::EncryptionHelper do
|
4
|
+
let(:algorithm) { 'AES-256-CBC' }
|
5
|
+
let(:key) { OpenSSL::Cipher.new(algorithm).random_key }
|
6
|
+
let(:encryption_helper) { TagAuth::EncryptionHelper.new(algorithm, key) }
|
7
|
+
let(:data) { "Test data" }
|
8
|
+
|
9
|
+
describe '#encrypt' do
|
10
|
+
it 'encrypts the data' do
|
11
|
+
encrypted_data = encryption_helper.encrypt(data)
|
12
|
+
expect(encrypted_data).not_to eq(data)
|
13
|
+
expect(encrypted_data).to be_a(String)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
describe '#decrypt' do
|
18
|
+
it 'decrypts the data back to the original' do
|
19
|
+
encrypted_data = encryption_helper.encrypt(data)
|
20
|
+
decrypted_data = encryption_helper.decrypt(encrypted_data)
|
21
|
+
expect(decrypted_data).to eq(data)
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'raises an error with invalid data' do
|
25
|
+
invalid_data = 'invalid data'
|
26
|
+
expect { encryption_helper.decrypt(invalid_data) }.to raise_error(ArgumentError)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
describe '#encrypt with various data types' do
|
31
|
+
it 'correctly encrypts an empty string' do
|
32
|
+
encrypted_data = encryption_helper.encrypt('')
|
33
|
+
expect(encrypted_data).not_to be_empty
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'correctly encrypts numeric data' do
|
37
|
+
encrypted_data = encryption_helper.encrypt('12345')
|
38
|
+
expect(encrypted_data).to be_a(String)
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'correctly encrypts binary data' do
|
42
|
+
binary_data = "\x00\x01\x02\x03"
|
43
|
+
encrypted_data = encryption_helper.encrypt(binary_data)
|
44
|
+
expect(encrypted_data).to be_a(String)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
describe '#decrypt with incorrect key' do
|
49
|
+
let(:wrong_key) { OpenSSL::Cipher.new(algorithm).random_key }
|
50
|
+
|
51
|
+
it 'raises an error with a wrong key' do
|
52
|
+
encrypted_data = encryption_helper.encrypt(data)
|
53
|
+
wrong_encryption_helper = TagAuth::EncryptionHelper.new(algorithm, wrong_key)
|
54
|
+
expect { wrong_encryption_helper.decrypt(encrypted_data) }.to raise_error(OpenSSL::Cipher::CipherError)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
describe '#initialize with incorrect algorithm' do
|
59
|
+
it 'raises an error with an unknown algorithm' do
|
60
|
+
expect { TagAuth::EncryptionHelper.new('unknown-algorithm', key) }.to raise_error(ArgumentError)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
describe '#encrypt and #decrypt idempotency' do
|
65
|
+
it 'returns to original state after consecutive encrypt and decrypt' do
|
66
|
+
3.times do
|
67
|
+
encrypted_data = encryption_helper.encrypt(data)
|
68
|
+
decrypted_data = encryption_helper.decrypt(encrypted_data)
|
69
|
+
expect(decrypted_data).to eq(data)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'devise'
|
3
|
+
|
4
|
+
RSpec.describe TagAuth::TokenAssigner do
|
5
|
+
let(:mock_model) { MockModel.new }
|
6
|
+
let(:token_assigner) { described_class.new(mock_model) }
|
7
|
+
let(:token) { 'token123' }
|
8
|
+
|
9
|
+
before do
|
10
|
+
allow(Devise).to receive(:friendly_token).and_return(token)
|
11
|
+
|
12
|
+
TagAuth.configure do |config|
|
13
|
+
config.token_validity_duration = 30.minutes
|
14
|
+
end
|
15
|
+
|
16
|
+
allow(MockModel).to receive(:where).and_return([])
|
17
|
+
end
|
18
|
+
|
19
|
+
describe '#assign_token' do
|
20
|
+
it 'assigns a unique token to the model instance' do
|
21
|
+
token_assigner.assign_token
|
22
|
+
expect(mock_model.authentication_token).to eq(token)
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'sets the token validity duration correctly' do
|
26
|
+
token_assigner.assign_token
|
27
|
+
expect(mock_model.authentication_token_valid_to).to be_within(1.second).of(DateTime.current + 30.minutes)
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'ensures token is regenerated until a unique one is found' do
|
31
|
+
existing_model = MockModel.new
|
32
|
+
existing_model.update(authentication_token: token)
|
33
|
+
|
34
|
+
allow(mock_model.class).to receive(:where).and_return([existing_model], [])
|
35
|
+
new_token = 'token456'
|
36
|
+
allow(Devise).to receive(:friendly_token).and_return(token, new_token)
|
37
|
+
|
38
|
+
token_assigner.assign_token
|
39
|
+
expect(mock_model.authentication_token).not_to eq(token)
|
40
|
+
expect(mock_model.authentication_token).to eq(new_token)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'tag_auth'
|
2
|
+
require 'generator_spec'
|
3
|
+
require 'rails/generators'
|
4
|
+
|
5
|
+
Dir['./spec/support/**/*.rb'].each { |f| require f }
|
6
|
+
|
7
|
+
RSpec.configure do |config|
|
8
|
+
# Enable flags like --only-failures and --next-failure
|
9
|
+
config.example_status_persistence_file_path = '.rspec_status'
|
10
|
+
|
11
|
+
# Disable RSpec exposing methods globally on `Module` and `main`
|
12
|
+
config.disable_monkey_patching!
|
13
|
+
|
14
|
+
config.expect_with :rspec do |c|
|
15
|
+
c.syntax = :expect
|
16
|
+
end
|
17
|
+
end
|
metadata
ADDED
@@ -0,0 +1,176 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: tag_auth
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Lucia Lopúchová
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2023-12-13 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: activerecord
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 4.2.0
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 4.2.0
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: devise
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '3.2'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '3.2'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '13.0'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '13.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rubocop
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '1.21'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '1.21'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: simple_token_authentication
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '1.0'
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '1.0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: generator_spec
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: 0.1.0
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: 0.1.0
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: rspec
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '3.12'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '3.12'
|
111
|
+
description: TagAuth integrates tag-based authentication into Rails applications using
|
112
|
+
Devise.
|
113
|
+
email:
|
114
|
+
- lucia.lopuchova@muziker.com
|
115
|
+
executables: []
|
116
|
+
extensions: []
|
117
|
+
extra_rdoc_files: []
|
118
|
+
files:
|
119
|
+
- CHANGELOG.md
|
120
|
+
- README.md
|
121
|
+
- lib/generators/tag_auth/controller_generator.rb
|
122
|
+
- lib/generators/tag_auth/tag_auth_generator.rb
|
123
|
+
- lib/generators/tag_auth/tag_authenticable_generator.rb
|
124
|
+
- lib/generators/tag_auth/templates/add_auth_tag_and_token_migration.rb.erb
|
125
|
+
- lib/generators/tag_auth/templates/tag_auth_controller.rb.erb
|
126
|
+
- lib/generators/tag_auth/templates/tag_auth_initializer.rb
|
127
|
+
- lib/generators/tag_auth/templates/tag_authenticable.rb.erb
|
128
|
+
- lib/tag_auth.rb
|
129
|
+
- lib/tag_auth/configuration.rb
|
130
|
+
- lib/tag_auth/encryption_helper.rb
|
131
|
+
- lib/tag_auth/tag_auth_token_authentication_handler.rb
|
132
|
+
- lib/tag_auth/token_assigner.rb
|
133
|
+
- lib/tag_auth/version.rb
|
134
|
+
- spec/dummy_app_files/routes.rb
|
135
|
+
- spec/lib/generators/tag_auth/controller_generator_spec.rb
|
136
|
+
- spec/lib/generators/tag_auth/tag_auth_generator_spec.rb
|
137
|
+
- spec/lib/generators/tag_auth/tag_authenticable_generator_spec.rb
|
138
|
+
- spec/lib/tag_auth/encryption_helper_spec.rb
|
139
|
+
- spec/lib/tag_auth/token_assigner_spec.rb
|
140
|
+
- spec/spec_helper.rb
|
141
|
+
- spec/support/mock_model.rb
|
142
|
+
homepage: https://github.com/lucialopuchova/tag_auth
|
143
|
+
licenses:
|
144
|
+
- MIT
|
145
|
+
metadata:
|
146
|
+
homepage_uri: https://github.com/lucialopuchova/tag_auth
|
147
|
+
source_code_uri: https://github.com/lucialopuchova/tag_auth
|
148
|
+
changelog_uri: https://github.com/lucialopuchova/tag_auth/blob/main/CHANGELOG.md
|
149
|
+
post_install_message:
|
150
|
+
rdoc_options: []
|
151
|
+
require_paths:
|
152
|
+
- lib
|
153
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
154
|
+
requirements:
|
155
|
+
- - ">="
|
156
|
+
- !ruby/object:Gem::Version
|
157
|
+
version: 2.6.0
|
158
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
159
|
+
requirements:
|
160
|
+
- - ">="
|
161
|
+
- !ruby/object:Gem::Version
|
162
|
+
version: '0'
|
163
|
+
requirements: []
|
164
|
+
rubygems_version: 3.4.1
|
165
|
+
signing_key:
|
166
|
+
specification_version: 4
|
167
|
+
summary: TagAuth provides tag authentication method
|
168
|
+
test_files:
|
169
|
+
- spec/dummy_app_files/routes.rb
|
170
|
+
- spec/lib/generators/tag_auth/controller_generator_spec.rb
|
171
|
+
- spec/lib/generators/tag_auth/tag_auth_generator_spec.rb
|
172
|
+
- spec/lib/generators/tag_auth/tag_authenticable_generator_spec.rb
|
173
|
+
- spec/lib/tag_auth/encryption_helper_spec.rb
|
174
|
+
- spec/lib/tag_auth/token_assigner_spec.rb
|
175
|
+
- spec/spec_helper.rb
|
176
|
+
- spec/support/mock_model.rb
|