two_factor_cookies 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 9713b369c8e8f380b513419c48aecf9ece9452b3
4
+ data.tar.gz: 6884bce7e414cafaaf41db49bd7219a890aef13e
5
+ SHA512:
6
+ metadata.gz: 7421c34e7c0b19cc41f20fa52141aa843e750689ebe1d0df4ed8f62d1ecd0c4f7e22797e9659724a5673916ec0b3d41dc4ab9ee3310ed786210d938ddffd0ff8
7
+ data.tar.gz: b70483ca83b32182b4db5e3a64f2bcf450c5262297548158d800d96cd77ce8af0e2612060054da7a7a5e4cf678b261d3a087bea1ab82fc415629dcb76e91fd32
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2019 Nicolai Bach Woller
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,35 @@
1
+ # TwoFactorCookies
2
+ Short description and motivation.
3
+
4
+ ## Usage
5
+ How to use my plugin.
6
+
7
+ ## Installation
8
+ Add this line to your application's Gemfile:
9
+
10
+ ```ruby
11
+ gem 'two_factor_cookies', git: 'git@bitbucket.org:cs2software/two_factor_cookies.git', branch: 'master'
12
+ ```
13
+
14
+ And then execute:
15
+ ```bash
16
+ $ bundle
17
+ ```
18
+
19
+ The gem is a rails engine, so it needs to be mounted to a location in `routes.rb`:
20
+ ```ruby
21
+ # two factor cookies
22
+ mount TwoFactorCookies::Engine, at: '/two_factor_cookies'
23
+ ```
24
+
25
+ Todo: Document initializeer
26
+
27
+ In your ApplicationController you must include TwoFactorAuthentication
28
+
29
+ The gem includes a template for submitting one time passwords. To override it, a partial named 'show' must be placed under `two_factor_cookies/two_factor_authentication`
30
+
31
+ ## Contributing
32
+ Contribution directions go here.
33
+
34
+ ## License
35
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,32 @@
1
+ begin
2
+ require 'bundler/setup'
3
+ rescue LoadError
4
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
5
+ end
6
+
7
+ require 'rdoc/task'
8
+
9
+ RDoc::Task.new(:rdoc) do |rdoc|
10
+ rdoc.rdoc_dir = 'rdoc'
11
+ rdoc.title = 'TwoFactorCookies'
12
+ rdoc.options << '--line-numbers'
13
+ rdoc.rdoc_files.include('README.md')
14
+ rdoc.rdoc_files.include('lib/**/*.rb')
15
+ end
16
+
17
+ APP_RAKEFILE = File.expand_path("test/dummy/Rakefile", __dir__)
18
+ load 'rails/tasks/engine.rake'
19
+
20
+ load 'rails/tasks/statistics.rake'
21
+
22
+ require 'bundler/gem_tasks'
23
+
24
+ require 'rake/testtask'
25
+
26
+ Rake::TestTask.new(:test) do |t|
27
+ t.libs << 'test'
28
+ t.pattern = 'test/**/*_test.rb'
29
+ t.verbose = false
30
+ end
31
+
32
+ task default: :test
@@ -0,0 +1 @@
1
+ //= link_directory ../stylesheets/two_factor_cookies .css
@@ -0,0 +1,15 @@
1
+ /*
2
+ * This is a manifest file that'll be compiled into application.css, which will include all the files
3
+ * listed below.
4
+ *
5
+ * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
6
+ * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path.
7
+ *
8
+ * You're free to add application-wide styles to this file and they'll appear at the bottom of the
9
+ * compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS
10
+ * files in this directory. Styles in this file should be added after the last require_* statement.
11
+ * It is generally better to create a new file per style scope.
12
+ *
13
+ *= require_tree .
14
+ *= require_self
15
+ */
@@ -0,0 +1,37 @@
1
+ module TwoFactorAuthenticate
2
+ extend ActiveSupport::Concern
3
+
4
+ included do
5
+ before_action :two_factor_authenticate!
6
+ end
7
+
8
+ private
9
+
10
+ def two_factor_authenticate!
11
+ return unless current_user
12
+ return unless current_user.enabled_two_factor? && current_user.confirmed_phone_number?
13
+ return if two_factor_approved?
14
+
15
+ redirect_to two_factor_cookies.show_two_factor_authentication_path
16
+ end
17
+
18
+ def two_factor_approved?
19
+ return false if cookies.encrypted[:mfa].nil?
20
+ return false if parsed_cookies[:user_name] != current_user.public_send(TwoFactorCookies.configuration.username_field_name)
21
+ return false unless additional_authentication_values_approved?
22
+
23
+ parsed_cookies[:approved]
24
+ end
25
+
26
+ def additional_authentication_values_approved?
27
+ TwoFactorCookies.configuration.additional_authentication_values.each_pair do |key, value|
28
+ return false if parsed_cookies[key] != eval(value)
29
+ end
30
+
31
+ true
32
+ end
33
+
34
+ def parsed_cookies
35
+ JSON.parse(cookies.encrypted[:mfa]).symbolize_keys
36
+ end
37
+ end
@@ -0,0 +1,60 @@
1
+ TwoFactorCookies.const_set('ToggleTwoFactorController',
2
+ Class.new('TwoFactorCookies::TwoFactorAuthenticationController'.constantize) do
3
+ def update
4
+ if TwoFactorCookies::OneTimePasswordGenerator.verify_code(
5
+ confirm_phone_number_params[:one_time_password],
6
+ parsed_mfa_cookie[:seed]
7
+ )
8
+ current_user.confirm_phone_number!
9
+
10
+ set_authenticated_cookie
11
+ flash[:notice] = I18n.t('two_factor_cookies.confirm_phone_number.flash.confirmed')
12
+ else
13
+ flash[:alert] = I18n.t('two_factor_cookies.confirm_phone_number.flash.wrong_one_time_password')
14
+ end
15
+
16
+ redirect_to eval(TwoFactorCookies.configuration.engine_name).public_send(
17
+ TwoFactorCookies.configuration.confirm_phone_number_success_route,
18
+ current_user.to_param)
19
+ end
20
+
21
+ def toggle_two_factor
22
+ if toggle_two_factor_params[:enabled_two_factor] == '1'
23
+ current_user.enable_two_factor!
24
+ current_user.update(update_params) if TwoFactorCookies.configuration.update_params
25
+ set_authenticated_cookie
26
+ else
27
+ current_user.disable_two_factor!
28
+ current_user.disaffirm_phone_number!
29
+ end
30
+
31
+ redirect_to eval(TwoFactorCookies.configuration.engine_name).public_send(
32
+ TwoFactorCookies.configuration.toggle_two_factor_success_route, current_user.to_param
33
+ )
34
+ end
35
+
36
+ def resend_code
37
+ send_otp
38
+
39
+ head :ok
40
+ end
41
+
42
+ private
43
+
44
+ def confirm_phone_number_params
45
+ params.require(:confirm_phone_number).permit(:one_time_password)
46
+ end
47
+
48
+ def toggle_two_factor_params
49
+ params.require(TwoFactorCookies.configuration.user_model_name).permit(
50
+ :enabled_two_factor
51
+ )
52
+ end
53
+
54
+ def update_params
55
+ params.require(TwoFactorCookies.configuration.user_model_name).permit(
56
+ TwoFactorCookies.configuration.update_params
57
+ )
58
+ end
59
+ end
60
+ )
@@ -0,0 +1,96 @@
1
+ TwoFactorCookies.const_set('TwoFactorAuthenticationController',
2
+ Class.new(TwoFactorCookies.configuration.two_factor_authentication_controller_parent.constantize) do
3
+ layout TwoFactorCookies.configuration.layout_path if TwoFactorCookies.configuration.layout_path
4
+
5
+ skip_before_action :two_factor_authenticate!
6
+
7
+ def show
8
+ send_otp unless otp_already_sent?
9
+
10
+ render :show, locals: { user: current_user }
11
+ end
12
+
13
+ def update
14
+ if otp_verified?
15
+ set_authenticated_cookie
16
+ redirect_to public_send(TwoFactorCookies.configuration.two_factor_authentication_success_route)
17
+ else
18
+ flash[:alert] = I18n.t('two_factor_cookies.errors.wrong_one_time_password')
19
+ redirect_to two_factor_cookies.show_two_factor_authentication_path
20
+ end
21
+ end
22
+
23
+ def resend_code
24
+ send_otp
25
+
26
+ redirect_to two_factor_cookies.show_two_factor_authentication_path
27
+ end
28
+
29
+ private
30
+
31
+ def otp_already_sent?
32
+ cookies.encrypted[:mfa].present? && parsed_mfa_cookie[:seed].present?
33
+ end
34
+
35
+ def otp_verified?
36
+ TwoFactorCookies::OneTimePasswordGenerator.verify_code(
37
+ two_factor_authentication_params[:one_time_password],
38
+ parsed_mfa_cookie[:seed]
39
+ )
40
+ end
41
+
42
+ def two_factor_authentication_params
43
+ params.require(:two_factor_authentication).permit(:one_time_password)
44
+ end
45
+
46
+ def send_otp
47
+ generated = TwoFactorCookies::OneTimePasswordGenerator.generate_code
48
+ TwoFactorCookies::TextMessage.send(
49
+ code: generated[:code],
50
+ user: current_user
51
+ )
52
+
53
+ set_seed_cookie(generated[:seed])
54
+ end
55
+
56
+ def set_authenticated_cookie
57
+ cookies.delete(:mfa)
58
+ cookies.encrypted[:mfa] = {
59
+ value: JSON.generate(
60
+ standard_values.merge(additional_authentication_values)
61
+ ),
62
+ expires: TwoFactorCookies.configuration.two_factor_authentication_expiry
63
+ }
64
+ end
65
+
66
+ def set_seed_cookie(seed)
67
+ cookies.delete(:mfa)
68
+ cookies.encrypted[:mfa] = {
69
+ value: JSON.generate(seed: seed, user_name: current_user.public_send(TwoFactorCookies.configuration.username_field_name)),
70
+ expires: TwoFactorCookies.configuration.otp_expiry
71
+ }
72
+ end
73
+
74
+ def parsed_mfa_cookie
75
+ JSON.parse(cookies.encrypted[:mfa]).symbolize_keys
76
+ end
77
+
78
+ def standard_values
79
+ {
80
+ approved: true,
81
+ user_name: current_user.public_send(TwoFactorCookies.configuration.username_field_name)
82
+ }
83
+ end
84
+
85
+ def additional_authentication_values
86
+ return {} unless TwoFactorCookies.configuration.additional_authentication_values
87
+
88
+ # TODO: Is there a better way, than to use eval?
89
+ additional_values = {}
90
+ TwoFactorCookies.configuration.additional_authentication_values.each_pair do |key, value|
91
+ additional_values.store(key, eval(value))
92
+ end
93
+ additional_values
94
+ end
95
+ end
96
+ )
@@ -0,0 +1,4 @@
1
+ module TwoFactorCookies
2
+ module ApplicationHelper
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module TwoFactorCookies
2
+ class ApplicationJob < ActiveJob::Base
3
+ end
4
+ end
@@ -0,0 +1,6 @@
1
+ module TwoFactorCookies
2
+ class ApplicationMailer < ActionMailer::Base
3
+ default from: 'from@example.com'
4
+ layout 'mailer'
5
+ end
6
+ end
@@ -0,0 +1,21 @@
1
+ module TwoFactorCookies
2
+ class OneTimePasswordGenerator
3
+ class << self
4
+ def generate_code
5
+ seed = Random.rand(10_000)
6
+
7
+ { seed: seed, code: generator.at(seed) }
8
+ end
9
+
10
+ def verify_code(code, seed)
11
+ !!generator.verify(code, seed)
12
+ end
13
+
14
+ private
15
+
16
+ def generator
17
+ @generator ||= ROTP::HOTP.new(TwoFactorCookies.configuration.otp_generation_secret_key)
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,26 @@
1
+ module TwoFactorCookies
2
+ class TextMessage
3
+ class << self
4
+ def send(user:, code:)
5
+ if Rails.env.development?
6
+ File.open(Rails.root.join('tmp', 'otp.txt'), 'w') { |file| file.write code }
7
+ else
8
+ client.messages.create(
9
+ body: I18n.t('two_factor_cookies.text_message.one_time_password', code: code),
10
+ from: TwoFactorCookies.configuration.twilio_phone_number,
11
+ to: user.public_send(TwoFactorCookies.configuration.phone_number_field_name)
12
+ )
13
+ end
14
+ end
15
+
16
+ private
17
+
18
+ def client
19
+ @client = Twilio::REST::Client.new(
20
+ TwoFactorCookies.configuration.twilio_account_sid,
21
+ TwoFactorCookies.configuration.twilio_auth_token
22
+ )
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,15 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>Two factor cookies</title>
5
+ <%= csrf_meta_tags %>
6
+ <%= csp_meta_tag %>
7
+
8
+ <%= stylesheet_link_tag "two_factor_cookies/application", media: "all" %>
9
+ </head>
10
+ <body>
11
+
12
+ <%= yield %>
13
+
14
+ </body>
15
+ </html>
@@ -0,0 +1,25 @@
1
+ <div class="row">
2
+ <div class="col-md-4">
3
+ </div>
4
+ <div class="col-md-4">
5
+ <h2><%= t('two_factor_cookies.show.header') %></h2>
6
+ <p><%= t('two_factor_cookies.show.help_text') %></p>
7
+ <%= form_with url: two_factor_cookies.update_two_factor_authentication_path, method: :patch do |f| %>
8
+ <div class="input-group">
9
+ <span class="input-group-addon">*</span>
10
+ <%= f.text_field 'two_factor_authentication[one_time_password]', placeholder: t('two_factor_cookies.show.placeholder'), autofocus: true, class: 'form-control' %>
11
+ </div>
12
+ <br/>
13
+ <div class="row">
14
+ <div class="col-md-6">
15
+ <%= link_to t('two_factor_cookies.show.resend_code'), two_factor_cookies.resend_code_two_factor_authentication_path, class: 'btn btn-default btn-block' %>
16
+ </div>
17
+ <div class="col-md-6">
18
+ <%= f.submit t('two_factor_cookies.show.submit_code'), class: 'btn btn-primary btn-block' %>
19
+ </div>
20
+ </div>
21
+ <% end %>
22
+ </div>
23
+ <div class="col-md-4">
24
+ </div>
25
+ </div>
@@ -0,0 +1,16 @@
1
+ en:
2
+ two_factor_cookies:
3
+ show:
4
+ placeholder: One Time Password
5
+ header: One Time Password
6
+ help_text: Type the one time password, you have received in a text
7
+ resend_code: Resend code
8
+ submit_code: Verify code
9
+ text_message:
10
+ one_time_password: Your one time password is %{code}
11
+ errors:
12
+ wrong_one_time_password: Wrong code - try again
13
+ confirm_phone_number:
14
+ flash:
15
+ confirmed: Phone number confirmed
16
+ wrong_one_time_password: Wrong code - try again
data/config/routes.rb ADDED
@@ -0,0 +1,11 @@
1
+ TwoFactorCookies::Engine.routes.draw do
2
+ # two_factor_authentication
3
+ get 'two_factor_authentication', to: 'two_factor_authentication#show', as: :show_two_factor_authentication
4
+ patch 'two_factor_authentication', to: 'two_factor_authentication#update', as: :update_two_factor_authentication
5
+ get 'two_factor_authentication/resend_code', to: 'two_factor_authentication#resend_code', as: :resend_code_two_factor_authentication
6
+
7
+ # confirm_phone_number
8
+ patch 'toggle_two_factor/:user_id', to: 'toggle_two_factor#update', as: :confirm_phone_number
9
+ patch 'toggle_two_factor/:user_id/toggle', to: 'toggle_two_factor#toggle_two_factor', as: :toggle_two_factor
10
+ get 'toggle_two_factor/:user_id/resend_code', to: 'toggle_two_factor#resend_code', as: :resend_code_confirm_phone_number
11
+ end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :two_factor_cookies do
3
+ # # Task goes here
4
+ # end
@@ -0,0 +1,38 @@
1
+ module TwoFactorCookies
2
+ class Configuration
3
+ attr_accessor :otp_generation_secret_key, :two_factor_authentication_success_route, :confirm_phone_number_success_route,
4
+ :toggle_two_factor_success_route, :two_factor_authentication_expiry, :otp_expiry, :twilio_account_sid,
5
+ :twilio_phone_number, :twilio_auth_token, :phone_number_field_name, :user_model_name, :user_model_namespace, :username_field_name,
6
+ :two_factor_authentication_controller_parent, :skip_before_action, :layout_path, :additional_authentication_values,
7
+ :update_params, :engine_name
8
+
9
+ def initialize
10
+ @otp_generation_secret_key = nil
11
+ @two_factor_authentication_expiry = 30.days.from_now
12
+ @otp_expiry = 30.minutes.from_now
13
+
14
+ @twilio_account_sid = nil
15
+ @twilio_phone_number = nil
16
+ @twilio_auth_token = nil
17
+
18
+ @user_model_namespace = nil
19
+ @user_model_name = :user
20
+ @phone_number_field_name = :phone_number
21
+ @username_field_name = :username
22
+
23
+ @two_factor_authentication_success_route = nil
24
+ @toggle_two_factor_success_route = nil
25
+ @confirm_phone_number_success_route = nil
26
+ @layout_path = nil
27
+ @two_factor_authentication_controller_parent = '::ApplicationController'
28
+
29
+ @additional_authentication_values = {}
30
+
31
+ @update_params = nil
32
+
33
+
34
+
35
+ @engine_name = 'main_app'
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,5 @@
1
+ module TwoFactorCookies
2
+ class Engine < ::Rails::Engine
3
+ isolate_namespace TwoFactorCookies
4
+ end
5
+ end
@@ -0,0 +1,3 @@
1
+ module TwoFactorCookies
2
+ VERSION = '0.1.0'
3
+ end
@@ -0,0 +1,22 @@
1
+ require 'two_factor_cookies/configuration'
2
+ require 'two_factor_cookies/engine'
3
+ require 'rotp'
4
+ require 'twilio-ruby'
5
+
6
+ module TwoFactorCookies
7
+ class << self
8
+ attr_accessor :configuration
9
+
10
+ def configuration
11
+ @configuration ||= Configuration.new
12
+ end
13
+
14
+ def reset
15
+ @configuration = Configuration.new
16
+ end
17
+
18
+ def configure
19
+ yield(configuration)
20
+ end
21
+ end
22
+ end
metadata ADDED
@@ -0,0 +1,167 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: two_factor_cookies
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Nicolai Bach Woller
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2019-08-01 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rails
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '5.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '5.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rotp
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '='
32
+ - !ruby/object:Gem::Version
33
+ version: 5.1.0
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '='
39
+ - !ruby/object:Gem::Version
40
+ version: 5.1.0
41
+ - !ruby/object:Gem::Dependency
42
+ name: twilio-ruby
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 5.25.1
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: 5.25.1
55
+ - !ruby/object:Gem::Dependency
56
+ name: m
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: minitest-spec-rails
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: mocha
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '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'
97
+ - !ruby/object:Gem::Dependency
98
+ name: sqlite3
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ description: The aim is to be configurable and work with as many kinds of authentication
112
+ as possible.
113
+ email:
114
+ - woller@traels.it
115
+ executables: []
116
+ extensions: []
117
+ extra_rdoc_files: []
118
+ files:
119
+ - MIT-LICENSE
120
+ - README.md
121
+ - Rakefile
122
+ - app/assets/config/two_factor_cookies_manifest.js
123
+ - app/assets/stylesheets/two_factor_cookies/application.css
124
+ - app/controllers/concerns/two_factor_authenticate.rb
125
+ - app/controllers/two_factor_cookies/toggle_two_factor_controller.rb
126
+ - app/controllers/two_factor_cookies/two_factor_authentication_controller.rb
127
+ - app/helpers/two_factor_cookies/application_helper.rb
128
+ - app/jobs/two_factor_cookies/application_job.rb
129
+ - app/mailers/two_factor_cookies/application_mailer.rb
130
+ - app/models/two_factor_cookies/one_time_password_generator.rb
131
+ - app/models/two_factor_cookies/text_message.rb
132
+ - app/views/layouts/two_factor_cookies/application.html.erb
133
+ - app/views/two_factor_cookies/two_factor_authentication/show.html.erb
134
+ - config/locales/en.yml
135
+ - config/routes.rb
136
+ - lib/tasks/two_factor_cookies_tasks.rake
137
+ - lib/two_factor_cookies.rb
138
+ - lib/two_factor_cookies/configuration.rb
139
+ - lib/two_factor_cookies/engine.rb
140
+ - lib/two_factor_cookies/version.rb
141
+ homepage: https://traels.it
142
+ licenses:
143
+ - MIT
144
+ metadata:
145
+ allowed_push_host: https://rubygems.org
146
+ post_install_message:
147
+ rdoc_options: []
148
+ require_paths:
149
+ - lib
150
+ required_ruby_version: !ruby/object:Gem::Requirement
151
+ requirements:
152
+ - - ">="
153
+ - !ruby/object:Gem::Version
154
+ version: '0'
155
+ required_rubygems_version: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - ">="
158
+ - !ruby/object:Gem::Version
159
+ version: '0'
160
+ requirements: []
161
+ rubyforge_project:
162
+ rubygems_version: 2.5.2
163
+ signing_key:
164
+ specification_version: 4
165
+ summary: Simple two factor logon - with Twilio SMS for code delivery
166
+ test_files: []
167
+ has_rdoc: