devise-otp 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
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