devise-2fa 0.1.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 (95) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +36 -0
  3. data/.hound.yml +2 -0
  4. data/.ruby-style.yml +1248 -0
  5. data/.travis.yml +28 -0
  6. data/Gemfile +25 -0
  7. data/LICENSE +21 -0
  8. data/README.md +130 -0
  9. data/Rakefile +41 -0
  10. data/app/controllers/devise/credentials_controller.rb +100 -0
  11. data/app/controllers/devise/tokens_controller.rb +99 -0
  12. data/app/views/devise/credentials/refresh.html.erb +20 -0
  13. data/app/views/devise/credentials/show.html.erb +23 -0
  14. data/app/views/devise/tokens/_token_secret.html.erb +19 -0
  15. data/app/views/devise/tokens/_trusted_devices.html.erb +10 -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 +19 -0
  19. data/config/locales/en.yml +57 -0
  20. data/devise-2fa.gemspec +27 -0
  21. data/lib/devise-2fa.rb +74 -0
  22. data/lib/devise-2fa/version.rb +5 -0
  23. data/lib/devise_two_factorable/controllers/helpers.rb +136 -0
  24. data/lib/devise_two_factorable/controllers/url_helpers.rb +30 -0
  25. data/lib/devise_two_factorable/engine.rb +22 -0
  26. data/lib/devise_two_factorable/helpers.rb +136 -0
  27. data/lib/devise_two_factorable/hooks.rb +11 -0
  28. data/lib/devise_two_factorable/hooks/sessions.rb +49 -0
  29. data/lib/devise_two_factorable/mapping.rb +12 -0
  30. data/lib/devise_two_factorable/models/two_factorable.rb +131 -0
  31. data/lib/devise_two_factorable/routes.rb +26 -0
  32. data/lib/devise_two_factorable/two_factorable.rb +131 -0
  33. data/lib/generators/active_record/devise_two_factor_generator.rb +32 -0
  34. data/lib/generators/active_record/templates/migration.rb +27 -0
  35. data/lib/generators/devise_two_factor/devise_two_factor_generator.rb +16 -0
  36. data/lib/generators/devise_two_factor/install_generator.rb +52 -0
  37. data/lib/generators/devise_two_factor/views_generator.rb +19 -0
  38. data/lib/generators/mongoid/devise_two_factor_generator.rb +34 -0
  39. data/test/dummy/README.rdoc +261 -0
  40. data/test/dummy/Rakefile +7 -0
  41. data/test/dummy/app/assets/javascripts/application.js +13 -0
  42. data/test/dummy/app/assets/stylesheets/application.css +13 -0
  43. data/test/dummy/app/controllers/application_controller.rb +4 -0
  44. data/test/dummy/app/controllers/posts_controller.rb +83 -0
  45. data/test/dummy/app/helpers/application_helper.rb +2 -0
  46. data/test/dummy/app/helpers/posts_helper.rb +2 -0
  47. data/test/dummy/app/mailers/.gitkeep +0 -0
  48. data/test/dummy/app/models/post.rb +2 -0
  49. data/test/dummy/app/models/user.rb +20 -0
  50. data/test/dummy/app/views/layouts/application.html.erb +14 -0
  51. data/test/dummy/app/views/posts/_form.html.erb +25 -0
  52. data/test/dummy/app/views/posts/edit.html.erb +6 -0
  53. data/test/dummy/app/views/posts/index.html.erb +25 -0
  54. data/test/dummy/app/views/posts/new.html.erb +5 -0
  55. data/test/dummy/app/views/posts/show.html.erb +15 -0
  56. data/test/dummy/config.ru +4 -0
  57. data/test/dummy/config/application.rb +67 -0
  58. data/test/dummy/config/boot.rb +10 -0
  59. data/test/dummy/config/database.yml +25 -0
  60. data/test/dummy/config/environment.rb +5 -0
  61. data/test/dummy/config/environments/development.rb +37 -0
  62. data/test/dummy/config/environments/production.rb +73 -0
  63. data/test/dummy/config/environments/test.rb +36 -0
  64. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  65. data/test/dummy/config/initializers/devise.rb +251 -0
  66. data/test/dummy/config/initializers/inflections.rb +15 -0
  67. data/test/dummy/config/initializers/mime_types.rb +5 -0
  68. data/test/dummy/config/initializers/secret_token.rb +8 -0
  69. data/test/dummy/config/initializers/session_store.rb +8 -0
  70. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  71. data/test/dummy/config/locales/en.yml +5 -0
  72. data/test/dummy/config/routes.rb +6 -0
  73. data/test/dummy/db/migrate/20130125101430_create_users.rb +9 -0
  74. data/test/dummy/db/migrate/20130131092406_add_devise_to_users.rb +52 -0
  75. data/test/dummy/db/migrate/20130131142320_create_posts.rb +10 -0
  76. data/test/dummy/db/migrate/20130131160351_devise_otp_add_to_users.rb +28 -0
  77. data/test/dummy/lib/assets/.gitkeep +0 -0
  78. data/test/dummy/public/404.html +26 -0
  79. data/test/dummy/public/422.html +26 -0
  80. data/test/dummy/public/500.html +25 -0
  81. data/test/dummy/public/favicon.ico +0 -0
  82. data/test/dummy/script/rails +6 -0
  83. data/test/integration/persistence_test.rb +63 -0
  84. data/test/integration/refresh_test.rb +103 -0
  85. data/test/integration/sign_in_test.rb +85 -0
  86. data/test/integration/token_test.rb +30 -0
  87. data/test/integration_tests_helper.rb +64 -0
  88. data/test/model_tests_helper.rb +20 -0
  89. data/test/models/two_factorable_test.rb +120 -0
  90. data/test/orm/active_record.rb +4 -0
  91. data/test/orm/mongoid.rb +13 -0
  92. data/test/support/mongoid.yml +6 -0
  93. data/test/support/symmetric_encryption.yml +70 -0
  94. data/test/test_helper.rb +18 -0
  95. metadata +269 -0
@@ -0,0 +1,28 @@
1
+ language: ruby
2
+ sudo: false
3
+ cache: bundler
4
+ rvm:
5
+ - 2.1
6
+ - 2.2
7
+ - 2.3
8
+ - rbx-2
9
+ - rbx
10
+ - jruby-9.0.5.0
11
+ script: bundle exec rake test
12
+ env:
13
+ matrix:
14
+ - DEVISE_ORM=active_record
15
+ - DEVISE_ORM=mongoid
16
+ gemfile:
17
+ - Gemfile
18
+ install: "travis_retry bundle install"
19
+ services:
20
+ - mongodb
21
+ matrix:
22
+ include:
23
+ - rvm: 2.3.0
24
+ gemfile: gemfiles/Gemfile.rails-master
25
+ env: DEVISE_ORM=active_record
26
+ - rvm: 2.3.0
27
+ gemfile: gemfiles/Gemfile.rails-master
28
+ env: DEVISE_ORM=mongoid
data/Gemfile ADDED
@@ -0,0 +1,25 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
5
+ gem 'rdoc'
6
+
7
+ group :test do
8
+ platforms :jruby do
9
+ gem 'activerecord-jdbcsqlite3-adapter', '>= 1.3.0.beta1'
10
+ end
11
+
12
+ platforms :ruby do
13
+ gem 'sqlite3', '~> 1.3.4'
14
+ end
15
+
16
+ gem 'devise', '~> 4.0'
17
+ gem 'activerecord', '~> 4.2.6'
18
+ gem 'mongo'
19
+
20
+ gem 'capybara'
21
+ gem 'shoulda'
22
+ gem 'selenium-webdriver'
23
+
24
+ gem 'minitest-reporters', '>= 0.5.0'
25
+ end
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2016
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,130 @@
1
+ # Devise::TwoFactor
2
+ [![Build Status](https://travis-ci.org/williamatodd/devise-2fa.png?branch=master)](https://travis-ci.org/williamatodd/devise-2fa)
3
+
4
+ Devise TwoFactor implements two-factor authentication for Devise, using an rfc6238 compatible Time-Based One-Time Password Algorithm.
5
+ * Uses [rotp library](https://github.com/mdp/rotp) for the generation and verification of codes.
6
+ * Uses [RQRCode](https://github.com/whomwah/rqrcode) to generate QR Code PNGs.
7
+ * Uses [SymmetricEncryption](https://github.com/rocketjob/symmetric-encryption) to generate QR Code PNGs.
8
+
9
+ It currently has the following features:
10
+
11
+ * Url based provisioning of token devices, compatible with multiple applications.
12
+ * Browsers can be designated as 'trusted' for a limited time.
13
+ * Two-factor authentication can be **optional** at user discretion or **mandatory** (users must enroll OTP after signing-in next time, before they can navigate the site).
14
+ * Optionally, users can obtain a list of HOTP recovery tokens to be used for emergency log-in in case the token device is lost or unavailable.
15
+
16
+ Compatible token devices are:
17
+
18
+ * [1Password](https://1password.com/)
19
+ * [Google Authenticator](https://code.google.com/p/google-authenticator/)
20
+ * [FreeOTP](https://fedorahosted.org/freeotp/)
21
+
22
+ ## Quick overview of Two Factors Authentication, using OTPs.
23
+
24
+ * A shared secret is generated on the server, and stored both on the token device (eg: the phone) and the server itself.
25
+ * The secret is used to generate short numerical tokens that are either time or sequence based.
26
+ * Tokens can be generated on a phone without internet connectivity.
27
+ * The token provides an additional layer of security against password theft.
28
+ * OTP's should always be used as a second factor of authentication(if your phone is lost, you account is still secured with a password)
29
+
30
+ Although there's an adjustable drift window, it is important that both the server and the token device (phone) have their clocks set (eg: using NTP).
31
+
32
+
33
+ ## Installation
34
+
35
+ Setup the symmetric-encryption gem by following the steps on the (configuration page)[http://rocketjob.github.io/symmetric-encryption/configuration.html]
36
+
37
+ Add this line to your application's Gemfile:
38
+
39
+ gem 'devise'
40
+ gem 'devise-2fa'
41
+
42
+ And then execute:
43
+
44
+ $ bundle
45
+
46
+ Or install it yourself as:
47
+
48
+ $ gem install devise-2fa
49
+
50
+
51
+ ### Devise Installation
52
+
53
+ To setup Devise, you need to do the following (but refer to https://github.com/plataformatec/devise for more information)
54
+
55
+ Install Devise:
56
+
57
+ rails g devise:install
58
+
59
+ Setup the User or Admin model
60
+
61
+ rails g devise MODEL
62
+
63
+ Configure your app for authorization, edit your Controller and add this before_action:
64
+
65
+ before_action :authenticate_user!
66
+
67
+ Make sure your "root" route is configured in config/routes.rb
68
+
69
+ ### Automatic Installation
70
+
71
+ Run the following generator to add the necessary configuration options to Devise's config file:
72
+
73
+ rails g devise_two_factor:install
74
+
75
+ After you've created your Devise user models (which is usually done with a "rails g devise MODEL"), set up your Devise TwoFactor additions:
76
+
77
+ rails g devise_two_factor MODEL
78
+
79
+ Don't forget to migrate:
80
+
81
+ rake db:migrate
82
+
83
+ ### Custom Views
84
+
85
+ If you want to customize your views (which you likely will want to), you can use the generator:
86
+
87
+ rails g devise_two_factor:views
88
+
89
+ ### I18n
90
+
91
+ The install generator also installs an english copy of a Devise TwoFactor i18n file. This can be modified (or used to create other language versions) and is located at: _config/locales/devise.two_factor.en.yml_
92
+
93
+
94
+ ## Usage
95
+
96
+ With this extension enabled, the following is expected behavior:
97
+
98
+ * Users may go to _/MODEL/token_ and enable their OTP state, they might be asked to provide their password again (and OTP token, if it's enabled)
99
+ * Once enabled they're shown an alphanumeric code (for manual provisioning) and a QR code, for automatic provisioning of their authentication device (for instance, Google Authenticator)
100
+ * If config.otp_mandatory or model_instance.otp_mandatory, users will be required to enable, and provision, next time they successfully sign-in.
101
+
102
+
103
+ ### Configuration Options
104
+
105
+ The install generator adds some options to the end of your Devise config file (config/initializers/devise.rb)
106
+
107
+ * `config.otp_mandatory` - OTP is mandatory, users are going to be asked to enroll the next time they sign in, before they can successfully complete the session establishment.
108
+ * `config.otp_authentication_timeout` - how long the user has to authenticate with their token. (defaults to `3.minutes`)
109
+ * `config.otp_drift_window` - a window which provides allowance for drift between a user's token device clock (and therefore their OTP tokens) and the authentication server's clock. Expressed in minutes centered at the current time. (default: `3`)
110
+ * `config.otp_credentials_refresh` - Users that have logged in longer than this time ago, are going to be asked their password (and an OTP challenge, if enabled) before they can see or change their otp informations. (defaults to `15.minutes`)
111
+ * `config.otp_recovery_tokens` - Whether the users are given a list of one-time recovery tokens, for emergency access (default: `10`, set to `false` to disable)
112
+ * `config.otp_trust_persistence` - The user is allowed to set his browser as "trusted", no more OTP challenges will be asked for that browser, for a limited time. (default: `1.month`, set to false to disable setting the browser as trusted)
113
+ * `config.otp_issuer` - The name of the token issuer, to be added to the provisioning url. Display will vary based on token application. (defaults to the Rails application class)
114
+
115
+ ## Contributing
116
+
117
+ 1. Fork it
118
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
119
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
120
+ 4. Push to the branch (`git push origin my-new-feature`)
121
+ 5. Create new Pull Request
122
+
123
+ ## Thanks
124
+
125
+ This extension is a hodgepodge of
126
+ [devise-otp](https://github.com/wmlele/devise-otp) which was forked from [devise_google_authenticator](https://github.com/AsteriskLabs/devise_google_authenticator), in conjunction with outstanding pull requests from both libraries. The changes contained within are a bit too aggressive for existing users, therefore this extension will forge it's own path.
127
+
128
+ ## License
129
+
130
+ MIT Licensed
@@ -0,0 +1,41 @@
1
+ #!/usr/bin/env rake
2
+ begin
3
+ require 'bundler/setup'
4
+ rescue LoadError
5
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
6
+ end
7
+ begin
8
+ require 'rdoc/task'
9
+ rescue LoadError
10
+ require 'rdoc/rdoc'
11
+ require 'rake/rdoctask'
12
+ RDoc::Task = Rake::RDocTask
13
+ end
14
+
15
+ RDoc::Task.new(:rdoc) do |rdoc|
16
+ rdoc.rdoc_dir = 'rdoc'
17
+ rdoc.title = 'Foobar'
18
+ rdoc.options << '--line-numbers'
19
+ rdoc.rdoc_files.include('README.rdoc')
20
+ rdoc.rdoc_files.include('lib/**/*.rb')
21
+ end
22
+
23
+ Bundler::GemHelper.install_tasks
24
+
25
+ require 'rake/testtask'
26
+ Rake::TestTask.new(:test) do |test|
27
+ test.libs << 'lib' << 'test'
28
+ test.pattern = 'test/**/*_test.rb'
29
+ test.verbose = true
30
+ end
31
+
32
+ desc 'Run Devise tests for all ORMs.'
33
+ task :tests do
34
+ Dir[File.join(File.dirname(__FILE__), 'test', 'orm', '*.rb')].each do |file|
35
+ orm = File.basename(file).split('.').first
36
+ system "rake test DEVISE_ORM=#{orm}"
37
+ end
38
+ end
39
+
40
+ desc 'Default: run tests for all ORMs.'
41
+ task default: :tests
@@ -0,0 +1,100 @@
1
+ class Devise::CredentialsController < DeviseController
2
+ helper_method :new_session_path if respond_to? :helper_method
3
+
4
+ prepend_before_action :authenticate_scope!, only: [:get_refresh, :set_refresh]
5
+ prepend_before_action :require_no_authentication, only: [:show, :update]
6
+
7
+ #
8
+ # show a request for the OTP token
9
+ #
10
+ def show
11
+ @challenge = params[:challenge]
12
+ @recovery = (params[:recovery] == 'true') && recovery_enabled?
13
+
14
+ if @challenge.nil?
15
+ redirect_to :root
16
+ else
17
+ self.resource = resource_class.find_valid_otp_challenge(@challenge)
18
+ if resource.nil?
19
+ redirect_to :root
20
+ elsif @recovery
21
+ @recovery_count = resource.otp_recovery_counter
22
+ render :show
23
+ else
24
+ render :show
25
+ end
26
+ end
27
+ end
28
+
29
+ #
30
+ # signs the resource in, if the OTP token is valid and the user has a valid challenge
31
+ #
32
+ def update
33
+ resource = resource_class.find_valid_otp_challenge(params[resource_name][:challenge])
34
+ recovery = (params[resource_name][:recovery] == 'true') && recovery_enabled?
35
+ token = params[resource_name][:token]
36
+
37
+ if token.blank?
38
+ otp_set_flash_message(:alert, :token_blank)
39
+ redirect_to credential_path_for(resource_name, challenge: params[resource_name][:challenge],
40
+ recovery: recovery)
41
+ elsif resource.nil?
42
+ otp_set_flash_message(:alert, :otp_session_invalid)
43
+ redirect_to new_session_path(resource_name)
44
+ else
45
+ if resource.otp_challenge_valid? && resource.validate_otp_token(params[resource_name][:token], recovery)
46
+ set_flash_message(:success, :signed_in) if is_navigational_format?
47
+ sign_in(resource_name, resource)
48
+
49
+ otp_refresh_credentials_for(resource)
50
+ respond_with resource, location: after_sign_in_path_for(resource)
51
+ else
52
+ otp_set_flash_message :alert, :token_invalid
53
+ redirect_to new_session_path(resource_name)
54
+ end
55
+ end
56
+ end
57
+
58
+ #
59
+ # displays the request for a credentials refresh
60
+ #
61
+ def get_refresh
62
+ ensure_resource!
63
+ render :refresh
64
+ end
65
+
66
+ #
67
+ # lets the user through is the refresh is valid
68
+ #
69
+ def set_refresh
70
+ ensure_resource!
71
+ # I am sure there's a much better way
72
+ if resource.valid_password?(params[resource_name][:refresh_password])
73
+ if resource.otp_enabled?
74
+ if resource.validate_otp_token(params[resource_name][:token])
75
+ done_valid_refresh
76
+ else
77
+ failed_refresh
78
+ end
79
+ else
80
+ done_valid_refresh
81
+ end
82
+ else
83
+ failed_refresh
84
+ end
85
+ end
86
+
87
+ private
88
+
89
+ def done_valid_refresh
90
+ otp_refresh_credentials_for(resource)
91
+ otp_set_flash_message :success, :valid_refresh if is_navigational_format?
92
+
93
+ respond_with resource, location: otp_fetch_refresh_return_url
94
+ end
95
+
96
+ def failed_refresh
97
+ otp_set_flash_message :alert, :invalid_refresh
98
+ render :refresh
99
+ end
100
+ end
@@ -0,0 +1,99 @@
1
+ class Devise::TokensController < DeviseController
2
+ include Devise::Controllers::Helpers
3
+
4
+ prepend_before_action :ensure_credentials_refresh
5
+ prepend_before_action :authenticate_scope!
6
+
7
+ protect_from_forgery except: [:clear_persistence, :delete_persistence]
8
+
9
+ #
10
+ # Displays the status of OTP authentication
11
+ #
12
+ def show
13
+ if resource.nil?
14
+ redirect_to stored_location_for(scope) || :root
15
+ else
16
+ render :show
17
+ end
18
+ end
19
+
20
+ #
21
+ # Updates the status of OTP authentication
22
+ #
23
+ def update
24
+ enabled = (params[resource_name][:otp_enabled] == '1')
25
+ if enabled ? resource.enable_otp! : resource.disable_otp!
26
+ otp_set_flash_message :success, :successfully_updated
27
+ end
28
+ render :show
29
+ end
30
+
31
+ #
32
+ # Resets OTP authentication, generates new credentials, sets it to off
33
+ #
34
+ def destroy
35
+ if resource.reset_otp_credentials!
36
+ otp_set_flash_message :success, :successfully_reset_creds
37
+ end
38
+ render :show
39
+ end
40
+
41
+ #
42
+ # makes the current browser persistent
43
+ #
44
+ def get_persistence
45
+ if otp_set_trusted_device_for(resource)
46
+ otp_set_flash_message :success, :successfully_set_persistence
47
+ end
48
+ redirect_to action: :show
49
+ end
50
+
51
+ #
52
+ # clears persistence for the current browser
53
+ #
54
+ def clear_persistence
55
+ if otp_clear_trusted_device_for(resource)
56
+ otp_set_flash_message :success, :successfully_cleared_persistence
57
+ end
58
+
59
+ redirect_to action: :show
60
+ end
61
+
62
+ #
63
+ # rehash the persistence secret, thus, making all the persistence cookies invalid
64
+ #
65
+ def delete_persistence
66
+ if otp_reset_persistence_for(resource)
67
+ otp_set_flash_message :notice, :successfully_reset_persistence
68
+ end
69
+
70
+ redirect_to action: :show
71
+ end
72
+
73
+ #
74
+ #
75
+ #
76
+ def recovery
77
+ respond_to do |format|
78
+ format.html
79
+ format.js
80
+ format.text do
81
+ send_data render_to_string(template: 'devise/tokens/recovery_codes.text.erb'), filename: 'recovery-codes.txt'
82
+ end
83
+ end
84
+ end
85
+
86
+ private
87
+
88
+ def ensure_credentials_refresh
89
+ ensure_resource!
90
+ if needs_credentials_refresh?(resource)
91
+ otp_set_flash_message :notice, :need_to_refresh_credentials
92
+ redirect_to refresh_credential_path_for(resource)
93
+ end
94
+ end
95
+
96
+ def scope
97
+ resource_name.to_sym
98
+ end
99
+ end