devise_twilio_two_factor 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.github/workflows/main.yml +27 -0
- data/.gitignore +11 -0
- data/.rspec +3 -0
- data/CHANGELOG.md +5 -0
- data/Gemfile +10 -0
- data/LICENSE.txt +21 -0
- data/README.md +59 -0
- data/Rakefile +8 -0
- data/app/controllers/devise/twilio_two_factor_controller.rb +83 -0
- data/app/views/devise/twilio_two_factor/show.html.erb +10 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/config/locales/de.yml +8 -0
- data/config/locales/en.yml +8 -0
- data/config/locales/es.yml +8 -0
- data/config/locales/fr.yml +8 -0
- data/config/locales/ru.yml +8 -0
- data/devise_twilio_two_factor.gemspec +48 -0
- data/lib/devise_twilio_two_factor/callbacks/twilio_two_factor_authenticatable.rb +17 -0
- data/lib/devise_twilio_two_factor/callbacks.rb +1 -0
- data/lib/devise_twilio_two_factor/controllers/helpers.rb +54 -0
- data/lib/devise_twilio_two_factor/controllers.rb +1 -0
- data/lib/devise_twilio_two_factor/models/twilio_two_factor_authenticatable.rb +44 -0
- data/lib/devise_twilio_two_factor/models.rb +1 -0
- data/lib/devise_twilio_two_factor/rails.rb +7 -0
- data/lib/devise_twilio_two_factor/routes.rb +11 -0
- data/lib/devise_twilio_two_factor/services/twilio_two_factor_client.rb +41 -0
- data/lib/devise_twilio_two_factor/services.rb +1 -0
- data/lib/devise_twilio_two_factor/version.rb +5 -0
- data/lib/devise_twilio_two_factor.rb +46 -0
- data/lib/generators/devise_twilio_two_factor/devise_twilio_two_factor_generator.rb +46 -0
- data/sig/devise_twilio_two_factor.rbs +4 -0
- data/spec/models/twilio_two_factor_authenticatable_spec.rb +89 -0
- data/spec/services/twilio_two_factor_auth_client_spec.rb +78 -0
- data/spec/spec_helper.rb +17 -0
- metadata +180 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 7b1ecc275a6e5ae2ca9c0cad512fc307cdf6bc46e374f231266f700a1a09fe71
|
4
|
+
data.tar.gz: 774d26123c2f820cab0babc3a049ebe78df4572cb386a9357dd1a8d6b14f62b6
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 722b23fa02287db9b63ee3bfef7cb3326d04d866befe62af8456a1c3d6df2ff8f20469de862e2fd93e76f5064896ff60d6f968c642f9d98ded90359fb29658e3
|
7
|
+
data.tar.gz: 44e29e36e736438b7d648085698d0e11fa710edc052be66ccf7b0e4e052783ed45a0008888904951addb41ea14e345c0f2f8f01cc27bae118d98de6cf950e966
|
@@ -0,0 +1,27 @@
|
|
1
|
+
name: Ruby
|
2
|
+
|
3
|
+
on:
|
4
|
+
push:
|
5
|
+
branches:
|
6
|
+
- main
|
7
|
+
|
8
|
+
pull_request:
|
9
|
+
|
10
|
+
jobs:
|
11
|
+
build:
|
12
|
+
runs-on: ubuntu-latest
|
13
|
+
name: Ruby ${{ matrix.ruby }}
|
14
|
+
strategy:
|
15
|
+
matrix:
|
16
|
+
ruby:
|
17
|
+
- '3.1.2'
|
18
|
+
|
19
|
+
steps:
|
20
|
+
- uses: actions/checkout@v3
|
21
|
+
- name: Set up Ruby
|
22
|
+
uses: ruby/setup-ruby@v1
|
23
|
+
with:
|
24
|
+
ruby-version: ${{ matrix.ruby }}
|
25
|
+
bundler-cache: true
|
26
|
+
- name: Run the default task
|
27
|
+
run: bundle exec rake
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/CHANGELOG.md
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2023 TODO: Write your name
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
# Devise Twilio Two Factor
|
2
|
+
|
3
|
+
The "Devise Twilio Two-Factor Authentication Gem" is an integration solution that brings together the robust security features of the Devise gem and the reliable functionality of Twilio APIs. With this solution, incorporating two-factor authentication for user authentication in your application has never been easier. By leveraging the power of Devise and Twilio together, your application can offer enhanced security measures to your users, reducing the likelihood of unauthorized access and potential data breaches.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add devise_twilio_two_factor to your Gemfile with:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
# Gemfile
|
11
|
+
|
12
|
+
gem 'devise_twilio_two_factor', '~> 0.1.0'
|
13
|
+
```
|
14
|
+
|
15
|
+
## Usage
|
16
|
+
|
17
|
+
To integrate the Devise Twilio Two-Factor Authentication Gem, you'll need:
|
18
|
+
|
19
|
+
- The [Devise gem](https://github.com/heartcombo/devise), set up according to their instructions.
|
20
|
+
- A [Twilio](https://github.com/heartcombo/devise) account with the account_sid and auth token from the console.
|
21
|
+
- A Verify Service with its ID.
|
22
|
+
|
23
|
+
Add them to your devise.rb
|
24
|
+
```ruby
|
25
|
+
config.twilio_account_sid = "ACXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
|
26
|
+
config.twilio_auth_token = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
|
27
|
+
config.twilio_verify_service_sid = "VAXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
|
28
|
+
```
|
29
|
+
|
30
|
+
From here, the generator should get you the rest of the way (you can skip the rest of the section):
|
31
|
+
```bash
|
32
|
+
./bin/rails generate devise_two_factor MODEL
|
33
|
+
```
|
34
|
+
|
35
|
+
To add two-factor authentication to a model, simply add the Devise Twilio Two-Factor Authenticatable module and specify two options:
|
36
|
+
|
37
|
+
1) The otp_destination option should represent the field containing the phone number or email address where the OTP code will be sent.
|
38
|
+
2) The communication_type option should be set to either "sms" or "email" depending on the desired mode of communication.
|
39
|
+
|
40
|
+
Ex. for a user with a phone field -> user.phone = '+18001234567'
|
41
|
+
```ruby
|
42
|
+
devise :twilio_two_factor_authenticatable, otp_destination: 'phone', communication_type: "sms"
|
43
|
+
```
|
44
|
+
|
45
|
+
lastly, just create a migration to add `otp_required_for_login:boolean` to the table of the resource you wish to add 2fa to.
|
46
|
+
|
47
|
+
## Development
|
48
|
+
|
49
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
50
|
+
|
51
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
52
|
+
|
53
|
+
## Contributing
|
54
|
+
|
55
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/devise_twilio_two_factor.
|
56
|
+
|
57
|
+
## License
|
58
|
+
|
59
|
+
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,83 @@
|
|
1
|
+
require 'devise/version'
|
2
|
+
|
3
|
+
class Devise::TwilioTwoFactorController < DeviseController
|
4
|
+
prepend_before_action :authenticate_scope!
|
5
|
+
before_action :prepare_and_validate, :handle_two_factor_authentication
|
6
|
+
|
7
|
+
def authenticate_scope!
|
8
|
+
self.resource = send("current_#{resource_name}")
|
9
|
+
end
|
10
|
+
|
11
|
+
def show
|
12
|
+
end
|
13
|
+
|
14
|
+
def update
|
15
|
+
render :show and return if params[:code].nil?
|
16
|
+
|
17
|
+
if resource.verify_otp_code(params[:code])
|
18
|
+
after_two_factor_success_for(resource)
|
19
|
+
else
|
20
|
+
after_two_factor_fail_for(resource)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def resend_code
|
25
|
+
resource.send_otp_code
|
26
|
+
redirect_to send("#{resource_name}_twilio_two_factor_path"), notice: I18n.t('devise.twilio_two_factor.code_has_been_sent')
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def after_two_factor_success_for(resource)
|
32
|
+
set_remember_two_factor_cookie(resource)
|
33
|
+
|
34
|
+
warden.session(resource_name)[TwoFactorAuthentication::NEED_AUTHENTICATION] = false
|
35
|
+
|
36
|
+
if respond_to?(:bypass_sign_in)
|
37
|
+
bypass_sign_in(resource, scope: resource_name)
|
38
|
+
else
|
39
|
+
sign_in(resource_name, resource, bypass: true)
|
40
|
+
end
|
41
|
+
set_flash_message :notice, :success
|
42
|
+
resource.update_attribute(:failed_attempts, 0)
|
43
|
+
|
44
|
+
redirect_to after_two_factor_success_path_for(resource)
|
45
|
+
end
|
46
|
+
|
47
|
+
def after_two_factor_fail_for(resource)
|
48
|
+
resource.failed_attempts += 1
|
49
|
+
resource.save
|
50
|
+
set_flash_message :alert, :attempt_failed, now: true
|
51
|
+
|
52
|
+
if resource.login_attempts_exceeded?
|
53
|
+
sign_out(resource)
|
54
|
+
redirect_to :root
|
55
|
+
else
|
56
|
+
render :show
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def set_remember_two_factor_cookie(resource)
|
61
|
+
expires_seconds = resource.class.remember_otp_session_for_seconds
|
62
|
+
|
63
|
+
if expires_seconds && expires_seconds > 0
|
64
|
+
cookies.signed[TwoFactorAuthentication::REMEMBER_TFA_COOKIE_NAME] = {
|
65
|
+
value: "#{resource.class}-#{resource.public_send(Devise.second_factor_resource_id)}",
|
66
|
+
expires: expires_seconds.seconds.from_now
|
67
|
+
}
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def after_two_factor_success_path_for(resource)
|
72
|
+
stored_location_for(resource_name) || :root
|
73
|
+
end
|
74
|
+
|
75
|
+
def prepare_and_validate
|
76
|
+
redirect_to :root and return if resource.nil?
|
77
|
+
|
78
|
+
if resource.login_attempts_exceeded?
|
79
|
+
sign_out(resource)
|
80
|
+
redirect_to :root
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
<h2>Enter OTP Code</h2>
|
2
|
+
|
3
|
+
<p><%= flash[:notice] %></p>
|
4
|
+
|
5
|
+
<%= form_tag([resource_name, :two_factor_authentication], :method => :put) do %>
|
6
|
+
<%= text_field_tag :code, '', autofocus: true %>
|
7
|
+
<%= submit_tag "Submit" %>
|
8
|
+
<% end %>
|
9
|
+
<%= link_to "Resend Code", send("resend_code_#{resource_name}_two_factor_authentication_path"), action: :get %>
|
10
|
+
<%= link_to "Sign out", send("destroy_#{resource_name}_session_path"), :method => :delete %>
|
data/bin/console
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "bundler/setup"
|
5
|
+
require "devise_twilio_two_factor"
|
6
|
+
|
7
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
8
|
+
# with your gem easier. You can also use a different console, if you like.
|
9
|
+
|
10
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
11
|
+
# require "pry"
|
12
|
+
# Pry.start
|
13
|
+
|
14
|
+
require "irb"
|
15
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
@@ -0,0 +1,8 @@
|
|
1
|
+
de:
|
2
|
+
devise:
|
3
|
+
twilio_two_factor:
|
4
|
+
success: "Ihre Zwei-Faktor-Authentifizierung war erfolgreich."
|
5
|
+
attempt_failed: "Authentifizierungsversuch fehlgeschlagen."
|
6
|
+
max_login_attempts_reached: "Ihr Zugang wurde ganz verweigert, da Sie Ihr Versuchslimit erreicht haben."
|
7
|
+
contact_administrator: "Kontaktieren Sie bitte einen Ihrer Administratoren."
|
8
|
+
code_has_been_sent: "Ihr Einmal-Passwort wurde verschickt."
|
@@ -0,0 +1,8 @@
|
|
1
|
+
en:
|
2
|
+
devise:
|
3
|
+
twilio_two_factor:
|
4
|
+
success: "Two factor authentication successful."
|
5
|
+
attempt_failed: "Attempt failed."
|
6
|
+
max_login_attempts_reached: "Access completely denied as you have reached your attempts limit"
|
7
|
+
contact_administrator: "Please contact your system administrator."
|
8
|
+
code_has_been_sent: "Your authentication code has been sent."
|
@@ -0,0 +1,8 @@
|
|
1
|
+
es:
|
2
|
+
devise:
|
3
|
+
twilio_two_factor:
|
4
|
+
success: "Autenticación multi-factor realizada exitosamente."
|
5
|
+
attempt_failed: "La autenticación ha fallado."
|
6
|
+
max_login_attempts_reached: "Has llegado al límite de intentos fallidos, acceso denegado."
|
7
|
+
contact_administrator: "Contacte a su administrador de sistema."
|
8
|
+
code_has_been_sent: "El código de autenticación ha sido enviado."
|
@@ -0,0 +1,8 @@
|
|
1
|
+
fr:
|
2
|
+
devise:
|
3
|
+
twilio_two_factor:
|
4
|
+
success: "Validation en deux étapes effectuée avec succès."
|
5
|
+
attempt_failed: "La connexion a échoué."
|
6
|
+
max_login_attempts_reached: "Limite de tentatives atteinte, accès refusé."
|
7
|
+
contact_administrator: "Merci de contacter votre administrateur système."
|
8
|
+
code_has_been_sent: "Votre code de validation envoyé."
|
@@ -0,0 +1,8 @@
|
|
1
|
+
ru:
|
2
|
+
devise:
|
3
|
+
twilio_two_factor:
|
4
|
+
success: "Двухфакторная авторизация успешно пройдена."
|
5
|
+
attempt_failed: "Неверный код."
|
6
|
+
max_login_attempts_reached: "Доступ заблокирован. Превышено число попыток авторизации"
|
7
|
+
contact_administrator: "Пожалуйста, свяжитесь с системным администратором."
|
8
|
+
code_has_been_sent: "Ваш персональный код был отправлен."
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'lib/devise_twilio_two_factor/version'
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = 'devise_twilio_two_factor'
|
7
|
+
spec.version = DeviseTwilioTwoFactor::VERSION
|
8
|
+
spec.authors = ['John Livingston']
|
9
|
+
spec.email = 'jclivingston316@gmail.com'
|
10
|
+
|
11
|
+
|
12
|
+
spec.platform = Gem::Platform::RUBY
|
13
|
+
spec.licenses = ['MIT']
|
14
|
+
spec.summary = 'Devise Twilio Verify Two Factor Authentication'
|
15
|
+
spec.homepage = 'https://github.com/jliv316/devise-twilio-two-factor'
|
16
|
+
spec.description = 'Devise Twilio Verify Two Factor Authentication'
|
17
|
+
spec.required_ruby_version = '>= 2.6.0'
|
18
|
+
|
19
|
+
# spec.metadata['allowed_push_host'] = "TODO: Set to your gem server 'https://example.com'"
|
20
|
+
|
21
|
+
spec.metadata['homepage_uri'] = spec.homepage
|
22
|
+
spec.metadata['source_code_uri'] = 'https://github.com/Jliv316/devise_twilio_two_factor'
|
23
|
+
# spec.metadata['changelog_uri'] = "TODO: Put your gem's CHANGELOG.md URL here."
|
24
|
+
|
25
|
+
# Specify which files should be added to the gem when it is released.
|
26
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
27
|
+
spec.files = Dir.chdir(__dir__) do
|
28
|
+
`git ls-files -z`.split("\x0").reject do |f|
|
29
|
+
(f == __FILE__) || f.match(%r{\A(?:(?:bin|test|spec|features)/|\.(?:git|travis|circleci)|appveyor)})
|
30
|
+
end
|
31
|
+
end
|
32
|
+
spec.bindir = 'exe'
|
33
|
+
spec.files = `git ls-files`.split("\n")
|
34
|
+
spec.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
35
|
+
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
36
|
+
spec.require_paths = ['lib']
|
37
|
+
|
38
|
+
|
39
|
+
|
40
|
+
spec.add_runtime_dependency 'railties', '>= 4.1.0'
|
41
|
+
spec.add_runtime_dependency 'devise', '~> 4.8.1'
|
42
|
+
spec.add_runtime_dependency 'twilio-ruby', '~> 5.66.0'
|
43
|
+
|
44
|
+
spec.add_development_dependency 'bundler', '> 1.0'
|
45
|
+
spec.add_development_dependency 'rspec', '> 3'
|
46
|
+
spec.add_development_dependency 'activemodel'
|
47
|
+
spec.add_development_dependency 'pry'
|
48
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
Warden::Manager.after_authentication do |user, auth, options|
|
2
|
+
if auth.env["action_dispatch.cookies"]
|
3
|
+
expected_cookie_value = "#{user.class}-#{user.public_send(Devise.second_factor_resource_id)}"
|
4
|
+
actual_cookie_value = auth.env["action_dispatch.cookies"].signed[TwoFactorAuthentication::REMEMBER_TFA_COOKIE_NAME]
|
5
|
+
bypass_by_cookie = actual_cookie_value == expected_cookie_value
|
6
|
+
end
|
7
|
+
|
8
|
+
if user.respond_to?(:otp_required_for_login) && !bypass_by_cookie
|
9
|
+
if auth.session(options[:scope])[TwoFactorAuthentication::NEED_AUTHENTICATION] = user.need_two_factor_authentication?(auth.request)
|
10
|
+
user.send_otp_code if user.send_new_otp_after_login?
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
Warden::Manager.before_logout do |user, auth, _options|
|
16
|
+
auth.cookies.delete TwoFactorAuthentication::REMEMBER_TFA_COOKIE_NAME if Devise.delete_cookie_on_logout
|
17
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require_relative "callbacks/twilio_two_factor_authenticatable"
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module DeviseTwilioTwoFactor
|
2
|
+
module Controllers
|
3
|
+
module Helpers
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
before_action :handle_two_factor_authentication
|
8
|
+
end
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
def handle_two_factor_authentication
|
13
|
+
unless devise_controller?
|
14
|
+
Devise.mappings.keys.flatten.any? do |scope|
|
15
|
+
if signed_in?(scope) && warden.session(scope)[TwoFactorAuthentication::NEED_AUTHENTICATION]
|
16
|
+
handle_failed_second_factor(scope)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def handle_failed_second_factor(scope)
|
23
|
+
if request.format.present?
|
24
|
+
if request.format.html?
|
25
|
+
session["#{scope}_return_to"] = request.original_fullpath if request.get?
|
26
|
+
redirect_to twilio_two_factor_path_for(scope)
|
27
|
+
elsif request.format.json?
|
28
|
+
session["#{scope}_return_to"] = root_path(format: :html)
|
29
|
+
render json: { redirect_to: twilio_two_factor_path_for(scope) }, status: :unauthorized
|
30
|
+
end
|
31
|
+
else
|
32
|
+
head :unauthorized
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def twilio_two_factor_path_for(resource_or_scope = nil)
|
37
|
+
scope = Devise::Mapping.find_scope!(resource_or_scope)
|
38
|
+
change_path = "#{scope}_twilio_two_factor_path"
|
39
|
+
send(change_path)
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
module Devise
|
47
|
+
module Controllers
|
48
|
+
module Helpers
|
49
|
+
def is_fully_authenticated?
|
50
|
+
!session["warden.user.user.session"].try(:[], TwoFactorAuthentication::NEED_AUTHENTICATION)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require_relative "controllers/helpers.rb"
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module Devise
|
2
|
+
module Models
|
3
|
+
module TwilioTwoFactorAuthenticatable
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
def send_otp_code
|
7
|
+
twilio_client.send_code
|
8
|
+
end
|
9
|
+
|
10
|
+
def verify_otp_code(code)
|
11
|
+
twilio_client.verify_code(code)
|
12
|
+
end
|
13
|
+
|
14
|
+
def login_attempts_exceeded?
|
15
|
+
self.failed_attempts.to_i >= Devise.maximum_attempts
|
16
|
+
end
|
17
|
+
|
18
|
+
def need_two_factor_authentication?(request)
|
19
|
+
self.otp_required_for_login
|
20
|
+
end
|
21
|
+
|
22
|
+
def send_new_otp_after_login?
|
23
|
+
self.otp_required_for_login
|
24
|
+
end
|
25
|
+
|
26
|
+
private def twilio_client
|
27
|
+
@twilio_client ||= TwilioTwoFactorAuthClient.new(self)
|
28
|
+
end
|
29
|
+
|
30
|
+
protected
|
31
|
+
module ClassMethods
|
32
|
+
Devise::Models.config(self,
|
33
|
+
:otp_code_length,
|
34
|
+
:otp_destination,
|
35
|
+
:communication_type,
|
36
|
+
:remember_otp_session_for_seconds,
|
37
|
+
:second_factor_resource_id,
|
38
|
+
:twilio_verify_service_sid,
|
39
|
+
:twilio_auth_token,
|
40
|
+
:twilio_account_sid)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require_relative "models/twilio_two_factor_authenticatable"
|
@@ -0,0 +1,11 @@
|
|
1
|
+
module ActionDispatch::Routing
|
2
|
+
class Mapper
|
3
|
+
protected
|
4
|
+
|
5
|
+
def devise_twilio_two_factor(mapping, controllers)
|
6
|
+
resource :twilio_two_factor, :only => [:show, :update, :resend_code], :path => mapping.path_names[:twilio_two_factor], :controller => controllers[:twilio_two_factor] do
|
7
|
+
collection { get "resend_code" }
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
class TwilioTwoFactorAuthClient
|
2
|
+
STATUS_PENDING = "pending"
|
3
|
+
STATUS_APPROVED = "approved"
|
4
|
+
|
5
|
+
def initialize(resource)
|
6
|
+
@resource = resource
|
7
|
+
@client = Twilio::REST::Client.new(resource.class.twilio_account_sid, resource.class.twilio_auth_token)
|
8
|
+
end
|
9
|
+
|
10
|
+
def send_code
|
11
|
+
begin
|
12
|
+
response = @client.verify
|
13
|
+
.v2
|
14
|
+
.services(@resource.class.twilio_verify_service_sid)
|
15
|
+
.verifications
|
16
|
+
.create(to: @resource.send(@resource.class.otp_destination), channel: @resource.class.communication_type)
|
17
|
+
|
18
|
+
return true if response.status == STATUS_PENDING
|
19
|
+
return false
|
20
|
+
rescue Twilio::REST::RestError => e
|
21
|
+
puts e.message
|
22
|
+
return false
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def verify_code(code)
|
27
|
+
begin
|
28
|
+
response = @client.verify
|
29
|
+
.v2
|
30
|
+
.services(@resource.class.twilio_verify_service_sid)
|
31
|
+
.verification_checks
|
32
|
+
.create(to: @resource.send(@resource.class.otp_destination), code: code)
|
33
|
+
return true if response.status == STATUS_APPROVED
|
34
|
+
return false
|
35
|
+
rescue Twilio::REST::RestError => e
|
36
|
+
puts e.message
|
37
|
+
return false
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
@@ -0,0 +1 @@
|
|
1
|
+
require_relative "services/twilio_two_factor_client"
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require "rubygems"
|
3
|
+
require "devise"
|
4
|
+
require "twilio-ruby"
|
5
|
+
require_relative "devise_twilio_two_factor/version"
|
6
|
+
require_relative "devise_twilio_two_factor/models"
|
7
|
+
require_relative "devise_twilio_two_factor/callbacks"
|
8
|
+
require_relative "devise_twilio_two_factor/services"
|
9
|
+
require_relative "devise_twilio_two_factor/rails"
|
10
|
+
require_relative "devise_twilio_two_factor/routes"
|
11
|
+
require_relative "devise_twilio_two_factor/controllers"
|
12
|
+
|
13
|
+
module Devise
|
14
|
+
mattr_accessor :otp_code_length
|
15
|
+
@@otp_code_length = 6
|
16
|
+
|
17
|
+
mattr_accessor :twilio_account_sid
|
18
|
+
@@twilio_account_sid = "ACXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
|
19
|
+
|
20
|
+
mattr_accessor :twilio_auth_token
|
21
|
+
@@twilio_auth_token = "token"
|
22
|
+
|
23
|
+
mattr_accessor :twilio_verify_service_sid
|
24
|
+
@@twilio_verify_service_sid = "VAXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
|
25
|
+
|
26
|
+
mattr_accessor :second_factor_resource_id
|
27
|
+
@@second_factor_resource_id = "id"
|
28
|
+
|
29
|
+
mattr_accessor :remember_otp_session_for_seconds
|
30
|
+
@@remember_otp_session_for_seconds = 30.minutes
|
31
|
+
|
32
|
+
mattr_accessor :delete_cookie_on_logout
|
33
|
+
@@delete_cookie_on_logout = true
|
34
|
+
end
|
35
|
+
|
36
|
+
module TwoFactorAuthentication
|
37
|
+
NEED_AUTHENTICATION = "otp_required_for_login"
|
38
|
+
REMEMBER_TFA_COOKIE_NAME = "remember_tfa"
|
39
|
+
|
40
|
+
module Controllers
|
41
|
+
autoload :Helpers, "devise_twilio_two_factor/controllers/helpers"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
Devise.add_module(:twilio_two_factor_authenticatable, :route => :twilio_two_factor,
|
46
|
+
:controller => :twilio_two_factor, :model => true)
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'rails/generators'
|
2
|
+
|
3
|
+
module DeviseTwilioTwoFactor
|
4
|
+
module Generators
|
5
|
+
class DeviseTwilioTwoFactorGenerator < Rails::Generators::NamedBase
|
6
|
+
desc 'Creates a migration to add the required attributes to NAME, and ' \
|
7
|
+
'adds the necessary Devise directives to the model'
|
8
|
+
|
9
|
+
def install_devise_twilio_two_factor
|
10
|
+
create_devise_twilio_two_factor_migration
|
11
|
+
inject_devise_directives_into_model
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def create_devise_twilio_two_factor_migration
|
17
|
+
migration_arguments = [
|
18
|
+
"add_devise_twilio_two_factor_to_#{plural_name}",
|
19
|
+
"otp_required_for_login:boolean",
|
20
|
+
]
|
21
|
+
|
22
|
+
Rails::Generators.invoke('active_record:migration', migration_arguments)
|
23
|
+
end
|
24
|
+
|
25
|
+
def inject_devise_directives_into_model
|
26
|
+
model_path = File.join('app', 'models', "#{file_path}.rb")
|
27
|
+
|
28
|
+
class_path = if namespaced?
|
29
|
+
class_name.to_s.split("::")
|
30
|
+
else
|
31
|
+
[class_name]
|
32
|
+
end
|
33
|
+
|
34
|
+
indent_depth = class_path.size
|
35
|
+
|
36
|
+
content = [
|
37
|
+
"devise :twilio_two_factor_authenticatable"
|
38
|
+
]
|
39
|
+
|
40
|
+
content = content.map { |line| " " * indent_depth + line }.join("\n") << "\n"
|
41
|
+
|
42
|
+
inject_into_class(model_path, class_path.last, content)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
class TwilioTwoFactorAuthenticatableDouble
|
4
|
+
extend ::ActiveModel::Callbacks
|
5
|
+
include ::ActiveModel::Validations::Callbacks
|
6
|
+
extend ::Devise::Models
|
7
|
+
|
8
|
+
devise :twilio_two_factor_authenticatable, otp_destination: "phone", communication_type: "sms"
|
9
|
+
end
|
10
|
+
|
11
|
+
RSpec.describe ::Devise::Models::TwilioTwoFactorAuthenticatable do
|
12
|
+
context 'When included in a class' do
|
13
|
+
subject { TwilioTwoFactorAuthenticatableDouble.new }
|
14
|
+
|
15
|
+
it 'creates class variables with the options passed' do
|
16
|
+
expect(subject.class.otp_destination).to eq("phone")
|
17
|
+
expect(subject.class.communication_type).to eq("sms")
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'has access to class variables defined in Devise' do
|
21
|
+
expect(subject.class.otp_code_length).to eq(6)
|
22
|
+
expect(subject.class.twilio_account_sid).to eq("ACXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX")
|
23
|
+
expect(subject.class.twilio_auth_token).to eq("token")
|
24
|
+
expect(subject.class.twilio_verify_service_sid).to eq("VAXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX")
|
25
|
+
expect(subject.class.second_factor_resource_id).to eq("id")
|
26
|
+
expect(subject.class.remember_otp_session_for_seconds).to eq(30.minutes)
|
27
|
+
end
|
28
|
+
|
29
|
+
describe ".send_otp_code" do
|
30
|
+
let(:twilio_client) { instance_double(TwilioTwoFactorAuthClient) }
|
31
|
+
before do
|
32
|
+
allow_any_instance_of(TwilioTwoFactorAuthClient).to receive(:send_code) { true }
|
33
|
+
allow(TwilioTwoFactorAuthClient).to receive(:new).and_return(twilio_client)
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'instanciates twilio client and tells client to send code' do
|
37
|
+
expect(TwilioTwoFactorAuthClient).to receive(:new).with(subject)
|
38
|
+
expect(twilio_client).to receive(:send_code)
|
39
|
+
|
40
|
+
subject.send_otp_code
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
describe '.verify_otp_code' do
|
45
|
+
let(:twilio_client) { instance_double(TwilioTwoFactorAuthClient) }
|
46
|
+
let(:code) { "123456" }
|
47
|
+
before do
|
48
|
+
allow_any_instance_of(TwilioTwoFactorAuthClient).to receive(:verify_code).with(code) { true }
|
49
|
+
allow(TwilioTwoFactorAuthClient).to receive(:new).and_return(twilio_client)
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'instantiates twilio client and calls verify_code' do
|
53
|
+
expect(TwilioTwoFactorAuthClient).to receive(:new).with(subject)
|
54
|
+
expect(twilio_client).to receive(:verify_code).with(code)
|
55
|
+
|
56
|
+
subject.verify_otp_code(code)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
describe '.login_attempts_exceeded?' do
|
61
|
+
it 'should return false unless locked_at is defined' do
|
62
|
+
allow_any_instance_of(TwilioTwoFactorAuthenticatableDouble).to receive(:failed_attempts) { 0 }
|
63
|
+
expect(subject.login_attempts_exceeded?).to eq(false)
|
64
|
+
end
|
65
|
+
|
66
|
+
it 'should return true if login_attempts exceeds max login attempts' do
|
67
|
+
allow_any_instance_of(TwilioTwoFactorAuthenticatableDouble).to receive(:failed_attempts) { 100 }
|
68
|
+
|
69
|
+
expect(subject.login_attempts_exceeded?).to eq(true)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
describe '.send_new_otp_after_login?' do
|
74
|
+
it 'should return false if otp_required_for_login is false' do
|
75
|
+
allow_any_instance_of(TwilioTwoFactorAuthenticatableDouble).to receive(:otp_required_for_login) { false }
|
76
|
+
|
77
|
+
expect(subject.send_new_otp_after_login?).to eq(false)
|
78
|
+
end
|
79
|
+
|
80
|
+
it 'should return true if otp_required_for_login is true' do
|
81
|
+
allow_any_instance_of(TwilioTwoFactorAuthenticatableDouble).to receive(:otp_required_for_login) { true }
|
82
|
+
|
83
|
+
expect(subject.send_new_otp_after_login?).to eq(true)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
|
@@ -0,0 +1,78 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
class TwilioTwoFactorAuthenticatableDouble
|
4
|
+
extend ::ActiveModel::Callbacks
|
5
|
+
include ::ActiveModel::Validations::Callbacks
|
6
|
+
extend ::Devise::Models
|
7
|
+
|
8
|
+
devise :twilio_two_factor_authenticatable, otp_destination: "phone", communication_type: "sms"
|
9
|
+
end
|
10
|
+
|
11
|
+
RSpec.describe TwilioTwoFactorAuthClient, type: :service do
|
12
|
+
let(:twilio_client) { instance_double(Twilio::REST::Client) }
|
13
|
+
let(:mock_number) { "+16309437616" }
|
14
|
+
let(:mock_twilio_response) { instance_double("twilio_response") }
|
15
|
+
let(:mock_code) { "123456" }
|
16
|
+
|
17
|
+
before do
|
18
|
+
allow_any_instance_of(TwilioTwoFactorAuthenticatableDouble).to receive(:phone).and_return(mock_number)
|
19
|
+
allow(Twilio::REST::Client).to receive(:new).and_return(twilio_client)
|
20
|
+
end
|
21
|
+
|
22
|
+
describe 'new' do
|
23
|
+
it 'should instantiate twilio client' do
|
24
|
+
expect(Twilio::REST::Client).to receive(:new).and_return(twilio_client)
|
25
|
+
|
26
|
+
TwilioTwoFactorAuthClient.new(TwilioTwoFactorAuthenticatableDouble.new)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
describe '#send_code' do
|
31
|
+
describe 'code was send successfully' do
|
32
|
+
it 'should have status pending and return true' do
|
33
|
+
allow(mock_twilio_response).to receive(:status).and_return("pending")
|
34
|
+
allow(twilio_client).to receive_message_chain(:verify, :v2, :services, :verifications, :create).and_return(mock_twilio_response)
|
35
|
+
expect(twilio_client).to receive_message_chain(:verify, :v2, :services, :verifications, :create)
|
36
|
+
|
37
|
+
response = TwilioTwoFactorAuthClient.new(TwilioTwoFactorAuthenticatableDouble.new).send_code
|
38
|
+
expect(response).to eq(true)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
describe 'failed to send code' do
|
43
|
+
it 'it should have status cancelled and return false' do
|
44
|
+
allow(mock_twilio_response).to receive(:status).and_return("cancelled")
|
45
|
+
allow(twilio_client).to receive_message_chain(:verify, :v2, :services, :verifications, :create).and_return(mock_twilio_response)
|
46
|
+
expect(twilio_client).to receive_message_chain(:verify, :v2, :services, :verifications, :create)
|
47
|
+
|
48
|
+
response = TwilioTwoFactorAuthClient.new(TwilioTwoFactorAuthenticatableDouble.new).send_code
|
49
|
+
expect(response).to eq(false)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
describe '#verify_code' do
|
55
|
+
describe 'code was verified' do
|
56
|
+
it 'should return a status of approved and true' do
|
57
|
+
allow(mock_twilio_response).to receive(:status).and_return("approved")
|
58
|
+
allow(twilio_client).to receive_message_chain(:verify, :v2, :services, :verification_checks, :create).and_return(mock_twilio_response)
|
59
|
+
expect(twilio_client).to receive_message_chain(:verify, :v2, :services, :verification_checks, :create)
|
60
|
+
|
61
|
+
response = TwilioTwoFactorAuthClient.new(TwilioTwoFactorAuthenticatableDouble.new).verify_code(mock_code)
|
62
|
+
expect(response).to eq(true)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
describe 'code could not be verified' do
|
67
|
+
it 'should return a status of pending and false' do
|
68
|
+
allow(mock_twilio_response).to receive(:status).and_return("pending")
|
69
|
+
allow(twilio_client).to receive_message_chain(:verify, :v2, :services, :verification_checks, :create).and_return(mock_twilio_response)
|
70
|
+
expect(twilio_client).to receive_message_chain(:verify, :v2, :services, :verification_checks, :create)
|
71
|
+
|
72
|
+
response = TwilioTwoFactorAuthClient.new(TwilioTwoFactorAuthenticatableDouble.new).verify_code(mock_code)
|
73
|
+
expect(response).to eq(false)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "devise_twilio_two_factor"
|
4
|
+
require 'active_model'
|
5
|
+
require 'devise'
|
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,180 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: devise_twilio_two_factor
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- John Livingston
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2023-03-28 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: railties
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 4.1.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.1.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: 4.8.1
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 4.8.1
|
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.66.0
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 5.66.0
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: bundler
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '1.0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '1.0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rspec
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '3'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '3'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: activemodel
|
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: pry
|
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: Devise Twilio Verify Two Factor Authentication
|
112
|
+
email: jclivingston316@gmail.com
|
113
|
+
executables: []
|
114
|
+
extensions: []
|
115
|
+
extra_rdoc_files: []
|
116
|
+
files:
|
117
|
+
- ".github/workflows/main.yml"
|
118
|
+
- ".gitignore"
|
119
|
+
- ".rspec"
|
120
|
+
- CHANGELOG.md
|
121
|
+
- Gemfile
|
122
|
+
- LICENSE.txt
|
123
|
+
- README.md
|
124
|
+
- Rakefile
|
125
|
+
- app/controllers/devise/twilio_two_factor_controller.rb
|
126
|
+
- app/views/devise/twilio_two_factor/show.html.erb
|
127
|
+
- bin/console
|
128
|
+
- bin/setup
|
129
|
+
- config/locales/de.yml
|
130
|
+
- config/locales/en.yml
|
131
|
+
- config/locales/es.yml
|
132
|
+
- config/locales/fr.yml
|
133
|
+
- config/locales/ru.yml
|
134
|
+
- devise_twilio_two_factor.gemspec
|
135
|
+
- lib/devise_twilio_two_factor.rb
|
136
|
+
- lib/devise_twilio_two_factor/callbacks.rb
|
137
|
+
- lib/devise_twilio_two_factor/callbacks/twilio_two_factor_authenticatable.rb
|
138
|
+
- lib/devise_twilio_two_factor/controllers.rb
|
139
|
+
- lib/devise_twilio_two_factor/controllers/helpers.rb
|
140
|
+
- lib/devise_twilio_two_factor/models.rb
|
141
|
+
- lib/devise_twilio_two_factor/models/twilio_two_factor_authenticatable.rb
|
142
|
+
- lib/devise_twilio_two_factor/rails.rb
|
143
|
+
- lib/devise_twilio_two_factor/routes.rb
|
144
|
+
- lib/devise_twilio_two_factor/services.rb
|
145
|
+
- lib/devise_twilio_two_factor/services/twilio_two_factor_client.rb
|
146
|
+
- lib/devise_twilio_two_factor/version.rb
|
147
|
+
- lib/generators/devise_twilio_two_factor/devise_twilio_two_factor_generator.rb
|
148
|
+
- sig/devise_twilio_two_factor.rbs
|
149
|
+
- spec/models/twilio_two_factor_authenticatable_spec.rb
|
150
|
+
- spec/services/twilio_two_factor_auth_client_spec.rb
|
151
|
+
- spec/spec_helper.rb
|
152
|
+
homepage: https://github.com/jliv316/devise-twilio-two-factor
|
153
|
+
licenses:
|
154
|
+
- MIT
|
155
|
+
metadata:
|
156
|
+
homepage_uri: https://github.com/jliv316/devise-twilio-two-factor
|
157
|
+
source_code_uri: https://github.com/Jliv316/devise_twilio_two_factor
|
158
|
+
post_install_message:
|
159
|
+
rdoc_options: []
|
160
|
+
require_paths:
|
161
|
+
- lib
|
162
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
163
|
+
requirements:
|
164
|
+
- - ">="
|
165
|
+
- !ruby/object:Gem::Version
|
166
|
+
version: 2.6.0
|
167
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
168
|
+
requirements:
|
169
|
+
- - ">="
|
170
|
+
- !ruby/object:Gem::Version
|
171
|
+
version: '0'
|
172
|
+
requirements: []
|
173
|
+
rubygems_version: 3.3.7
|
174
|
+
signing_key:
|
175
|
+
specification_version: 4
|
176
|
+
summary: Devise Twilio Verify Two Factor Authentication
|
177
|
+
test_files:
|
178
|
+
- spec/models/twilio_two_factor_authenticatable_spec.rb
|
179
|
+
- spec/services/twilio_two_factor_auth_client_spec.rb
|
180
|
+
- spec/spec_helper.rb
|