devise-otp 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. checksums.yaml +5 -5
  2. data/.github/workflows/ci.yml +36 -0
  3. data/.gitignore +0 -0
  4. data/Gemfile +1 -22
  5. data/LICENSE.txt +0 -0
  6. data/README.md +43 -66
  7. data/Rakefile +0 -0
  8. data/app/assets/javascripts/devise-otp.js +1 -0
  9. data/app/assets/javascripts/qrcode.js +609 -0
  10. data/app/controllers/devise_otp/devise/credentials_controller.rb +102 -0
  11. data/app/controllers/devise_otp/devise/tokens_controller.rb +112 -0
  12. data/app/views/devise/credentials/refresh.html.erb +19 -0
  13. data/app/views/devise/credentials/show.html.erb +31 -0
  14. data/app/views/devise/tokens/_token_secret.html.erb +23 -0
  15. data/app/views/devise/tokens/_trusted_devices.html.erb +12 -0
  16. data/app/views/devise/tokens/recovery.html.erb +21 -0
  17. data/app/views/devise/tokens/recovery_codes.text.erb +3 -0
  18. data/app/views/devise/tokens/show.html.erb +21 -0
  19. data/config/locales/en.yml +10 -10
  20. data/devise-otp.gemspec +14 -9
  21. data/docs/QR_CODES.md +48 -0
  22. data/lib/devise-otp/version.rb +1 -1
  23. data/lib/devise-otp.rb +22 -14
  24. data/lib/devise_otp_authenticatable/controllers/helpers.rb +29 -16
  25. data/lib/devise_otp_authenticatable/controllers/url_helpers.rb +6 -9
  26. data/lib/devise_otp_authenticatable/engine.rb +22 -13
  27. data/lib/devise_otp_authenticatable/hooks/sessions.rb +8 -7
  28. data/lib/devise_otp_authenticatable/hooks.rb +1 -1
  29. data/lib/devise_otp_authenticatable/models/otp_authenticatable.rb +28 -28
  30. data/lib/devise_otp_authenticatable/routes.rb +9 -10
  31. data/lib/generators/active_record/devise_otp_generator.rb +1 -1
  32. data/lib/generators/active_record/templates/migration.rb +1 -2
  33. data/lib/generators/devise_otp/devise_otp_generator.rb +0 -0
  34. data/lib/generators/devise_otp/install_generator.rb +30 -5
  35. data/lib/generators/devise_otp/views_generator.rb +2 -3
  36. data/test/dummy/README.rdoc +0 -0
  37. data/test/dummy/Rakefile +0 -0
  38. data/test/dummy/app/assets/config/manifest.js +2 -0
  39. data/test/dummy/app/assets/javascripts/application.js +1 -0
  40. data/test/dummy/app/assets/stylesheets/application.css +0 -0
  41. data/test/dummy/app/controllers/application_controller.rb +1 -1
  42. data/test/dummy/app/controllers/posts_controller.rb +2 -0
  43. data/test/dummy/app/helpers/application_helper.rb +0 -0
  44. data/test/dummy/app/helpers/posts_helper.rb +0 -0
  45. data/test/dummy/app/mailers/.gitkeep +0 -0
  46. data/test/dummy/app/models/post.rb +0 -0
  47. data/test/dummy/app/models/user.rb +1 -1
  48. data/test/dummy/app/views/layouts/application.html.erb +0 -0
  49. data/test/dummy/app/views/posts/_form.html.erb +0 -0
  50. data/test/dummy/app/views/posts/edit.html.erb +0 -0
  51. data/test/dummy/app/views/posts/index.html.erb +0 -0
  52. data/test/dummy/app/views/posts/new.html.erb +0 -0
  53. data/test/dummy/app/views/posts/show.html.erb +0 -0
  54. data/test/dummy/config/application.rb +2 -1
  55. data/test/dummy/config/boot.rb +0 -0
  56. data/test/dummy/config/database.yml +1 -1
  57. data/test/dummy/config/environment.rb +0 -0
  58. data/test/dummy/config/environments/development.rb +0 -7
  59. data/test/dummy/config/environments/production.rb +0 -4
  60. data/test/dummy/config/environments/test.rb +0 -0
  61. data/test/dummy/config/initializers/backtrace_silencers.rb +0 -0
  62. data/test/dummy/config/initializers/devise.rb +0 -0
  63. data/test/dummy/config/initializers/inflections.rb +0 -0
  64. data/test/dummy/config/initializers/mime_types.rb +0 -0
  65. data/test/dummy/config/initializers/secret_token.rb +0 -0
  66. data/test/dummy/config/initializers/session_store.rb +0 -0
  67. data/test/dummy/config/initializers/wrap_parameters.rb +0 -0
  68. data/test/dummy/config/locales/en.yml +0 -0
  69. data/test/dummy/config/routes.rb +0 -0
  70. data/test/dummy/config.ru +0 -0
  71. data/test/dummy/db/migrate/20130125101430_create_users.rb +1 -1
  72. data/test/dummy/db/migrate/20130131092406_add_devise_to_users.rb +1 -1
  73. data/test/dummy/db/migrate/20130131142320_create_posts.rb +1 -1
  74. data/test/dummy/db/migrate/20130131160351_devise_otp_add_to_users.rb +2 -2
  75. data/test/dummy/db/test.sqlite3-journal +0 -0
  76. data/test/dummy/lib/assets/.gitkeep +0 -0
  77. data/test/dummy/public/404.html +0 -0
  78. data/test/dummy/public/422.html +0 -0
  79. data/test/dummy/public/500.html +0 -0
  80. data/test/dummy/public/favicon.ico +0 -0
  81. data/test/integration/persistence_test.rb +81 -0
  82. data/test/integration/refresh_test.rb +2 -18
  83. data/test/integration/sign_in_test.rb +14 -4
  84. data/test/integration/token_test.rb +31 -0
  85. data/test/integration_tests_helper.rb +19 -2
  86. data/test/model_tests_helper.rb +0 -0
  87. data/test/models/otp_authenticatable_test.rb +14 -9
  88. data/test/orm/active_record.rb +3 -1
  89. data/test/test_helper.rb +71 -2
  90. metadata +135 -24
  91. data/.travis.yml +0 -11
  92. data/app/controllers/devise_otp/credentials_controller.rb +0 -106
  93. data/app/controllers/devise_otp/tokens_controller.rb +0 -105
  94. data/app/views/devise_otp/credentials/refresh.html.erb +0 -20
  95. data/app/views/devise_otp/credentials/show.html.erb +0 -23
  96. data/app/views/devise_otp/tokens/_token_secret.html.erb +0 -17
  97. data/app/views/devise_otp/tokens/recovery.html.erb +0 -21
  98. data/app/views/devise_otp/tokens/show.html.erb +0 -31
  99. data/lib/devise_otp_authenticatable/mapping.rb +0 -19
@@ -0,0 +1,102 @@
1
+ module DeviseOtp
2
+ module Devise
3
+ class CredentialsController < DeviseController
4
+ helper_method :new_session_path
5
+
6
+ prepend_before_action :authenticate_scope!, :only => [:get_refresh, :set_refresh]
7
+ prepend_before_action :require_no_authentication, :only => [ :show, :update ]
8
+
9
+ #
10
+ # show a request for the OTP token
11
+ #
12
+ def show
13
+ @challenge = params[:challenge]
14
+ @recovery = (params[:recovery] == 'true') && recovery_enabled?
15
+
16
+ if @challenge.nil?
17
+ redirect_to :root
18
+ else
19
+ self.resource = resource_class.find_valid_otp_challenge(@challenge)
20
+ if resource.nil?
21
+ redirect_to :root
22
+ elsif @recovery
23
+ @recovery_count = resource.otp_recovery_counter
24
+ render :show
25
+ else
26
+ render :show
27
+ end
28
+ end
29
+ end
30
+
31
+ #
32
+ # signs the resource in, if the OTP token is valid and the user has a valid challenge
33
+ #
34
+ def update
35
+ resource = resource_class.find_valid_otp_challenge(params[resource_name][:challenge])
36
+ recovery = (params[resource_name][:recovery] == 'true') && recovery_enabled?
37
+ token = params[resource_name][:token]
38
+
39
+ if token.blank?
40
+ otp_set_flash_message(:alert, :token_blank)
41
+ redirect_to otp_credential_path_for(resource_name, :challenge => params[resource_name][:challenge],
42
+ :recovery => recovery)
43
+ elsif resource.nil?
44
+ otp_set_flash_message(:alert, :otp_session_invalid)
45
+ redirect_to new_session_path(resource_name)
46
+ else
47
+ if resource.otp_challenge_valid? && resource.validate_otp_token(params[resource_name][:token], recovery)
48
+ set_flash_message(:success, :signed_in) if is_navigational_format?
49
+ sign_in(resource_name, resource)
50
+
51
+ otp_set_trusted_device_for(resource) if params[:enable_persistence] == "true"
52
+ otp_refresh_credentials_for(resource)
53
+ respond_with resource, :location => after_sign_in_path_for(resource)
54
+ else
55
+ otp_set_flash_message :alert, :token_invalid
56
+ redirect_to new_session_path(resource_name)
57
+ end
58
+ end
59
+ end
60
+
61
+ #
62
+ # displays the request for a credentials refresh
63
+ #
64
+ def get_refresh
65
+ ensure_resource!
66
+ render :refresh
67
+ end
68
+
69
+ #
70
+ # lets the user through is the refresh is valid
71
+ #
72
+ def set_refresh
73
+ ensure_resource!
74
+
75
+ if resource.valid_password?(params[resource_name][:refresh_password])
76
+ done_valid_refresh
77
+ else
78
+ failed_refresh
79
+ end
80
+ end
81
+
82
+ private
83
+
84
+ def done_valid_refresh
85
+ otp_refresh_credentials_for(resource)
86
+ otp_set_flash_message :success, :valid_refresh if is_navigational_format?
87
+
88
+ respond_with resource, :location => otp_fetch_refresh_return_url
89
+ end
90
+
91
+ def failed_refresh
92
+ otp_set_flash_message :alert, :invalid_refresh
93
+ render :refresh
94
+ end
95
+
96
+ def self.controller_path
97
+ "#{::Devise.otp_controller_path}/credentials"
98
+ end
99
+
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,112 @@
1
+ module DeviseOtp
2
+ module Devise
3
+ class TokensController < DeviseController
4
+ include ::Devise::Controllers::Helpers
5
+
6
+ prepend_before_action :ensure_credentials_refresh
7
+ prepend_before_action :authenticate_scope!
8
+
9
+ protect_from_forgery :except => [:clear_persistence, :delete_persistence]
10
+
11
+ #
12
+ # Displays the status of OTP authentication
13
+ #
14
+ def show
15
+ if resource.nil?
16
+ redirect_to stored_location_for(scope) || :root
17
+ else
18
+ render :show
19
+ end
20
+ end
21
+
22
+ #
23
+ # Updates the status of OTP authentication
24
+ #
25
+ def update
26
+ enabled = params[resource_name][:otp_enabled] == '1'
27
+ if (enabled ? resource.enable_otp! : resource.disable_otp!)
28
+ otp_set_flash_message :success, :successfully_updated
29
+ end
30
+
31
+ render :show
32
+ end
33
+
34
+ #
35
+ # Resets OTP authentication, generates new credentials, sets it to off
36
+ #
37
+ def destroy
38
+ if resource.reset_otp_credentials!
39
+ otp_set_flash_message :success, :successfully_reset_creds
40
+ end
41
+
42
+ redirect_to :action => :show
43
+ end
44
+
45
+ #
46
+ # makes the current browser persistent
47
+ #
48
+ def get_persistence
49
+ if otp_set_trusted_device_for(resource)
50
+ otp_set_flash_message :success, :successfully_set_persistence
51
+ end
52
+
53
+ redirect_to :action => :show
54
+ end
55
+
56
+ #
57
+ # clears persistence for the current browser
58
+ #
59
+ def clear_persistence
60
+ if otp_clear_trusted_device_for(resource)
61
+ otp_set_flash_message :success, :successfully_cleared_persistence
62
+ end
63
+
64
+ redirect_to :action => :show
65
+ end
66
+
67
+ #
68
+ # rehash the persistence secret, thus, making all the persistence cookies invalid
69
+ #
70
+ def delete_persistence
71
+ if otp_reset_persistence_for(resource)
72
+ otp_set_flash_message :notice, :successfully_reset_persistence
73
+ end
74
+
75
+ redirect_to :action => :show
76
+ end
77
+
78
+ #
79
+ #
80
+ #
81
+ def recovery
82
+ respond_to do |format|
83
+ format.html
84
+ format.js
85
+ format.text do
86
+ send_data render_to_string(template: "#{controller_path}/recovery_codes"), filename: "otp-recovery-codes.txt", format: "text"
87
+ end
88
+ end
89
+ end
90
+
91
+ private
92
+
93
+ def ensure_credentials_refresh
94
+ ensure_resource!
95
+
96
+ if needs_credentials_refresh?(resource)
97
+ otp_set_flash_message :notice, :need_to_refresh_credentials
98
+ redirect_to refresh_otp_credential_path_for(resource)
99
+ end
100
+ end
101
+
102
+ def scope
103
+ resource_name.to_sym
104
+ end
105
+
106
+ def self.controller_path
107
+ "#{::Devise.otp_controller_path}/tokens"
108
+ end
109
+
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,19 @@
1
+ <h2><%= I18n.t('title', :scope => 'devise.otp.credentials_refresh') %></h2>
2
+ <p><%= I18n.t('explain', :scope => 'devise.otp.credentials_refresh') %></p>
3
+
4
+ <%= form_for(resource, :as => resource_name, :url => [:refresh, resource_name, :otp_credential], :html => { :method => :put, "data-turbo" => false }) do |f| %>
5
+
6
+ <%= render "devise/shared/error_messages", resource: resource %>
7
+
8
+ <div>
9
+ <%= f.label :email %><br />
10
+ <%= f.text_field :email, :disabled => :true%>
11
+ </div>
12
+
13
+ <div>
14
+ <%= f.label :password %><br />
15
+ <%= f.password_field :refresh_password, :autocomplete => :off, :autofocus => true %>
16
+ </div>
17
+
18
+ <div><%= f.submit I18n.t(:go_on, :scope => 'devise.otp.credentials_refresh') %></div>
19
+ <% end %>
@@ -0,0 +1,31 @@
1
+ <h2><%= I18n.t('title', :scope => 'devise.otp.submit_token') %></h2>
2
+ <p><%= I18n.t('explain', :scope => 'devise.otp.submit_token') %></p>
3
+
4
+ <%= form_for(resource, :as => resource_name, :url => [resource_name, :otp_credential], :html => { :method => :put, "data-turbo" => false }) do |f| %>
5
+
6
+ <%= f.hidden_field :challenge, {:value => @challenge} %>
7
+ <%= f.hidden_field :recovery, {:value => @recovery} %>
8
+
9
+ <% if @recovery %>
10
+ <p>
11
+ <%= f.label :token, I18n.t('recovery_prompt', :scope => 'devise.otp.submit_token') %><br />
12
+ <%= f.text_field :otp_recovery_counter, :autocomplete => :off, :disabled => true, :size => 4 %>
13
+ </p>
14
+ <% else %>
15
+ <p>
16
+ <%= f.label :token, I18n.t('prompt', :scope => 'devise.otp.submit_token') %><br />
17
+ </p>
18
+ <% end %>
19
+
20
+ <%= f.text_field :token, :autocomplete => :off, :autofocus => true, :size => 6, :value => '' %><br>
21
+
22
+ <%= label_tag :enable_persistence do %>
23
+ <%= check_box_tag :enable_persistence, true, false %> Remember this browser
24
+ <% end %>
25
+
26
+ <p><%= f.submit I18n.t('submit', :scope => 'devise.otp.submit_token') %></p>
27
+
28
+ <% if !@recovery && recovery_enabled? %>
29
+ <p><%= link_to I18n.t('recovery_link', :scope => 'devise.otp.submit_token'), otp_credential_path_for(resource_name, :challenge => @challenge, :recovery => true) %></p>
30
+ <% end %>
31
+ <% end %>
@@ -0,0 +1,23 @@
1
+ <h3><%= I18n.t('title', :scope => 'devise.otp.token_secret') %></h3>
2
+ <p><%= I18n.t('explain', :scope => 'devise.otp.token_secret') %></p>
3
+
4
+ <%= otp_authenticator_token_image(resource) %>
5
+
6
+ <p>
7
+ <strong><%= I18n.t('manual_provisioning', :scope => 'devise.otp.token_secret') %>:</strong>
8
+ <code><%= resource.otp_auth_secret %></code>
9
+ </p>
10
+
11
+ <p><%= button_to I18n.t('reset_otp', :scope => 'devise.otp.token_secret'), @resource, :method => :delete, :data => { "turbo-method": "DELETE" } %></p>
12
+
13
+ <p>
14
+ <%= I18n.t('reset_explain', :scope => 'devise.otp.token_secret') %>
15
+ <strong><%= I18n.t('reset_explain_warn', :scope => 'devise.otp.token_secret') %></strong>
16
+ </p>
17
+
18
+ <%- if recovery_enabled? %>
19
+ <h3><%= I18n.t('title', :scope => 'devise.otp.tokens.recovery') %></h3>
20
+ <p><%= I18n.t('explain', :scope => 'devise.otp.tokens.recovery') %></p>
21
+ <p><%= link_to I18n.t('codes_list', :scope => 'devise.otp.tokens.recovery'), recovery_otp_token_for(resource_name) %></p>
22
+ <p><%= link_to I18n.t('download_codes', :scope => 'devise.otp.tokens.recovery'), recovery_otp_token_for(resource_name, format: :text) %></p>
23
+ <% end %>
@@ -0,0 +1,12 @@
1
+ <h3><%= I18n.t('title', :scope => 'devise.otp.trusted_browsers') %></h3>
2
+ <p><%= I18n.t('explain', :scope => 'devise.otp.trusted_browsers') %></p>
3
+
4
+ <%- if is_otp_trusted_browser_for? resource %>
5
+ <p><em><%= I18n.t('browser_trusted', :scope => 'devise.otp.trusted_browsers') %></em></p>
6
+ <p><%= link_to I18n.t('trust_remove', :scope => 'devise.otp.trusted_browsers'), persistence_otp_token_path_for(resource_name), :method => :post, :data => { "turbo-method": "POST" } %></p>
7
+ <% else %>
8
+ <p><%= I18n.t('browser_not_trusted', :scope => 'devise.otp.trusted_browsers') %></p>
9
+ <p><%= link_to I18n.t('trust_add', :scope => 'devise.otp.trusted_browsers'), persistence_otp_token_path_for(resource_name) %></p>
10
+ <% end %>
11
+
12
+ <p><%= button_to I18n.t('trust_clear', :scope => 'devise.otp.trusted_browsers'), persistence_otp_token_path_for(resource_name), :method => :delete, :data => { "turbo-method": "DELETE" } %></p>
@@ -0,0 +1,21 @@
1
+ <h2><%= I18n.t('title', :scope => 'devise.otp.tokens.recovery') %></h2>
2
+ <p><%= I18n.t('explain', :scope => 'devise.otp.tokens.recovery') %></p>
3
+
4
+ <table>
5
+ <caption>
6
+ <thead>
7
+ <tr>
8
+ <th><%= I18n.t('sequence', :scope => 'devise.otp.tokens.recovery') %></th>
9
+ <th><%= I18n.t('code', :scope => 'devise.otp.tokens.recovery') %></th>
10
+ </tr>
11
+ </thead>
12
+ <tbody>
13
+ <%- resource.next_otp_recovery_tokens.each do |seq, code| %>
14
+ <tr>
15
+ <td><%= seq %></td>
16
+ <td><%= code %></td>
17
+ </tr>
18
+ <% end %>
19
+ </tbody>
20
+ </caption>
21
+ </table>
@@ -0,0 +1,3 @@
1
+ <% resource.next_otp_recovery_tokens.each do |seq, code| %>
2
+ <%= code %>
3
+ <% end %>
@@ -0,0 +1,21 @@
1
+ <h2><%= I18n.t('title', :scope => 'devise.otp.tokens') %></h2>
2
+ <p><%= I18n.t('explain', :scope => 'devise.otp.tokens') %></p>
3
+
4
+ <%= form_for(resource, :as => resource_name, :url => [resource_name, :otp_token], :html => { :method => :put, "data-turbo" => false }) do |f| %>
5
+
6
+ <%= render "devise/shared/error_messages", resource: resource %>
7
+
8
+ <h3><%= I18n.t('enable_request', :scope => 'devise.otp.tokens') %></h3>
9
+
10
+ <p>
11
+ <%= f.label :otp_enabled, I18n.t('status', :scope => 'devise.otp.tokens') %><br />
12
+ <%= f.check_box :otp_enabled %>
13
+ </p>
14
+
15
+ <p><%= f.submit I18n.t('submit', :scope => 'devise.otp.tokens') %></p>
16
+ <% end %>
17
+
18
+ <%- if resource.otp_enabled? %>
19
+ <%= render :partial => 'token_secret' if resource.otp_enabled? %>
20
+ <%= render :partial => 'trusted_devices' if trusted_devices_enabled? %>
21
+ <% end %>
@@ -34,7 +34,7 @@ en:
34
34
 
35
35
  tokens:
36
36
  title: 'Two-factors Authentication:'
37
- explain: 'Two factors does this and that and that also'
37
+ explain: 'Two factors authentication adds an additional layer of security to your account. When logging in you will be asked for a code that you can generate on a physical device, like your phone.'
38
38
  enable_request: 'Would you like to enable Two Factors Authenticator?'
39
39
 
40
40
  status: 'Enable Two-Factors Authentication.'
@@ -54,13 +54,13 @@ en:
54
54
  sequence: 'Sequence'
55
55
  code: 'Recovery Code'
56
56
  codes_list: 'Here is the list of your recovery codes'
57
+ download_codes: 'Download recovery codes'
57
58
 
58
-
59
- trusted_devices:
60
- title: 'Trusted Devices'
61
- explain: 'Trusted Device are ....'
62
- device_trusted: 'Your device is trusted.'
63
- device_not_trusted: 'Your device is not trusted.'
64
- trust_remove: 'Remove this device from the list of trusted devices'
65
- trust_add: 'Trust this device'
66
- trust_clear: 'Clear the list of trusted devices'
59
+ trusted_browsers:
60
+ title: 'Trusted Browsers'
61
+ explain: 'If you set your browser as trusted, you will not be asked to provide a Two-factor authentication token when logging in from that browser.'
62
+ browser_trusted: 'Your browser is trusted.'
63
+ browser_not_trusted: 'Your browser is not trusted.'
64
+ trust_remove: 'Remove this browser from the list of trusted browsers'
65
+ trust_add: 'Trust this browser'
66
+ trust_clear: 'Clear the list of trusted browsers'
data/devise-otp.gemspec CHANGED
@@ -1,25 +1,30 @@
1
- # -*- encoding: utf-8 -*-
2
- lib = File.expand_path('../lib', __FILE__)
3
- $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
- require 'devise-otp/version'
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'lib/devise-otp/version'
5
4
 
6
5
  Gem::Specification.new do |gem|
7
6
  gem.name = "devise-otp"
8
7
  gem.version = Devise::Otp::VERSION
9
- gem.authors = ["Lele Forzani"]
10
- gem.email = ["lele@windmill.it"]
8
+ gem.authors = ["Lele Forzani", "Josef Strzibny"]
9
+ gem.email = ["lele@windmill.it", "strzibny@strzibny.name"]
11
10
  gem.description = %q{Time Based OTP/rfc6238 compatible authentication for Devise}
12
11
  gem.summary = %q{Time Based OTP/rfc6238 compatible authentication for Devise}
13
12
  gem.homepage = "http://git.windmill.it/wm/devise-otp"
14
13
 
15
14
  gem.files = `git ls-files`.split($/)
16
- gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
17
15
  gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
18
16
  gem.require_paths = ["lib"]
19
17
 
20
- gem.add_runtime_dependency 'rails', '>= 3.2.6', '< 5'
21
- gem.add_runtime_dependency 'devise', '>= 3.1.0', '< 4.0.0'
18
+ gem.add_runtime_dependency 'rails', '>= 7.0', '< 7.1'
19
+ gem.add_runtime_dependency 'devise', '>= 4.8.0', '< 4.9.0'
22
20
  gem.add_runtime_dependency 'rotp', '>= 2.0.0'
23
21
 
22
+ gem.add_development_dependency "capybara"
23
+ gem.add_development_dependency "cuprite"
24
+ gem.add_development_dependency "minitest-reporters", ">= 0.5.0"
25
+ gem.add_development_dependency "puma"
26
+ gem.add_development_dependency "rdoc"
27
+ gem.add_development_dependency "shoulda"
28
+ gem.add_development_dependency "sprockets-rails"
24
29
  gem.add_development_dependency "sqlite3"
25
30
  end
data/docs/QR_CODES.md ADDED
@@ -0,0 +1,48 @@
1
+ # QR code rendering
2
+
3
+ By default, Devise OTP assumes that you use [Sprockets](https://github.com/rails/sprockets) to render assets and so will use the ([qrcode.js](/app/assets/javascripts/qrcode.js)) embeded library to render the QR code.
4
+
5
+ To do that, add the the following line to your `application.js` file:
6
+
7
+ //= require devise-otp
8
+
9
+ You can change this behavior by overriding the `otp_authenticator_token_image` method in your view helper to call `otp_authenticator_token_image_google`:
10
+
11
+ ```ruby
12
+ def otp_authenticator_token_image(resource)
13
+ otp_authenticator_token_image_google(resource.otp_provisioning_uri)
14
+ end
15
+ ```
16
+
17
+ This will call [Google API](https://github.com/wmlele/devise-otp/tree/master/lib/devise_otp_authenticatable/controllers/helpers.rb#L160) to render the QR code.
18
+
19
+ If your application is configured to use CSP policies, you'll need to authorize `chart.googleapis.com`. Here's an example with [secure_headers](https://github.com/github/secure_headers)):
20
+
21
+ ```ruby
22
+ config.csp[:img_src] << 'chart.googleapis.com'
23
+ ```
24
+
25
+ A third option consists in installing [jquery-qrcode]https://github.com/jeromeetienne/jquery-qrcode with Yarn or [shakapacker](https://github.com/shakacode/shakapacker) and overriding `otp_authenticator_token_image` to render some HTML :
26
+
27
+ ```ruby
28
+ def otp_authenticator_token_image(resource)
29
+ tag(:span, data: { toggle: 'qrcode', otp_url: resource.otp_provisioning_uri, width: 192, height: 192, render: 'canvas' })
30
+ end
31
+ ```
32
+ The QR code is then rendered by `jquery-qrcode` by setting a JS listener in your `application.js` :
33
+
34
+ ```js
35
+ $(document).on('turbo:load', function() {
36
+ return $('[data-toggle=qrcode]').each(function() {
37
+ var data;
38
+ data = $(this).data();
39
+ return $(this).qrcode({
40
+ text: data['otpUrl'],
41
+ width: data['width'],
42
+ height: data['height'],
43
+ render: data['render']
44
+ });
45
+ });
46
+ });
47
+ ```
48
+ This way you don't rely on external services to render the QR codes.
@@ -1,5 +1,5 @@
1
1
  module Devise
2
2
  module Otp
3
- VERSION = "0.2.0"
3
+ VERSION = "0.3.0"
4
4
  end
5
5
  end
data/lib/devise-otp.rb CHANGED
@@ -9,10 +9,8 @@ require 'active_support/concern'
9
9
 
10
10
  require 'devise'
11
11
 
12
-
13
12
  module Devise
14
13
 
15
-
16
14
  #
17
15
  #
18
16
  #
@@ -28,14 +26,21 @@ module Devise
28
26
  #
29
27
  #
30
28
  #
31
- mattr_accessor :recovery_tokens
32
- @@recovery_tokens = 5 ## false to disable
29
+ mattr_accessor :otp_recovery_tokens
30
+ @@otp_recovery_tokens = 10 ## false to disable
31
+
32
+ #
33
+ # If the user is given the chance to set his browser as trusted, how long will it stay trusted.
34
+ # set to nil/false to disable the ability to set a device as trusted
35
+ #
36
+ mattr_accessor :otp_trust_persistence
37
+ @@otp_trust_persistence = 30.days
33
38
 
34
39
  #
35
40
  #
36
41
  #
37
42
  mattr_accessor :otp_drift_window
38
- @@otp_drift_window = 3 # in seconds
43
+ @@otp_drift_window = 3 # in minutes
39
44
 
40
45
  #
41
46
  # if the user wants to change Otp settings,
@@ -46,20 +51,24 @@ module Devise
46
51
  @@otp_credentials_refresh = 15.minutes # or like 15.minutes, false to disable
47
52
 
48
53
  #
49
- # the user identifier for the token is <email>/Application_name
54
+ # the name of the token issuer
50
55
  #
51
- mattr_accessor :otp_uri_application
52
- @@otp_uri_application = Rails.application.class.parent_name
56
+ mattr_accessor :otp_issuer
57
+ @@otp_issuer = Rails.application.class.module_parent_name
53
58
 
54
- module Otp
55
59
 
60
+ #
61
+ # custom view path
62
+ #
63
+ mattr_accessor :otp_controller_path
64
+ @@otp_controller_path = "devise"
65
+
66
+ module Otp
56
67
  end
57
68
  end
58
69
 
59
70
  module DeviseOtpAuthenticatable
60
-
61
71
  autoload :Hooks, 'devise_otp_authenticatable/hooks'
62
- autoload :Mapping, 'devise_otp_authenticatable/mapping'
63
72
 
64
73
  module Controllers
65
74
  autoload :Helpers, 'devise_otp_authenticatable/controllers/helpers'
@@ -67,10 +76,9 @@ module DeviseOtpAuthenticatable
67
76
  end
68
77
  end
69
78
 
70
-
71
79
  require 'devise_otp_authenticatable/routes'
72
80
  require 'devise_otp_authenticatable/engine'
73
81
 
74
82
  Devise.add_module :otp_authenticatable,
75
- :controller => :otp_tokens,
76
- :model => 'devise_otp_authenticatable/models/otp_authenticatable', :route => :otp
83
+ :controller => :tokens,
84
+ :model => 'devise_otp_authenticatable/models/otp_authenticatable', :route => :otp