devise-otp 0.1.1

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 (83) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +42 -0
  3. data/.travis.yml +11 -0
  4. data/Gemfile +25 -0
  5. data/LICENSE.txt +22 -0
  6. data/README.md +124 -0
  7. data/Rakefile +42 -0
  8. data/app/controllers/devise_otp/credentials_controller.rb +106 -0
  9. data/app/controllers/devise_otp/tokens_controller.rb +105 -0
  10. data/app/views/devise_otp/credentials/refresh.html.erb +20 -0
  11. data/app/views/devise_otp/credentials/show.html.erb +23 -0
  12. data/app/views/devise_otp/tokens/_token_secret.html.erb +17 -0
  13. data/app/views/devise_otp/tokens/recovery.html.erb +21 -0
  14. data/app/views/devise_otp/tokens/show.html.erb +31 -0
  15. data/config/locales/en.yml +66 -0
  16. data/devise-otp.gemspec +25 -0
  17. data/lib/devise-otp.rb +76 -0
  18. data/lib/devise-otp/version.rb +5 -0
  19. data/lib/devise_otp_authenticatable/controllers/helpers.rb +144 -0
  20. data/lib/devise_otp_authenticatable/controllers/url_helpers.rb +35 -0
  21. data/lib/devise_otp_authenticatable/engine.rb +23 -0
  22. data/lib/devise_otp_authenticatable/hooks.rb +13 -0
  23. data/lib/devise_otp_authenticatable/hooks/sessions.rb +57 -0
  24. data/lib/devise_otp_authenticatable/mapping.rb +19 -0
  25. data/lib/devise_otp_authenticatable/models/otp_authenticatable.rb +140 -0
  26. data/lib/devise_otp_authenticatable/routes.rb +30 -0
  27. data/lib/generators/active_record/devise_otp_generator.rb +13 -0
  28. data/lib/generators/active_record/templates/migration.rb +28 -0
  29. data/lib/generators/devise_otp/devise_otp_generator.rb +17 -0
  30. data/lib/generators/devise_otp/install_generator.rb +31 -0
  31. data/lib/generators/devise_otp/views_generator.rb +19 -0
  32. data/test/dummy/README.rdoc +261 -0
  33. data/test/dummy/Rakefile +7 -0
  34. data/test/dummy/app/assets/javascripts/application.js +13 -0
  35. data/test/dummy/app/assets/stylesheets/application.css +13 -0
  36. data/test/dummy/app/controllers/application_controller.rb +4 -0
  37. data/test/dummy/app/controllers/posts_controller.rb +83 -0
  38. data/test/dummy/app/helpers/application_helper.rb +2 -0
  39. data/test/dummy/app/helpers/posts_helper.rb +2 -0
  40. data/test/dummy/app/mailers/.gitkeep +0 -0
  41. data/test/dummy/app/models/post.rb +2 -0
  42. data/test/dummy/app/models/user.rb +20 -0
  43. data/test/dummy/app/views/layouts/application.html.erb +14 -0
  44. data/test/dummy/app/views/posts/_form.html.erb +25 -0
  45. data/test/dummy/app/views/posts/edit.html.erb +6 -0
  46. data/test/dummy/app/views/posts/index.html.erb +25 -0
  47. data/test/dummy/app/views/posts/new.html.erb +5 -0
  48. data/test/dummy/app/views/posts/show.html.erb +15 -0
  49. data/test/dummy/config.ru +4 -0
  50. data/test/dummy/config/application.rb +68 -0
  51. data/test/dummy/config/boot.rb +10 -0
  52. data/test/dummy/config/database.yml +25 -0
  53. data/test/dummy/config/environment.rb +5 -0
  54. data/test/dummy/config/environments/development.rb +37 -0
  55. data/test/dummy/config/environments/production.rb +73 -0
  56. data/test/dummy/config/environments/test.rb +36 -0
  57. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  58. data/test/dummy/config/initializers/devise.rb +253 -0
  59. data/test/dummy/config/initializers/inflections.rb +15 -0
  60. data/test/dummy/config/initializers/mime_types.rb +5 -0
  61. data/test/dummy/config/initializers/secret_token.rb +8 -0
  62. data/test/dummy/config/initializers/session_store.rb +8 -0
  63. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  64. data/test/dummy/config/locales/en.yml +5 -0
  65. data/test/dummy/config/routes.rb +6 -0
  66. data/test/dummy/db/migrate/20130125101430_create_users.rb +9 -0
  67. data/test/dummy/db/migrate/20130131092406_add_devise_to_users.rb +53 -0
  68. data/test/dummy/db/migrate/20130131142320_create_posts.rb +10 -0
  69. data/test/dummy/db/migrate/20130131160351_devise_otp_add_to_users.rb +28 -0
  70. data/test/dummy/lib/assets/.gitkeep +0 -0
  71. data/test/dummy/public/404.html +26 -0
  72. data/test/dummy/public/422.html +26 -0
  73. data/test/dummy/public/500.html +25 -0
  74. data/test/dummy/public/favicon.ico +0 -0
  75. data/test/dummy/script/rails +6 -0
  76. data/test/integration/refresh_test.rb +92 -0
  77. data/test/integration/sign_in_test.rb +77 -0
  78. data/test/integration_tests_helper.rb +48 -0
  79. data/test/model_tests_helper.rb +22 -0
  80. data/test/models/otp_authenticatable_test.rb +116 -0
  81. data/test/orm/active_record.rb +4 -0
  82. data/test/test_helper.rb +19 -0
  83. metadata +237 -0
@@ -0,0 +1,20 @@
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 }) do |f| %>
5
+
6
+ <%= devise_error_messages! %>
7
+
8
+ <div><%= f.label :email %><br />
9
+ <%= f.text_field :email, :disabled => :true%></div>
10
+
11
+ <div><%= f.label :password %><br />
12
+ <%= f.password_field :refresh_password, :autocomplete => :off, :autofocus => true %></div>
13
+
14
+ <%- if resource.otp_enabled? %>
15
+ <div><%= f.label :token, I18n.t(:token, {:scope => 'devise.otp.credentials_refresh'}) %></p><br />
16
+ <%= f.password_field :token, :autocomplete => :off%></div>
17
+ <% end %>
18
+
19
+ <div><%= f.submit I18n.t(:go_on, {:scope => 'devise.otp.credentials_refresh'}) %></div>
20
+ <% end %>
@@ -0,0 +1,23 @@
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 }) do |f| %>
5
+
6
+ <%= f.hidden_field :challenge, {:value => @challenge} %>
7
+ <%= f.hidden_field :recovery, {:value => @recovery} %>
8
+
9
+ <%- if @recovery %>
10
+ <p><%= f.label :token, I18n.t('recovery_prompt', {:scope => 'devise.otp.submit_token'}) %><br />
11
+ <%= f.text_field :otp_recovery_counter, :autocomplete => :off, :disabled => true, :size => 4 %>
12
+ <% else %>
13
+ <p><%= f.label :token, I18n.t('prompt', {:scope => 'devise.otp.submit_token'}) %><br />
14
+ <% end %>
15
+
16
+ <%= f.text_field :token, :autocomplete => :off, :autofocus => true, :size => 6, :value => '' %>
17
+ </p>
18
+
19
+ <p><%= f.submit I18n.t('submit', {:scope => 'devise.otp.submit_token'}) %></p>
20
+ <%- if !@recovery && resource_class.recovery_tokens %>
21
+ <p><%= link_to I18n.t('recovery_link', {:scope => 'devise.otp.submit_token'}), otp_credential_path_for(resource_name, :challenge => @challenge, :recovery => true) %></p>
22
+ <% end %>
23
+ <% end %>
@@ -0,0 +1,17 @@
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><strong><%= I18n.t('manual_provisioning', {:scope => 'devise.otp.token_secret'}) %>:</strong>
7
+ <code><%= resource.otp_auth_secret %></code></p>
8
+
9
+ <p><%= link_to I18n.t('reset_otp', {:scope => 'devise.otp.token_secret'}), @resource, :method => :delete %></p>
10
+ <p><%= I18n.t('reset_explain', {:scope => 'devise.otp.token_secret'}) %>
11
+ <strong><%= I18n.t('reset_explain_warn', {:scope => 'devise.otp.token_secret'}) %></strong></p>
12
+
13
+ <%- if recovery_enabled? %>
14
+ <h3><%= I18n.t('title', {:scope => 'devise.otp.tokens.recovery'}) %></h3>
15
+ <p><%= I18n.t('explain', {:scope => 'devise.otp.tokens.recovery'}) %></p>
16
+ <p><%= link_to I18n.t('codes_list', {:scope => 'devise.otp.tokens.recovery'}), recovery_otp_token_for(resource_name) %></p>
17
+ <% end %>
@@ -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,31 @@
1
+ <h2><%= I18n.t('title', {:scope => 'devise.otp.tokens'}) %></h2>
2
+ <p><%= I18n.t('caption', {:scope => 'devise.otp.tokens'}) %></p>
3
+
4
+ <p><%= I18n.t('explain', {:scope => 'devise.otp.tokens'}) %></p>
5
+
6
+ <%= form_for(resource, :as => resource_name, :url => [resource_name, :otp_token], :html => { :method => :put }) do |f| %>
7
+
8
+ <%= devise_error_messages! %>
9
+
10
+ <h3><%= I18n.t('enable_request', {:scope => 'devise.otp.tokens'}) %></h3>
11
+
12
+ <p><%= f.label :otp_enabled, I18n.t('status', {:scope => 'devise.otp.tokens'}) %><br />
13
+ <%= f.check_box :otp_enabled %></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
+
21
+ <h3><%= I18n.t('title', {:scope => 'devise.otp.trusted_devices'}) %></h3>
22
+ <p><%= I18n.t('explain', {:scope => 'devise.otp.trusted_devices'}) %></p>
23
+ <%- if is_otp_trusted_device_for? resource %>
24
+ <p><em><%= I18n.t('device_trusted', {:scope => 'devise.otp.trusted_devices'}) %></em></p>
25
+ <p><%= link_to I18n.t('trust_remove', {:scope => 'devise.otp.trusted_devices'}), persistence_otp_token_path_for(resource_name), :method => :post %></p>
26
+ <% else %>
27
+ <p><%= I18n.t('device_not_trusted', {:scope => 'devise.otp.trusted_devices'}) %></p>
28
+ <p><%= link_to I18n.t('trust_add', {:scope => 'devise.otp.trusted_devices'}), persistence_otp_token_path_for(resource_name) %></p>
29
+ <% end %>
30
+ <p><%= link_to I18n.t('trust_clear', {:scope => 'devise.otp.trusted_devices'}), persistence_otp_token_path_for(resource_name), :method => :delete %></p>
31
+ <% end %>
@@ -0,0 +1,66 @@
1
+ en:
2
+ devise:
3
+
4
+ otp:
5
+ submit_token:
6
+ title: 'Check Token'
7
+ explain: "You're getting this because you enabled two-factors authentication on your account"
8
+ prompt: 'Please enter your two-factors authentication token:'
9
+ recovery_prompt: 'Please enter your recovery code:'
10
+ submit: 'Submit Token'
11
+ recovery_link: "I don't have my device, I want to use a recovery code"
12
+
13
+ credentials:
14
+ token_invalid: 'The token you provided was invalid.'
15
+ token_blank: 'You need to type in the token you generated with your device.'
16
+ need_to_refresh_credentials: 'We need to check your credentials before you can change these settings.'
17
+ valid_refresh: 'Thank you, your credentials were accepted.'
18
+ invalid_refresh: 'Sorry, you provided the wrong credentials.'
19
+
20
+ credentials_refresh:
21
+ title: 'Please enter your password again.'
22
+ explain: 'In order to ensure this is safe, please enter your password again.'
23
+ go_on: 'Continue...'
24
+ identity: 'Identity:'
25
+ token: 'Your two-factors authentication token'
26
+
27
+ token_secret:
28
+ title: 'Your token secret'
29
+ explain: 'Take a photo of this QR code with your mobile'
30
+ manual_provisioning: 'Manual provisioning code'
31
+ reset_otp: 'Reset your Two Factors Authentication status'
32
+ reset_explain: 'This will reset your credentials, and disable two-factors authentication.'
33
+ reset_explain_warn: 'You will need to enroll your mobile device again.'
34
+
35
+ tokens:
36
+ title: 'Two-factors Authentication:'
37
+ explain: 'Two factors does this and that and that also'
38
+ enable_request: 'Would you like to enable Two Factors Authenticator?'
39
+
40
+ status: 'Enable Two-Factors Authentication.'
41
+ submit: 'Continue...'
42
+
43
+ successfully_updated: 'Your two-factors authentication settings have been updated.'
44
+ successfully_reset_creds: 'Your two-factors credentials has been reset.'
45
+ successfully_set_persistence: 'Your device is now trusted.'
46
+ successfully_cleared_persistence: 'Your device has been removed from the list of trusted devices.'
47
+ successfully_reset_persistence: 'Your list of trusted devices has been cleared.'
48
+
49
+ need_to_refresh_credentials: 'We need to check your credentials before you can change these settings.'
50
+
51
+ recovery:
52
+ title: 'Your Emergency Recovery Codes'
53
+ explain: 'Take note or print these recovery codes. The will allow you to log back in in case your token device is lost, stolen, or unavailable.'
54
+ sequence: 'Sequence'
55
+ code: 'Recovery Code'
56
+ codes_list: 'Here is the list of your recovery codes'
57
+
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'
@@ -0,0 +1,25 @@
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'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "devise-otp"
8
+ gem.version = Devise::Otp::VERSION
9
+ gem.authors = ["Lele Forzani"]
10
+ gem.email = ["lele@windmill.it"]
11
+ gem.description = %q{Time Based OTP/rfc6238 compatible authentication for Devise}
12
+ gem.summary = %q{Time Based OTP/rfc6238 compatible authentication for Devise}
13
+ gem.homepage = "http://git.windmill.it/wm/devise-otp"
14
+
15
+ gem.files = `git ls-files`.split($/)
16
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
17
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
18
+ gem.require_paths = ["lib"]
19
+
20
+ gem.add_runtime_dependency 'rails', '>= 3.2.6', '< 5'
21
+ gem.add_runtime_dependency 'devise', '~> 3.1.0'
22
+ gem.add_runtime_dependency 'rotp', '>= 1.4.0'
23
+
24
+ gem.add_development_dependency "sqlite3"
25
+ end
@@ -0,0 +1,76 @@
1
+ require "devise-otp/version"
2
+
3
+ # cherry pick active-support extensions
4
+ #require 'active_record/connection_adapters/abstract/schema_definitions'
5
+ require 'active_support/core_ext/integer'
6
+ require 'active_support/core_ext/string'
7
+ require 'active_support/ordered_hash'
8
+ require 'active_support/concern'
9
+
10
+ require 'devise'
11
+
12
+
13
+ module Devise
14
+
15
+
16
+ #
17
+ #
18
+ #
19
+ mattr_accessor :otp_mandatory
20
+ @@otp_mandatory = false
21
+
22
+ #
23
+ #
24
+ #
25
+ mattr_accessor :otp_authentication_timeout
26
+ @@otp_authentication_timeout = 3.minutes
27
+
28
+ #
29
+ #
30
+ #
31
+ mattr_accessor :recovery_tokens
32
+ @@recovery_tokens = 5 ## false to disable
33
+
34
+ #
35
+ #
36
+ #
37
+ mattr_accessor :otp_drift_window
38
+ @@otp_drift_window = 3 # in seconds
39
+
40
+ #
41
+ # if the user wants to change Otp settings,
42
+ # ask the password (and the token) again if this time has passed since the last
43
+ # time the user has provided valid credentials
44
+ #
45
+ mattr_accessor :otp_credentials_refresh
46
+ @@otp_credentials_refresh = 15.minutes # or like 15.minutes, false to disable
47
+
48
+ #
49
+ # the user identifier for the token is <email>/Application_name
50
+ #
51
+ mattr_accessor :otp_uri_application
52
+ @@otp_uri_application = Rails.application.class.parent_name
53
+
54
+ module Otp
55
+
56
+ end
57
+ end
58
+
59
+ module DeviseOtpAuthenticatable
60
+
61
+ autoload :Hooks, 'devise_otp_authenticatable/hooks'
62
+ autoload :Mapping, 'devise_otp_authenticatable/mapping'
63
+
64
+ module Controllers
65
+ autoload :Helpers, 'devise_otp_authenticatable/controllers/helpers'
66
+ autoload :UrlHelpers, 'devise_otp_authenticatable/controllers/url_helpers'
67
+ end
68
+ end
69
+
70
+
71
+ require 'devise_otp_authenticatable/routes'
72
+ require 'devise_otp_authenticatable/engine'
73
+
74
+ Devise.add_module :otp_authenticatable,
75
+ :controller => :otp_tokens,
76
+ :model => 'devise_otp_authenticatable/models/otp_authenticatable', :route => :otp
@@ -0,0 +1,5 @@
1
+ module Devise
2
+ module Otp
3
+ VERSION = "0.1.1"
4
+ end
5
+ end
@@ -0,0 +1,144 @@
1
+ module DeviseOtpAuthenticatable
2
+
3
+ module Controllers
4
+ module Helpers
5
+
6
+
7
+ def authenticate_scope!
8
+ send(:"authenticate_#{resource_name}!", :force => true)
9
+ self.resource = send("current_#{resource_name}")
10
+ end
11
+
12
+ #
13
+ # similar to DeviseController#set_flash_message, but sets the scope inside
14
+ # the otp controller
15
+ #
16
+ def otp_set_flash_message(key, kind, options={})
17
+ options[:scope] ||= "devise.otp.#{controller_name}"
18
+ options[:default] = Array(options[:default]).unshift(kind.to_sym)
19
+ options[:resource_name] = resource_name
20
+ options = devise_i18n_options(options) if respond_to?(:devise_i18n_options, true)
21
+ message = I18n.t("#{options[:resource_name]}.#{kind}", options)
22
+ flash[key] = message if message.present?
23
+ end
24
+
25
+ def otp_t()
26
+
27
+ end
28
+
29
+
30
+ def recovery_enabled?
31
+ resource_class.recovery_tokens && (resource_class.recovery_tokens > 0)
32
+ end
33
+
34
+ #
35
+ # Sanity check for resource validity
36
+ #
37
+ def ensure_resource!
38
+ if resource.nil?
39
+ raise ArgumentError, "Should not happen"
40
+ end
41
+ end
42
+
43
+
44
+ # fixme do cookies and persistence need to be scoped? probably
45
+
46
+ #
47
+ # check if the resource needs a credentials refresh. IE, they need to be asked a password again to access
48
+ # this resource.
49
+ #
50
+ def needs_credentials_refresh?(resource)
51
+ return false unless resource.class.otp_credentials_refresh
52
+
53
+ (!session[otp_scoped_refresh_property].present? ||
54
+ (session[otp_scoped_refresh_property] < DateTime.now)).tap { |need| otp_set_refresh_return_url if need }
55
+ end
56
+
57
+ #
58
+ # credentials are refreshed
59
+ #
60
+ def otp_refresh_credentials_for(resource)
61
+ return false unless resource.class.otp_credentials_refresh
62
+ session[otp_scoped_refresh_property] = (Time.now + resource.class.otp_credentials_refresh)
63
+ end
64
+
65
+
66
+ #
67
+ # is the current browser trusted?
68
+ #
69
+ def is_otp_trusted_device_for?(resource)
70
+ if cookies[otp_scoped_persistence_cookie].present?
71
+ cookies.signed[otp_scoped_persistence_cookie] ==
72
+ [resource.class.serialize_into_cookie(resource), resource.otp_persistence_seed].tap do
73
+ end
74
+ else
75
+ false
76
+ end
77
+ end
78
+
79
+ #
80
+ # make the current browser trusted
81
+ #
82
+ def otp_set_trusted_device_for(resource)
83
+ cookies.signed[otp_scoped_persistence_cookie] = {
84
+ :httponly => true,
85
+ :expires => 30.days.from_now,
86
+ :value => [resource.class.serialize_into_cookie(resource), resource.otp_persistence_seed]
87
+ }
88
+ end
89
+
90
+ def otp_set_refresh_return_url
91
+ session[otp_scoped_refresh_return_url_property] = request.fullpath
92
+ end
93
+
94
+ def otp_fetch_refresh_return_url
95
+ session.delete(otp_scoped_refresh_return_url_property) { :root }
96
+
97
+ end
98
+
99
+ def otp_scoped_refresh_return_url_property
100
+ "otp_#{resource_name}refresh_return_url".to_sym
101
+ end
102
+
103
+ def otp_scoped_refresh_property
104
+ "otp_#{resource_name}refresh_after".to_sym
105
+ end
106
+
107
+ def otp_scoped_persistence_cookie
108
+ "otp_#{resource_name}_device_trusted"
109
+ end
110
+
111
+ #
112
+ # make the current browser NOT trusted
113
+ #
114
+ def otp_clear_trusted_device_for(resource)
115
+ cookies.delete(otp_scoped_persistence_cookie)
116
+ end
117
+
118
+
119
+ #
120
+ # clears the persistence list for this kind of resource
121
+ #
122
+ def otp_reset_persistence_for(resource)
123
+ otp_clear_trusted_device_for(resource)
124
+ resource.reset_otp_persistence!
125
+ end
126
+
127
+ #
128
+ # returns the URL for the QR Code to initialize the Authenticator device
129
+ #
130
+ def otp_authenticator_token_image(resource)
131
+ otp_authenticator_token_image_google(resource.otp_provisioning_uri)
132
+ end
133
+
134
+ private
135
+
136
+ def otp_authenticator_token_image_google(otp_url)
137
+ otp_url = Rack::Utils.escape(otp_url)
138
+ url = "https://chart.googleapis.com/chart?chs=200x200&chld=M|0&cht=qr&chl=#{otp_url}"
139
+ image_tag(url, :alt => 'OTP Url QRCode')
140
+ end
141
+
142
+ end
143
+ end
144
+ end
@@ -0,0 +1,35 @@
1
+ module DeviseOtpAuthenticatable
2
+ module Controllers
3
+
4
+ module UrlHelpers
5
+
6
+
7
+ def recovery_otp_token_for(resource_or_scope, opts = {})
8
+ scope = Devise::Mapping.find_scope!(resource_or_scope)
9
+ send("recovery_#{scope}_otp_token_path", opts)
10
+ end
11
+
12
+
13
+ def refresh_otp_credential_path_for(resource_or_scope, opts = {})
14
+ scope = Devise::Mapping.find_scope!(resource_or_scope)
15
+ send("refresh_#{scope}_otp_credential_path", opts)
16
+ end
17
+
18
+ def persistence_otp_token_path_for(resource_or_scope, opts = {})
19
+ scope = Devise::Mapping.find_scope!(resource_or_scope)
20
+ send("persistence_#{scope}_otp_token_path", opts)
21
+ end
22
+
23
+ def otp_token_path_for(resource_or_scope, opts = {})
24
+ scope = Devise::Mapping.find_scope!(resource_or_scope)
25
+ send("#{scope}_otp_token_path", opts)
26
+ end
27
+
28
+ def otp_credential_path_for(resource_or_scope, opts = {})
29
+ scope = Devise::Mapping.find_scope!(resource_or_scope)
30
+ send("#{scope}_otp_credential_path", opts)
31
+ end
32
+
33
+ end
34
+ end
35
+ end