devise-authy 1.11.0 → 2.2.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b4dcb41db3fa90c5055347a840927fe3480f2eb6622499775dac3996f9d3683a
4
- data.tar.gz: e7d144883aa6ac75efb34ef13f1a9145529604d6dc9fc648a2427f9749e8f719
3
+ metadata.gz: 9157eb5b102b0297c975ea71d190f500e0bf6dc5831717df0ca5695b9f0af13e
4
+ data.tar.gz: 7582bfb6b18310dd7697a808460ac87282d1c2e3fc9fd09838c431d69315d655
5
5
  SHA512:
6
- metadata.gz: 89a291d8930edb905bae19949b253b645f7aaa319ff631fe0ec19da5133a363d0fcbbaa54e706e1f1495d8a219f8c4e2925309f9f22cfba94a531fb9413e3425
7
- data.tar.gz: a89caa9abd9fbbcc088175b66949f945602a7758dc120b12a274bd0967ea41d9a42fc13b86561e51dfd269c33bb4174b6b024b499c2d98b93d0e7746b64f6dca
6
+ metadata.gz: 36cc430cf270f535c3068c15668a398bbc9a297d345bb0c659db49ef5fb9febb43761abcf118c340476797a8c7150de37cb6ddcd0caf14b408cdecb94ab856f6
7
+ data.tar.gz: 856434e4038f931c25f84ddac5281f0b5f762e4a6d9d27a355362822342c255bea58ee6b087a52d912802b654436ef7505efc6a310375cafb67bdfafa5d800e1
data/.gitignore CHANGED
@@ -31,14 +31,15 @@ build/
31
31
  Gemfile.lock
32
32
  .ruby-version
33
33
  .ruby-gemset
34
+ gemfiles/*.lock
34
35
 
35
36
  # unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
36
37
  .rvmrc
37
38
 
38
- spec/rails-app/tmp
39
- spec/rails-app/db
40
- spec/rails-app/log
41
- *.sqlite3DS_Store
39
+ **/*.sqlite
40
+ **/*.log
42
41
 
43
42
  initializers/authy.rb
44
43
  .byebug_history
44
+
45
+ .rspec_status
data/.rspec CHANGED
@@ -1 +1,2 @@
1
1
  --color
2
+ --require ./spec/spec_helper
@@ -1,18 +1,17 @@
1
1
  language: ruby
2
- before_install:
3
- - "find /home/travis/.rvm/rubies -wholename '*default/bundler-*.gemspec' -delete"
4
- - rvm @global do gem uninstall bundler -a -x
5
- - rvm @global do yes | gem install bundler -v '< 2.0.0'
6
- - cd spec/rails-app && BUNDLE_GEMFILE=$TRAVIS_BUILD_DIR/spec/rails-app/Gemfile bundle install && cd ../..
7
2
  script: bundle exec rspec
8
3
  rvm:
4
+ - 2.7
9
5
  - 2.6
10
6
  - 2.5
11
7
  - 2.4
12
- - 2.3
13
- - 2.2
14
8
  - ruby-head
9
+ gemfile:
10
+ - gemfiles/rails_5_2.gemfile
11
+ - gemfiles/rails_6.gemfile
15
12
  matrix:
16
13
  allow_failures:
17
14
  - rvm: ruby-head
18
- - rvm: 2.2
15
+ exclude:
16
+ - rvm: 2.4
17
+ gemfile: gemfiles/rails_6.gemfile
@@ -0,0 +1,21 @@
1
+ appraise "rails-5-2" do
2
+ gem "rails", "~> 5.2.0"
3
+ gem "sqlite3", "~> 1.3.13"
4
+
5
+ group :development, :test do
6
+ gem 'factory_girl_rails', :require => false
7
+ gem 'rspec-rails', "~>4.0.0.beta3", :require => false
8
+ gem 'database_cleaner', :require => false
9
+ end
10
+ end
11
+
12
+ appraise "rails-6" do
13
+ gem "rails", "~> 6.0.0"
14
+ gem "sqlite3", "~> 1.4"
15
+
16
+ group :development, :test do
17
+ gem 'factory_girl_rails', :require => false
18
+ gem 'rspec-rails', "~>4.0.0.beta3", :require => false
19
+ gem 'database_cleaner', :require => false
20
+ end
21
+ end if RUBY_VERSION.to_f >= 2.5
@@ -9,6 +9,55 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
9
9
 
10
10
  ...
11
11
 
12
+ ## [2.2.1] - 2020-10-13
13
+
14
+ ### Fixed
15
+
16
+ - If the app offers a QR code scan and user fails to verify authy installation, the QR code wasn't shown again. Fixed in (#149)
17
+
18
+ ## [2.2.0] - 2020-06-04
19
+
20
+ ### Fixed
21
+
22
+ - Don't delete user in Authy if another user has the same authy_id (#144)
23
+
24
+ ## [2.1.0] - 2020-05-05
25
+
26
+ ### Added
27
+
28
+ - Support for generic authenticator tokens (#141)
29
+
30
+ ### Fixed
31
+
32
+ - Can remember device when enabling 2FA for the first time (#139)
33
+
34
+ ## [2.0.0] - 2020-04-28
35
+
36
+ Releasing this as version 2 because there is a significant change in dependencies. Minimum version of Rails is now 5 and of Devise is now 4. Otherwise the gem should work as before.
37
+
38
+ ### Added
39
+
40
+ - HTTP Only flag to remember_device cookie (#116 thanks @agronv)
41
+ - Remembers device when user logs in with One Touch (#128 thanks @cplopez4)
42
+ - Autocomplete attributes for HTML form (#130)
43
+
44
+ ### Changed
45
+
46
+ - Mocked API calls in test suite (#123)
47
+ - Full test suite refactor (#124)
48
+ - Increased required version for Devise and Rails (#125)
49
+ - Stopped calling `signed_in?` before it is needed (#126)
50
+
51
+ ### Fixes
52
+
53
+ - Remembers user correctly when logging in with One Touch (#129)
54
+
55
+ ## [1.11.1] - 2019-02-02
56
+
57
+ ### Fixed
58
+
59
+ - Using the version before loading it broke everything. :facepalm:
60
+
12
61
  ## [1.11.0] - 2019-02-01
13
62
 
14
63
  ### Fixed
data/Gemfile CHANGED
@@ -2,28 +2,5 @@ source 'https://rubygems.org'
2
2
 
3
3
  gemspec
4
4
 
5
- group :test do
6
- gem 'rails', '~> 4.2.7'
7
- gem 'sqlite3'
8
-
9
- # Use SCSS for stylesheets
10
- gem 'sass-rails', '~> 5.0'
11
-
12
- # Use Uglifier as compressor for JavaScript assets
13
- gem 'uglifier', '>= 1.3.0'
14
-
15
- # Use CoffeeScript for .coffee assets and views
16
- gem 'coffee-rails', '~> 4.1.0'
17
-
18
- # Use jquery as the JavaScript library
19
- gem 'jquery-rails'
20
-
21
- gem 'launchy'
22
- gem 'rspec-rails'
23
- gem 'database_cleaner'
24
- gem 'capybara'
25
- gem 'test-unit'
26
- end
27
-
28
5
  # bundle exec rake doc:rails generates the API under doc/api.
29
6
  gem 'sdoc', '~> 0.4.0', group: :doc
data/README.md CHANGED
@@ -1,10 +1,28 @@
1
1
  # Authy Devise [![Build Status](https://travis-ci.org/twilio/authy-devise.svg?branch=master)](https://travis-ci.org/twilio/authy-devise)
2
2
 
3
- This is a [Devise](https://github.com/plataformatec/devise) extension to add Two-Factor Authentication with Authy to your rails application.
3
+ This is a [Devise](https://github.com/plataformatec/devise) extension to add [Two-Factor Authentication with Authy](https://www.twilio.com/docs/authy) to your Rails application.
4
+
5
+ * [Pre-requisites](#pre-requisites)
6
+ * [Demo](#demo)
7
+ * [Getting started](#getting-started)
8
+ * [Configuring Models](#configuring-models)
9
+ * [With the generator](#with-the-generator)
10
+ * [Manually](#manually)
11
+ * [Final steps](#final-steps)
12
+ * [Custom Views](#custom-views)
13
+ * [Request a phone call](#request-a-phone-call)
14
+ * [Custom Redirect Paths (eg. using modules)](#custom-redirect-paths-eg-using-modules)
15
+ * [I18n](#i18n)
16
+ * [Session variables](#session-variables)
17
+ * [OneTouch support](#onetouch-support)
18
+ * [Generic authenticator token support](#generic-authenticator-token-support)
19
+ * [Rails 5 CSRF protection](#rails-5-csrf-protection)
20
+ * [Running Tests](#running-tests)
21
+ * [Copyright](#copyright)
4
22
 
5
23
  ## Pre-requisites
6
24
 
7
- To use the Authy API you will need a Twilio Account, [sign up for a free account here](https://www.twilio.com/try-twilio).
25
+ To use the Authy API you will need a Twilio Account, [sign up for a free Twilio account here](https://www.twilio.com/try-twilio).
8
26
 
9
27
  Create an [Authy Application in the Twilio console](https://www.twilio.com/console/authy/applications) and take note of the API key.
10
28
 
@@ -42,24 +60,28 @@ You can add devise_authy to your user model in two ways.
42
60
 
43
61
  #### With the generator
44
62
 
45
- This is the easiest way and is recommended. Run the following command:
63
+ Run the following command:
46
64
 
47
65
  ```bash
48
66
  rails g devise_authy [MODEL_NAME]
49
67
  ```
50
68
 
69
+ To support account locking (recommended), you must add `:authy_lockable` to the `devise :authy_authenticatable, ...` configuration in your model as this is not yet supported by the generator.
70
+
51
71
  #### Manually
52
72
 
53
- Add `:authy_authenticatable` to the `devise` options in your Devise user model:
73
+ Add `:authy_authenticatable` and `:authy_lockable` to the `devise` options in your Devise user model:
54
74
 
55
75
  ```ruby
56
- devise :authy_authenticatable, :database_authenticatable
76
+ devise :authy_authenticatable, :authy_lockable, :database_authenticatable, :lockable
57
77
  ```
58
78
 
79
+ (Note, `:authy_lockable` is optional but recommended. It should be used with Devise's own `:lockable` module).
80
+
59
81
  Also add a new migration. For example, if you are adding to the `User` model, use this migration:
60
82
 
61
83
  ```ruby
62
- class DeviseAuthyAddToUsers < ActiveRecord::Migration[5.2]
84
+ class DeviseAuthyAddToUsers < ActiveRecord::Migration[6.0]
63
85
  def self.up
64
86
  change_table :users do |t|
65
87
  t.string :authy_id
@@ -177,6 +199,20 @@ To enable [Authy push authentication](https://www.twilio.com/authy/features/push
177
199
  config.authy_enable_onetouch = true
178
200
  ```
179
201
 
202
+ ## Generic authenticator token support
203
+
204
+ Authy supports other authenticator apps by providing a QR code that your users can scan.
205
+
206
+ > **To use this feature, you need to enable it in your [Twilio Console](https://www.twilio.com/console/authy/applications)**
207
+
208
+ Once you have enabled generic authenticator tokens, you can enable this in devise-authy by modifying the Devise config file `config/initializers/devise.rb` and adding the configuration:
209
+
210
+ ```
211
+ config.authy_enable_qr_code = true
212
+ ```
213
+
214
+ This will display a QR code on the verification screen (you still need to take a user's phone number and country code). If you have implemented your own views, the QR code URL is available on the verification page as `@authy_qr_code`.
215
+
180
216
  ## Rails 5 CSRF protection
181
217
 
182
218
  In Rails 5 `protect_from_forgery` is no longer prepended to the `before_action` chain. If you call `authenticate_user` before `protect_from_forgery` your request will result in a "Can't verify CSRF token authenticity" error.
@@ -205,13 +241,6 @@ Now on the project root run the following commands:
205
241
  $ bundle exec rspec spec/
206
242
  ```
207
243
 
208
- ## Backporting to Rails 3
209
-
210
- While we are not currently supporting Rails 3, there's an active fork that maintains the backwards compatibility.
211
-
212
- https://github.com/gcosta/authy-devise
213
-
214
244
  ## Copyright
215
245
 
216
- Copyright (c) 2012-2020 Authy Inc. See LICENSE.txt for
217
- further details.
246
+ Copyright (c) 2012-2020 Authy Inc. See LICENSE.txt for further details.
@@ -5,17 +5,25 @@ class Devise::DeviseAuthyController < DeviseController
5
5
  prepend_before_action :find_resource_and_require_password_checked, :only => [
6
6
  :GET_verify_authy, :POST_verify_authy, :GET_authy_onetouch_status
7
7
  ]
8
+
9
+ prepend_before_action :check_resource_has_authy_id, :only => [
10
+ :GET_verify_authy_installation, :POST_verify_authy_installation
11
+ ]
12
+
13
+ prepend_before_action :check_resource_not_authy_enabled, :only => [
14
+ :GET_verify_authy_installation, :POST_verify_authy_installation
15
+ ]
16
+
8
17
  prepend_before_action :authenticate_scope!, :only => [
9
- :GET_enable_authy, :POST_enable_authy,
10
- :GET_verify_authy_installation, :POST_verify_authy_installation,
11
- :POST_disable_authy
18
+ :GET_enable_authy, :POST_enable_authy, :GET_verify_authy_installation,
19
+ :POST_verify_authy_installation, :POST_disable_authy
12
20
  ]
21
+
13
22
  include Devise::Controllers::Helpers
14
23
 
15
24
  def GET_verify_authy
16
- @authy_id = @resource.authy_id
17
25
  if resource_class.authy_enable_onetouch
18
- approval_request = send_one_touch_request['approval_request']
26
+ approval_request = send_one_touch_request(@resource.authy_id)['approval_request']
19
27
  @onetouch_uuid = approval_request['uuid'] if approval_request.present?
20
28
  end
21
29
  render :verify_authy
@@ -30,10 +38,8 @@ class Devise::DeviseAuthyController < DeviseController
30
38
  })
31
39
 
32
40
  if token.ok?
33
- remember_device if params[:remember_device].to_i == 1
34
- if session.delete("#{resource_name}_remember_me") == true && @resource.respond_to?(:remember_me=)
35
- @resource.remember_me = true
36
- end
41
+ remember_device(@resource.id) if params[:remember_device].to_i == 1
42
+ remember_user
37
43
  record_authy_authentication
38
44
  respond_with resource, :location => after_sign_in_path_for(@resource)
39
45
  else
@@ -61,13 +67,11 @@ class Devise::DeviseAuthyController < DeviseController
61
67
  if @authy_user.ok?
62
68
  resource.authy_id = @authy_user.id
63
69
  if resource.save
64
- set_flash_message(:notice, :enabled)
70
+ redirect_to [resource_name, :verify_authy_installation] and return
65
71
  else
66
72
  set_flash_message(:error, :not_enabled)
67
73
  redirect_to after_authy_enabled_path_for(resource) and return
68
74
  end
69
-
70
- redirect_to [resource_name, :verify_authy_installation]
71
75
  else
72
76
  set_flash_message(:error, :not_enabled)
73
77
  render :enable_authy
@@ -76,22 +80,39 @@ class Devise::DeviseAuthyController < DeviseController
76
80
 
77
81
  # Disable 2FA
78
82
  def POST_disable_authy
79
- response = Authy::API.delete_user(:id => resource.authy_id)
80
-
81
- if response.ok?
82
- resource.update_attribute(:authy_enabled, false)
83
- resource.update_attribute(:authy_id, nil)
83
+ authy_id = resource.authy_id
84
+ resource.assign_attributes(:authy_enabled => false, :authy_id => nil)
85
+ resource.save(:validate => false)
86
+
87
+ other_resource = resource.class.find_by(:authy_id => authy_id)
88
+ if other_resource
89
+ # If another resource has the same authy_id, do not delete the user from
90
+ # the API.
84
91
  forget_device
85
-
86
92
  set_flash_message(:notice, :disabled)
87
93
  else
88
- set_flash_message(:error, :not_disabled)
94
+ response = Authy::API.delete_user(:id => authy_id)
95
+ if response.ok?
96
+ forget_device
97
+ set_flash_message(:notice, :disabled)
98
+ else
99
+ # If deleting the user from the API fails, set everything back to what
100
+ # it was before.
101
+ # I'm not sure this is a good idea, but it was existing behaviour.
102
+ # Could be changed in a major version bump.
103
+ resource.assign_attributes(:authy_enabled => true, :authy_id => authy_id)
104
+ resource.save(:validate => false)
105
+ set_flash_message(:error, :not_disabled)
106
+ end
89
107
  end
90
-
91
108
  redirect_to after_authy_disabled_path_for(resource)
92
109
  end
93
110
 
94
111
  def GET_verify_authy_installation
112
+ if resource_class.authy_enable_qr_code
113
+ response = Authy::API.request_qr_code(id: resource.authy_id)
114
+ @authy_qr_code = response.qr_code
115
+ end
95
116
  render :verify_authy_installation
96
117
  end
97
118
 
@@ -105,26 +126,34 @@ class Devise::DeviseAuthyController < DeviseController
105
126
  self.resource.authy_enabled = token.ok?
106
127
 
107
128
  if token.ok? && self.resource.save
129
+ remember_device(@resource.id) if params[:remember_device].to_i == 1
108
130
  record_authy_authentication
109
131
  set_flash_message(:notice, :enabled)
110
132
  redirect_to after_authy_verified_path_for(resource)
111
133
  else
134
+ if resource_class.authy_enable_qr_code
135
+ response = Authy::API.request_qr_code(id: resource.authy_id)
136
+ @authy_qr_code = response.qr_code
137
+ end
112
138
  handle_invalid_token :verify_authy_installation, :not_enabled
113
139
  end
114
140
  end
115
141
 
116
142
  def GET_authy_onetouch_status
117
- status = Authy::API.get_request("onetouch/json/approval_requests/#{params[:onetouch_uuid]}")['approval_request']['status']
143
+ response = Authy::OneTouch.approval_request_status(:uuid => params[:onetouch_uuid])
144
+ status = response.dig('approval_request', 'status')
118
145
  case status
119
146
  when 'pending'
120
147
  head 202
121
148
  when 'approved'
149
+ remember_device(@resource.id) if params[:remember_device].to_i == 1
150
+ remember_user
122
151
  record_authy_authentication
123
152
  render json: { redirect: after_sign_in_path_for(@resource) }
124
153
  when 'denied'
125
154
  head :unauthorized
126
155
  else
127
- head :error
156
+ head :internal_server_error
128
157
  end
129
158
  end
130
159
 
@@ -172,6 +201,16 @@ class Devise::DeviseAuthyController < DeviseController
172
201
  end
173
202
  end
174
203
 
204
+ def check_resource_has_authy_id
205
+ redirect_to [resource_name, :enable_authy] if !resource.authy_id
206
+ end
207
+
208
+ def check_resource_not_authy_enabled
209
+ if resource.authy_id && resource.authy_enabled
210
+ redirect_to after_authy_verified_path_for(resource)
211
+ end
212
+ end
213
+
175
214
  protected
176
215
 
177
216
  def after_authy_enabled_path_for(resource)
@@ -202,4 +241,10 @@ class Devise::DeviseAuthyController < DeviseController
202
241
  def after_account_is_locked
203
242
  sign_out_and_redirect @resource
204
243
  end
244
+
245
+ def remember_user
246
+ if session.delete("#{resource_name}_remember_me") == true && @resource.respond_to?(:remember_me=)
247
+ @resource.remember_me = true
248
+ end
249
+ end
205
250
  end
@@ -1,4 +1,22 @@
1
1
  class DeviseAuthy::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.
2
20
  def sign_in(resource_or_scope, *args)
3
21
  resource = args.last || resource_or_scope
4
22
 
@@ -5,7 +5,7 @@
5
5
  <%= verify_authy_form do %>
6
6
  <legend><%= I18n.t('submit_token_title', {:scope => 'devise'}) %></legend>
7
7
  <%= label_tag 'authy-token' %>
8
- <%= text_field_tag :token, "", :autocomplete => :off, :id => 'authy-token' %>
8
+ <%= text_field_tag :token, "", :autocomplete => "one-time-code", :inputmode => "numeric", :pattern => "[0-9]*", :id => 'authy-token' %>
9
9
  <label>
10
10
  <%= check_box_tag :remember_device %>
11
11
  <span><%= I18n.t('remember_device', {:scope => 'devise'}) %></span>
@@ -25,11 +25,12 @@
25
25
  (function(){
26
26
  var onetouchInterval = setInterval(function(){
27
27
  var onetouchRequest = new XMLHttpRequest();
28
+ var rememberDevice = document.getElementById("remember_device").checked ? '1' : '0';
28
29
  onetouchRequest.addEventListener("load", function(){
29
30
  if(this.status != 202) clearInterval(onetouchInterval);
30
31
  if(this.status == 200) window.location = JSON.parse(this.responseText).redirect;
31
32
  });
32
- onetouchRequest.open("GET", "<%= polymorphic_path [resource_name, :authy_onetouch_status] %>?onetouch_uuid=<%= @onetouch_uuid %>");
33
+ onetouchRequest.open("GET", "<%= polymorphic_path [resource_name, :authy_onetouch_status] %>?remember_device="+rememberDevice+"&onetouch_uuid=<%= @onetouch_uuid %>");
33
34
  onetouchRequest.send();
34
35
  }, 3000);
35
36
  })();
@@ -4,7 +4,7 @@
4
4
  %legend= I18n.t('submit_token_title', {:scope => 'devise'})
5
5
  = hidden_field_tag :"#{resource_name}_id", @resource.id
6
6
  = label_tag 'authy-token'
7
- = text_field_tag :token, "", :autocomplete => :off, :id => 'authy-token'
7
+ = text_field_tag :token, "", :autocomplete => "one-time-code", :inputmode => "numeric", :pattern => "[0-9]*", :id => 'authy-token'
8
8
  %label
9
9
  = check_box_tag :remember_device
10
10
  %span= I18n.t('remember_device', {:scope => 'devise'})
@@ -22,11 +22,12 @@
22
22
  (function(){
23
23
  var onetouchInterval = setInterval(function(){
24
24
  var onetouchRequest = new XMLHttpRequest();
25
+ var rememberDevice = document.getElementById("remember_device").checked ? '1' : '0';
25
26
  onetouchRequest.addEventListener("load", function(){
26
27
  if(this.status != 202) clearInterval(onetouchInterval);
27
28
  if(this.status == 200) window.location = JSON.parse(this.responseText).redirect;
28
29
  });
29
- onetouchRequest.open("GET", "#{polymorphic_path [resource_name, :authy_onetouch_status]}?onetouch_uuid=#{@onetouch_uuid}");
30
+ onetouchRequest.open("GET", "#{polymorphic_path [resource_name, :authy_onetouch_status]}?remember_device="+rememberDevice+"&onetouch_uuid=#{@onetouch_uuid}");
30
31
  onetouchRequest.send();
31
32
  }, 3000);
32
33
  })();
@@ -1,10 +1,18 @@
1
1
  <h2><%= I18n.t('authy_verify_installation_title', {:scope => 'devise'}) %></h2>
2
2
 
3
+ <% if @authy_qr_code %>
4
+ <%= image_tag @authy_qr_code, :size => '256x256', :alt => I18n.t('authy_qr_code_alt', {:scope => 'devise'}) %>
5
+ <p><%= I18n.t('authy_qr_code_instructions', {:scope => 'devise'}) %></p>
6
+ <% end %>
7
+
3
8
  <%= verify_authy_installation_form do %>
4
9
  <legend><%= I18n.t('submit_token_title', {:scope => 'devise'}) %></legend>
5
10
  <%= label_tag :token %>
6
- <%= text_field_tag :token, "", :autocomplete => :off, :id => 'authy-token' %>
11
+ <%= text_field_tag :token, "", :autocomplete => "one-time-code", :inputmode => "numeric", :pattern => "[0-9]*", :id => 'authy-token' %>
12
+ <label>
13
+ <%= check_box_tag :remember_device %>
14
+ <span><%= I18n.t('remember_device', {:scope => 'devise'}) %></span>
15
+ </label>
7
16
  <%= authy_request_sms_link %>
8
17
  <%= submit_tag I18n.t('enable_my_account', {:scope => 'devise'}), :class => 'btn' %>
9
- <% end %>
10
-
18
+ <% end %>
@@ -1,8 +1,16 @@
1
1
  %h2= I18n.t('authy_verify_installation_title', {:scope => 'devise'})
2
+
3
+ - if @authy_qr_code
4
+ = image_tag @authy_qr_code, :size => '256x256', :alt => I18n.t('authy_qr_code_alt', {:scope => 'devise'})
5
+ %p= I18n.t('authy_qr_code_instructions', {:scope => 'devise'})
6
+
2
7
  = verify_authy_installation_form do
3
8
  %legend= I18n.t('submit_token_title', {:scope => 'devise'})
4
9
  = label_tag :token
5
- = text_field_tag :token, "", :autocomplete => :off, :id => 'authy-token'
10
+ = text_field_tag :token, "", :autocomplete => "one-time-code", :inputmode => "numeric", :pattern => "[0-9]*", :id => 'authy-token'
11
+ %label
12
+ = check_box_tag :remember_device
13
+ %span= I18n.t('remember_device', {:scope => 'devise'})
6
14
  = authy_request_sms_link
7
15
  = submit_tag I18n.t('enable_my_account', {:scope => 'devise'}), :class => 'btn'
8
16
 
@@ -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
@@ -14,6 +14,9 @@ en:
14
14
  authy_verify_installation_title: 'Verify your account'
15
15
  enable_my_account: 'Enable my account'
16
16
 
17
+ authy_qr_code_alt: 'QR code for scanning with your authenticator app.'
18
+ authy_qr_code_instructions: 'Scan this QR code with your authenticator application and enter the code below.'
19
+
17
20
  devise_authy:
18
21
  user:
19
22
  enabled: 'Two factor authentication was enabled'
@@ -12,29 +12,39 @@ Gem::Specification.new do |spec|
12
12
 
13
13
  spec.summary = %q{Authy plugin for Devise.}
14
14
  spec.description = %q{Authy plugin to add two factor authentication to Devise.}
15
- spec.homepage = "https://github.com/authy/authy-devise"
15
+ spec.homepage = "https://github.com/twilio/authy-devise"
16
16
  spec.license = "MIT"
17
17
 
18
18
  spec.metadata = {
19
- "bug_tracker_uri" => "https://github.com/authy/authy-devise/issues",
20
- "change_log_uri" => "https://github.com/authy/authy-devise/blob/master/CHANGELOG.md",
21
- "documentation_uri" => "https://github.com/authy/authy-devise",
22
- "homepage_uri" => "https://github.com/authy/authy-devise",
23
- "source_code_uri" => "https://github.com/authy/authy-devise"
19
+ "bug_tracker_uri" => "https://github.com/twilio/authy-devise/issues",
20
+ "change_log_uri" => "https://github.com/twilio/authy-devise/blob/master/CHANGELOG.md",
21
+ "documentation_uri" => "https://github.com/twilio/authy-devise",
22
+ "homepage_uri" => "https://github.com/twilio/authy-devise",
23
+ "source_code_uri" => "https://github.com/twilio/authy-devise"
24
24
  }
25
25
 
26
26
  spec.files = `git ls-files -z`.split("\x0").reject do |f|
27
- f.match(%r{^(test|spec|features|authy-devise-demo)/})
27
+ f.match(%r{^(test|spec|features)/})
28
28
  end
29
29
  spec.require_paths = ["lib"]
30
30
 
31
- spec.add_dependency "devise", ">= 3.0.0"
31
+ spec.add_dependency "devise", ">= 4.0.0"
32
32
  spec.add_dependency "authy", ">= 2.7.5"
33
33
 
34
+ spec.add_development_dependency "appraisal", "~> 2.2"
34
35
  spec.add_development_dependency "bundler", ">= 1.16"
35
- spec.add_development_dependency "rake", "~> 10.0"
36
+ spec.add_development_dependency "rake"
37
+ spec.add_development_dependency "combustion", "~> 1.1"
36
38
  spec.add_development_dependency "rspec", "~> 3.0"
39
+ spec.add_development_dependency "rspec-rails"
40
+ spec.add_development_dependency "rails-controller-testing", "~> 1.0"
37
41
  spec.add_development_dependency "yard", "~> 0.9.11"
38
42
  spec.add_development_dependency "rdoc", "~> 4.3.0"
39
- spec.add_development_dependency "simplecov", "~> 0.16.1"
43
+ spec.add_development_dependency "simplecov", "~> 0.17.1"
44
+ spec.add_development_dependency "webmock", "~> 3.7.6"
45
+ spec.add_development_dependency "rails", ">= 5"
46
+ spec.add_development_dependency "sqlite3"
47
+ spec.add_development_dependency "generator_spec"
48
+ spec.add_development_dependency "database_cleaner", "~> 1.7"
49
+ spec.add_development_dependency "factory_bot_rails", "~> 5.1.1"
40
50
  end
@@ -0,0 +1,2 @@
1
+ ---
2
+ BUNDLE_RETRY: "1"
@@ -0,0 +1,15 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "sdoc", "~> 0.4.0", group: :doc
6
+ gem "rails", "~> 5.2.0"
7
+ gem "sqlite3", "~> 1.3.13"
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,15 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "sdoc", "~> 0.4.0", group: :doc
6
+ gem "rails", "~> 6.0.0"
7
+ gem "sqlite3", "~> 1.4"
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: "../"
@@ -3,12 +3,11 @@ require 'active_support/core_ext/integer/time'
3
3
  require 'devise'
4
4
  require 'authy'
5
5
 
6
- Authy.user_agent = "DeviseAuthy/#{DeviseAuthy::VERSION} - #{Authy.user_agent}"
7
-
8
6
  module Devise
9
- mattr_accessor :authy_remember_device, :authy_enable_onetouch
7
+ mattr_accessor :authy_remember_device, :authy_enable_onetouch, :authy_enable_qr_code
10
8
  @@authy_remember_device = 1.month
11
9
  @@authy_enable_onetouch = false
10
+ @@authy_enable_qr_code = false
12
11
  end
13
12
 
14
13
  module DeviseAuthy
@@ -30,5 +29,7 @@ require 'devise-authy/models/authy_authenticatable'
30
29
  require 'devise-authy/models/authy_lockable'
31
30
  require 'devise-authy/version'
32
31
 
32
+ Authy.user_agent = "DeviseAuthy/#{DeviseAuthy::VERSION} - #{Authy.user_agent}"
33
+
33
34
  Devise.add_module :authy_authenticatable, :model => 'devise-authy/models/authy_authenticatable', :controller => :devise_authy, :route => :authy
34
35
  Devise.add_module :authy_lockable, :model => 'devise-authy/models/authy_lockable'
@@ -9,11 +9,11 @@ module DeviseAuthy
9
9
 
10
10
  private
11
11
 
12
- def remember_device
13
- id = @resource.id
12
+ def remember_device(id)
14
13
  cookies.signed[:remember_device] = {
15
14
  :value => {expires: Time.now.to_i, id: id}.to_json,
16
15
  :secure => !(Rails.env.test? || Rails.env.development?),
16
+ :httponly => !(Rails.env.test? || Rails.env.development?),
17
17
  :expires => resource_class.authy_remember_device.from_now
18
18
  }
19
19
  end
@@ -40,7 +40,7 @@ module DeviseAuthy
40
40
  end
41
41
 
42
42
  def is_signing_in?
43
- if devise_controller? && signed_in?(resource_name) &&
43
+ if devise_controller? &&
44
44
  is_devise_sessions_controller? &&
45
45
  self.action_name == "create"
46
46
  return true
@@ -76,8 +76,8 @@ module DeviseAuthy
76
76
  send(:"#{scope}_verify_authy_path")
77
77
  end
78
78
 
79
- def send_one_touch_request
80
- Authy::OneTouch.send_approval_request(id: @authy_id, message: I18n.t('request_to_login', { :scope => 'devise' }))
79
+ def send_one_touch_request(authy_id)
80
+ Authy::OneTouch.send_approval_request(id: authy_id, message: I18n.t('request_to_login', { :scope => 'devise' }))
81
81
  end
82
82
 
83
83
  def record_authy_authentication
@@ -17,7 +17,7 @@ module Devise
17
17
  where(authy_id: authy_id).first
18
18
  end
19
19
 
20
- Devise::Models.config(self, :authy_remember_device, :authy_enable_onetouch)
20
+ Devise::Models.config(self, :authy_remember_device, :authy_enable_onetouch, :authy_enable_qr_code)
21
21
  end
22
22
  end
23
23
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DeviseAuthy
4
- VERSION = '1.11.0'
5
- end
4
+ VERSION = '2.2.1'
5
+ end
@@ -11,12 +11,12 @@ module ActiveRecord
11
11
 
12
12
  private
13
13
 
14
- def rails5?
15
- Rails.version.start_with? '5'
14
+ def versioned_migrations?
15
+ Rails::VERSION::MAJOR >= 5
16
16
  end
17
17
 
18
18
  def migration_version
19
- "[#{Rails::VERSION::MAJOR}.#{Rails::VERSION::MINOR}]" if rails5?
19
+ "[#{Rails::VERSION::MAJOR}.#{Rails::VERSION::MINOR}]" if versioned_migrations?
20
20
  end
21
21
  end
22
22
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module DeviseAuthy
2
4
  module Generators
3
5
  class DeviseAuthyGenerator < Rails::Generators::NamedBase
@@ -7,7 +9,7 @@ module DeviseAuthy
7
9
  desc "Add :authy_authenticatable directive in the given model, plus accessors. Also generate migration for ActiveRecord"
8
10
 
9
11
  def inject_devise_authy_content
10
- path = File.join("app","models","#{file_path}.rb")
12
+ path = File.join(destination_root, "app","models","#{file_path}.rb")
11
13
  if File.exists?(path) &&
12
14
  !File.read(path).include?("authy_authenticatable")
13
15
  inject_into_file(path,
@@ -19,7 +21,7 @@ module DeviseAuthy
19
21
  !File.read(path).include?(":authy_id")
20
22
  inject_into_file(path,
21
23
  ":authy_id, :last_sign_in_with_authy, ",
22
- :after => "attr_accessible ")
24
+ :after => "attr_accessible ")
23
25
  end
24
26
  end
25
27
 
@@ -1,7 +1,9 @@
1
+ require "rails/generators"
2
+
1
3
  module DeviseAuthy
2
4
  module Generators
3
5
  # Install Generator
4
- class InstallGenerator < Rails::Generators::Base
6
+ class InstallGenerator < ::Rails::Generators::Base
5
7
  source_root File.expand_path("../../templates", __FILE__)
6
8
 
7
9
  class_option :haml, :type => :boolean, :required => false, :default => false, :desc => "Generate views in Haml"
@@ -15,8 +17,10 @@ module DeviseAuthy
15
17
  " # How long should the user's device be remembered for.\n" +
16
18
  " # config.authy_remember_device = 1.month\n\n" +
17
19
  " # Should Authy OneTouch be enabled?\n" +
18
- " # config.authy_enable_onetouch = false\n\n", :after => "Devise.setup do |config|\n"
19
-
20
+ " # config.authy_enable_onetouch = false\n\n" +
21
+ " # Should generating QR codes for other authenticator apps be enabled?\n" +
22
+ " # Note: you need to enable this in your Twilio console.\n" +
23
+ " # config.authy_enable_qr_code = false\n\n", :after => "Devise.setup do |config|\n"
20
24
  end
21
25
 
22
26
  def add_initializer
@@ -61,14 +65,14 @@ module DeviseAuthy
61
65
  @
62
66
  },
63
67
  :erb => {
64
- :before => %r{\s*</\s*head\s*>\s*},
68
+ :before => %r{\s*<\/\s*head\s*>\s*},
65
69
  :content => %@
66
70
  <%=javascript_include_tag "https://www.authy.com/form.authy.min.js" %>
67
71
  <%=stylesheet_link_tag "https://www.authy.com/form.authy.min.css" %>
68
72
  @
69
73
  }
70
74
  }.each do |extension, opts|
71
- file_path = "app/views/layouts/application.html.#{extension}"
75
+ file_path = File.join(destination_root, "app", "views", "layouts", "application.html.#{extension}")
72
76
  if File.exists?(file_path) && !File.read(file_path).include?("form.authy.min.js")
73
77
  inject_into_file(file_path, opts.delete(:content), opts)
74
78
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: devise-authy
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.11.0
4
+ version: 2.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Authy Inc.
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-01-31 00:00:00.000000000 Z
11
+ date: 2020-10-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: devise
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: 3.0.0
19
+ version: 4.0.0
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: 3.0.0
26
+ version: 4.0.0
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: authy
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -38,6 +38,20 @@ dependencies:
38
38
  - - ">="
39
39
  - !ruby/object:Gem::Version
40
40
  version: 2.7.5
41
+ - !ruby/object:Gem::Dependency
42
+ name: appraisal
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '2.2'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '2.2'
41
55
  - !ruby/object:Gem::Dependency
42
56
  name: bundler
43
57
  requirement: !ruby/object:Gem::Requirement
@@ -54,18 +68,32 @@ dependencies:
54
68
  version: '1.16'
55
69
  - !ruby/object:Gem::Dependency
56
70
  name: rake
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: combustion
57
85
  requirement: !ruby/object:Gem::Requirement
58
86
  requirements:
59
87
  - - "~>"
60
88
  - !ruby/object:Gem::Version
61
- version: '10.0'
89
+ version: '1.1'
62
90
  type: :development
63
91
  prerelease: false
64
92
  version_requirements: !ruby/object:Gem::Requirement
65
93
  requirements:
66
94
  - - "~>"
67
95
  - !ruby/object:Gem::Version
68
- version: '10.0'
96
+ version: '1.1'
69
97
  - !ruby/object:Gem::Dependency
70
98
  name: rspec
71
99
  requirement: !ruby/object:Gem::Requirement
@@ -80,6 +108,34 @@ dependencies:
80
108
  - - "~>"
81
109
  - !ruby/object:Gem::Version
82
110
  version: '3.0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: rspec-rails
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: rails-controller-testing
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - "~>"
130
+ - !ruby/object:Gem::Version
131
+ version: '1.0'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - "~>"
137
+ - !ruby/object:Gem::Version
138
+ version: '1.0'
83
139
  - !ruby/object:Gem::Dependency
84
140
  name: yard
85
141
  requirement: !ruby/object:Gem::Requirement
@@ -114,14 +170,98 @@ dependencies:
114
170
  requirements:
115
171
  - - "~>"
116
172
  - !ruby/object:Gem::Version
117
- version: 0.16.1
173
+ version: 0.17.1
174
+ type: :development
175
+ prerelease: false
176
+ version_requirements: !ruby/object:Gem::Requirement
177
+ requirements:
178
+ - - "~>"
179
+ - !ruby/object:Gem::Version
180
+ version: 0.17.1
181
+ - !ruby/object:Gem::Dependency
182
+ name: webmock
183
+ requirement: !ruby/object:Gem::Requirement
184
+ requirements:
185
+ - - "~>"
186
+ - !ruby/object:Gem::Version
187
+ version: 3.7.6
188
+ type: :development
189
+ prerelease: false
190
+ version_requirements: !ruby/object:Gem::Requirement
191
+ requirements:
192
+ - - "~>"
193
+ - !ruby/object:Gem::Version
194
+ version: 3.7.6
195
+ - !ruby/object:Gem::Dependency
196
+ name: rails
197
+ requirement: !ruby/object:Gem::Requirement
198
+ requirements:
199
+ - - ">="
200
+ - !ruby/object:Gem::Version
201
+ version: '5'
202
+ type: :development
203
+ prerelease: false
204
+ version_requirements: !ruby/object:Gem::Requirement
205
+ requirements:
206
+ - - ">="
207
+ - !ruby/object:Gem::Version
208
+ version: '5'
209
+ - !ruby/object:Gem::Dependency
210
+ name: sqlite3
211
+ requirement: !ruby/object:Gem::Requirement
212
+ requirements:
213
+ - - ">="
214
+ - !ruby/object:Gem::Version
215
+ version: '0'
216
+ type: :development
217
+ prerelease: false
218
+ version_requirements: !ruby/object:Gem::Requirement
219
+ requirements:
220
+ - - ">="
221
+ - !ruby/object:Gem::Version
222
+ version: '0'
223
+ - !ruby/object:Gem::Dependency
224
+ name: generator_spec
225
+ requirement: !ruby/object:Gem::Requirement
226
+ requirements:
227
+ - - ">="
228
+ - !ruby/object:Gem::Version
229
+ version: '0'
230
+ type: :development
231
+ prerelease: false
232
+ version_requirements: !ruby/object:Gem::Requirement
233
+ requirements:
234
+ - - ">="
235
+ - !ruby/object:Gem::Version
236
+ version: '0'
237
+ - !ruby/object:Gem::Dependency
238
+ name: database_cleaner
239
+ requirement: !ruby/object:Gem::Requirement
240
+ requirements:
241
+ - - "~>"
242
+ - !ruby/object:Gem::Version
243
+ version: '1.7'
244
+ type: :development
245
+ prerelease: false
246
+ version_requirements: !ruby/object:Gem::Requirement
247
+ requirements:
248
+ - - "~>"
249
+ - !ruby/object:Gem::Version
250
+ version: '1.7'
251
+ - !ruby/object:Gem::Dependency
252
+ name: factory_bot_rails
253
+ requirement: !ruby/object:Gem::Requirement
254
+ requirements:
255
+ - - "~>"
256
+ - !ruby/object:Gem::Version
257
+ version: 5.1.1
118
258
  type: :development
119
259
  prerelease: false
120
260
  version_requirements: !ruby/object:Gem::Requirement
121
261
  requirements:
122
262
  - - "~>"
123
263
  - !ruby/object:Gem::Version
124
- version: 0.16.1
264
+ version: 5.1.1
125
265
  description: Authy plugin to add two factor authentication to Devise.
126
266
  email:
127
267
  - support@authy.com
@@ -133,6 +273,7 @@ files:
133
273
  - ".gitignore"
134
274
  - ".rspec"
135
275
  - ".travis.yml"
276
+ - Appraisals
136
277
  - CHANGELOG.md
137
278
  - Gemfile
138
279
  - LICENSE.txt
@@ -149,8 +290,12 @@ files:
149
290
  - app/views/devise/verify_authy.html.haml
150
291
  - app/views/devise/verify_authy_installation.html.erb
151
292
  - app/views/devise/verify_authy_installation.html.haml
293
+ - config.ru
152
294
  - config/locales/en.yml
153
295
  - devise-authy.gemspec
296
+ - gemfiles/.bundle/config
297
+ - gemfiles/rails_5_2.gemfile
298
+ - gemfiles/rails_6.gemfile
154
299
  - lib/devise-authy.rb
155
300
  - lib/devise-authy/controllers/helpers.rb
156
301
  - lib/devise-authy/controllers/view_helpers.rb
@@ -165,15 +310,15 @@ files:
165
310
  - lib/generators/active_record/templates/migration.rb
166
311
  - lib/generators/devise_authy/devise_authy_generator.rb
167
312
  - lib/generators/devise_authy/install_generator.rb
168
- homepage: https://github.com/authy/authy-devise
313
+ homepage: https://github.com/twilio/authy-devise
169
314
  licenses:
170
315
  - MIT
171
316
  metadata:
172
- bug_tracker_uri: https://github.com/authy/authy-devise/issues
173
- change_log_uri: https://github.com/authy/authy-devise/blob/master/CHANGELOG.md
174
- documentation_uri: https://github.com/authy/authy-devise
175
- homepage_uri: https://github.com/authy/authy-devise
176
- source_code_uri: https://github.com/authy/authy-devise
317
+ bug_tracker_uri: https://github.com/twilio/authy-devise/issues
318
+ change_log_uri: https://github.com/twilio/authy-devise/blob/master/CHANGELOG.md
319
+ documentation_uri: https://github.com/twilio/authy-devise
320
+ homepage_uri: https://github.com/twilio/authy-devise
321
+ source_code_uri: https://github.com/twilio/authy-devise
177
322
  post_install_message:
178
323
  rdoc_options: []
179
324
  require_paths:
@@ -189,7 +334,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
189
334
  - !ruby/object:Gem::Version
190
335
  version: '0'
191
336
  requirements: []
192
- rubygems_version: 3.0.1
337
+ rubygems_version: 3.0.3
193
338
  signing_key:
194
339
  specification_version: 4
195
340
  summary: Authy plugin for Devise.