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 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
@@ -0,0 +1,5 @@
1
+ ## [Unreleased]
2
+
3
+ ## [0.1.0] - 2023-09-22
4
+
5
+ - Initial release
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,13 @@
1
+ module TagAuth
2
+ class Configuration
3
+ attr_accessor :token_validity_duration
4
+
5
+ def initialize
6
+ @token_validity_duration = 5.minutes
7
+ end
8
+
9
+ def configure
10
+ yield self if block_given?
11
+ end
12
+ end
13
+ 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
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TagAuth
4
+ VERSION = '0.1.0'
5
+ 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,4 @@
1
+ # Mock routes
2
+ Rails.application.routes.draw do
3
+ root to: "home#index"
4
+ 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
+
@@ -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
@@ -0,0 +1,9 @@
1
+ class MockModel
2
+ attr_accessor :authentication_token, :authentication_token_valid_to
3
+
4
+ def update(attributes)
5
+ attributes.each do |key, value|
6
+ send("#{key}=", value)
7
+ end
8
+ end
9
+ 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