tag_auth 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 +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
|