From 56f099da28ae306f8448aafbc9da5a3f71286d21 Mon Sep 17 00:00:00 2001 From: Sam Clegg Date: Fri, 26 Aug 2016 16:50:53 -0400 Subject: [PATCH 01/51] Bump version 1.1.5 -> 2.0.0 There have been a couple of recent breaking changes so bumping the major version is required. --- README.md | 28 ++++++++++++++---------- lib/two_factor_authentication/version.rb | 2 +- 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 020acadb..39af964d 100644 --- a/README.md +++ b/README.md @@ -156,25 +156,31 @@ Below is an example using ERB: <% end %> <%= link_to "Sign out", destroy_user_session_path, :method => :delete %> - ``` -#### Enable TOTP support for existing users +#### Upgrading from version 1.X to 2.X + +The following database fields are new in version 2. + +- `direct_otp` +- `direct_otp_sent_at` +- `totp_timestamp` + +To add them, generate a migration such as: + + $ rails g migration AddTwoFactorFieldsToUsers direct_otp:string direct_otp_sent_at:datetime totp_timestamp:timestamp -If you have existing users that need to be provided with a OTP secret key, so -they can use TOTP, create a rake task. It could look like this one below: +The `otp_secret_key` is not only required for users who use Google Authentictor, +so unless it has been shared with the user it should be set to `nil`. The +following psudo-code is an example of how this might be done: ```ruby -desc 'rake task to update users with otp secret key' -task :update_users_with_otp_secret_key => :environment do - User.find_each do |user| - user.generate_totp_secret - user.save! - puts "Rake[:update_users_with_otp_secret_key] => OTP secret key set to '#{key}' for User '#{user.email}'" +User.find_each do |user| do + if !uses_authentictor_app(user) + user.otp_secret_key = nil end end ``` -Then run the task with `bundle exec rake update_users_with_otp_secret_key` #### Adding the TOTP encryption option to an existing app diff --git a/lib/two_factor_authentication/version.rb b/lib/two_factor_authentication/version.rb index f75a7a6f..29165aea 100644 --- a/lib/two_factor_authentication/version.rb +++ b/lib/two_factor_authentication/version.rb @@ -1,3 +1,3 @@ module TwoFactorAuthentication - VERSION = "1.1.5".freeze + VERSION = "2.0.0".freeze end From ffbad708486b4555a278db66f09c21fe52812500 Mon Sep 17 00:00:00 2001 From: Gaurish Sharma Date: Tue, 8 Nov 2016 19:03:55 +0530 Subject: [PATCH 02/51] Add compat with older versions of devise --- .../devise/two_factor_authentication_controller.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/controllers/devise/two_factor_authentication_controller.rb b/app/controllers/devise/two_factor_authentication_controller.rb index 6d2b4873..5b923829 100644 --- a/app/controllers/devise/two_factor_authentication_controller.rb +++ b/app/controllers/devise/two_factor_authentication_controller.rb @@ -26,7 +26,11 @@ def after_two_factor_success_for(resource) set_remember_two_factor_cookie(resource) warden.session(resource_name)[TwoFactorAuthentication::NEED_AUTHENTICATION] = false - bypass_sign_in(resource, scope: resource_name) + if respond_to?(:bypass_sign_in) + bypass_sign_in(resource, scope: resource_name) + else + sign_in(resource_name, resource, bypass: true) + end set_flash_message :notice, :success resource.update_attribute(:second_factor_attempts_count, 0) From 348c2a1cce351de62293c361f6458db9c0fce3bd Mon Sep 17 00:00:00 2001 From: Gaurish Sharma Date: Sat, 12 Nov 2016 12:36:20 +0530 Subject: [PATCH 03/51] [PR Feedback] Use Devise::Version over respond_to? --- .../devise/two_factor_authentication_controller.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/controllers/devise/two_factor_authentication_controller.rb b/app/controllers/devise/two_factor_authentication_controller.rb index 5b923829..cf9bd6e2 100644 --- a/app/controllers/devise/two_factor_authentication_controller.rb +++ b/app/controllers/devise/two_factor_authentication_controller.rb @@ -1,3 +1,5 @@ +require 'devise/version' + class Devise::TwoFactorAuthenticationController < DeviseController prepend_before_action :authenticate_scope! before_action :prepare_and_validate, :handle_two_factor_authentication @@ -26,7 +28,9 @@ def after_two_factor_success_for(resource) set_remember_two_factor_cookie(resource) warden.session(resource_name)[TwoFactorAuthentication::NEED_AUTHENTICATION] = false - if respond_to?(:bypass_sign_in) + # For compatability with devise versions below v4.2.0 + # https://github.com/plataformatec/devise/commit/2044fffa25d781fcbaf090e7728b48b65c854ccb + if Devise::VERSION.to_f >= 4.2 bypass_sign_in(resource, scope: resource_name) else sign_in(resource_name, resource, bypass: true) From 41a01acd85024564d4b6454901e13ccb074d4466 Mon Sep 17 00:00:00 2001 From: Benjamin Wols Date: Thu, 1 Dec 2016 18:57:26 +0100 Subject: [PATCH 04/51] Fix merge conflict --- lib/two_factor_authentication.rb | 3 +++ .../hooks/two_factor_authenticatable.rb | 4 ++++ .../models/two_factor_authenticatable.rb | 3 ++- 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/two_factor_authentication.rb b/lib/two_factor_authentication.rb index 59ffa039..969aff44 100644 --- a/lib/two_factor_authentication.rb +++ b/lib/two_factor_authentication.rb @@ -30,6 +30,9 @@ module Devise mattr_accessor :second_factor_resource_id @@second_factor_resource_id = 'id' + + mattr_accessor :delete_cookie_on_logout + @@delete_cookie_on_logout = false end module TwoFactorAuthentication diff --git a/lib/two_factor_authentication/hooks/two_factor_authenticatable.rb b/lib/two_factor_authentication/hooks/two_factor_authenticatable.rb index 159ae144..0edda438 100644 --- a/lib/two_factor_authentication/hooks/two_factor_authenticatable.rb +++ b/lib/two_factor_authentication/hooks/two_factor_authenticatable.rb @@ -11,3 +11,7 @@ end end end + +Warden::Manager.before_logout do |user, auth, _options| + auth.cookies.delete TwoFactorAuthentication::REMEMBER_TFA_COOKIE_NAME if user.class.delete_cookie_on_logout +end diff --git a/lib/two_factor_authentication/models/two_factor_authenticatable.rb b/lib/two_factor_authentication/models/two_factor_authenticatable.rb index 7d4a3306..b1c696c1 100644 --- a/lib/two_factor_authentication/models/two_factor_authenticatable.rb +++ b/lib/two_factor_authentication/models/two_factor_authenticatable.rb @@ -16,7 +16,8 @@ def has_one_time_password(options = {}) ::Devise::Models.config( self, :max_login_attempts, :allowed_otp_drift_seconds, :otp_length, :remember_otp_session_for_seconds, :otp_secret_encryption_key, - :direct_otp_length, :direct_otp_valid_for, :totp_timestamp) + :direct_otp_length, :direct_otp_valid_for, :totp_timestamp, :delete_cookie_on_logout + ) end module InstanceMethodsOnActivation From 4cc5762e833961a5e21ee1ac716234735e265897 Mon Sep 17 00:00:00 2001 From: Benjamin Wols Date: Thu, 1 Dec 2016 19:30:52 +0100 Subject: [PATCH 05/51] Add test for deleting cookie on logout --- spec/features/two_factor_authenticatable_spec.rb | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/spec/features/two_factor_authenticatable_spec.rb b/spec/features/two_factor_authenticatable_spec.rb index ebde2d80..d433d636 100644 --- a/spec/features/two_factor_authenticatable_spec.rb +++ b/spec/features/two_factor_authenticatable_spec.rb @@ -174,6 +174,18 @@ def sms_sign_in visit dashboard_path expect(page).to have_content("Enter the code that was sent to you") end + + scenario 'Delete cookie when user logs out if enabled' do + user.class.delete_cookie_on_logout = true + + login_as user + logout + + login_as user + + visit dashboard_path + expect(page).to have_content("Enter the code that was sent to you") + end end it 'sets the warden session need_two_factor_authentication key to true' do From a97b3f6e413d801e02a25dac8544a2ffc514cfcd Mon Sep 17 00:00:00 2001 From: Benjamin Wols Date: Thu, 1 Dec 2016 18:57:35 +0100 Subject: [PATCH 06/51] Update readme --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index dabdea41..7d097010 100644 --- a/README.md +++ b/README.md @@ -97,6 +97,7 @@ config.direct_otp_length = 6 # Direct OTP code length config.remember_otp_session_for_seconds = 30.days # Time before browser has to perform 2fA again. Default is 0. config.otp_secret_encryption_key = ENV['OTP_SECRET_ENCRYPTION_KEY'] config.second_factor_resource_id = 'id' # Field or method name used to set value for 2fA remember cookie +config.delete_cookie_on_logout = false # Delete cookie when user signs out, to force 2fA again on login ``` The `otp_secret_encryption_key` must be a random key that is not stored in the DB, and is not checked in to your repo. It is recommended to store it in an From d1e1a663cd5aa8ebdae9ca6c812bd589e1bf9553 Mon Sep 17 00:00:00 2001 From: Benjamin Wols Date: Thu, 1 Dec 2016 19:50:57 +0100 Subject: [PATCH 07/51] Access config via Devise instead user.class --- .../hooks/two_factor_authenticatable.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/two_factor_authentication/hooks/two_factor_authenticatable.rb b/lib/two_factor_authentication/hooks/two_factor_authenticatable.rb index 0edda438..254df845 100644 --- a/lib/two_factor_authentication/hooks/two_factor_authenticatable.rb +++ b/lib/two_factor_authentication/hooks/two_factor_authenticatable.rb @@ -13,5 +13,5 @@ end Warden::Manager.before_logout do |user, auth, _options| - auth.cookies.delete TwoFactorAuthentication::REMEMBER_TFA_COOKIE_NAME if user.class.delete_cookie_on_logout + auth.cookies.delete TwoFactorAuthentication::REMEMBER_TFA_COOKIE_NAME if Devise.delete_cookie_on_logout end From d87b7b31c6d1fcccda49a4ca69900fe08b547968 Mon Sep 17 00:00:00 2001 From: shaunakpp Date: Fri, 24 Feb 2017 18:11:59 +0530 Subject: [PATCH 08/51] README typo fix[ci skip] --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 8c64b883..71b28677 100644 --- a/README.md +++ b/README.md @@ -176,8 +176,8 @@ To add them, generate a migration such as: $ rails g migration AddTwoFactorFieldsToUsers direct_otp:string direct_otp_sent_at:datetime totp_timestamp:timestamp The `otp_secret_key` is not only required for users who use Google Authentictor, -so unless it has been shared with the user it should be set to `nil`. The -following psudo-code is an example of how this might be done: +so unless it has been shared with the user it should be set to `nil`. The +following pseudo-code is an example of how this might be done: ```ruby User.find_each do |user| do From a32f71a534a893ef6169bfe03e1e6c6c5d619339 Mon Sep 17 00:00:00 2001 From: Tigran Apoyan Date: Wed, 1 Mar 2017 19:43:59 +0400 Subject: [PATCH 09/51] Update two_factor_authenticatable.rb --- .../models/two_factor_authenticatable.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/two_factor_authentication/models/two_factor_authenticatable.rb b/lib/two_factor_authentication/models/two_factor_authenticatable.rb index 7d4a3306..376038e2 100644 --- a/lib/two_factor_authentication/models/two_factor_authenticatable.rb +++ b/lib/two_factor_authentication/models/two_factor_authenticatable.rb @@ -153,7 +153,8 @@ def encryption_options_for(value) value: value, key: Devise.otp_secret_encryption_key, iv: iv_for_attribute, - salt: salt_for_attribute + salt: salt_for_attribute, + algorithm: 'aes-256-cbc' } end From 68f407c632a840c4eddd2d74b583e06e061db2a9 Mon Sep 17 00:00:00 2001 From: newtrat Date: Tue, 14 Mar 2017 19:01:00 -0400 Subject: [PATCH 10/51] Fix OpenSSL deprecation warning OpenSSL::Cipher::Cipher has been deprecated in favor of OpenSSL::Cipher --- .../models/two_factor_authenticatable.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/two_factor_authentication/models/two_factor_authenticatable.rb b/lib/two_factor_authentication/models/two_factor_authenticatable.rb index 376038e2..716d676d 100644 --- a/lib/two_factor_authentication/models/two_factor_authenticatable.rb +++ b/lib/two_factor_authentication/models/two_factor_authenticatable.rb @@ -162,7 +162,7 @@ def iv_for_attribute(algorithm = 'aes-256-cbc') iv = encrypted_otp_secret_key_iv if iv.nil? - algo = OpenSSL::Cipher::Cipher.new(algorithm) + algo = OpenSSL::Cipher.new(algorithm) iv = [algo.random_iv].pack('m') self.encrypted_otp_secret_key_iv = iv end From 57517e5a05d15333f17627126f9b3874095d27cf Mon Sep 17 00:00:00 2001 From: newtrat Date: Tue, 14 Mar 2017 19:47:02 -0400 Subject: [PATCH 11/51] Add new encryption algorithm to Encryptor test Since now always includes , the test for passing the correct options to Encryptor should also include that option. --- .../models/two_factor_authenticatable_spec.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/spec/lib/two_factor_authentication/models/two_factor_authenticatable_spec.rb b/spec/lib/two_factor_authentication/models/two_factor_authenticatable_spec.rb index 272d0cfd..4197b43c 100644 --- a/spec/lib/two_factor_authentication/models/two_factor_authenticatable_spec.rb +++ b/spec/lib/two_factor_authentication/models/two_factor_authenticatable_spec.rb @@ -289,7 +289,8 @@ def instance.send_two_factor_authentication_code(code) value: 'testing', key: Devise.otp_secret_encryption_key, iv: iv.unpack('m').first, - salt: salt.unpack('m').first + salt: salt.unpack('m').first, + algorithm: 'aes-256-cbc' ) expect(instance.encrypted_otp_secret_key).to eq [encrypted].pack('m') From 0d9bc8da0eb8cc1eb993ada615505f48b1e729be Mon Sep 17 00:00:00 2001 From: Philipp Staender Date: Sat, 18 Mar 2017 11:00:48 +0100 Subject: [PATCH 12/51] Check and use if newer bypass_sign_in method exists in devise --- app/controllers/devise/two_factor_authentication_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/devise/two_factor_authentication_controller.rb b/app/controllers/devise/two_factor_authentication_controller.rb index cf9bd6e2..2cd8d6fa 100644 --- a/app/controllers/devise/two_factor_authentication_controller.rb +++ b/app/controllers/devise/two_factor_authentication_controller.rb @@ -30,7 +30,7 @@ def after_two_factor_success_for(resource) warden.session(resource_name)[TwoFactorAuthentication::NEED_AUTHENTICATION] = false # For compatability with devise versions below v4.2.0 # https://github.com/plataformatec/devise/commit/2044fffa25d781fcbaf090e7728b48b65c854ccb - if Devise::VERSION.to_f >= 4.2 + if respond_to?(:bypass_sign_in) bypass_sign_in(resource, scope: resource_name) else sign_in(resource_name, resource, bypass: true) From 44197776913096210251c01f7cb965e0668b4c99 Mon Sep 17 00:00:00 2001 From: Ryan McGeary Date: Mon, 1 May 2017 17:44:14 -0400 Subject: [PATCH 13/51] Switch badges to vector in README Low-res raster makes eyes bleed on retina displays :sunglasses: [skip ci] --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 71b28677..472414fd 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/Houdini/two_factor_authentication?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Build Status](https://travis-ci.org/Houdini/two_factor_authentication.svg?branch=master)](https://travis-ci.org/Houdini/two_factor_authentication) -[![Code Climate](https://codeclimate.com/github/Houdini/two_factor_authentication.png)](https://codeclimate.com/github/Houdini/two_factor_authentication) +[![Code Climate](https://codeclimate.com/github/Houdini/two_factor_authentication.svg)](https://codeclimate.com/github/Houdini/two_factor_authentication) ## Features From 96abae0a3076615c8c6005ea392aedf145a95e7a Mon Sep 17 00:00:00 2001 From: Dmitrii Golub Date: Fri, 12 May 2017 00:15:55 +0300 Subject: [PATCH 14/51] fix test in models/two_factor_authenticatable_spec.rb --- .../models/two_factor_authenticatable_spec.rb | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/spec/lib/two_factor_authentication/models/two_factor_authenticatable_spec.rb b/spec/lib/two_factor_authentication/models/two_factor_authenticatable_spec.rb index 272d0cfd..05fb72d8 100644 --- a/spec/lib/two_factor_authentication/models/two_factor_authenticatable_spec.rb +++ b/spec/lib/two_factor_authentication/models/two_factor_authenticatable_spec.rb @@ -280,16 +280,20 @@ def instance.send_two_factor_authentication_code(code) to raise_error ArgumentError end - it 'passes in the correct options to Encryptor' do + it 'passes in the correct options to Encryptor. + We test here output of + Devise::Models::TwoFactorAuthenticatable::EncryptionInstanceMethods.encryption_options_for' do instance.otp_secret_key = 'testing' iv = instance.encrypted_otp_secret_key_iv salt = instance.encrypted_otp_secret_key_salt + # it's important here to put the same crypto algorithm from that method encrypted = Encryptor.encrypt( value: 'testing', key: Devise.otp_secret_encryption_key, iv: iv.unpack('m').first, - salt: salt.unpack('m').first + salt: salt.unpack('m').first, + algorithm: 'aes-256-cbc' ) expect(instance.encrypted_otp_secret_key).to eq [encrypted].pack('m') From 21236609b2642c43b20e662d8bf6cbe95db183e4 Mon Sep 17 00:00:00 2001 From: Edward Simpson Date: Sun, 28 May 2017 23:23:52 -0700 Subject: [PATCH 15/51] Doc change re otp_secret_key for version 1 to 2 upgrade Resource attribute otp_secret_key only needs a value if the user requires a TOTP value. --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 472414fd..73c7cd57 100644 --- a/README.md +++ b/README.md @@ -175,14 +175,15 @@ To add them, generate a migration such as: $ rails g migration AddTwoFactorFieldsToUsers direct_otp:string direct_otp_sent_at:datetime totp_timestamp:timestamp -The `otp_secret_key` is not only required for users who use Google Authentictor, +The `otp_secret_key` is only required for users who use TOTP (Google Authenticator) codes, so unless it has been shared with the user it should be set to `nil`. The following pseudo-code is an example of how this might be done: ```ruby User.find_each do |user| do - if !uses_authentictor_app(user) + if !uses_authenticator_app(user) user.otp_secret_key = nil + user.save! end end ``` From 93674d2aeaae50e49ae17ad89e1c21f6009fe3ab Mon Sep 17 00:00:00 2001 From: Jonathan Kirst Date: Wed, 5 Jul 2017 20:41:22 +0000 Subject: [PATCH 16/51] dynamically generate path based on resource scope --- app/views/devise/two_factor_authentication/show.html.erb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/views/devise/two_factor_authentication/show.html.erb b/app/views/devise/two_factor_authentication/show.html.erb index 552fd7d8..0ffa2248 100644 --- a/app/views/devise/two_factor_authentication/show.html.erb +++ b/app/views/devise/two_factor_authentication/show.html.erb @@ -12,8 +12,8 @@ <% end %> <% if resource.direct_otp %> -<%= link_to "Resend Code", resend_code_user_two_factor_authentication_path, action: :get %> + <%= link_to "Resend Code", send("resend_code_#{resource_name}_two_factor_authentication_path"), action: :get %> <% else %> -<%= link_to "Send me a code instead", resend_code_user_two_factor_authentication_path, action: :get %> +<%= link_to "Send me a code instead", send("resend_code_#{resource_name}_two_factor_authentication_path"), action: :get %> <% end %> -<%= link_to "Sign out", destroy_user_session_path, :method => :delete %> +<%= link_to "Sign out", send("destroy_#{resource_name}_session_path"), :method => :delete %> From 38803d82b3259fcebbf649e2fd62d06f75a8e229 Mon Sep 17 00:00:00 2001 From: Leandro Marcucci Date: Fri, 14 Jul 2017 17:22:19 -0300 Subject: [PATCH 17/51] Makes encrypt/decrypt method names unique: This is an attempt to play nice with other gems that might define methods that encrypt or decrypt data in a model. --- .../models/two_factor_authenticatable.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/two_factor_authentication/models/two_factor_authenticatable.rb b/lib/two_factor_authentication/models/two_factor_authenticatable.rb index 716d676d..58845167 100644 --- a/lib/two_factor_authentication/models/two_factor_authenticatable.rb +++ b/lib/two_factor_authentication/models/two_factor_authenticatable.rb @@ -113,16 +113,16 @@ def clear_direct_otp module EncryptionInstanceMethods def otp_secret_key - decrypt(encrypted_otp_secret_key) + otp_decrypt(encrypted_otp_secret_key) end def otp_secret_key=(value) - self.encrypted_otp_secret_key = encrypt(value) + self.encrypted_otp_secret_key = otp_encrypt(value) end private - def decrypt(encrypted_value) + def otp_decrypt(encrypted_value) return encrypted_value if encrypted_value.blank? encrypted_value = encrypted_value.unpack('m').first @@ -137,7 +137,7 @@ def decrypt(encrypted_value) value end - def encrypt(value) + def otp_encrypt(value) return value if value.blank? value = value.to_s From 9c1af25aadf4b809af5bd2a2ade1e5e6d21f52a6 Mon Sep 17 00:00:00 2001 From: Dmitrii Golub Date: Tue, 18 Jul 2017 22:08:10 +0300 Subject: [PATCH 18/51] Bump to version 2.0.1 --- lib/two_factor_authentication/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/two_factor_authentication/version.rb b/lib/two_factor_authentication/version.rb index 29165aea..a39aa7f6 100644 --- a/lib/two_factor_authentication/version.rb +++ b/lib/two_factor_authentication/version.rb @@ -1,3 +1,3 @@ module TwoFactorAuthentication - VERSION = "2.0.0".freeze + VERSION = "2.0.1".freeze end From 5865994f8162db46b9904ef0abadaefbf0a4cfee Mon Sep 17 00:00:00 2001 From: Rostislav Katin Date: Sat, 4 Nov 2017 01:21:01 +0300 Subject: [PATCH 19/51] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 73c7cd57..41653564 100644 --- a/README.md +++ b/README.md @@ -242,7 +242,7 @@ steps: end end end - ``` + ``` 5. Generate a migration to remove the `:otp_secret_key` column: ``` From 8c37495d30e0fd11499e5a2e516b55969b0adcf1 Mon Sep 17 00:00:00 2001 From: Ryan McGeary Date: Fri, 19 Jan 2018 14:46:18 -0700 Subject: [PATCH 20/51] Fix README markdown formatting --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 41a010ce..1615b649 100644 --- a/README.md +++ b/README.md @@ -8,8 +8,8 @@ ## Features * Support for 2 types of OTP codes - 1. Codes delivered directly to the user - 2. TOTP (Google Authenticator) codes based on a shared secret (HMAC) + 1. Codes delivered directly to the user + 2. TOTP (Google Authenticator) codes based on a shared secret (HMAC) * Configurable OTP code digit length * Configurable max login attempts * Customizable logic to determine if a user needs two factor authentication From ea27bcd960bf824d042fab7b2919f54bfe8dc875 Mon Sep 17 00:00:00 2001 From: Ryan McGeary Date: Mon, 29 Jan 2018 15:17:46 -0700 Subject: [PATCH 21/51] Remove duplicate pry entry in Gemfile It's already in gemspec --- Gemfile | 1 - 1 file changed, 1 deletion(-) diff --git a/Gemfile b/Gemfile index 70ab496c..59ad4951 100644 --- a/Gemfile +++ b/Gemfile @@ -27,5 +27,4 @@ end group :test do gem 'rack_session_access' gem 'ammeter' - gem 'pry' end From bf37c45ccb299ae2b3521114d8f3b2540279347b Mon Sep 17 00:00:00 2001 From: Ryan McGeary Date: Mon, 29 Jan 2018 15:20:28 -0700 Subject: [PATCH 22/51] Enhance travis build matrix for supported Ruby and Rails versions Also enables fast_finish --- .travis.yml | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index cafa2230..4db311b9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,22 +1,21 @@ language: ruby env: - - "RAILS_VERSION=4.0" - - "RAILS_VERSION=4.1" - "RAILS_VERSION=4.2" + - "RAILS_VERSION=5.1" - "RAILS_VERSION=master" rvm: - - 2.1 - 2.2 - - 2.3.1 + - 2.3.6 + - 2.4.3 + - 2.5.0 matrix: + fast_finish: true allow_failures: - env: "RAILS_VERSION=master" exclude: - - rvm: 2.1 - env: RAILS_VERSION=master - rvm: 2.2 env: RAILS_VERSION=master From ce054cd001133b80feba933df565f9d889b22170 Mon Sep 17 00:00:00 2001 From: Ryan McGeary Date: Mon, 29 Jan 2018 15:21:07 -0700 Subject: [PATCH 23/51] Upgrade capybara for Rails 5 support --- two_factor_authentication.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/two_factor_authentication.gemspec b/two_factor_authentication.gemspec index d606d6ed..a5b886d6 100644 --- a/two_factor_authentication.gemspec +++ b/two_factor_authentication.gemspec @@ -33,7 +33,7 @@ Gem::Specification.new do |s| s.add_development_dependency 'bundler' s.add_development_dependency 'rake' s.add_development_dependency 'rspec-rails', '>= 3.0.1' - s.add_development_dependency 'capybara', '2.4.1' + s.add_development_dependency 'capybara', '~> 2.5' s.add_development_dependency 'pry' s.add_development_dependency 'timecop' end From f6b011d33d9d7a526db821b0f1c1467261b7c1f2 Mon Sep 17 00:00:00 2001 From: Ryan McGeary Date: Mon, 29 Jan 2018 15:21:43 -0700 Subject: [PATCH 24/51] Add Rails 5 support to specs - Replace silence_stream with ActiveRecord::Migration.suppress_messages - Resolve rspec-rails changes --- .../two_factor_authentication_controller_spec.rb | 12 ++++++++++-- spec/support/authenticated_model_helper.rb | 2 +- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/spec/controllers/two_factor_authentication_controller_spec.rb b/spec/controllers/two_factor_authentication_controller_spec.rb index 100876ad..d578d0bb 100644 --- a/spec/controllers/two_factor_authentication_controller_spec.rb +++ b/spec/controllers/two_factor_authentication_controller_spec.rb @@ -2,6 +2,14 @@ describe Devise::TwoFactorAuthenticationController, type: :controller do describe 'is_fully_authenticated? helper' do + def post_code(code) + if Rails::VERSION::MAJOR >= 5 + post :update, params: { code: code } + else + post :update, code: code + end + end + before do sign_in end @@ -9,7 +17,7 @@ context 'after user enters valid OTP code' do it 'returns true' do controller.current_user.send_new_otp - post :update, code: controller.current_user.direct_otp + post_code controller.current_user.direct_otp expect(subject.is_fully_authenticated?).to eq true end end @@ -24,7 +32,7 @@ context 'when user enters an invalid OTP' do it 'returns false' do - post :update, code: '12345' + post_code '12345' expect(subject.is_fully_authenticated?).to eq false end diff --git a/spec/support/authenticated_model_helper.rb b/spec/support/authenticated_model_helper.rb index 42696e68..8138dc58 100644 --- a/spec/support/authenticated_model_helper.rb +++ b/spec/support/authenticated_model_helper.rb @@ -29,7 +29,7 @@ def generate_unique_email end def create_table_for_nonencrypted_user - silence_stream(STDOUT) do + ActiveRecord::Migration.suppress_messages do ActiveRecord::Schema.define(version: 1) do create_table 'users', force: :cascade do |t| t.string 'email', default: '', null: false From 5bf0aaf114ac1aa4832f35f6e3ef947b002b41d5 Mon Sep 17 00:00:00 2001 From: Ryan McGeary Date: Mon, 29 Jan 2018 15:32:55 -0700 Subject: [PATCH 25/51] Only run Ruby 2.2 against Rails 4 --- .travis.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 4db311b9..2a8f2960 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,7 +6,6 @@ env: - "RAILS_VERSION=master" rvm: - - 2.2 - 2.3.6 - 2.4.3 - 2.5.0 @@ -15,9 +14,9 @@ matrix: fast_finish: true allow_failures: - env: "RAILS_VERSION=master" - exclude: + include: - rvm: 2.2 - env: RAILS_VERSION=master + env: RAILS_VERSION=4.2 before_install: - gem update bundler From 6050115a2c0a9f339f9df216c6518ea6c13ca8d2 Mon Sep 17 00:00:00 2001 From: Ryan McGeary Date: Mon, 29 Jan 2018 15:53:33 -0700 Subject: [PATCH 26/51] Made Rails 5.1 the "default" version of rails in Gemfile --- Gemfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile b/Gemfile index 59ad4951..72a62fd7 100644 --- a/Gemfile +++ b/Gemfile @@ -9,7 +9,7 @@ rails = case rails_version when "master" {github: "rails/rails"} when "default" - "~> 4.1" + "~> 5.1" else "~> #{rails_version}" end From f677f1fe303f4fc6a57ab074dc38c519eead9268 Mon Sep 17 00:00:00 2001 From: Ryan McGeary Date: Mon, 29 Jan 2018 15:54:02 -0700 Subject: [PATCH 27/51] Normalize migrations between Rails 4 and Rails 5 --- .travis.yml | 2 +- .../20140403184646_devise_create_users.rb | 2 +- ..._two_factor_authentication_add_to_users.rb | 2 +- .../20140407215513_add_nickanme_to_users.rb | 2 +- ...224171231_add_encrypted_columns_to_user.rb | 2 +- .../20151224180310_populate_otp_column.rb | 2 +- ...8230340_remove_otp_secret_key_from_user.rb | 2 +- .../20160209032439_devise_create_admins.rb | 2 +- spec/rails_app/db/schema.rb | 55 +++++++++---------- 9 files changed, 34 insertions(+), 37 deletions(-) diff --git a/.travis.yml b/.travis.yml index 2a8f2960..94be5d0b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -22,6 +22,6 @@ before_install: - gem update bundler before_script: - - bundle exec rake app:db:migrate + - bundle exec rake app:db:setup script: bundle exec rake spec diff --git a/spec/rails_app/db/migrate/20140403184646_devise_create_users.rb b/spec/rails_app/db/migrate/20140403184646_devise_create_users.rb index 74adf30c..83d51c94 100644 --- a/spec/rails_app/db/migrate/20140403184646_devise_create_users.rb +++ b/spec/rails_app/db/migrate/20140403184646_devise_create_users.rb @@ -1,4 +1,4 @@ -class DeviseCreateUsers < ActiveRecord::Migration +class DeviseCreateUsers < ActiveRecord::Migration[4.2] def change create_table(:users) do |t| ## Database authenticatable diff --git a/spec/rails_app/db/migrate/20140407172619_two_factor_authentication_add_to_users.rb b/spec/rails_app/db/migrate/20140407172619_two_factor_authentication_add_to_users.rb index 5720bb88..e3c2bf61 100644 --- a/spec/rails_app/db/migrate/20140407172619_two_factor_authentication_add_to_users.rb +++ b/spec/rails_app/db/migrate/20140407172619_two_factor_authentication_add_to_users.rb @@ -1,4 +1,4 @@ -class TwoFactorAuthenticationAddToUsers < ActiveRecord::Migration +class TwoFactorAuthenticationAddToUsers < ActiveRecord::Migration[4.2] def up change_table :users do |t| t.string :otp_secret_key diff --git a/spec/rails_app/db/migrate/20140407215513_add_nickanme_to_users.rb b/spec/rails_app/db/migrate/20140407215513_add_nickanme_to_users.rb index ee3fa8f6..87f8217f 100644 --- a/spec/rails_app/db/migrate/20140407215513_add_nickanme_to_users.rb +++ b/spec/rails_app/db/migrate/20140407215513_add_nickanme_to_users.rb @@ -1,4 +1,4 @@ -class AddNickanmeToUsers < ActiveRecord::Migration +class AddNickanmeToUsers < ActiveRecord::Migration[4.2] def change change_table :users do |t| t.column :nickname, :string, limit: 64 diff --git a/spec/rails_app/db/migrate/20151224171231_add_encrypted_columns_to_user.rb b/spec/rails_app/db/migrate/20151224171231_add_encrypted_columns_to_user.rb index 67f80a6b..ba6b5281 100644 --- a/spec/rails_app/db/migrate/20151224171231_add_encrypted_columns_to_user.rb +++ b/spec/rails_app/db/migrate/20151224171231_add_encrypted_columns_to_user.rb @@ -1,4 +1,4 @@ -class AddEncryptedColumnsToUser < ActiveRecord::Migration +class AddEncryptedColumnsToUser < ActiveRecord::Migration[4.2] def change add_column :users, :encrypted_otp_secret_key, :string add_column :users, :encrypted_otp_secret_key_iv, :string diff --git a/spec/rails_app/db/migrate/20151224180310_populate_otp_column.rb b/spec/rails_app/db/migrate/20151224180310_populate_otp_column.rb index a51222fe..ab34526d 100644 --- a/spec/rails_app/db/migrate/20151224180310_populate_otp_column.rb +++ b/spec/rails_app/db/migrate/20151224180310_populate_otp_column.rb @@ -1,4 +1,4 @@ -class PopulateOtpColumn < ActiveRecord::Migration +class PopulateOtpColumn < ActiveRecord::Migration[4.2] def up User.reset_column_information diff --git a/spec/rails_app/db/migrate/20151228230340_remove_otp_secret_key_from_user.rb b/spec/rails_app/db/migrate/20151228230340_remove_otp_secret_key_from_user.rb index 9ddd608e..40ebc073 100644 --- a/spec/rails_app/db/migrate/20151228230340_remove_otp_secret_key_from_user.rb +++ b/spec/rails_app/db/migrate/20151228230340_remove_otp_secret_key_from_user.rb @@ -1,4 +1,4 @@ -class RemoveOtpSecretKeyFromUser < ActiveRecord::Migration +class RemoveOtpSecretKeyFromUser < ActiveRecord::Migration[4.2] def change remove_column :users, :otp_secret_key, :string end diff --git a/spec/rails_app/db/migrate/20160209032439_devise_create_admins.rb b/spec/rails_app/db/migrate/20160209032439_devise_create_admins.rb index e598f423..8ec40e48 100644 --- a/spec/rails_app/db/migrate/20160209032439_devise_create_admins.rb +++ b/spec/rails_app/db/migrate/20160209032439_devise_create_admins.rb @@ -1,4 +1,4 @@ -class DeviseCreateAdmins < ActiveRecord::Migration +class DeviseCreateAdmins < ActiveRecord::Migration[4.2] def change create_table(:admins) do |t| ## Database authenticatable diff --git a/spec/rails_app/db/schema.rb b/spec/rails_app/db/schema.rb index 5f011618..02725d08 100644 --- a/spec/rails_app/db/schema.rb +++ b/spec/rails_app/db/schema.rb @@ -1,4 +1,3 @@ -# encoding: UTF-8 # This file is auto-generated from the current state of the database. Instead # of editing this file, please use the migrations feature of Active Record to # incrementally modify your database, and then regenerate this schema definition. @@ -14,45 +13,43 @@ ActiveRecord::Schema.define(version: 20160209032439) do create_table "admins", force: :cascade do |t| - t.string "email", default: "", null: false - t.string "encrypted_password", default: "", null: false - t.string "reset_password_token" + t.string "email", default: "", null: false + t.string "encrypted_password", default: "", null: false + t.string "reset_password_token" t.datetime "reset_password_sent_at" t.datetime "remember_created_at" - t.integer "sign_in_count", default: 0, null: false + t.integer "sign_in_count", default: 0, null: false t.datetime "current_sign_in_at" t.datetime "last_sign_in_at" - t.string "current_sign_in_ip" - t.string "last_sign_in_ip" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false + t.string "current_sign_in_ip" + t.string "last_sign_in_ip" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["email"], name: "index_admins_on_email", unique: true + t.index ["reset_password_token"], name: "index_admins_on_reset_password_token", unique: true end - add_index "admins", ["email"], name: "index_admins_on_email", unique: true - add_index "admins", ["reset_password_token"], name: "index_admins_on_reset_password_token", unique: true - create_table "users", force: :cascade do |t| - t.string "email", default: "", null: false - t.string "encrypted_password", default: "", null: false - t.string "reset_password_token" + t.string "email", default: "", null: false + t.string "encrypted_password", default: "", null: false + t.string "reset_password_token" t.datetime "reset_password_sent_at" t.datetime "remember_created_at" - t.integer "sign_in_count", default: 0, null: false + t.integer "sign_in_count", default: 0, null: false t.datetime "current_sign_in_at" t.datetime "last_sign_in_at" - t.string "current_sign_in_ip" - t.string "last_sign_in_ip" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - t.integer "second_factor_attempts_count", default: 0 - t.string "nickname", limit: 64 - t.string "encrypted_otp_secret_key" - t.string "encrypted_otp_secret_key_iv" - t.string "encrypted_otp_secret_key_salt" + t.string "current_sign_in_ip" + t.string "last_sign_in_ip" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.integer "second_factor_attempts_count", default: 0 + t.string "nickname", limit: 64 + t.string "encrypted_otp_secret_key" + t.string "encrypted_otp_secret_key_iv" + t.string "encrypted_otp_secret_key_salt" + t.index ["email"], name: "index_users_on_email", unique: true + t.index ["encrypted_otp_secret_key"], name: "index_users_on_encrypted_otp_secret_key", unique: true + t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true end - add_index "users", ["email"], name: "index_users_on_email", unique: true - add_index "users", ["encrypted_otp_secret_key"], name: "index_users_on_encrypted_otp_secret_key", unique: true - add_index "users", ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true - end From 91e8eb40d7d2bab34d77a90a816dec56195c5ffb Mon Sep 17 00:00:00 2001 From: Ryan McGeary Date: Mon, 29 Jan 2018 14:26:23 -0700 Subject: [PATCH 28/51] Delegate logic of send_new_otp to user#send_new_otp_after_login? Instead of keeping this logic nested in the Warden hook, call a method on the user object to determine if a new OTP code should be delivered. --- .../hooks/two_factor_authenticatable.rb | 2 +- .../models/two_factor_authenticatable.rb | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/two_factor_authentication/hooks/two_factor_authenticatable.rb b/lib/two_factor_authentication/hooks/two_factor_authenticatable.rb index 254df845..3ff03415 100644 --- a/lib/two_factor_authentication/hooks/two_factor_authenticatable.rb +++ b/lib/two_factor_authentication/hooks/two_factor_authenticatable.rb @@ -7,7 +7,7 @@ if user.respond_to?(:need_two_factor_authentication?) && !bypass_by_cookie if auth.session(options[:scope])[TwoFactorAuthentication::NEED_AUTHENTICATION] = user.need_two_factor_authentication?(auth.request) - user.send_new_otp unless user.totp_enabled? + user.send_new_otp if user.send_new_otp_after_login? end end end diff --git a/lib/two_factor_authentication/models/two_factor_authenticatable.rb b/lib/two_factor_authentication/models/two_factor_authenticatable.rb index 68cf7ae2..3c7de704 100644 --- a/lib/two_factor_authentication/models/two_factor_authenticatable.rb +++ b/lib/two_factor_authentication/models/two_factor_authenticatable.rb @@ -62,6 +62,10 @@ def send_new_otp(options = {}) send_two_factor_authentication_code(direct_otp) end + def send_new_otp_after_login? + !totp_enabled? + end + def send_two_factor_authentication_code(code) raise NotImplementedError.new("No default implementation - please define in your class.") end From a9e9093de8e1c0564d4bb1ed97ca693274b51409 Mon Sep 17 00:00:00 2001 From: Kevin Robatel Date: Thu, 8 Feb 2018 11:52:13 +0100 Subject: [PATCH 29/51] Return JSON with 'redirect_to' when handle_failed_second_factor --- lib/two_factor_authentication/controllers/helpers.rb | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/lib/two_factor_authentication/controllers/helpers.rb b/lib/two_factor_authentication/controllers/helpers.rb index f8a084d4..c3b01f6c 100644 --- a/lib/two_factor_authentication/controllers/helpers.rb +++ b/lib/two_factor_authentication/controllers/helpers.rb @@ -20,9 +20,14 @@ def handle_two_factor_authentication end def handle_failed_second_factor(scope) - if request.format.present? and request.format.html? - session["#{scope}_return_to"] = request.original_fullpath if request.get? - redirect_to two_factor_authentication_path_for(scope) + if request.format.present? + if request.format.html? + session["#{scope}_return_to"] = request.original_fullpath if request.get? + redirect_to two_factor_authentication_path_for(scope) + elsif request.format.json? + session["#{scope}_return_to"] = root_path(format: :html) + render json: { redirect_to: two_factor_authentication_path_for(scope) }, status: :unauthorized + end else render nothing: true, status: :unauthorized end From 0cad30df200b419cbb3f7cde38ab7e2aa518e992 Mon Sep 17 00:00:00 2001 From: Dmitrii Golub Date: Thu, 29 Mar 2018 18:36:09 +0300 Subject: [PATCH 30/51] Bump to version 2.1.0 --- lib/two_factor_authentication/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/two_factor_authentication/version.rb b/lib/two_factor_authentication/version.rb index a39aa7f6..7213a12b 100644 --- a/lib/two_factor_authentication/version.rb +++ b/lib/two_factor_authentication/version.rb @@ -1,3 +1,3 @@ module TwoFactorAuthentication - VERSION = "2.0.1".freeze + VERSION = "2.1.0".freeze end From f448cc7db6357b574979562972b67c998ea934a0 Mon Sep 17 00:00:00 2001 From: Bill Kirtley Date: Thu, 7 Jun 2018 16:24:08 -0400 Subject: [PATCH 31/51] Replace `render :nothing` with `head` fixes Houdini/two_factor_authentication#147 --- lib/two_factor_authentication/controllers/helpers.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/two_factor_authentication/controllers/helpers.rb b/lib/two_factor_authentication/controllers/helpers.rb index f8a084d4..9a92fa75 100644 --- a/lib/two_factor_authentication/controllers/helpers.rb +++ b/lib/two_factor_authentication/controllers/helpers.rb @@ -24,7 +24,7 @@ def handle_failed_second_factor(scope) session["#{scope}_return_to"] = request.original_fullpath if request.get? redirect_to two_factor_authentication_path_for(scope) else - render nothing: true, status: :unauthorized + head :unauthorized end end From 8b3c424a4ac0acd980fa371183d361893ad3929d Mon Sep 17 00:00:00 2001 From: Ladislav Gallay Date: Tue, 10 Jul 2018 17:08:44 +0200 Subject: [PATCH 32/51] Fix integer to seconds in remember_otp_session_for_seconds --- app/controllers/devise/two_factor_authentication_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/devise/two_factor_authentication_controller.rb b/app/controllers/devise/two_factor_authentication_controller.rb index 2cd8d6fa..0e7aed84 100644 --- a/app/controllers/devise/two_factor_authentication_controller.rb +++ b/app/controllers/devise/two_factor_authentication_controller.rb @@ -47,7 +47,7 @@ def set_remember_two_factor_cookie(resource) if expires_seconds && expires_seconds > 0 cookies.signed[TwoFactorAuthentication::REMEMBER_TFA_COOKIE_NAME] = { value: "#{resource.class}-#{resource.public_send(Devise.second_factor_resource_id)}", - expires: expires_seconds.from_now + expires: expires_seconds.seconds.from_now } end end From e5aaf1603ecbbe7a4d1a6eaf0c9fe422e15cf767 Mon Sep 17 00:00:00 2001 From: Dmitrii Golub Date: Tue, 10 Jul 2018 18:25:07 +0300 Subject: [PATCH 33/51] Bump to version 2.1.1 --- lib/two_factor_authentication/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/two_factor_authentication/version.rb b/lib/two_factor_authentication/version.rb index 7213a12b..7ed0623b 100644 --- a/lib/two_factor_authentication/version.rb +++ b/lib/two_factor_authentication/version.rb @@ -1,3 +1,3 @@ module TwoFactorAuthentication - VERSION = "2.1.0".freeze + VERSION = "2.1.1".freeze end From b57edd59faf15618fb280827285483da103b1f86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9sar=20Izurieta?= Date: Mon, 27 Aug 2018 23:04:45 +0200 Subject: [PATCH 34/51] Load ActiveRecord functionality only if ActiveRecord itself is loaded --- lib/two_factor_authentication.rb | 3 +-- lib/two_factor_authentication/orm/active_record.rb | 2 ++ 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/two_factor_authentication.rb b/lib/two_factor_authentication.rb index 969aff44..7b1bbbc1 100644 --- a/lib/two_factor_authentication.rb +++ b/lib/two_factor_authentication.rb @@ -2,7 +2,6 @@ require 'devise' require 'active_support/concern' require "active_model" -require "active_record" require "active_support/core_ext/class/attribute_accessors" require "cgi" @@ -47,7 +46,7 @@ module Controllers Devise.add_module :two_factor_authenticatable, :model => 'two_factor_authentication/models/two_factor_authenticatable', :controller => :two_factor_authentication, :route => :two_factor_authentication -require 'two_factor_authentication/orm/active_record' +require 'two_factor_authentication/orm/active_record' if defined?(ActiveRecord::Base) require 'two_factor_authentication/routes' require 'two_factor_authentication/models/two_factor_authenticatable' require 'two_factor_authentication/rails' diff --git a/lib/two_factor_authentication/orm/active_record.rb b/lib/two_factor_authentication/orm/active_record.rb index 616862ee..8053ee30 100644 --- a/lib/two_factor_authentication/orm/active_record.rb +++ b/lib/two_factor_authentication/orm/active_record.rb @@ -1,3 +1,5 @@ +require "active_record" + module TwoFactorAuthentication module Orm module ActiveRecord From 8887babd762995e1421b28039a0e0908935a5707 Mon Sep 17 00:00:00 2001 From: Alaina Hardie Date: Thu, 25 Oct 2018 14:59:53 -0600 Subject: [PATCH 35/51] Strip Spaces from TOTP Code Some users enter the TOTP code with a space, which breaks authentication. This strips the space from the user-entered TOTP code and validates against it. --- .../models/two_factor_authenticatable.rb | 6 +++++- .../models/two_factor_authenticatable_spec.rb | 5 +++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/lib/two_factor_authentication/models/two_factor_authenticatable.rb b/lib/two_factor_authentication/models/two_factor_authenticatable.rb index 3c7de704..bd1df46e 100644 --- a/lib/two_factor_authentication/models/two_factor_authenticatable.rb +++ b/lib/two_factor_authentication/models/two_factor_authenticatable.rb @@ -39,7 +39,7 @@ def authenticate_totp(code, options = {}) drift = options[:drift] || self.class.allowed_otp_drift_seconds raise "authenticate_totp called with no otp_secret_key set" if totp_secret.nil? totp = ROTP::TOTP.new(totp_secret, digits: digits) - new_timestamp = totp.verify_with_drift_and_prior(code, drift, totp_timestamp) + new_timestamp = totp.verify_with_drift_and_prior(without_spaces(code), drift, totp_timestamp) return false unless new_timestamp self.totp_timestamp = new_timestamp true @@ -103,6 +103,10 @@ def create_direct_otp(options = {}) private + def without_spaces(code) + code.gsub(/\s/, '') + end + def random_base10(digits) SecureRandom.random_number(10**digits).to_s.rjust(digits, '0') end diff --git a/spec/lib/two_factor_authentication/models/two_factor_authenticatable_spec.rb b/spec/lib/two_factor_authentication/models/two_factor_authenticatable_spec.rb index 05fb72d8..3a932d60 100644 --- a/spec/lib/two_factor_authentication/models/two_factor_authenticatable_spec.rb +++ b/spec/lib/two_factor_authentication/models/two_factor_authenticatable_spec.rb @@ -86,6 +86,11 @@ def do_invoke(code, user) expect(do_invoke(code, instance)).to eq(true) end + it 'authenticates a code entered with a space' do + code = @totp_helper.totp_code.insert(3, ' ') + expect(do_invoke(code, instance)).to eq(true) + end + it 'does not authenticate an old code' do code = @totp_helper.totp_code(1.minutes.ago.to_i) expect(do_invoke(code, instance)).to eq(false) From d462080c479825da2fcd27acf38c9dddd0a6ae78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Riveros?= Date: Tue, 27 Nov 2018 14:36:23 -0300 Subject: [PATCH 36/51] fix rotp 4 breaking authenticate totp --- .../models/two_factor_authenticatable.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/two_factor_authentication/models/two_factor_authenticatable.rb b/lib/two_factor_authentication/models/two_factor_authenticatable.rb index bd1df46e..eb6398c8 100644 --- a/lib/two_factor_authentication/models/two_factor_authenticatable.rb +++ b/lib/two_factor_authentication/models/two_factor_authenticatable.rb @@ -39,7 +39,10 @@ def authenticate_totp(code, options = {}) drift = options[:drift] || self.class.allowed_otp_drift_seconds raise "authenticate_totp called with no otp_secret_key set" if totp_secret.nil? totp = ROTP::TOTP.new(totp_secret, digits: digits) - new_timestamp = totp.verify_with_drift_and_prior(without_spaces(code), drift, totp_timestamp) + new_timestamp = totp.verify( + without_spaces(code), + drift_ahead: drift, drift_behind: drift, after: totp_timestamp + ) return false unless new_timestamp self.totp_timestamp = new_timestamp true From edcbc389d9a3c7dfc92b5cfe9e7338e374a2eace Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Riveros?= Date: Tue, 27 Nov 2018 15:12:27 -0300 Subject: [PATCH 37/51] rotp 4.0.0 dependency --- two_factor_authentication.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/two_factor_authentication.gemspec b/two_factor_authentication.gemspec index a5b886d6..a95b2ed8 100644 --- a/two_factor_authentication.gemspec +++ b/two_factor_authentication.gemspec @@ -27,7 +27,7 @@ Gem::Specification.new do |s| s.add_runtime_dependency 'rails', '>= 3.1.1' s.add_runtime_dependency 'devise' s.add_runtime_dependency 'randexp' - s.add_runtime_dependency 'rotp', '>= 3.2.0' + s.add_runtime_dependency 'rotp', '>= 4.0.0' s.add_runtime_dependency 'encryptor' s.add_development_dependency 'bundler' From dbd14d6b5b93f602479f405326ed3dbf9992a9d1 Mon Sep 17 00:00:00 2001 From: Tectract Date: Fri, 30 Nov 2018 11:54:43 -0700 Subject: [PATCH 38/51] Update README.md Update README.md --- README.md | 173 ++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 155 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 41a010ce..37b63c6d 100644 --- a/README.md +++ b/README.md @@ -225,24 +225,24 @@ steps: Open the generated file, and replace its contents with the following: ```ruby class PopulateEncryptedOtpFields < ActiveRecord::Migration - def up - User.reset_column_information - - User.find_each do |user| - user.otp_secret_key = user.read_attribute('otp_secret_key') - user.save! - end - end - - def down - User.reset_column_information - - User.find_each do |user| - user.otp_secret_key = ROTP::Base32.random_base32 - user.save! - end - end - end + def up + User.reset_column_information + + User.find_each do |user| + user.otp_secret_key = user.read_attribute('otp_secret_key') + user.save! + end + end + + def down + User.reset_column_information + + User.find_each do |user| + user.otp_secret_key = ROTP::Base32.random_base32 + user.save! + end + end + end ``` 5. Generate a migration to remove the `:otp_secret_key` column: @@ -263,6 +263,143 @@ after them): bundle exec rake db:rollback STEP=3 ``` +#### Critical Security Note! Add before_action to your user registration controllers + +You should have a file registrations_controller.rb in your controllers folder +to overwrite/customize user registrations. It should include the lines below, for 2FA protection of user model updates, meaning that users can only access the users/edit page after confirming 2FA fully, not simply by logging in. Otherwise the entire 2FA system can be bypassed! + + ```ruby + class RegistrationsController < Devise::RegistrationsController + before_action :confirm_two_factor_authenticated, except: [:new, :create, :cancel] + + protected + + def confirm_two_factor_authenticated + return if is_fully_authenticated? + + flash[:error] = t('devise.errors.messages.user_not_authenticated') + redirect_to user_two_factor_authentication_url + end + end + ``` + +#### Critical Security Note! Add 2FA validation to your custom user actions + +Make sure you are passing the 2FA secret codes securely and checking for them upon critical user actions, such as API key updates, user email or pgp pubkey updates, or any other changess to private/secure account-related details. Validate the secret during the initial 2FA key/secret verification by the user also, of course. + + For example, a simple account_controller.rb may look something like this: + + ``` + require 'json' + + class AccountController < ApplicationController + before_action :require_signed_in! + before_action :authenticate_user! + respond_to :html, :json + + def account_API + resp = {} + begin + if(account_params["twoFAKey"] && account_params["twoFASecret"]) + current_user.otp_secret_key = account_params["twoFAKey"] + if(current_user.authenticate_totp(account_params["twoFASecret"])) + # user has validated their temporary 2FA code, save it to their account, enable 2FA on this account + current_user.save! + resp['success'] = "passed 2FA validation!" + else + resp['error'] = "failed 2FA validation!" + end + elsif(param[:userAccountStuff] && param[:userAccountWidget]) + #before updating important user account stuff and widgets, + #check to see that the 2FA secret has also been passed in, and verify it... + if(account_params["twoFASecret"] && current_user.totp_enabled? && current_user.authenticate_totp(account_params["twoFASecret"])) + # user has passed 2FA checks, do cool user account stuff here + ... + else + # user failed 2FA check! No cool user stuff happens! + resp[error] = 'You failed 2FA validation!' + end + + ... + end + else + resp['error'] = 'unknown format error, not saved!' + end + rescue Exception => e + puts "WARNING: account api threw error : '#{e}' for user #{current_user.username}" + #print "error trace: #{e.backtrace}\n" + resp['error'] = "unanticipated server response" + end + render json: resp.to_json + end + + def account_params + params.require(:twoFA).permit(:userAccountStuff, :userAcountWidget, :twoFAKey, :twoFASecret) + end + end + ``` + + ### Example App [TwoFactorAuthenticationExample](https://github.com/Houdini/TwoFactorAuthenticationExample) + + +### Example user actions + +to use an ENV VAR for the 2FA encryption key: + +config.otp_secret_encryption_key = ENV['OTP_SECRET_ENCRYPTION_KEY'] + +to set up TOTP for Google Authenticator for user: + + ``` + current_user.otp_secret_key = current_user.generate_totp_secret + current_user.save! + ``` + +( encrypted db fields are set upon user model save action, +rails c access relies on setting env var: OTP_SECRET_ENCRYPTION_KEY ) + +to check if user has input the correct code (from the QR display page) +before saving the user model: + + ``` + current_user.authenticate_totp('123456') + ``` + +additional note: + + ``` + current_user.otp_secret_key + ``` + +This returns the OTP secret key in plaintext for the user (if you have set the env var) in the console +the string used for generating the QR given to the user for their Google Auth is something like: + +otpauth://totp/LABEL?secret=p6wwetjnkjnrcmpd (example secret used here) + +where LABEL should be something like "example.com (Username)", which shows up in their GA app to remind them the code is for example.com + +this returns true or false with an allowed_otp_drift_seconds 'grace period' + +to set TOTP to DISABLED for a user account: + + ``` + current_user.second_factor_attempts_count=nil + current_user.encrypted_otp_secret_key=nil + current_user.encrypted_otp_secret_key_iv=nil + current_user.encrypted_otp_secret_key_salt=nil + current_user.direct_otp=nil + current_user.direct_otp_sent_at=nil + current_user.totp_timestamp=nil + current_user.direct_otp=nil + current_user.otp_secret_key=nil + current_user.otp_confirmed=nil + current_user.save! (if in ruby code instead of console) + current_user.direct_otp? => false + current_user.totp_enabled? => false + ``` + + + From 3365885325324dedd3da25d9904cbd2956921712 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Riveros?= Date: Sun, 20 Jan 2019 23:57:51 -0300 Subject: [PATCH 39/51] default param padded removed --- spec/support/totp_helper.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/support/totp_helper.rb b/spec/support/totp_helper.rb index 1a49c811..060489dc 100644 --- a/spec/support/totp_helper.rb +++ b/spec/support/totp_helper.rb @@ -6,6 +6,6 @@ def initialize(secret_key, otp_length) end def totp_code(time = Time.now) - ROTP::TOTP.new(@secret_key, digits: @otp_length).at(time, true) + ROTP::TOTP.new(@secret_key, digits: @otp_length).at(time) end end From 5fd39c166a819449d1c8cbc7d7fbeccc59b2dcca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Riveros?= Date: Sun, 20 Jan 2019 23:58:44 -0300 Subject: [PATCH 40/51] 16 to 32 lenght --- .../models/two_factor_authenticatable_spec.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/spec/lib/two_factor_authentication/models/two_factor_authenticatable_spec.rb b/spec/lib/two_factor_authentication/models/two_factor_authenticatable_spec.rb index 3a932d60..6fb4f505 100644 --- a/spec/lib/two_factor_authentication/models/two_factor_authenticatable_spec.rb +++ b/spec/lib/two_factor_authentication/models/two_factor_authenticatable_spec.rb @@ -138,12 +138,12 @@ def instance.send_two_factor_authentication_code(code) it "returns uri with user's email" do expect(instance.provisioning_uri). - to match(%r{otpauth://totp/houdini@example.com\?secret=\w{16}}) + to match(%r{otpauth://totp/houdini@example.com\?secret=\w{32}}) end it 'returns uri with issuer option' do expect(instance.provisioning_uri('houdini')). - to match(%r{otpauth://totp/houdini\?secret=\w{16}$}) + to match(%r{otpauth://totp/houdini\?secret=\w{32}$}) end it 'returns uri with issuer option' do @@ -155,7 +155,7 @@ def instance.send_two_factor_authentication_code(code) expect(uri.host).to eq('totp') expect(uri.path).to eq('/Magic:houdini') expect(params['issuer'].shift).to eq('Magic') - expect(params['secret'].shift).to match(/\w{16}/) + expect(params['secret'].shift).to match(/\w{32}/) end end end @@ -168,10 +168,10 @@ def instance.send_two_factor_authentication_code(code) shared_examples 'generate_totp_secret' do |klass| let(:instance) { klass.new } - it 'returns a 16 character string' do + it 'returns a 32 character string' do secret = instance.generate_totp_secret - expect(secret).to match(/\w{16}/) + expect(secret).to match(/\w{32}/) end end From afd20039ee4d57f09d39d4f32b29328d52921fc8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Riveros?= Date: Mon, 21 Jan 2019 00:36:34 -0300 Subject: [PATCH 41/51] bundler 2 and rails 4.2 conflict --- .travis.yml | 11 ++++++----- Gemfile | 2 +- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index 94be5d0b..c3fc35f6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,13 +2,13 @@ language: ruby env: - "RAILS_VERSION=4.2" - - "RAILS_VERSION=5.1" + - "RAILS_VERSION=5.2" - "RAILS_VERSION=master" rvm: - - 2.3.6 - - 2.4.3 - - 2.5.0 + - 2.3.8 + - 2.4.5 + - 2.5.3 matrix: fast_finish: true @@ -19,7 +19,8 @@ matrix: env: RAILS_VERSION=4.2 before_install: - - gem update bundler + - gem uninstall -v '>= 2' -i $(rvm gemdir)@global -ax bundler || true + - gem install bundler -v '< 2' before_script: - bundle exec rake app:db:setup diff --git a/Gemfile b/Gemfile index 72a62fd7..810ce296 100644 --- a/Gemfile +++ b/Gemfile @@ -9,7 +9,7 @@ rails = case rails_version when "master" {github: "rails/rails"} when "default" - "~> 5.1" + "~> 5.2" else "~> #{rails_version}" end From 06bfb50250d45cd044cbf034c9a0a56295e8b6a5 Mon Sep 17 00:00:00 2001 From: Mark Date: Sat, 26 Jan 2019 18:08:28 +0800 Subject: [PATCH 42/51] Update README.md Use correct module name --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 41a010ce..dbe56c2a 100644 --- a/README.md +++ b/README.md @@ -54,7 +54,7 @@ migration in `db/migrate/`, which will add the following columns to your table: #### Manual initial setup If you prefer to set up the model and migration manually, add the -`:two_factor_authentication` option to your existing devise options, such as: +`:two_factor_authenticatable` option to your existing devise options, such as: ```ruby devise :database_authenticatable, :registerable, :recoverable, :rememberable, From dd72a24ea8c44ca6bb102128cffe5ae3a5628153 Mon Sep 17 00:00:00 2001 From: Dmitrii Golub Date: Tue, 29 Jan 2019 17:59:41 +0300 Subject: [PATCH 43/51] update spec schema.rb --- spec/rails_app/db/schema.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/rails_app/db/schema.rb b/spec/rails_app/db/schema.rb index 02725d08..9c0d0f18 100644 --- a/spec/rails_app/db/schema.rb +++ b/spec/rails_app/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20160209032439) do +ActiveRecord::Schema.define(version: 2016_02_09_032439) do create_table "admins", force: :cascade do |t| t.string "email", default: "", null: false From f9042487e8dd665922f5aa58cb1a55d640233e2f Mon Sep 17 00:00:00 2001 From: Dmitrii Golub Date: Tue, 29 Jan 2019 18:13:39 +0300 Subject: [PATCH 44/51] bump version to 2.2.0 --- lib/two_factor_authentication/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/two_factor_authentication/version.rb b/lib/two_factor_authentication/version.rb index 7ed0623b..239fae10 100644 --- a/lib/two_factor_authentication/version.rb +++ b/lib/two_factor_authentication/version.rb @@ -1,3 +1,3 @@ module TwoFactorAuthentication - VERSION = "2.1.1".freeze + VERSION = "2.2.0".freeze end From 153b016bfa0f86f44ca315ca31db015d121aefba Mon Sep 17 00:00:00 2001 From: Jan Bussieck Date: Fri, 1 Feb 2019 16:10:08 +0100 Subject: [PATCH 45/51] Add german translations --- config/locales/de.yml | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 config/locales/de.yml diff --git a/config/locales/de.yml b/config/locales/de.yml new file mode 100644 index 00000000..253e6058 --- /dev/null +++ b/config/locales/de.yml @@ -0,0 +1,8 @@ +de: + devise: + two_factor_authentication: + success: "Ihre Zwei-Faktor-Authentifizierung war erfolgreich." + attempt_failed: "Authentifizierungsversuch fehlgeschlagen." + max_login_attempts_reached: "Ihr Zugang wurde ganz verweigert, da Sie Ihr Versuchslimit erreicht haben." + contact_administrator: "Kontaktieren Sie bitte einen Ihrer Administratoren." + code_has_been_sent: "Ihr Einmal-Passwort wurde verschickt." From 8170ec049d49610b9f655e9f8711f08196362f6d Mon Sep 17 00:00:00 2001 From: Ace Subido and Mark Chavez Date: Thu, 21 Feb 2019 14:40:33 +0800 Subject: [PATCH 46/51] Update README Remove unused column `otp_confirmed` --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 1a5e7818..f4f91d1a 100644 --- a/README.md +++ b/README.md @@ -395,7 +395,6 @@ to set TOTP to DISABLED for a user account: current_user.totp_timestamp=nil current_user.direct_otp=nil current_user.otp_secret_key=nil - current_user.otp_confirmed=nil current_user.save! (if in ruby code instead of console) current_user.direct_otp? => false current_user.totp_enabled? => false From 8f9377b28f0adf2650d3a4be0ae9878ef521fb60 Mon Sep 17 00:00:00 2001 From: Jasper van den Berg Date: Fri, 14 Jun 2019 15:48:27 +0200 Subject: [PATCH 47/51] Use ROTP::Base32.random instead of ROTP::Base32.random_base32 if available --- .../models/two_factor_authenticatable.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/two_factor_authentication/models/two_factor_authenticatable.rb b/lib/two_factor_authentication/models/two_factor_authenticatable.rb index eb6398c8..f5b94e96 100644 --- a/lib/two_factor_authentication/models/two_factor_authenticatable.rb +++ b/lib/two_factor_authentication/models/two_factor_authenticatable.rb @@ -92,7 +92,7 @@ def confirm_totp_secret(secret, code, options = {}) end def generate_totp_secret - ROTP::Base32.random_base32 + ROTP::Base32.try(:random) || ROTP::Base32.random_base32 end def create_direct_otp(options = {}) From 82e5aff7b344ac2c5f4543a3887db3e6bfcd0008 Mon Sep 17 00:00:00 2001 From: Dmitrii Golub Date: Thu, 4 Jul 2019 18:38:21 +0300 Subject: [PATCH 48/51] Additional comments in generate_totp_secret method --- .../models/two_factor_authenticatable.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/two_factor_authentication/models/two_factor_authenticatable.rb b/lib/two_factor_authentication/models/two_factor_authenticatable.rb index f5b94e96..6d73a0fb 100644 --- a/lib/two_factor_authentication/models/two_factor_authenticatable.rb +++ b/lib/two_factor_authentication/models/two_factor_authenticatable.rb @@ -92,6 +92,9 @@ def confirm_totp_secret(secret, code, options = {}) end def generate_totp_secret + # ROTP gem since version 5 to version 5.1 + # at version 5.1 ROTP gem reinstates. + # Details: https://github.com/mdp/rotp/blob/master/CHANGELOG.md#510 ROTP::Base32.try(:random) || ROTP::Base32.random_base32 end From 2d3650dd5941a6efd478db95e8a0c56adb1fc12f Mon Sep 17 00:00:00 2001 From: Gustavo Lazaro Amendola <38653102+gustavokitman@users.noreply.github.com> Date: Tue, 23 Jul 2019 13:29:54 +0100 Subject: [PATCH 49/51] Autofocus MFA code text field This is a small change which will make users life so much easier. --- app/views/devise/two_factor_authentication/show.html.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/devise/two_factor_authentication/show.html.erb b/app/views/devise/two_factor_authentication/show.html.erb index 0ffa2248..9a3ae060 100644 --- a/app/views/devise/two_factor_authentication/show.html.erb +++ b/app/views/devise/two_factor_authentication/show.html.erb @@ -7,7 +7,7 @@

<%= flash[:notice] %>

<%= form_tag([resource_name, :two_factor_authentication], :method => :put) do %> - <%= text_field_tag :code %> + <%= text_field_tag :code, '', autofocus: true %> <%= submit_tag "Submit" %> <% end %> From c21a935de796b6c0e41120dec661f68899b1e186 Mon Sep 17 00:00:00 2001 From: Przemyslaw Mroczek Date: Thu, 17 Sep 2020 20:11:33 +0200 Subject: [PATCH 50/51] Make current migration safe using index concurrently --- lib/generators/active_record/templates/migration.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/generators/active_record/templates/migration.rb b/lib/generators/active_record/templates/migration.rb index 251ef402..f14a733d 100644 --- a/lib/generators/active_record/templates/migration.rb +++ b/lib/generators/active_record/templates/migration.rb @@ -1,4 +1,6 @@ class TwoFactorAuthenticationAddTo<%= table_name.camelize %> < ActiveRecord::Migration + disable_ddl_transaction! + def change add_column :<%= table_name %>, :second_factor_attempts_count, :integer, default: 0 add_column :<%= table_name %>, :encrypted_otp_secret_key, :string @@ -8,6 +10,6 @@ def change add_column :<%= table_name %>, :direct_otp_sent_at, :datetime add_column :<%= table_name %>, :totp_timestamp, :timestamp - add_index :<%= table_name %>, :encrypted_otp_secret_key, unique: true + add_index :<%= table_name %>, :encrypted_otp_secret_key, unique: true, algorithm: :concurrently end end From a0b2ed0dd2818cdfd0c88b973b2daa6cf09096e1 Mon Sep 17 00:00:00 2001 From: Alessio Signorini Date: Thu, 25 Feb 2021 13:33:06 -0800 Subject: [PATCH 51/51] Added license tag (MIT) to gemspec --- two_factor_authentication.gemspec | 1 + 1 file changed, 1 insertion(+) diff --git a/two_factor_authentication.gemspec b/two_factor_authentication.gemspec index a95b2ed8..9580f117 100644 --- a/two_factor_authentication.gemspec +++ b/two_factor_authentication.gemspec @@ -9,6 +9,7 @@ Gem::Specification.new do |s| s.email = ["dmitrii.golub@gmail.com"] s.homepage = "https://github.com/Houdini/two_factor_authentication" s.summary = %q{Two factor authentication plugin for devise} + s.license = "MIT" s.description = <<-EOF ### Features ### * control sms code pattern