two_factor_authentication 0.2 → 1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +1 -0
- data/README.md +32 -19
- data/app/controllers/devise/two_factor_authentication_controller.rb +3 -3
- data/app/views/devise/two_factor_authentication/max_login_attempts_reached.html.erb +2 -2
- data/config/locales/en.yml +1 -1
- data/lib/generators/active_record/templates/migration.rb +9 -2
- data/lib/two_factor_authentication.rb +8 -5
- data/lib/two_factor_authentication/controllers/helpers.rb +11 -4
- data/lib/two_factor_authentication/hooks/two_factor_authenticatable.rb +1 -4
- data/lib/two_factor_authentication/models/two_factor_authenticatable.rb +50 -12
- data/lib/two_factor_authentication/schema.rb +2 -2
- data/lib/two_factor_authentication/version.rb +1 -1
- data/spec/lib/two_factor_authentication/models/two_factor_authenticatable_spec.rb +70 -0
- data/spec/spec_helper.rb +21 -0
- data/spec/support/authenticated_model_helper.rb +29 -0
- data/two_factor_authentication.gemspec +2 -0
- metadata +56 -29
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 39274f1841e29f847f6c2900a9228493e6103243
|
4
|
+
data.tar.gz: c0c3e1aebe06d8981301dfa2ccd85ab6f347b6e0
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 3e4bf30aa6f90afd6ee31400b4acd5e75dfc6fa49162b9ceb18042e406c55b956a0841466b9881c289b842870cb56ca985005e639578861259862c9c04209e3c
|
7
|
+
data.tar.gz: 91f57824cfc4ea2588cd7658c50eb8ee0b221b6dff7c45362d2d2542c51911c61f3f88483e9d79951d14c1c61fb3a4c80a11065bf77a513a0cc5f15d41bd713f
|
data/.gitignore
CHANGED
data/README.md
CHANGED
@@ -32,6 +32,20 @@ Finally, run the migration with:
|
|
32
32
|
|
33
33
|
bundle exec rake db:migrate
|
34
34
|
|
35
|
+
Add the following line to your model to fully enable two-factor auth:
|
36
|
+
|
37
|
+
has_one_time_password
|
38
|
+
|
39
|
+
Set config values if desired for maximum second factor attempts count and allowed time drift for one-time passwords:
|
40
|
+
|
41
|
+
config.max_login_attempts = 3
|
42
|
+
config.allowed_otp_drift_seconds = 30
|
43
|
+
|
44
|
+
Override the method to send one-time passwords in your model, this is automatically called when a user logs in:
|
45
|
+
|
46
|
+
def send_two_factor_authentication_code
|
47
|
+
# use Model#otp_code and send via SMS, etc.
|
48
|
+
end
|
35
49
|
|
36
50
|
### Manual installation
|
37
51
|
|
@@ -42,23 +56,22 @@ To manually enable two factor authentication for the User model, you should add
|
|
42
56
|
:recoverable, :rememberable, :trackable, :validatable, :two_factor_authenticatable
|
43
57
|
```
|
44
58
|
|
45
|
-
|
59
|
+
Add the following line to your model to fully enable two-factor auth:
|
46
60
|
|
47
|
-
|
48
|
-
config.login_code_random_pattern = /\w+/
|
49
|
-
config.max_login_attempts = 3
|
50
|
-
```
|
61
|
+
has_one_time_password
|
51
62
|
|
52
|
-
|
63
|
+
Set config values if desired for maximum second factor attempts count and allowed time drift for one-time passwords:
|
53
64
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
65
|
+
config.max_login_attempts = 3
|
66
|
+
config.allowed_otp_drift_seconds = 30
|
67
|
+
|
68
|
+
Override the method to send one-time passwords in your model, this is automatically called when a user logs in:
|
58
69
|
|
59
|
-
|
70
|
+
def send_two_factor_authentication_code
|
71
|
+
# use Model#otp_code and send via SMS, etc.
|
72
|
+
end
|
60
73
|
|
61
|
-
### Customisation
|
74
|
+
### Customisation and Usage
|
62
75
|
|
63
76
|
By default second factor authentication enabled for each user, you can change it with this method in your User model:
|
64
77
|
|
@@ -70,12 +83,12 @@ By default second factor authentication enabled for each user, you can change it
|
|
70
83
|
|
71
84
|
this will disable two factor authentication for local users
|
72
85
|
|
73
|
-
|
86
|
+
This gem is compatible with Google Authenticator (https://support.google.com/accounts/answer/1066447?hl=en). You can generate provisioning uris by invoking the following method on your model:
|
74
87
|
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
88
|
+
user.provisioning_uri #This assumes a user model with an email attributes
|
89
|
+
|
90
|
+
This provisioning uri can then be turned in to a QR code if desired so that users may add the app to Google Authenticator easily. Once this is done they may retrieve a one-time password directly from the Google Authenticator app as well as through whatever method you define in `send_two_factor_authentication_code`
|
91
|
+
|
92
|
+
### Example
|
80
93
|
|
81
|
-
|
94
|
+
[TwoFactorAuthenticationExample](https://github.com/Houdini/TwoFactorAuthenticationExample)
|
@@ -7,8 +7,8 @@ class Devise::TwoFactorAuthenticationController < DeviseController
|
|
7
7
|
|
8
8
|
def update
|
9
9
|
render :show and return if params[:code].nil?
|
10
|
-
|
11
|
-
if
|
10
|
+
|
11
|
+
if resource.authenticate_otp(params[:code])
|
12
12
|
warden.session(resource_name)[:need_two_factor_authentication] = false
|
13
13
|
sign_in resource_name, resource, :bypass => true
|
14
14
|
redirect_to stored_location_for(resource_name) || :root
|
@@ -16,7 +16,7 @@ class Devise::TwoFactorAuthenticationController < DeviseController
|
|
16
16
|
else
|
17
17
|
resource.second_factor_attempts_count += 1
|
18
18
|
resource.save
|
19
|
-
set_flash_message :
|
19
|
+
set_flash_message :error, :attempt_failed
|
20
20
|
if resource.max_login_attempts?
|
21
21
|
sign_out(resource)
|
22
22
|
render :template => 'devise/two_factor_authentication/max_login_attempts_reached' and return
|
@@ -1,3 +1,3 @@
|
|
1
|
-
<h2>Access
|
2
|
-
<p>Please contact your system administrator
|
1
|
+
<h2>Access completely denied as you have reached your attempts limit = <%= @limit %>.</h2>
|
2
|
+
<p>Please contact your system administrator.</p>
|
3
3
|
|
data/config/locales/en.yml
CHANGED
@@ -1,8 +1,15 @@
|
|
1
1
|
class TwoFactorAuthenticationAddTo<%= table_name.camelize %> < ActiveRecord::Migration
|
2
|
-
def
|
2
|
+
def up
|
3
3
|
change_table :<%= table_name %> do |t|
|
4
|
-
t.string :
|
4
|
+
t.string :otp_secret_key
|
5
5
|
t.integer :second_factor_attempts_count, :default => 0
|
6
6
|
end
|
7
|
+
|
8
|
+
add_index :<%= table_name %>, :otp_secret_key, :unique => true
|
9
|
+
end
|
10
|
+
|
11
|
+
def down
|
12
|
+
remove_column :<%= table_name %>, :otp_secret_key
|
13
|
+
remove_column :<%= table_name %>, :second_factor_attempts_count
|
7
14
|
end
|
8
15
|
end
|
@@ -1,15 +1,18 @@
|
|
1
1
|
require 'two_factor_authentication/version'
|
2
|
-
require 'randexp'
|
3
2
|
require 'devise'
|
4
|
-
require 'digest'
|
5
3
|
require 'active_support/concern'
|
4
|
+
require "active_model"
|
5
|
+
require "active_record"
|
6
|
+
require "active_support/core_ext/class/attribute_accessors"
|
7
|
+
require "cgi"
|
8
|
+
require "rotp"
|
6
9
|
|
7
10
|
module Devise
|
8
|
-
mattr_accessor :login_code_random_pattern
|
9
|
-
@@login_code_random_pattern = /\w+/
|
10
|
-
|
11
11
|
mattr_accessor :max_login_attempts
|
12
12
|
@@max_login_attempts = 3
|
13
|
+
|
14
|
+
mattr_accessor :allowed_otp_drift_seconds
|
15
|
+
@@allowed_otp_drift_seconds = 30
|
13
16
|
end
|
14
17
|
|
15
18
|
module TwoFactorAuthentication
|
@@ -10,17 +10,24 @@ module TwoFactorAuthentication
|
|
10
10
|
private
|
11
11
|
|
12
12
|
def handle_two_factor_authentication
|
13
|
-
|
13
|
+
unless devise_controller?
|
14
14
|
Devise.mappings.keys.flatten.any? do |scope|
|
15
15
|
if signed_in?(scope) and warden.session(scope)[:need_two_factor_authentication]
|
16
|
-
|
17
|
-
redirect_to two_factor_authentication_path_for(scope)
|
18
|
-
return
|
16
|
+
handle_failed_second_factor(scope)
|
19
17
|
end
|
20
18
|
end
|
21
19
|
end
|
22
20
|
end
|
23
21
|
|
22
|
+
def handle_failed_second_factor(scope)
|
23
|
+
if request.format.present? and request.format.html?
|
24
|
+
session["#{scope}_return_tor"] = request.path if request.get?
|
25
|
+
redirect_to two_factor_authentication_path_for(scope)
|
26
|
+
else
|
27
|
+
render nothing: true, status: :unauthorized
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
24
31
|
def two_factor_authentication_path_for(resource_or_scope = nil)
|
25
32
|
scope = Devise::Mapping.find_scope!(resource_or_scope)
|
26
33
|
change_path = "#{scope}_two_factor_authentication_path"
|
@@ -1,10 +1,7 @@
|
|
1
1
|
Warden::Manager.after_authentication do |user, auth, options|
|
2
2
|
if user.respond_to?(:need_two_factor_authentication?)
|
3
3
|
if auth.session(options[:scope])[:need_two_factor_authentication] = user.need_two_factor_authentication?(auth.request)
|
4
|
-
|
5
|
-
user.second_factor_pass_code = Digest::MD5.hexdigest(code)
|
6
|
-
user.save
|
7
|
-
user.send_two_factor_authentication_code(code)
|
4
|
+
user.send_two_factor_authentication_code
|
8
5
|
end
|
9
6
|
end
|
10
7
|
end
|
@@ -5,23 +5,61 @@ module Devise
|
|
5
5
|
extend ActiveSupport::Concern
|
6
6
|
|
7
7
|
module ClassMethods
|
8
|
-
|
9
|
-
end
|
8
|
+
def has_one_time_password(options = {})
|
10
9
|
|
11
|
-
|
12
|
-
|
13
|
-
end
|
10
|
+
cattr_accessor :otp_column_name
|
11
|
+
self.otp_column_name = "otp_secret_key"
|
14
12
|
|
15
|
-
|
16
|
-
|
17
|
-
|
13
|
+
include InstanceMethodsOnActivation
|
14
|
+
|
15
|
+
before_create { self.otp_column = ROTP::Base32.random_base32 }
|
18
16
|
|
19
|
-
|
20
|
-
|
17
|
+
if respond_to?(:attributes_protected_by_default)
|
18
|
+
def self.attributes_protected_by_default #:nodoc:
|
19
|
+
super + [self.otp_column_name]
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
::Devise::Models.config(self, :max_login_attempts, :allowed_otp_drift_seconds)
|
21
24
|
end
|
22
25
|
|
23
|
-
|
24
|
-
|
26
|
+
module InstanceMethodsOnActivation
|
27
|
+
def authenticate_otp(code, options = {})
|
28
|
+
totp = ROTP::TOTP.new(self.otp_column)
|
29
|
+
drift = options[:drift] || self.class.allowed_otp_drift_seconds
|
30
|
+
|
31
|
+
totp.verify_with_drift(code, drift)
|
32
|
+
end
|
33
|
+
|
34
|
+
def otp_code(time = Time.now)
|
35
|
+
ROTP::TOTP.new(self.otp_column).at(time)
|
36
|
+
end
|
37
|
+
|
38
|
+
def provisioning_uri(account = nil)
|
39
|
+
account ||= self.email if self.respond_to?(:email)
|
40
|
+
ROTP::TOTP.new(self.otp_column).provisioning_uri(account)
|
41
|
+
end
|
42
|
+
|
43
|
+
def otp_column
|
44
|
+
self.send(self.class.otp_column_name)
|
45
|
+
end
|
46
|
+
|
47
|
+
def otp_column=(attr)
|
48
|
+
self.send("#{self.class.otp_column_name}=", attr)
|
49
|
+
end
|
50
|
+
|
51
|
+
def need_two_factor_authentication?(request)
|
52
|
+
true
|
53
|
+
end
|
54
|
+
|
55
|
+
def send_two_factor_authentication_code
|
56
|
+
raise NotImplementedError.new("No default implementation - please define in your class.")
|
57
|
+
end
|
58
|
+
|
59
|
+
def max_login_attempts?
|
60
|
+
second_factor_attempts_count >= self.class.max_login_attempts
|
61
|
+
end
|
62
|
+
|
25
63
|
end
|
26
64
|
end
|
27
65
|
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
include AuthenticatedModelHelper
|
3
|
+
|
4
|
+
|
5
|
+
describe Devise::Models::TwoFactorAuthenticatable, '#otp_code' do
|
6
|
+
let(:instance) { AuthenticatedModelHelper.create_new_user }
|
7
|
+
subject { instance.otp_code(time) }
|
8
|
+
let(:time) { 1392852456 }
|
9
|
+
|
10
|
+
it "should return an error if no secret is set" do
|
11
|
+
expect {
|
12
|
+
subject
|
13
|
+
}.to raise_error
|
14
|
+
end
|
15
|
+
|
16
|
+
context "secret is set" do
|
17
|
+
before :each do
|
18
|
+
instance.otp_secret_key = "2z6hxkdwi3uvrnpn"
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should not return an error" do
|
22
|
+
subject
|
23
|
+
end
|
24
|
+
|
25
|
+
context "with a known time" do
|
26
|
+
let(:time) { 1392852756 }
|
27
|
+
|
28
|
+
it "should return a known result" do
|
29
|
+
expect(subject).to eq(562202)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
describe Devise::Models::TwoFactorAuthenticatable, '#authenticate_otp' do
|
36
|
+
let(:instance) { AuthenticatedModelHelper.create_new_user }
|
37
|
+
|
38
|
+
before :each do
|
39
|
+
instance.otp_secret_key = "2z6hxkdwi3uvrnpn"
|
40
|
+
end
|
41
|
+
|
42
|
+
def do_invoke code, options = {}
|
43
|
+
instance.authenticate_otp(code, options)
|
44
|
+
end
|
45
|
+
|
46
|
+
it "should be able to authenticate a recently created code" do
|
47
|
+
code = instance.otp_code
|
48
|
+
expect(do_invoke(code)).to eq(true)
|
49
|
+
end
|
50
|
+
|
51
|
+
it "should not authenticate an old code" do
|
52
|
+
code = instance.otp_code(1.minutes.ago.to_i)
|
53
|
+
expect(do_invoke(code)).to eq(false)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
describe Devise::Models::TwoFactorAuthenticatable, '#send_two_factor_authentication_code' do
|
58
|
+
|
59
|
+
it "should raise an error by default" do
|
60
|
+
instance = AuthenticatedModelHelper.create_new_user
|
61
|
+
expect {
|
62
|
+
instance.send_two_factor_authentication_code
|
63
|
+
}.to raise_error(NotImplementedError)
|
64
|
+
end
|
65
|
+
|
66
|
+
it "should be overrideable" do
|
67
|
+
instance = AuthenticatedModelHelper.create_new_user_with_overrides
|
68
|
+
expect(instance.send_two_factor_authentication_code).to eq("Code sent")
|
69
|
+
end
|
70
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require "rubygems"
|
2
|
+
require "bundler/setup"
|
3
|
+
|
4
|
+
require 'two_factor_authentication'
|
5
|
+
|
6
|
+
|
7
|
+
Dir["#{Dir.pwd}/spec/support/**/*.rb"].each {|f| require f}
|
8
|
+
|
9
|
+
|
10
|
+
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
|
11
|
+
RSpec.configure do |config|
|
12
|
+
config.treat_symbols_as_metadata_keys_with_true_values = true
|
13
|
+
config.run_all_when_everything_filtered = true
|
14
|
+
config.filter_run :focus
|
15
|
+
|
16
|
+
# Run specs in random order to surface order dependencies. If you find an
|
17
|
+
# order dependency and want to debug it, you can fix the order by providing
|
18
|
+
# the seed, which is printed after each run.
|
19
|
+
# --seed 1234
|
20
|
+
config.order = 'random'
|
21
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module AuthenticatedModelHelper
|
2
|
+
|
3
|
+
class User
|
4
|
+
extend ActiveModel::Callbacks
|
5
|
+
include ActiveModel::Validations
|
6
|
+
include Devise::Models::TwoFactorAuthenticatable
|
7
|
+
|
8
|
+
define_model_callbacks :create
|
9
|
+
attr_accessor :otp_secret_key, :email
|
10
|
+
|
11
|
+
has_one_time_password
|
12
|
+
end
|
13
|
+
|
14
|
+
class UserWithOverrides < User
|
15
|
+
|
16
|
+
def send_two_factor_authentication_code
|
17
|
+
"Code sent"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def create_new_user
|
22
|
+
User.new
|
23
|
+
end
|
24
|
+
|
25
|
+
def create_new_user_with_overrides
|
26
|
+
UserWithOverrides.new
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
@@ -24,9 +24,11 @@ Gem::Specification.new do |s|
|
|
24
24
|
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
25
25
|
s.require_paths = ["lib"]
|
26
26
|
|
27
|
+
s.add_development_dependency "rspec"
|
27
28
|
s.add_runtime_dependency 'rails', '>= 3.1.1'
|
28
29
|
s.add_runtime_dependency 'devise'
|
29
30
|
s.add_runtime_dependency 'randexp'
|
31
|
+
s.add_runtime_dependency 'rotp'
|
30
32
|
|
31
33
|
s.add_development_dependency 'bundler'
|
32
34
|
end
|
metadata
CHANGED
@@ -1,83 +1,105 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: two_factor_authentication
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: '0
|
5
|
-
prerelease:
|
4
|
+
version: '1.0'
|
6
5
|
platform: ruby
|
7
6
|
authors:
|
8
7
|
- Dmitrii Golub
|
9
8
|
autorequire:
|
10
9
|
bindir: bin
|
11
10
|
cert_chain: []
|
12
|
-
date:
|
11
|
+
date: 2014-03-28 00:00:00.000000000 Z
|
13
12
|
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rspec
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - '>='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - '>='
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
14
27
|
- !ruby/object:Gem::Dependency
|
15
28
|
name: rails
|
16
29
|
requirement: !ruby/object:Gem::Requirement
|
17
|
-
none: false
|
18
30
|
requirements:
|
19
|
-
- -
|
31
|
+
- - '>='
|
20
32
|
- !ruby/object:Gem::Version
|
21
33
|
version: 3.1.1
|
22
34
|
type: :runtime
|
23
35
|
prerelease: false
|
24
36
|
version_requirements: !ruby/object:Gem::Requirement
|
25
|
-
none: false
|
26
37
|
requirements:
|
27
|
-
- -
|
38
|
+
- - '>='
|
28
39
|
- !ruby/object:Gem::Version
|
29
40
|
version: 3.1.1
|
30
41
|
- !ruby/object:Gem::Dependency
|
31
42
|
name: devise
|
32
43
|
requirement: !ruby/object:Gem::Requirement
|
33
|
-
none: false
|
34
44
|
requirements:
|
35
|
-
- -
|
45
|
+
- - '>='
|
36
46
|
- !ruby/object:Gem::Version
|
37
47
|
version: '0'
|
38
48
|
type: :runtime
|
39
49
|
prerelease: false
|
40
50
|
version_requirements: !ruby/object:Gem::Requirement
|
41
|
-
none: false
|
42
51
|
requirements:
|
43
|
-
- -
|
52
|
+
- - '>='
|
44
53
|
- !ruby/object:Gem::Version
|
45
54
|
version: '0'
|
46
55
|
- !ruby/object:Gem::Dependency
|
47
56
|
name: randexp
|
48
57
|
requirement: !ruby/object:Gem::Requirement
|
49
|
-
none: false
|
50
58
|
requirements:
|
51
|
-
- -
|
59
|
+
- - '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - '>='
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rotp
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - '>='
|
52
74
|
- !ruby/object:Gem::Version
|
53
75
|
version: '0'
|
54
76
|
type: :runtime
|
55
77
|
prerelease: false
|
56
78
|
version_requirements: !ruby/object:Gem::Requirement
|
57
|
-
none: false
|
58
79
|
requirements:
|
59
|
-
- -
|
80
|
+
- - '>='
|
60
81
|
- !ruby/object:Gem::Version
|
61
82
|
version: '0'
|
62
83
|
- !ruby/object:Gem::Dependency
|
63
84
|
name: bundler
|
64
85
|
requirement: !ruby/object:Gem::Requirement
|
65
|
-
none: false
|
66
86
|
requirements:
|
67
|
-
- -
|
87
|
+
- - '>='
|
68
88
|
- !ruby/object:Gem::Version
|
69
89
|
version: '0'
|
70
90
|
type: :development
|
71
91
|
prerelease: false
|
72
92
|
version_requirements: !ruby/object:Gem::Requirement
|
73
|
-
none: false
|
74
93
|
requirements:
|
75
|
-
- -
|
94
|
+
- - '>='
|
76
95
|
- !ruby/object:Gem::Version
|
77
96
|
version: '0'
|
78
|
-
description:
|
79
|
-
|
80
|
-
|
97
|
+
description: |2
|
98
|
+
### Features ###
|
99
|
+
* control sms code pattern
|
100
|
+
* configure max login attempts
|
101
|
+
* per user level control if he really need two factor authentication
|
102
|
+
* your own sms logic
|
81
103
|
email:
|
82
104
|
- dmitrii.golub@gmail.com
|
83
105
|
executables: []
|
@@ -105,30 +127,35 @@ files:
|
|
105
127
|
- lib/two_factor_authentication/routes.rb
|
106
128
|
- lib/two_factor_authentication/schema.rb
|
107
129
|
- lib/two_factor_authentication/version.rb
|
130
|
+
- spec/lib/two_factor_authentication/models/two_factor_authenticatable_spec.rb
|
131
|
+
- spec/spec_helper.rb
|
132
|
+
- spec/support/authenticated_model_helper.rb
|
108
133
|
- two_factor_authentication.gemspec
|
109
134
|
homepage: https://github.com/Houdini/two_factor_authentication
|
110
135
|
licenses: []
|
136
|
+
metadata: {}
|
111
137
|
post_install_message:
|
112
138
|
rdoc_options: []
|
113
139
|
require_paths:
|
114
140
|
- lib
|
115
141
|
required_ruby_version: !ruby/object:Gem::Requirement
|
116
|
-
none: false
|
117
142
|
requirements:
|
118
|
-
- -
|
143
|
+
- - '>='
|
119
144
|
- !ruby/object:Gem::Version
|
120
145
|
version: '0'
|
121
146
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
122
|
-
none: false
|
123
147
|
requirements:
|
124
|
-
- -
|
148
|
+
- - '>='
|
125
149
|
- !ruby/object:Gem::Version
|
126
150
|
version: '0'
|
127
151
|
requirements: []
|
128
152
|
rubyforge_project: two_factor_authentication
|
129
|
-
rubygems_version: 1.
|
153
|
+
rubygems_version: 2.1.11
|
130
154
|
signing_key:
|
131
|
-
specification_version:
|
155
|
+
specification_version: 4
|
132
156
|
summary: Two factor authentication plugin for devise
|
133
|
-
test_files:
|
157
|
+
test_files:
|
158
|
+
- spec/lib/two_factor_authentication/models/two_factor_authenticatable_spec.rb
|
159
|
+
- spec/spec_helper.rb
|
160
|
+
- spec/support/authenticated_model_helper.rb
|
134
161
|
has_rdoc:
|