devise-twilio-verify 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +7 -0
  2. data/.document +5 -0
  3. data/.github/workflows/build.yml +32 -0
  4. data/.gitignore +45 -0
  5. data/.rspec +2 -0
  6. data/Appraisals +22 -0
  7. data/CHANGELOG.md +15 -0
  8. data/Gemfile +3 -0
  9. data/LICENSE.txt +20 -0
  10. data/README.md +266 -0
  11. data/Rakefile +29 -0
  12. data/app/assets/javascripts/devise_twilio_verify.js +12 -0
  13. data/app/assets/stylesheets/devise_twilio_verify.css +26 -0
  14. data/app/assets/stylesheets/devise_twilio_verify.sass +24 -0
  15. data/app/controllers/devise/devise_twilio_verify_controller.rb +183 -0
  16. data/app/controllers/devise_twilio_verify/passwords_controller.rb +30 -0
  17. data/app/services/twilio_verify_service.rb +66 -0
  18. data/app/views/devise/enable_twilio_verify.html.erb +7 -0
  19. data/app/views/devise/enable_twilio_verify.html.haml +5 -0
  20. data/app/views/devise/verify_twilio_verify.html.erb +16 -0
  21. data/app/views/devise/verify_twilio_verify.html.haml +13 -0
  22. data/app/views/devise/verify_twilio_verify_installation.html.erb +18 -0
  23. data/app/views/devise/verify_twilio_verify_installation.html.haml +16 -0
  24. data/config/locales/en.yml +27 -0
  25. data/config.ru +9 -0
  26. data/devise-twilio-verify.gemspec +49 -0
  27. data/gemfiles/.bundle/config +2 -0
  28. data/gemfiles/rails_5_2.gemfile +14 -0
  29. data/gemfiles/rails_6.gemfile +15 -0
  30. data/lib/devise-twilio-verify/controllers/helpers.rb +87 -0
  31. data/lib/devise-twilio-verify/controllers/view_helpers.rb +50 -0
  32. data/lib/devise-twilio-verify/hooks/twilio_verify_authenticatable.rb +7 -0
  33. data/lib/devise-twilio-verify/mapping.rb +16 -0
  34. data/lib/devise-twilio-verify/models/twilio_verify_authenticatable.rb +21 -0
  35. data/lib/devise-twilio-verify/models/twilio_verify_lockable.rb +43 -0
  36. data/lib/devise-twilio-verify/rails.rb +16 -0
  37. data/lib/devise-twilio-verify/routes.rb +21 -0
  38. data/lib/devise-twilio-verify/version.rb +5 -0
  39. data/lib/devise-twilio-verify.rb +32 -0
  40. data/lib/generators/active_record/devise_twilio_verify_generator.rb +23 -0
  41. data/lib/generators/active_record/templates/migration.rb +18 -0
  42. data/lib/generators/devise_twilio_verify/devise_twilio_verify_generator.rb +30 -0
  43. data/lib/generators/devise_twilio_verify/install_generator.rb +80 -0
  44. metadata +343 -0
@@ -0,0 +1,183 @@
1
+ class Devise::DeviseTwilioVerifyController < DeviseController
2
+ prepend_before_action :find_resource, :only => [
3
+ :request_sms
4
+ ]
5
+ prepend_before_action :find_resource_and_require_password_checked, :only => [
6
+ :GET_verify_twilio_verify, :POST_verify_twilio_verify
7
+ ]
8
+
9
+ prepend_before_action :check_resource_not_twilio_verify_enabled, :only => [
10
+ :GET_verify_twilio_verify_installation, :POST_verify_twilio_verify_installation
11
+ ]
12
+
13
+ prepend_before_action :authenticate_scope!, :only => [
14
+ :GET_enable_twilio_verify, :POST_enable_twilio_verify, :GET_verify_twilio_verify_installation,
15
+ :POST_verify_twilio_verify_installation, :POST_disable_twilio_verify
16
+ ]
17
+
18
+ include Devise::Controllers::Helpers
19
+
20
+ def GET_verify_twilio_verify
21
+ render :verify_twilio_verify
22
+ end
23
+
24
+ # verify 2fa
25
+ def POST_verify_twilio_verify
26
+ if @resource.mobile_phone.blank? || params[:token].blank?
27
+ return handle_invalid_token :verify_twilio_verify, :invalid_token
28
+ end
29
+
30
+ begin
31
+ verification_check = TwilioVerifyService.verify_sms_token(@resource.mobile_phone, params[:token])
32
+ verification_check = verification_check.status == 'approved'
33
+ rescue Twilio::REST::RestError
34
+ verification_check = false
35
+ end
36
+
37
+ # Hack to reproduce authy functionality of being able to verify 2FA via SMS or TOTP
38
+ # not ideal as there could be network delays, but there is currently no alternative
39
+ if !verification_check && @resource.twilio_totp_factor_sid.present?
40
+ verification_check = TwilioVerifyService.verify_totp_token(@resource, params[:token])
41
+ verification_check = verification_check.status == 'approved'
42
+ end
43
+
44
+ if verification_check
45
+ remember_device(@resource.id) if params[:remember_device].to_i == 1
46
+ remember_user
47
+ record_twilio_verify_authentication
48
+ respond_with resource, :location => after_sign_in_path_for(@resource)
49
+ else
50
+ handle_invalid_token :verify_twilio_verify, :invalid_token
51
+ end
52
+ end
53
+
54
+ # enable 2fa
55
+ def POST_enable_twilio_verify
56
+ if resource.update(twilio_verify_enabled: true)
57
+ redirect_to [resource_name, :verify_twilio_verify_installation] and return
58
+ else
59
+ set_flash_message(:error, :not_enabled)
60
+ redirect_to after_twilio_verify_enabled_path_for(resource) and return
61
+ end
62
+ end
63
+
64
+ # Disable 2FA
65
+ def POST_disable_twilio_verify
66
+ resource.assign_attributes(twilio_verify_enabled: false)
67
+ resource.save(:validate => false)
68
+ redirect_to after_twilio_verify_disabled_path_for(resource)
69
+ end
70
+
71
+ def GET_verify_twilio_verify_installation
72
+ if resource_class.twilio_verify_enable_qr_code
73
+ #response = Authy::API.request_qr_code(id: resource.authy_id)
74
+ #@twilio_verify_qr_code = response.qr_code
75
+ end
76
+ render :verify_twilio_verify_installation
77
+ end
78
+
79
+ def POST_verify_twilio_verify_installation
80
+ if @resource.mobile_phone.blank? || params[:token].blank?
81
+ return handle_invalid_token :verify_twilio_verify_installation, :not_enabled
82
+ end
83
+
84
+ verification_check = TwilioVerifyService.verify_sms_token(@resource.mobile_phone, params[:token])
85
+
86
+ self.resource.twilio_verify_enabled = token.ok?
87
+
88
+ if token.ok? && self.resource.save
89
+ remember_device(@resource.id) if params[:remember_device].to_i == 1
90
+ record_twilio_verify_authentication
91
+ set_flash_message(:notice, :enabled)
92
+ redirect_to after_twilio_verify_verified_path_for(resource)
93
+ else
94
+ if resource_class.twilio_verify_enable_qr_code
95
+ #response = Authy::API.request_qr_code(id: resource.authy_id)
96
+ #@twilio_verify_qr_code = response.qr_code
97
+ end
98
+ handle_invalid_token :verify_twilio_verify_installation, :not_enabled
99
+ end
100
+ end
101
+
102
+ def request_sms
103
+ if @resource.blank? || @resource.mobile_phone.blank?
104
+ render :json => {:sent => false, :message => "User couldn't be found."}
105
+ return
106
+ end
107
+
108
+ verification = TwilioVerifyService.send_sms_token(@resource.mobile_phone)
109
+ success = verification.status == 'pending'
110
+
111
+ render json: {
112
+ sent: success,
113
+ message: success ? 'Token was sent.' : 'Token was not sent, please try again.'
114
+ }
115
+ end
116
+
117
+ private
118
+
119
+ def authenticate_scope!
120
+ send(:"authenticate_#{resource_name}!", :force => true)
121
+ self.resource = send("current_#{resource_name}")
122
+ @resource = resource
123
+ end
124
+
125
+ def find_resource
126
+ @resource = send("current_#{resource_name}")
127
+
128
+ if @resource.nil?
129
+ @resource = resource_class.find_by_id(session["#{resource_name}_id"])
130
+ end
131
+ end
132
+
133
+ def find_resource_and_require_password_checked
134
+ find_resource
135
+
136
+ if @resource.nil? || session[:"#{resource_name}_password_checked"].to_s != "true"
137
+ redirect_to invalid_resource_path
138
+ end
139
+ end
140
+
141
+ def check_resource_not_twilio_verify_enabled
142
+ if resource.twilio_verify_enabled
143
+ redirect_to after_twilio_verify_verified_path_for(resource)
144
+ end
145
+ end
146
+
147
+ protected
148
+
149
+ def after_twilio_verify_enabled_path_for(resource)
150
+ root_path
151
+ end
152
+
153
+ def after_twilio_verify_verified_path_for(resource)
154
+ after_twilio_verify_enabled_path_for(resource)
155
+ end
156
+
157
+ def after_twilio_verify_disabled_path_for(resource)
158
+ root_path
159
+ end
160
+
161
+ def invalid_resource_path
162
+ root_path
163
+ end
164
+
165
+ def handle_invalid_token(view, error_message)
166
+ if @resource.respond_to?(:invalid_twilio_verify_attempt!) && @resource.invalid_twilio_verify_attempt!
167
+ after_account_is_locked
168
+ else
169
+ set_flash_message(:error, error_message)
170
+ render view
171
+ end
172
+ end
173
+
174
+ def after_account_is_locked
175
+ sign_out_and_redirect @resource
176
+ end
177
+
178
+ def remember_user
179
+ if session.delete("#{resource_name}_remember_me") == true && @resource.respond_to?(:remember_me=)
180
+ @resource.remember_me = true
181
+ end
182
+ end
183
+ end
@@ -0,0 +1,30 @@
1
+ class DeviseTwilioVerify::PasswordsController < Devise::PasswordsController
2
+ ##
3
+ # In the passwords controller a user can update their password using a
4
+ # recovery token. If `Devise.sign_in_after_reset_password` is `true` then the
5
+ # user is signed in immediately with the
6
+ # `Devise::Controllers::SignInOut#sign_in` method. However, if the user has
7
+ # 2FA enabled they should enter their second factor before they are signed in.
8
+ #
9
+ # This method overrides `Devise::Controllers::SignInOut#sign_in` but only
10
+ # within the `Devise::PasswordsController`. If the user needs to verify 2FA
11
+ # then `sign_in` returns `true`. This short circuits the method before it can
12
+ # call `warden.set_user` and log the user in.
13
+ #
14
+ # The user is redirected to `after_resetting_password_path_for(user)` at which
15
+ # point, since the user is not logged in, redirects again to sign in.
16
+ #
17
+ # This doesn't retain the expected behaviour of
18
+ # `Devise.sign_in_after_reset_password`, but is forgivable because this
19
+ # shouldn't be an avenue to bypass 2FA.
20
+ def sign_in(resource_or_scope, *args)
21
+ resource = args.last || resource_or_scope
22
+
23
+ if resource.respond_to?(:with_twilio_verify_authentication?) && resource.with_twilio_verify_authentication?(request)
24
+ # Do nothing. Because we need verify the 2FA
25
+ true
26
+ else
27
+ super
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,66 @@
1
+ class TwilioVerifyService
2
+ attr_reader :twilio_client, :twilio_account_sid, :twilio_auth_token, :twilio_verify_service_sid
3
+
4
+ def self.send_sms_token(phone_number)
5
+ new.twilio_verify_service.verifications.create(to: e164_format(phone_number), channel: 'sms')
6
+ end
7
+
8
+ def self.verify_sms_token(phone_number, token)
9
+ new.twilio_verify_service.verification_checks.create(to: e164_format(phone_number), code: token)
10
+ end
11
+
12
+ def self.verify_totp_token(user, token)
13
+ new.twilio_verify_service_v2
14
+ .entities([Rails.env, user.id].join('-'))
15
+ .challenges
16
+ .create(auth_payload: token, factor_sid: user.twilio_totp_factor_sid)
17
+ end
18
+
19
+ def self.setup_totp_service(user)
20
+ new_factor = new.twilio_verify_service_v2
21
+ .entities([Rails.env, user.id].join('-'))
22
+ .new_factors
23
+ .create(friendly_name: user.to_s, factor_type: 'totp')
24
+
25
+ user.update(twilio_totp_factor_sid: new_factor.sid)
26
+
27
+ # Now in your app, take the new_factor.binding.uri
28
+ # and generate a qr code to present to the user to scan to add the app to their authenticator app
29
+ new_factor
30
+ end
31
+
32
+ def self.register_totp_service(user, token)
33
+ # After user adds the app to their authenticator app, register the user by having them confirm a token
34
+ # if this returns factor.status == 'verified', the user has been properly setup
35
+ new.twilio_verify_service_v2
36
+ .entities([Rails.env, user.id].join('-'))
37
+ .factors(user.twilio_totp_factor_sid)
38
+ .update(auth_payload: token)
39
+ end
40
+
41
+ def self.e164_format(phone_number)
42
+ "+1#{phone_number.gsub(/[^0-9a-z\\s]/i, '')}"
43
+ end
44
+
45
+ def initialize
46
+ @twilio_account_sid = Rails.application.credentials.twilio_account_sid || ENV['TWILIO_ACCOUNT_SID']
47
+ @twilio_auth_token = Rails.application.credentials.twilio_auth_token || ENV['TWILIO_AUTH_TOKEN']
48
+ @twilio_verify_service_sid = Rails.application.credentials.twilio_verify_service_sid || ENV['TWILIO_VERIFY_SERVICE_SID']
49
+
50
+ raise 'Missing Twilio credentials' unless @twilio_account_sid && @twilio_auth_token && @twilio_verify_service_sid
51
+
52
+ @twilio_client = Twilio::REST::Client.new(@twilio_account_sid, @twilio_auth_token)
53
+ end
54
+
55
+ def twilio_verify_service
56
+ twilio_client.verify.services(twilio_verify_service_sid)
57
+ end
58
+
59
+ def twilio_verify_service_v2
60
+ twilio_client.verify.v2.services(twilio_verify_service_sid)
61
+ end
62
+
63
+ def e164_format(phone_number)
64
+ self.class.e164_format(phone_number)
65
+ end
66
+ end
@@ -0,0 +1,7 @@
1
+ <h2><%= I18n.t('twilio_verify_register_title', scope: 'devise') %></h2>
2
+
3
+ <%= enable_twilio_verify_form do %>
4
+ <%= text_field_tag :country_code, '', :autocomplete => :off, :placeholder => I18n.t('devise.country'), :id => "twilio-verify-countries"%>
5
+ <%= text_field_tag :cellphone, '', :autocomplete => :off, :placeholder => I18n.t('devise.cellphone'), :id => "twilio-verify-cellphone"%>
6
+ <p><%= submit_tag I18n.t('enable_twilio_verify', scope: 'devise') %></p>
7
+ <% end %>
@@ -0,0 +1,5 @@
1
+ %h2= I18n.t('twilio_verify_register_title', scope: 'devise')
2
+ = enable_twilio_verify_form do
3
+ = text_field_tag :country_code, '', :autocomplete => :off, :placeholder => I18n.t('devise.country'), :id => "twilio-verify-countries"
4
+ = text_field_tag :cellphone, '', :autocomplete => :off, :placeholder => I18n.t('devise.cellphone'), :id => "twilio-verify-cellphone"
5
+ %p= submit_tag I18n.t('enable_twilio_verify', scope: 'devise')
@@ -0,0 +1,16 @@
1
+ <h2>
2
+ <%= I18n.t('submit_token_title', scope: 'devise') %>
3
+ </h2>
4
+
5
+ <%= verify_twilio_verify_form do %>
6
+ <legend><%= I18n.t('submit_token_title', scope: 'devise') %></legend>
7
+ <%= label_tag 'twilio-verify-token' %>
8
+ <%= text_field_tag :token, "", :autocomplete => "one-time-code", :inputmode => "numeric", :pattern => "[0-9]*", :id => 'twilio-verify-token' %>
9
+ <label>
10
+ <%= check_box_tag :remember_device %>
11
+ <span><%= I18n.t('remember_device', scope: 'devise') %></span>
12
+ </label>
13
+
14
+ <%= twilio_verify_request_sms_link %>
15
+ <%= submit_tag I18n.t('submit_token', scope: 'devise'), :class => 'btn' %>
16
+ <% end %>
@@ -0,0 +1,13 @@
1
+ %h2= I18n.t('twilio_verify_register_title', scope: 'devise')
2
+
3
+ = verify_twilio_verify_form do
4
+ %legend= I18n.t('submit_token_title', scope: 'devise')
5
+ = hidden_field_tag :"#{resource_name}_id", @resource.id
6
+ = label_tag 'twilio-verify-token'
7
+ = text_field_tag :token, "", :autocomplete => "one-time-code", :inputmode => "numeric", :pattern => "[0-9]*", :id => 'twilio-verify-token'
8
+ %label
9
+ = check_box_tag :remember_device
10
+ %span= I18n.t('remember_device', scope: 'devise')
11
+
12
+ = twilio_verify_request_sms_link
13
+ = submit_tag I18n.t('submit_token', scope: 'devise'), :class => 'btn'
@@ -0,0 +1,18 @@
1
+ <h2><%= I18n.t('twilio_verify_verify_installation_title', scope: 'devise') %></h2>
2
+
3
+ <% if @twilio_verify_qr_code %>
4
+ <%= image_tag @twilio_verify_qr_code, :size => '256x256', :alt => I18n.t('twilio_verify_qr_code_alt', scope: 'devise') %>
5
+ <p><%= I18n.t('twilio_verify_qr_code_instructions', scope: 'devise') %></p>
6
+ <% end %>
7
+
8
+ <%= verify_twilio_verify_installation_form do %>
9
+ <legend><%= I18n.t('submit_token_title', scope: 'devise') %></legend>
10
+ <%= label_tag :token %>
11
+ <%= text_field_tag :token, "", :autocomplete => "one-time-code", :inputmode => "numeric", :pattern => "[0-9]*", :id => 'twilio-verify-token' %>
12
+ <label>
13
+ <%= check_box_tag :remember_device %>
14
+ <span><%= I18n.t('remember_device', scope: 'devise') %></span>
15
+ </label>
16
+ <%= twilio_verify_request_sms_link %>
17
+ <%= submit_tag I18n.t('enable_my_account', scope: 'devise'), :class => 'btn' %>
18
+ <% end %>
@@ -0,0 +1,16 @@
1
+ %h2= I18n.t('twilio_verify_verify_installation_title', scope: 'devise')
2
+
3
+ - if @twilio_verify_qr_code
4
+ = image_tag @twilio_verify_qr_code, :size => '256x256', :alt => I18n.t('twilio_verify_qr_code_alt', scope: 'devise')
5
+ %p= I18n.t('twilio_verify_qr_code_instructions', scope: 'devise')
6
+
7
+ = verify_twilio_verify_installation_form do
8
+ %legend= I18n.t('submit_token_title', scope: 'devise')
9
+ = label_tag :token
10
+ = text_field_tag :token, "", :autocomplete => "one-time-code", :inputmode => "numeric", :pattern => "[0-9]*", :id => 'twilio-verify-token'
11
+ %label
12
+ = check_box_tag :remember_device
13
+ %span= I18n.t('remember_device', scope: 'devise')
14
+ = twilio_verify_request_sms_link
15
+ = submit_tag I18n.t('enable_my_account', scope: 'devise'), :class => 'btn'
16
+
@@ -0,0 +1,27 @@
1
+ en:
2
+ devise:
3
+ submit_token: 'Check Token'
4
+ submit_token_title: 'Please enter your auth token:'
5
+ twilio_verify_register_title: 'Enable Two factor authentication'
6
+ enable_twilio_verify: 'Enable'
7
+ cellphone: 'Enter your cellphone'
8
+ country: 'Enter your country'
9
+ request_sms: 'Request SMS'
10
+ remember_device: 'Remember Device'
11
+ request_to_login: 'Request to Login'
12
+
13
+ twilio_verify_verify_installation_title: 'Verify your account'
14
+ enable_my_account: 'Enable my account'
15
+
16
+ twilio_verify_qr_code_alt: 'QR code for scanning with your authenticator app.'
17
+ twilio_verify_qr_code_instructions: 'Scan this QR code with your authenticator application and enter the code below.'
18
+
19
+ devise_twilio_verify:
20
+ user:
21
+ enabled: 'Two factor authentication was enabled'
22
+ not_enabled: 'Something went wrong while enabling two factor authentication'
23
+ disabled: 'Two factor authentication was disabled'
24
+ not_disabled: 'Something went wrong while disabling two factor authentication'
25
+ signed_in: 'Signed in with two factor authentication successfully.'
26
+ already_enabled: 'Two factor authentication is already enabled.'
27
+ invalid_token: 'The entered token is invalid'
data/config.ru ADDED
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rubygems"
4
+ require "bundler"
5
+
6
+ Bundler.require :default, :development
7
+
8
+ Combustion.initialize! :all
9
+ run Combustion::Application
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ lib = File.expand_path("../lib", __FILE__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+ require "devise-twilio-verify/version"
6
+
7
+ Gem::Specification.new do |spec|
8
+ spec.name = "devise-twilio-verify"
9
+ spec.version = DeviseTwilioVerify::VERSION
10
+ spec.authors = ["Jay Wolff"]
11
+
12
+ spec.summary = %q{Twilio Verify plugin to add two factor authentication to Devise.}
13
+ spec.description = %q{Twilio Verify plugin to add two factor authentication to Devise. This gem is meant to make migrating from authy to twilio verify as simple as possible, please see the README for details.}
14
+ spec.homepage = "https://github.com/jayywolff/twilio-verify-devise"
15
+ spec.license = "MIT"
16
+
17
+ spec.metadata = {
18
+ "bug_tracker_uri" => "https://github.com/jayywolff/twilio-verify-devise/issues",
19
+ "change_log_uri" => "https://github.com/jayywolff/twilio-verify-devise/blob/master/CHANGELOG.md",
20
+ "documentation_uri" => "https://github.com/jayywolff/twilio-verify-devise",
21
+ "homepage_uri" => "https://github.com/jayywolff/twilio-verify-devise",
22
+ "source_code_uri" => "https://github.com/jayywolff/twilio-verify-devise"
23
+ }
24
+
25
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
26
+ f.match(%r{^(test|spec|features)/})
27
+ end
28
+ spec.require_paths = ["lib"]
29
+
30
+ spec.add_dependency "devise", ">= 4.0.0"
31
+ spec.add_dependency "twilio-ruby", "~> 5.74"
32
+
33
+ spec.add_development_dependency "appraisal", "~> 2.2"
34
+ spec.add_development_dependency "bundler", ">= 1.16"
35
+ spec.add_development_dependency "rake"
36
+ spec.add_development_dependency "combustion", "~> 1.1"
37
+ spec.add_development_dependency "rspec", "~> 3.0"
38
+ spec.add_development_dependency "rspec-rails"
39
+ spec.add_development_dependency "rails-controller-testing", "~> 1.0"
40
+ spec.add_development_dependency "yard", "~> 0.9.11"
41
+ spec.add_development_dependency "rdoc", "~> 4.3.0"
42
+ spec.add_development_dependency "simplecov", "~> 0.17.1"
43
+ spec.add_development_dependency "webmock", "~> 3.11.0"
44
+ spec.add_development_dependency "rails", ">= 5"
45
+ spec.add_development_dependency "sqlite3"
46
+ spec.add_development_dependency "generator_spec"
47
+ spec.add_development_dependency "database_cleaner", "~> 1.7"
48
+ spec.add_development_dependency "factory_bot_rails", "~> 5.1.1"
49
+ end
@@ -0,0 +1,2 @@
1
+ ---
2
+ BUNDLE_RETRY: "1"
@@ -0,0 +1,14 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "rails", "~> 5.2.0"
6
+ gem "sqlite3", "~> 1.3.13"
7
+
8
+ group :development, :test do
9
+ gem "factory_girl_rails", require: false
10
+ gem "rspec-rails", "~>4.0.0.beta3", require: false
11
+ gem "database_cleaner", require: false
12
+ end
13
+
14
+ gemspec path: "../"
@@ -0,0 +1,15 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "rails", "~> 6.0.0"
6
+ gem "sqlite3", "~> 1.4"
7
+ gem "net-smtp"
8
+
9
+ group :development, :test do
10
+ gem "factory_girl_rails", require: false
11
+ gem "rspec-rails", "~>4.0.0.beta3", require: false
12
+ gem "database_cleaner", require: false
13
+ end
14
+
15
+ gemspec path: "../"
@@ -0,0 +1,87 @@
1
+ module DeviseTwilioVerify
2
+ module Controllers
3
+ module Helpers
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ before_action :check_request_and_redirect_to_verify_token, :if => :is_signing_in?
8
+ end
9
+
10
+ private
11
+
12
+ def remember_device(id)
13
+ cookies.signed[:remember_device] = {
14
+ :value => {expires: Time.now.to_i, id: id}.to_json,
15
+ :secure => !(Rails.env.test? || Rails.env.development?),
16
+ :httponly => !(Rails.env.test? || Rails.env.development?),
17
+ :expires => resource_class.twilio_verify_remember_device.from_now
18
+ }
19
+ end
20
+
21
+ def forget_device
22
+ cookies.delete :remember_device
23
+ end
24
+
25
+ def require_token?
26
+ id = warden.session(resource_name)[:id]
27
+ cookie = cookies.signed[:remember_device]
28
+ return true if cookie.blank?
29
+
30
+ # require token for old cookies which just have expiration time and no id
31
+ return true if cookie.to_s =~ %r{\A\d+\z}
32
+
33
+ cookie = JSON.parse(cookie) rescue ""
34
+ return cookie.blank? || (Time.now.to_i - cookie['expires'].to_i) > \
35
+ resource_class.twilio_verify_remember_device.to_i || cookie['id'] != id
36
+ end
37
+
38
+ def is_devise_sessions_controller?
39
+ self.class == Devise::SessionsController || self.class.ancestors.include?(Devise::SessionsController)
40
+ end
41
+
42
+ def is_signing_in?
43
+ if devise_controller? &&
44
+ is_devise_sessions_controller? &&
45
+ self.action_name == "create"
46
+ return true
47
+ end
48
+
49
+ return false
50
+ end
51
+
52
+ def check_request_and_redirect_to_verify_token
53
+ if signed_in?(resource_name) &&
54
+ warden.session(resource_name)[:with_twilio_verify_authentication] &&
55
+ require_token?
56
+ # login with 2fa
57
+ id = warden.session(resource_name)[:id]
58
+
59
+ remember_me = (params.fetch(resource_name, {})[:remember_me].to_s == "1")
60
+ return_to = session["#{resource_name}_return_to"]
61
+ sign_out
62
+
63
+ session["#{resource_name}_id"] = id
64
+ # this is safe to put in the session because the cookie is signed
65
+ session["#{resource_name}_password_checked"] = true
66
+ session["#{resource_name}_remember_me"] = remember_me
67
+ session["#{resource_name}_return_to"] = return_to if return_to
68
+
69
+ redirect_to verify_twilio_verify_path_for(resource_name)
70
+ return
71
+ end
72
+ end
73
+
74
+ def verify_twilio_verify_path_for(resource_or_scope = nil)
75
+ scope = Devise::Mapping.find_scope!(resource_or_scope)
76
+ send(:"#{scope}_verify_twilio_verify_path")
77
+ end
78
+
79
+ def record_twilio_verify_authentication
80
+ @resource.update_attribute(:last_sign_in_with_twilio_verify, DateTime.now)
81
+ session["#{resource_name}_twilio_verify_token_checked"] = true
82
+ sign_in(resource_name, @resource)
83
+ set_flash_message(:notice, :signed_in) if is_navigational_format?
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,50 @@
1
+ module DeviseTwilioVerify
2
+ module Views
3
+ module Helpers
4
+ def twilio_verify_request_sms_link(opts = {})
5
+ title = opts.delete(:title) do
6
+ I18n.t('request_sms', scope: 'devise')
7
+ end
8
+ opts = {
9
+ :id => "twilio-verify-request-sms-link",
10
+ :method => :post,
11
+ :remote => true
12
+ }.merge(opts)
13
+
14
+ link_to(
15
+ title,
16
+ url_for([resource_name.to_sym, :request_sms]),
17
+ opts
18
+ )
19
+ end
20
+
21
+ def verify_twilio_verify_form(opts = {}, &block)
22
+ opts = default_opts.merge(:id => 'devise_twilio_verify').merge(opts)
23
+ form_tag([resource_name.to_sym, :verify_twilio_verify], opts) do
24
+ buffer = hidden_field_tag(:"#{resource_name}_id", @resource.id)
25
+ buffer << capture(&block)
26
+ end
27
+ end
28
+
29
+ def enable_twilio_verify_form(opts = {}, &block)
30
+ opts = default_opts.merge(opts)
31
+ form_tag([resource_name.to_sym, :enable_twilio_verify], opts) do
32
+ capture(&block)
33
+ end
34
+ end
35
+
36
+ def verify_twilio_verify_installation_form(opts = {}, &block)
37
+ opts = default_opts.merge(opts)
38
+ form_tag([resource_name.to_sym, :verify_twilio_verify_installation], opts) do
39
+ capture(&block)
40
+ end
41
+ end
42
+
43
+ private
44
+
45
+ def default_opts
46
+ { :class => 'twilio-verify-form', :method => :post }
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,7 @@
1
+ Warden::Manager.after_authentication do |user, auth, options|
2
+ if user.respond_to?(:with_twilio_verify_authentication?)
3
+ if auth.session(options[:scope])[:with_twilio_verify_authentication] = user.with_twilio_verify_authentication?(auth.request)
4
+ auth.session(options[:scope])[:id] = user.id
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,16 @@
1
+ module DeviseTwilioVerify
2
+ module Mapping
3
+ private
4
+ def default_controllers(options)
5
+ options[:controllers] ||= {}
6
+ options[:controllers][:passwords] ||= "devise_twilio_verify/passwords"
7
+ super
8
+ end
9
+
10
+ def default_path_names(options)
11
+ options[:path_names] ||= {}
12
+ options[:path_names][:request_sms] ||= 'request-sms'
13
+ super
14
+ end
15
+ end
16
+ end