active_model_otp 1.1.0 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 9d9aedb666900aee840275449fcdb4c2fb912c27
4
- data.tar.gz: de9ef98a499709f05a3a836cd50242c74f8b1fdf
3
+ metadata.gz: a53fb4e15fa9408222f26928a84d9b1f67919b86
4
+ data.tar.gz: f436275d42c9e8e78c074809be7cbc06da3e9c16
5
5
  SHA512:
6
- metadata.gz: bc0e46abcdae3948c4ea3bf51d92848ecc887d83efb611f7828b830d45f4d2372e0aa45d1c229e627de6e5d404c1ebb626d4c130ed6a3f14980cae6de33da122
7
- data.tar.gz: 302bcfde4c0b3d33355cf44ed9b8896a1f83c615c3fafdc9c741ae171955ab78197e5de9b31ac0a4cc7d60fd801c2718c08949f7fc724e8b821e852d10a67179
6
+ metadata.gz: 4c409cf8191c2511f7f7c984d0b2623463f65a835e5b86d6cec3dd59b9f91836f8a5d5308b164d328c773dbebe76c51e0c9ae0503048bc81e8326280d57a2379
7
+ data.tar.gz: 2b69f18d93545ea5e0e41bc3bc10930887f2f7f1b95dbee947b6e9eed256cc5b78f80014fa7e11e2f3d271f204ccf5dad9b412f9c1193404dcda64478e1d116a
@@ -1,4 +1,11 @@
1
- #unreleased
1
+ #v1.2.0
2
+ - Added Counter based OTP (HOTP) (@ResultsMayVary ) https://github.com/heapsource/active_model_otp/pull/19
3
+ - Adding options to provisioning uri, so we can include issuer (@doon) https://github.com/heapsource/active_model_otp/pull/15
4
+
5
+ #v1.1.0
6
+ - Add function to re-geterante the OTP secret (@TikiTDO) https://github.com/heapsource/active_model_otp/pull/14
7
+ - Added option to pass OTP length (@shivanibhanwal) https://github.com/heapsource/active_model_otp/pull/13
8
+
2
9
  #v1.0.0
3
10
  - Avoid overriding predefined otp_column value when initializing resource (Ilan Stern) https://github.com/heapsource/active_model_otp/pull/10
4
11
  - Pad OTP codes with less than 6 digits (Johan Brissmyr) https://github.com/heapsource/active_model_otp/pull/7
data/README.md CHANGED
@@ -1,8 +1,12 @@
1
1
  [![Build Status](https://travis-ci.org/heapsource/active_model_otp.png)](https://travis-ci.org/heapsource/active_model_otp)
2
+ [![Gem Version](https://badge.fury.io/rb/active_model_otp.svg)](http://badge.fury.io/rb/active_model_otp)
3
+ [![Dependency Status](https://gemnasium.com/heapsource/active_model_otp.svg)](https://gemnasium.com/heapsource/active_model_otp)
4
+ [![Code Climate](https://codeclimate.com/github/heapsource/active_model_otp/badges/gpa.svg)](https://codeclimate.com/github/heapsource/active_model_otp)
5
+
2
6
 
3
7
  # ActiveModel::Otp
4
8
 
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
9
+ **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 a User model and some authentication to do it. Inspired by AM::SecurePassword
6
10
 
7
11
  ## Installation
8
12
 
@@ -14,7 +18,7 @@ And then execute:
14
18
 
15
19
  $ bundle
16
20
 
17
- Or install it yourself as:
21
+ Or install it yourself as follows:
18
22
 
19
23
  $ gem install active_model_otp
20
24
 
@@ -29,7 +33,7 @@ rails g migration AddOtpSecretKeyToUsers otp_secret_key:string
29
33
  create db/migrate/20130707010931_add_otp_secret_key_to_users.rb
30
34
  ```
31
35
 
32
- We’ll then need to run rake db:migrate to update the users table in the database. The next step is to update the model code. We need to use has_one_time_password to tell it will be use TFA.
36
+ We’ll then need to run rake db:migrate to update the users table in the database. The next step is to update the model code. We need to use has_one_time_password to make it use TFA.
33
37
 
34
38
  ```ruby
35
39
  class User < ActiveRecord::Base
@@ -42,20 +46,19 @@ Note: If you're adding this to an existing user model you'll need to generate *o
42
46
  User.all.each { |user| user.update_attribute(:otp_secret_key, ROTP::Base32.random_base32) }
43
47
  ```
44
48
 
45
- For use a custom column for store the secret key field you can us the column_name option
49
+ To use a custom column to store the secret key field you can use the column_name option. It is also possible to generate codes with a specified length.
46
50
 
47
51
  ```ruby
48
52
  class User < ActiveRecord::Base
49
- has_one_time_password column_name: :my_otp_secret_column
53
+ has_one_time_password column_name: :my_otp_secret_column, length: 4
50
54
  end
51
55
  ```
52
56
 
57
+ ## Usage
53
58
 
54
- ##Usage
55
-
56
- 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.
59
+ The has_one_time_password statement provides to the model some useful methods in order to implement our TFA system. AMo:Otp generates one time passwords according to [TOTP RFC 6238](http://tools.ietf.org/html/rfc4226) and the [HOTP RFC 4226](http://tools.ietf.org/html/rfc4226). This is compatible with Google Authenticator apps available for Android and iPhone, and now in use on GMail.
57
60
 
58
- The otp_secret_key is saved automatically when a object is created,
61
+ The otp_secret_key is saved automatically when an object is created,
59
62
 
60
63
  ```ruby
61
64
  user = User.create(email: "hello@heapsource.com")
@@ -63,9 +66,9 @@ user.otp_secret_key
63
66
  => "jt3gdd2qm6su5iqh"
64
67
  ```
65
68
 
66
- **Note:** You can fork the applications for [iPhone](https://github.com/heapsource/google-authenticator) & [Android](https://github.com/heapsource/google-authenticator.android) and customize it
69
+ **Note:** You can fork the applications for [iPhone](https://github.com/heapsource/google-authenticator) & [Android](https://github.com/heapsource/google-authenticator.android) and customize them
67
70
 
68
- ### Getting current code (ex. to send via SMS)
71
+ ### Getting current code (e.g. to send via SMS)
69
72
  ```ruby
70
73
  user.otp_code # => '186522'
71
74
  sleep 30
@@ -94,19 +97,66 @@ sleep 30 # lets wait again
94
97
  user.authenticate_otp('186522', drift: 60) # => true
95
98
  ```
96
99
 
100
+ ## Counter based OTP
101
+
102
+ An additonal counter field is required in our ``User`` Model
103
+
104
+ ```ruby
105
+ rails g migration AddCounterForOtpToUsers otp_counter:integer
106
+ =>
107
+ invoke active_record
108
+ create db/migrate/20130707010931_add_counter_for_otp_to_users.rb
109
+ ```
110
+
111
+ In addition set the counter flag option to true
112
+
113
+ ```ruby
114
+ class User < ActiveRecord::Base
115
+ has_one_time_password counter_based: true
116
+ end
117
+ ```
118
+
119
+ And for a custom counter column
120
+
121
+ ```ruby
122
+ class User < ActiveRecord::Base
123
+ has_one_time_password counter_based: true, counter_column_name: :my_otp_secret_counter_column
124
+ end
125
+ ```
126
+
127
+ Authentication is done the same. You can manually adjust the counter for your usage or set auto_increment on success to true.
128
+
129
+ ```ruby
130
+ user.authenticate_otp('186522') # => true
131
+ user.authenticate_otp('186522', auto_increment: true) # => true
132
+ user.authenticate_otp('186522') # => false
133
+ user.otp_counter -= 1
134
+ user.authenticate_otp('186522') # => true
135
+ ```
136
+
137
+ When retrieving an ```otp_code``` you can also pass the ```auto_increment``` option.
138
+
139
+ ```ruby
140
+ user.otp_code # => '186522'
141
+ user.otp_code # => '186522'
142
+ user.otp_code(auto_increment: true) # => '768273'
143
+ user.otp_code(auto_increment: true) # => '002811'
144
+ user.otp_code # => '002811'
145
+ ```
146
+
97
147
  ## Google Authenticator Compatible
98
148
 
99
149
  The library works with the Google Authenticator iPhone and Android app, and also includes the ability to generate provisioning URI's to use with the QR Code scanner built into the app.
100
150
 
101
151
  ```ruby
102
- # Use you user's emails for generate the provisioning_url
152
+ # Use your user's email address to generate the provisioning_url
103
153
  user.provisioning_uri # => 'otpauth://totp/hello@heapsource.com?secret=2z6hxkdwi3uvrnpn'
104
154
 
105
- # Use a custom fied for generate the provisioning_url
155
+ # Use a custom field to generate the provisioning_url
106
156
  user.provisioning_uri("hello") # => 'otpauth://totp/hello?secret=2z6hxkdwi3uvrnpn'
107
157
  ```
108
158
 
109
- This can then be rendered as a QR Code which can then be scanned and added to the users list of OTP credentials.
159
+ This can then be rendered as a QR Code which can be scanned and added to the users list of OTP credentials.
110
160
 
111
161
  ### Working example
112
162
 
@@ -5,20 +5,27 @@ module ActiveModel
5
5
  module ClassMethods
6
6
 
7
7
  def has_one_time_password(options = {})
8
-
9
- cattr_accessor :otp_column_name
10
- class_attribute :otp_digits
8
+ cattr_accessor :otp_column_name, :otp_counter_column_name
9
+ class_attribute :otp_digits, :otp_counter_based
11
10
 
12
11
  self.otp_column_name = (options[:column_name] || "otp_secret_key").to_s
13
12
  self.otp_digits = options[:length] || 6
14
13
 
14
+ self.otp_counter_based = (options[:counter_based] || false)
15
+ self.otp_counter_column_name = (
16
+ options[:counter_column_name] || "otp_counter"
17
+ ).to_s
18
+
15
19
  include InstanceMethodsOnActivation
16
20
 
17
- before_create { self.otp_regenerate_secret if !self.otp_column}
21
+ before_create do
22
+ self.otp_regenerate_secret if !otp_column
23
+ self.otp_regenerate_counter if otp_counter_based && !otp_counter
24
+ end
18
25
 
19
26
  if respond_to?(:attributes_protected_by_default)
20
27
  def self.attributes_protected_by_default #:nodoc:
21
- super + [self.otp_column_name]
28
+ super + [otp_column_name, otp_counter_column_name]
22
29
  end
23
30
  end
24
31
  end
@@ -29,29 +36,56 @@ module ActiveModel
29
36
  self.otp_column = ROTP::Base32.random_base32
30
37
  end
31
38
 
39
+ def otp_regenerate_counter
40
+ self.otp_counter = 1
41
+ end
42
+
32
43
  def authenticate_otp(code, options = {})
33
- totp = ROTP::TOTP.new(self.otp_column, {digits: self.otp_digits})
34
- if drift = options[:drift]
35
- totp.verify_with_drift(code, drift)
44
+ if otp_counter_based
45
+ hotp = ROTP::HOTP.new(otp_column, digits: otp_digits)
46
+ result = hotp.verify(code, otp_counter)
47
+ if result && options[:auto_increment]
48
+ self.otp_counter += 1
49
+ save if !new_record?
50
+ end
51
+ result
36
52
  else
37
- totp.verify(code)
53
+ totp = ROTP::TOTP.new(otp_column, digits: otp_digits)
54
+ if drift = options[:drift]
55
+ totp.verify_with_drift(code, drift)
56
+ else
57
+ totp.verify(code)
58
+ end
38
59
  end
39
60
  end
40
61
 
41
62
  def otp_code(options = {})
42
- if options.is_a? Hash
43
- time = options.fetch(:time, Time.now)
44
- padding = options.fetch(:padding, true)
63
+ if otp_counter_based
64
+ if options[:auto_increment]
65
+ self.otp_counter += 1
66
+ save if !new_record?
67
+ end
68
+ ROTP::HOTP.new(otp_column, digits: otp_digits).at(self.otp_counter)
45
69
  else
46
- time = options
47
- padding = true
70
+ if options.is_a? Hash
71
+ time = options.fetch(:time, Time.now)
72
+ padding = options.fetch(:padding, true)
73
+ else
74
+ time = options
75
+ padding = true
76
+ end
77
+ ROTP::TOTP.new(otp_column, digits: otp_digits).at(time, padding)
48
78
  end
49
- ROTP::TOTP.new(self.otp_column, {digits: self.otp_digits}).at(time, padding)
50
79
  end
51
80
 
52
- def provisioning_uri(account = nil)
81
+ def provisioning_uri(account = nil, options = {})
53
82
  account ||= self.email if self.respond_to?(:email)
54
- ROTP::TOTP.new(self.otp_column).provisioning_uri(account)
83
+
84
+ if otp_counter_based
85
+ ROTP::HOTP.new(otp_column, options).provisioning_uri(account)
86
+ else
87
+ ROTP::TOTP.new(otp_column, options).provisioning_uri(account)
88
+ end
55
89
  end
56
90
 
57
91
  def otp_column
@@ -61,6 +95,14 @@ module ActiveModel
61
95
  def otp_column=(attr)
62
96
  self.send("#{self.class.otp_column_name}=", attr)
63
97
  end
98
+
99
+ def otp_counter
100
+ self.send(self.class.otp_counter_column_name)
101
+ end
102
+
103
+ def otp_counter=(attr)
104
+ self.send("#{self.class.otp_counter_column_name}=", attr)
105
+ end
64
106
  end
65
107
  end
66
108
  end
@@ -1,5 +1,5 @@
1
1
  module ActiveModel
2
2
  module Otp
3
- VERSION = "1.1.0"
3
+ VERSION = "1.2.0"
4
4
  end
5
5
  end
@@ -61,6 +61,13 @@ class OtpTest < MiniTest::Unit::TestCase
61
61
  assert_match %r{otpauth://totp/roberto@heapsource\.com\?secret=\w{16}}, @visitor.provisioning_uri
62
62
  end
63
63
 
64
+ def test_provisioning_uri_with_options
65
+ assert_match %r{otpauth://totp/roberto@heapsource\.com\?issuer=Example&secret=\w{16}},@user.provisioning_uri(nil,issuer: "Example")
66
+ assert_match %r{otpauth://totp/roberto@heapsource\.com\?issuer=Example&secret=\w{16}}, @visitor.provisioning_uri(nil,issuer: "Example")
67
+ assert_match %r{otpauth://totp/roberto\?issuer=Example&secret=\w{16}}, @user.provisioning_uri("roberto", issuer: "Example")
68
+ assert_match %r{otpauth://totp/roberto\?issuer=Example&secret=\w{16}}, @visitor.provisioning_uri("roberto", issuer: "Example")
69
+ end
70
+
64
71
  def test_regenerate_otp
65
72
  secret = @user.otp_column
66
73
  @user.otp_regenerate_secret
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: active_model_otp
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.0
4
+ version: 1.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Guillermo Iguaran
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2014-10-10 00:00:00.000000000 Z
13
+ date: 2015-02-26 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: activemodel