active_model_otp 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,18 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ .ruby-version
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in has_one_time_password.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Guillermo Iguaran, Roberto Miranda, Firebase.co
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,131 @@
1
+ # ActiveModel::Otp
2
+
3
+ **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
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'active_model_otp'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install active_model_otp
18
+
19
+ ## Setting your Model
20
+
21
+ We're going to add a field to our ``User`` Model, so each user can have an otp secret key. The next step is to run the migration generator in order to add the secret key field.
22
+
23
+ ```ruby
24
+ rails g migration AddOtpSecretKeyToUsers otp_secret_key:string
25
+ =>
26
+ invoke active_record
27
+ create db/migrate/20130707010931_add_otp_secret_key_to_users.rb
28
+ ```
29
+
30
+ 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.
31
+
32
+ ```ruby
33
+ class User < ActiveRecord::Base
34
+ has_one_time_password
35
+ end
36
+ ```
37
+
38
+
39
+ ##Usage
40
+
41
+ The has_one_time_password sentence provides to the model some useful methods in order to implement our TFA system.
42
+ The otp_secret_key is saved automatically when a object is created, otp_secret_key is generated 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.
43
+
44
+ ```ruby
45
+ user = User.create(email: "hello@heapsource.com")
46
+ user.otp_secret_key
47
+ => "jt3gdd2qm6su5iqh"
48
+ ```
49
+
50
+ **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
51
+
52
+ ### Getting current code (ex. to send via SMS)
53
+ ```ruby
54
+ user.otp_code # => '186522'
55
+ sleep 30
56
+ user.otp_code # => '850738'
57
+ ```
58
+
59
+ ### Authenticating using a code
60
+
61
+ ```ruby
62
+ user.authenticate_otp('186522') # => true
63
+ sleep 30 # let's wait 30 secs
64
+ user.authenticate_otp('186522') # => false
65
+ ```
66
+
67
+ ### Authenticating using a slightly old code
68
+
69
+ ```ruby
70
+ user.authenticate_otp('186522') # => true
71
+ sleep 30 # lets wait again
72
+ user.authenticate_otp('186522', drift: 60) # => true
73
+ ```
74
+
75
+ ## Google Authenticator Compatible
76
+
77
+ 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.
78
+
79
+ ```ruby
80
+ # Use you user's emails for generate the provision_url
81
+ user.provision_uri # => 'otpauth://totp/hello@heapsource.com?secret=2z6hxkdwi3uvrnpn'
82
+
83
+ # Use a custom fied for generate the provision_url
84
+ user.provision_uri("hello") # => 'otpauth://totp/hello?secret=2z6hxkdwi3uvrnpn'
85
+ ```
86
+
87
+ This can then be rendered as a QR Code which can then be scanned and added to the users list of OTP credentials.
88
+
89
+ ### Working example
90
+
91
+ Scan the following barcode with your phone, using Google Authenticator
92
+
93
+ ![QRCODE](http://qrfree.kaywa.com/?l=1&s=8&d=otpauth%3A%2F%2Ftotp%2Froberto%40heapsource.com%3Fsecret%3D2z6hxkdwi3uvrnpn)
94
+
95
+ Now run the following and compare the output
96
+
97
+ ```ruby
98
+ require "active_model_otp"
99
+ class User
100
+ extend ActiveModel::Callbacks
101
+ include ActiveModel::Validations
102
+ include ActiveModel::OneTimePassword
103
+
104
+ define_model_callbacks :create
105
+ attr_accessor :otp_secret_key, :email
106
+
107
+ has_one_time_password
108
+ end
109
+ user = User.new
110
+ user.email = 'roberto@heapsource.com'
111
+ user.otp_secret_key = "2z6hxkdwi3uvrnpn"
112
+ puts "Current code #{user.otp_code}"
113
+ ```
114
+
115
+ ### Useful Examples
116
+
117
+ #### Generating QR Code with Google Charts API
118
+
119
+ #### Generating QR Code with rqrcode and chunky_png
120
+
121
+ #### Sendind code via email with Twilio
122
+
123
+ #### Using with Mongoid
124
+
125
+ ## Contributing
126
+
127
+ 1. Fork it
128
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
129
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
130
+ 4. Push to the branch (`git push origin my-new-feature`)
131
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,27 @@
1
+ require "bundler/gem_tasks"
2
+ require 'rake/testtask'
3
+ Bundler::GemHelper.install_tasks
4
+
5
+ task :console do
6
+ puts "Loading development console..."
7
+ system("irb -r active_model_otp")
8
+ end
9
+
10
+ task :help do
11
+ puts "Available rake tasks: "
12
+ puts "rake console - Run a IRB console with all enviroment loaded"
13
+ puts "rake test - Run tests"
14
+ end
15
+
16
+ task :test do
17
+ Dir.chdir('test')
18
+ end
19
+
20
+ Rake::TestTask.new(:test) do |t|
21
+ t.libs << '../lib'
22
+ t.libs << '../test'
23
+ t.test_files = FileList['*_test.rb']
24
+ t.verbose = false
25
+ end
26
+
27
+ task :default => :test
@@ -0,0 +1,27 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'active_model/otp/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "active_model_otp"
8
+ spec.version = ActiveModel::Otp::VERSION
9
+ spec.authors = ["Guillermo Iguaran", "Roberto Miranda", "Firebase.co"]
10
+ spec.email = ["guilleiguaran@gmail.com", "rjmaltamar@gmail.com", "hello@firebase.co"]
11
+ spec.description = %q{Adds methods to set and authenticate against one time passwords. Inspired in AM::SecurePassword"}
12
+ spec.summary = "Adds methods to set and authenticate against one time passwords."
13
+ spec.homepage = ""
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_dependency "activemodel"
22
+ spec.add_dependency "rotp"
23
+
24
+ spec.add_development_dependency "bundler", "~> 1.3"
25
+ spec.add_development_dependency "rake"
26
+ spec.add_development_dependency "minitest"
27
+ end
@@ -0,0 +1,41 @@
1
+ module ActiveModel
2
+ module OneTimePassword
3
+ extend ActiveSupport::Concern
4
+
5
+ module ClassMethods
6
+ def has_one_time_password(options = {})
7
+
8
+ include InstanceMethodsOnActivation
9
+
10
+ before_create { self.otp_secret_key = ROTP::Base32.random_base32 }
11
+
12
+ if respond_to?(:attributes_protected_by_default)
13
+ def self.attributes_protected_by_default #:nodoc:
14
+ super + ["otp_secret_key"]
15
+ end
16
+ end
17
+ end
18
+ end
19
+
20
+ module InstanceMethodsOnActivation
21
+ def authenticate_otp(code, options = {})
22
+ totp = ROTP::TOTP.new(self.otp_secret_key)
23
+ if drift = options[:drift]
24
+ totp.verify_with_drift(code, drift)
25
+ else
26
+ totp.verify(code)
27
+ end
28
+ end
29
+
30
+ def otp_code(time = Time.now)
31
+ ROTP::TOTP.new(self.otp_secret_key).at(time)
32
+ end
33
+
34
+ def provisioning_uri(account = nil)
35
+ account ||= self.email if self.respond_to?(:email)
36
+ ROTP::TOTP.new(self.otp_secret_key).provisioning_uri(account)
37
+ end
38
+
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,5 @@
1
+ module ActiveModel
2
+ module Otp
3
+ VERSION = "0.0.1"
4
+ end
5
+ end
@@ -0,0 +1,8 @@
1
+ require "active_model"
2
+ require "active_support/core_ext/class/attribute_accessors"
3
+ require "rotp"
4
+ require "active_model/one_time_password"
5
+
6
+ ActiveSupport.on_load(:active_record) do
7
+ include ActiveModel::OneTimePassword
8
+ end
@@ -0,0 +1,11 @@
1
+ class User
2
+ extend ActiveModel::Callbacks
3
+ include ActiveModel::Validations
4
+ include ActiveModel::OneTimePassword
5
+
6
+ define_model_callbacks :create
7
+ attr_accessor :otp_secret_key, :email
8
+
9
+ has_one_time_password
10
+
11
+ end
@@ -0,0 +1,33 @@
1
+ require "test_helper"
2
+
3
+ class OtpTest < MiniTest::Unit::TestCase
4
+ def setup
5
+ @user = User.new
6
+ @user.email = 'roberto@heapsource.com'
7
+ @user.run_callbacks :create
8
+ end
9
+
10
+ def test_authenticate_with_otp
11
+ code = @user.otp_code
12
+
13
+ assert @user.authenticate_otp(code)
14
+ end
15
+
16
+ def test_authenticate_with_otp_when_drift_is_allowed
17
+ code = @user.otp_code(Time.now - 30)
18
+
19
+ assert @user.authenticate_otp(code, drift: 60)
20
+ end
21
+
22
+ def test_otp_code
23
+ assert_match(/\d{6}/, @user.otp_code.to_s)
24
+ end
25
+
26
+ def test_provisioning_uri_with_provided_account
27
+ assert_match %r{otpauth://totp/roberto\?secret=\w{16}}, @user.provisioning_uri("roberto")
28
+ end
29
+
30
+ def test_provisioning_uri_with_email_field
31
+ assert_match %r{otpauth://totp/roberto@heapsource\.com\?secret=\w{16}}, @user.provisioning_uri
32
+ end
33
+ end
@@ -0,0 +1,12 @@
1
+ testdir = File.dirname(__FILE__)
2
+ $LOAD_PATH.unshift testdir unless $LOAD_PATH.include?(testdir)
3
+
4
+ libdir = File.dirname(File.dirname(__FILE__)) + '/lib'
5
+ $LOAD_PATH.unshift libdir unless $LOAD_PATH.include?(libdir)
6
+
7
+ require "rubygems"
8
+ require "active_model_otp"
9
+ require "minitest/unit"
10
+ require "minitest/autorun"
11
+
12
+ Dir["models/*.rb"].each {|file| require file }
metadata ADDED
@@ -0,0 +1,146 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: active_model_otp
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Guillermo Iguaran
9
+ - Roberto Miranda
10
+ - Firebase.co
11
+ autorequire:
12
+ bindir: bin
13
+ cert_chain: []
14
+ date: 2013-07-11 00:00:00.000000000 Z
15
+ dependencies:
16
+ - !ruby/object:Gem::Dependency
17
+ name: activemodel
18
+ requirement: !ruby/object:Gem::Requirement
19
+ none: false
20
+ requirements:
21
+ - - ! '>='
22
+ - !ruby/object:Gem::Version
23
+ version: '0'
24
+ type: :runtime
25
+ prerelease: false
26
+ version_requirements: !ruby/object:Gem::Requirement
27
+ none: false
28
+ requirements:
29
+ - - ! '>='
30
+ - !ruby/object:Gem::Version
31
+ version: '0'
32
+ - !ruby/object:Gem::Dependency
33
+ name: rotp
34
+ requirement: !ruby/object:Gem::Requirement
35
+ none: false
36
+ requirements:
37
+ - - ! '>='
38
+ - !ruby/object:Gem::Version
39
+ version: '0'
40
+ type: :runtime
41
+ prerelease: false
42
+ version_requirements: !ruby/object:Gem::Requirement
43
+ none: false
44
+ requirements:
45
+ - - ! '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ - !ruby/object:Gem::Dependency
49
+ name: bundler
50
+ requirement: !ruby/object:Gem::Requirement
51
+ none: false
52
+ requirements:
53
+ - - ~>
54
+ - !ruby/object:Gem::Version
55
+ version: '1.3'
56
+ type: :development
57
+ prerelease: false
58
+ version_requirements: !ruby/object:Gem::Requirement
59
+ none: false
60
+ requirements:
61
+ - - ~>
62
+ - !ruby/object:Gem::Version
63
+ version: '1.3'
64
+ - !ruby/object:Gem::Dependency
65
+ name: rake
66
+ requirement: !ruby/object:Gem::Requirement
67
+ none: false
68
+ requirements:
69
+ - - ! '>='
70
+ - !ruby/object:Gem::Version
71
+ version: '0'
72
+ type: :development
73
+ prerelease: false
74
+ version_requirements: !ruby/object:Gem::Requirement
75
+ none: false
76
+ requirements:
77
+ - - ! '>='
78
+ - !ruby/object:Gem::Version
79
+ version: '0'
80
+ - !ruby/object:Gem::Dependency
81
+ name: minitest
82
+ requirement: !ruby/object:Gem::Requirement
83
+ none: false
84
+ requirements:
85
+ - - ! '>='
86
+ - !ruby/object:Gem::Version
87
+ version: '0'
88
+ type: :development
89
+ prerelease: false
90
+ version_requirements: !ruby/object:Gem::Requirement
91
+ none: false
92
+ requirements:
93
+ - - ! '>='
94
+ - !ruby/object:Gem::Version
95
+ version: '0'
96
+ description: Adds methods to set and authenticate against one time passwords. Inspired
97
+ in AM::SecurePassword"
98
+ email:
99
+ - guilleiguaran@gmail.com
100
+ - rjmaltamar@gmail.com
101
+ - hello@firebase.co
102
+ executables: []
103
+ extensions: []
104
+ extra_rdoc_files: []
105
+ files:
106
+ - .gitignore
107
+ - Gemfile
108
+ - LICENSE.txt
109
+ - README.md
110
+ - Rakefile
111
+ - active_model_otp.gemspec
112
+ - lib/active_model/one_time_password.rb
113
+ - lib/active_model/otp/version.rb
114
+ - lib/active_model_otp.rb
115
+ - test/models/user.rb
116
+ - test/one_time_password_test.rb
117
+ - test/test_helper.rb
118
+ homepage: ''
119
+ licenses:
120
+ - MIT
121
+ post_install_message:
122
+ rdoc_options: []
123
+ require_paths:
124
+ - lib
125
+ required_ruby_version: !ruby/object:Gem::Requirement
126
+ none: false
127
+ requirements:
128
+ - - ! '>='
129
+ - !ruby/object:Gem::Version
130
+ version: '0'
131
+ required_rubygems_version: !ruby/object:Gem::Requirement
132
+ none: false
133
+ requirements:
134
+ - - ! '>='
135
+ - !ruby/object:Gem::Version
136
+ version: '0'
137
+ requirements: []
138
+ rubyforge_project:
139
+ rubygems_version: 1.8.25
140
+ signing_key:
141
+ specification_version: 3
142
+ summary: Adds methods to set and authenticate against one time passwords.
143
+ test_files:
144
+ - test/models/user.rb
145
+ - test/one_time_password_test.rb
146
+ - test/test_helper.rb