active_model_otp 0.0.1 → 0.0.2
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.
- data/.travis.yml +11 -0
- data/README.md +7 -2
- data/active_model_otp.gemspec +1 -1
- data/lib/active_model/one_time_password.rb +16 -5
- data/lib/active_model/otp/version.rb +1 -1
- data/lib/active_model_otp.rb +1 -0
- data/test/models/visitor.rb +12 -0
- data/test/one_time_password_test.rb +13 -3
- metadata +6 -3
data/.travis.yml
ADDED
data/README.md
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
[](https://travis-ci.org/heapsource/active_model_otp)
|
2
|
+
|
1
3
|
# ActiveModel::Otp
|
2
4
|
|
3
5
|
**ActiveModel::Otp** makes adding **Two Factor Authentication**(TFA) to a model simple, let's see what's required to get AMo::Otp working in our Application, using Rails 4.0 (AMo::Otp is also compatible with Rails 3.x versions) we're going to use an User model and some authentication to it. Inspired in AM::SecurePassword
|
@@ -38,8 +40,9 @@ end
|
|
38
40
|
|
39
41
|
##Usage
|
40
42
|
|
41
|
-
The has_one_time_password sentence provides to the model some useful methods in order to implement our TFA system.
|
42
|
-
|
43
|
+
The has_one_time_password sentence provides to the model some useful methods in order to implement our TFA system. AMo:Otp generates one time passwords according to [RFC 4226](http://tools.ietf.org/html/rfc4226) and the [HOTP RFC](http://tools.ietf.org/html/draft-mraihi-totp-timebased-00). This is compatible with Google Authenticator apps available for Android and iPhone, and now in use on GMail.
|
44
|
+
|
45
|
+
The otp_secret_key is saved automatically when a object is created,
|
43
46
|
|
44
47
|
```ruby
|
45
48
|
user = User.create(email: "hello@heapsource.com")
|
@@ -112,6 +115,8 @@ user.otp_secret_key = "2z6hxkdwi3uvrnpn"
|
|
112
115
|
puts "Current code #{user.otp_code}"
|
113
116
|
```
|
114
117
|
|
118
|
+
**Note:** otp_secret_key must be generated using RFC 3548 base32 key strings (for compatilibity with google authenticator)
|
119
|
+
|
115
120
|
### Useful Examples
|
116
121
|
|
117
122
|
#### Generating QR Code with Google Charts API
|
data/active_model_otp.gemspec
CHANGED
@@ -6,7 +6,7 @@ require 'active_model/otp/version'
|
|
6
6
|
Gem::Specification.new do |spec|
|
7
7
|
spec.name = "active_model_otp"
|
8
8
|
spec.version = ActiveModel::Otp::VERSION
|
9
|
-
spec.authors = ["Guillermo Iguaran", "Roberto Miranda", "
|
9
|
+
spec.authors = ["Guillermo Iguaran", "Roberto Miranda", "Heapsource"]
|
10
10
|
spec.email = ["guilleiguaran@gmail.com", "rjmaltamar@gmail.com", "hello@firebase.co"]
|
11
11
|
spec.description = %q{Adds methods to set and authenticate against one time passwords. Inspired in AM::SecurePassword"}
|
12
12
|
spec.summary = "Adds methods to set and authenticate against one time passwords."
|
@@ -5,13 +5,16 @@ module ActiveModel
|
|
5
5
|
module ClassMethods
|
6
6
|
def has_one_time_password(options = {})
|
7
7
|
|
8
|
+
cattr_accessor :otp_column_name
|
9
|
+
self.otp_column_name = (options[:column_name] || "otp_secret_key").to_s
|
10
|
+
|
8
11
|
include InstanceMethodsOnActivation
|
9
12
|
|
10
|
-
before_create { self.
|
13
|
+
before_create { self.otp_column = ROTP::Base32.random_base32 }
|
11
14
|
|
12
15
|
if respond_to?(:attributes_protected_by_default)
|
13
16
|
def self.attributes_protected_by_default #:nodoc:
|
14
|
-
super + [
|
17
|
+
super + [self.otp_column_name]
|
15
18
|
end
|
16
19
|
end
|
17
20
|
end
|
@@ -19,7 +22,7 @@ module ActiveModel
|
|
19
22
|
|
20
23
|
module InstanceMethodsOnActivation
|
21
24
|
def authenticate_otp(code, options = {})
|
22
|
-
totp = ROTP::TOTP.new(self.
|
25
|
+
totp = ROTP::TOTP.new(self.otp_column)
|
23
26
|
if drift = options[:drift]
|
24
27
|
totp.verify_with_drift(code, drift)
|
25
28
|
else
|
@@ -28,12 +31,20 @@ module ActiveModel
|
|
28
31
|
end
|
29
32
|
|
30
33
|
def otp_code(time = Time.now)
|
31
|
-
ROTP::TOTP.new(self.
|
34
|
+
ROTP::TOTP.new(self.otp_column).at(time)
|
32
35
|
end
|
33
36
|
|
34
37
|
def provisioning_uri(account = nil)
|
35
38
|
account ||= self.email if self.respond_to?(:email)
|
36
|
-
ROTP::TOTP.new(self.
|
39
|
+
ROTP::TOTP.new(self.otp_column).provisioning_uri(account)
|
40
|
+
end
|
41
|
+
|
42
|
+
def otp_column
|
43
|
+
self.send(self.class.otp_column_name)
|
44
|
+
end
|
45
|
+
|
46
|
+
def otp_column=(attr)
|
47
|
+
self.send("#{self.class.otp_column_name}=", attr)
|
37
48
|
end
|
38
49
|
|
39
50
|
end
|
data/lib/active_model_otp.rb
CHANGED
@@ -5,29 +5,39 @@ class OtpTest < MiniTest::Unit::TestCase
|
|
5
5
|
@user = User.new
|
6
6
|
@user.email = 'roberto@heapsource.com'
|
7
7
|
@user.run_callbacks :create
|
8
|
+
@visitor = Visitor.new
|
9
|
+
@visitor.email = 'roberto@heapsource.com'
|
10
|
+
@visitor.run_callbacks :create
|
8
11
|
end
|
9
12
|
|
10
13
|
def test_authenticate_with_otp
|
11
14
|
code = @user.otp_code
|
12
|
-
|
13
15
|
assert @user.authenticate_otp(code)
|
16
|
+
|
17
|
+
code = @visitor.otp_code
|
18
|
+
assert @visitor.authenticate_otp(code)
|
14
19
|
end
|
15
20
|
|
16
21
|
def test_authenticate_with_otp_when_drift_is_allowed
|
17
22
|
code = @user.otp_code(Time.now - 30)
|
18
|
-
|
19
23
|
assert @user.authenticate_otp(code, drift: 60)
|
24
|
+
|
25
|
+
code = @visitor.otp_code(Time.now - 30)
|
26
|
+
assert @visitor.authenticate_otp(code, drift: 60)
|
20
27
|
end
|
21
28
|
|
22
29
|
def test_otp_code
|
23
|
-
assert_match(/\d{6}/, @user.otp_code.to_s)
|
30
|
+
assert_match(/\d{5,6}/, @user.otp_code.to_s)
|
31
|
+
assert_match(/\d{5,6}/, @visitor.otp_code.to_s)
|
24
32
|
end
|
25
33
|
|
26
34
|
def test_provisioning_uri_with_provided_account
|
27
35
|
assert_match %r{otpauth://totp/roberto\?secret=\w{16}}, @user.provisioning_uri("roberto")
|
36
|
+
assert_match %r{otpauth://totp/roberto\?secret=\w{16}}, @visitor.provisioning_uri("roberto")
|
28
37
|
end
|
29
38
|
|
30
39
|
def test_provisioning_uri_with_email_field
|
31
40
|
assert_match %r{otpauth://totp/roberto@heapsource\.com\?secret=\w{16}}, @user.provisioning_uri
|
41
|
+
assert_match %r{otpauth://totp/roberto@heapsource\.com\?secret=\w{16}}, @visitor.provisioning_uri
|
32
42
|
end
|
33
43
|
end
|
metadata
CHANGED
@@ -1,17 +1,17 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: active_model_otp
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- Guillermo Iguaran
|
9
9
|
- Roberto Miranda
|
10
|
-
-
|
10
|
+
- Heapsource
|
11
11
|
autorequire:
|
12
12
|
bindir: bin
|
13
13
|
cert_chain: []
|
14
|
-
date: 2013-
|
14
|
+
date: 2013-12-04 00:00:00.000000000 Z
|
15
15
|
dependencies:
|
16
16
|
- !ruby/object:Gem::Dependency
|
17
17
|
name: activemodel
|
@@ -104,6 +104,7 @@ extensions: []
|
|
104
104
|
extra_rdoc_files: []
|
105
105
|
files:
|
106
106
|
- .gitignore
|
107
|
+
- .travis.yml
|
107
108
|
- Gemfile
|
108
109
|
- LICENSE.txt
|
109
110
|
- README.md
|
@@ -113,6 +114,7 @@ files:
|
|
113
114
|
- lib/active_model/otp/version.rb
|
114
115
|
- lib/active_model_otp.rb
|
115
116
|
- test/models/user.rb
|
117
|
+
- test/models/visitor.rb
|
116
118
|
- test/one_time_password_test.rb
|
117
119
|
- test/test_helper.rb
|
118
120
|
homepage: ''
|
@@ -142,5 +144,6 @@ specification_version: 3
|
|
142
144
|
summary: Adds methods to set and authenticate against one time passwords.
|
143
145
|
test_files:
|
144
146
|
- test/models/user.rb
|
147
|
+
- test/models/visitor.rb
|
145
148
|
- test/one_time_password_test.rb
|
146
149
|
- test/test_helper.rb
|