Skip to content

Commit 06a0824

Browse files
committed
Transfer code from website to standalone gem
Also stopped using Jeweler in favor of managing the gemspec on my own.
1 parent f7a1165 commit 06a0824

18 files changed

+555
-64
lines changed

Diff for: .document

-5
This file was deleted.

Diff for: Gemfile

+18-7
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,25 @@
1-
source "http://rubygems.org"
1+
source 'http://rubygems.org'
22
# Add dependencies required to use your gem here.
33
# Example:
4-
# gem "activesupport", ">= 2.3.5"
4+
# gem 'activesupport', '>= 2.3.5'
55

66
# Add dependencies to develop your gem here.
77
# Include everything needed to run rake, tests, features, etc.
8+
gem 'rails', '~> 3.2'
9+
gem 'activesupport', '~> 3.2'
10+
gem 'attr_encrypted'
11+
gem 'devise'
12+
gem 'rotp'
13+
14+
gemspec
15+
816
group :development do
9-
gem "rspec", "~> 2.8.0"
10-
gem "rdoc", "~> 3.12"
11-
gem "bundler", "~> 1.0"
12-
gem "jeweler", "~> 2.0.1"
13-
gem "simplecov", ">= 0"
17+
gem 'bundler', '~> 1.0'
18+
end
19+
20+
group :test do
21+
gem 'rspec', '~> 2.8.0'
22+
gem 'simplecov', '>= 0'
23+
gem 'faker'
24+
gem 'timecop'
1425
end

Diff for: Gemfile.lock

+133
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
PATH
2+
remote: .
3+
specs:
4+
devise-two-factor (0.1.0)
5+
6+
GEM
7+
remote: http://rubygems.org/
8+
specs:
9+
actionmailer (3.2.18)
10+
actionpack (= 3.2.18)
11+
mail (~> 2.5.4)
12+
actionpack (3.2.18)
13+
activemodel (= 3.2.18)
14+
activesupport (= 3.2.18)
15+
builder (~> 3.0.0)
16+
erubis (~> 2.7.0)
17+
journey (~> 1.0.4)
18+
rack (~> 1.4.5)
19+
rack-cache (~> 1.2)
20+
rack-test (~> 0.6.1)
21+
sprockets (~> 2.2.1)
22+
activemodel (3.2.18)
23+
activesupport (= 3.2.18)
24+
builder (~> 3.0.0)
25+
activerecord (3.2.18)
26+
activemodel (= 3.2.18)
27+
activesupport (= 3.2.18)
28+
arel (~> 3.0.2)
29+
tzinfo (~> 0.3.29)
30+
activeresource (3.2.18)
31+
activemodel (= 3.2.18)
32+
activesupport (= 3.2.18)
33+
activesupport (3.2.18)
34+
i18n (~> 0.6, >= 0.6.4)
35+
multi_json (~> 1.0)
36+
arel (3.0.3)
37+
attr_encrypted (1.3.2)
38+
encryptor (>= 1.3.0)
39+
bcrypt (3.1.7)
40+
builder (3.0.4)
41+
devise (3.2.4)
42+
bcrypt (~> 3.0)
43+
orm_adapter (~> 0.1)
44+
railties (>= 3.2.6, < 5)
45+
thread_safe (~> 0.1)
46+
warden (~> 1.2.3)
47+
diff-lcs (1.1.3)
48+
docile (1.1.3)
49+
encryptor (1.3.0)
50+
erubis (2.7.0)
51+
faker (1.3.0)
52+
i18n (~> 0.5)
53+
hike (1.2.3)
54+
i18n (0.6.9)
55+
journey (1.0.4)
56+
json (1.8.1)
57+
mail (2.5.4)
58+
mime-types (~> 1.16)
59+
treetop (~> 1.4.8)
60+
mime-types (1.25.1)
61+
multi_json (1.10.0)
62+
orm_adapter (0.5.0)
63+
polyglot (0.3.4)
64+
rack (1.4.5)
65+
rack-cache (1.2)
66+
rack (>= 0.4)
67+
rack-ssl (1.3.4)
68+
rack
69+
rack-test (0.6.2)
70+
rack (>= 1.0)
71+
rails (3.2.18)
72+
actionmailer (= 3.2.18)
73+
actionpack (= 3.2.18)
74+
activerecord (= 3.2.18)
75+
activeresource (= 3.2.18)
76+
activesupport (= 3.2.18)
77+
bundler (~> 1.0)
78+
railties (= 3.2.18)
79+
railties (3.2.18)
80+
actionpack (= 3.2.18)
81+
activesupport (= 3.2.18)
82+
rack-ssl (~> 1.3.2)
83+
rake (>= 0.8.7)
84+
rdoc (~> 3.4)
85+
thor (>= 0.14.6, < 2.0)
86+
rake (10.3.1)
87+
rdoc (3.12.2)
88+
json (~> 1.4)
89+
rotp (1.6.1)
90+
rspec (2.8.0)
91+
rspec-core (~> 2.8.0)
92+
rspec-expectations (~> 2.8.0)
93+
rspec-mocks (~> 2.8.0)
94+
rspec-core (2.8.0)
95+
rspec-expectations (2.8.0)
96+
diff-lcs (~> 1.1.2)
97+
rspec-mocks (2.8.0)
98+
simplecov (0.8.2)
99+
docile (~> 1.1.0)
100+
multi_json
101+
simplecov-html (~> 0.8.0)
102+
simplecov-html (0.8.0)
103+
sprockets (2.2.2)
104+
hike (~> 1.2)
105+
multi_json (~> 1.0)
106+
rack (~> 1.0)
107+
tilt (~> 1.1, != 1.3.0)
108+
thor (0.19.1)
109+
thread_safe (0.3.3)
110+
tilt (1.4.1)
111+
timecop (0.7.1)
112+
treetop (1.4.15)
113+
polyglot
114+
polyglot (>= 0.3.1)
115+
tzinfo (0.3.39)
116+
warden (1.2.3)
117+
rack (>= 1.0)
118+
119+
PLATFORMS
120+
ruby
121+
122+
DEPENDENCIES
123+
activesupport (~> 3.2)
124+
attr_encrypted
125+
bundler (~> 1.0)
126+
devise
127+
devise-two-factor!
128+
faker
129+
rails (~> 3.2)
130+
rotp
131+
rspec (~> 2.8.0)
132+
simplecov
133+
timecop

Diff for: README.rdoc

-19
This file was deleted.

Diff for: Rakefile

-24
Original file line numberDiff line numberDiff line change
@@ -11,20 +11,6 @@ rescue Bundler::BundlerError => e
1111
end
1212
require 'rake'
1313

14-
require 'jeweler'
15-
Jeweler::Tasks.new do |gem|
16-
# gem is a Gem::Specification... see http://guides.rubygems.org/specification-reference/ for more options
17-
gem.name = "devise-two-factor"
18-
gem.homepage = "http://github.com/ShaneWilton/devise-two-factor"
19-
gem.license = "MIT"
20-
gem.summary = %Q{TODO: one-line summary of your gem}
21-
gem.description = %Q{TODO: longer description of your gem}
22-
gem.email = "[email protected]"
23-
gem.authors = ["Shane Wilton"]
24-
# dependencies defined in Gemfile
25-
end
26-
Jeweler::RubygemsDotOrgTasks.new
27-
2814
require 'rspec/core'
2915
require 'rspec/core/rake_task'
3016
RSpec::Core::RakeTask.new(:spec) do |spec|
@@ -38,13 +24,3 @@ task :simplecov do
3824
end
3925

4026
task :default => :spec
41-
42-
require 'rdoc/task'
43-
Rake::RDocTask.new do |rdoc|
44-
version = File.exist?('VERSION') ? File.read('VERSION') : ""
45-
46-
rdoc.rdoc_dir = 'rdoc'
47-
rdoc.title = "devise-two-factor #{version}"
48-
rdoc.rdoc_files.include('README*')
49-
rdoc.rdoc_files.include('lib/**/*.rb')
50-
end

Diff for: devise-two-factor.gemspec

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
$:.push File.expand_path("../lib", __FILE__)
2+
require "devise_two_factor/version"
3+
4+
Gem::Specification.new do |s|
5+
s.name = "devise-two-factor"
6+
s.version = DeviseTwoFactor::VERSION.dup
7+
s.platform = Gem::Platform::RUBY
8+
s.licenses = ["MIT"]
9+
s.summary = "Barebones two-factor authentication with Devise"
10+
s.email = "[email protected]"
11+
s.homepage = "https://github.com/tinfoil/devise-two-factor"
12+
s.description = "Barebones two-factor authentication with Devise"
13+
s.authors = ['Shane Wilton']
14+
15+
s.rubyforge_project = "devise-two-factor"
16+
17+
s.files = `git ls-files`.split("\n")
18+
s.test_files = `git ls-files -- spec/*`.split("\n")
19+
s.require_paths = ["lib"]
20+
end

Diff for: lib/devise-two-factor.rb

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
require 'attr_encrypted'
2+
require 'devise'
3+
require 'active_support/concern'
4+
require 'rotp'
5+
6+
module Devise
7+
mattr_accessor :otp_secret_length
8+
@@otp_secret_length = 128
9+
10+
mattr_accessor :otp_allowed_drift
11+
@@otp_allowed_drift = 30
12+
13+
mattr_accessor :otp_secret_encryption_key
14+
@@otp_secret_encryption_key = nil
15+
16+
mattr_accessor :otp_backup_code_length
17+
@@otp_backup_code_length = 16
18+
19+
mattr_accessor :otp_number_of_backup_codes
20+
@@otp_number_of_backup_codes = 5
21+
end
22+
23+
require 'devise/models/two_factor_authenticatable'
24+
require 'devise/models/two_factor_backupable'

Diff for: lib/devise/models/two_factor_authenticatable.rb

+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
module Devise
2+
module Models
3+
module TwoFactorAuthenticatable
4+
extend ActiveSupport::Concern
5+
include Devise::Models::DatabaseAuthenticatable
6+
7+
included do
8+
attr_encrypted :otp_secret, :key => self.otp_secret_encryption_key
9+
10+
attr_accessor :otp_attempt
11+
attr_accessible :otp_attempt
12+
end
13+
14+
def self.required_fields(klass)
15+
[:encrypted_otp_secret, :encrypted_otp_secret_iv, :encrypted_otp_secret_salt]
16+
end
17+
18+
# This defaults to the model's otp_secret
19+
# If this hasn't been generated yet, pass a secret as an option
20+
def valid_otp?(code, options = {})
21+
otp_secret = options[:otp_secret] || self.otp_secret
22+
return false unless otp_secret.present?
23+
24+
totp = ROTP::TOTP.new(otp_secret)
25+
totp.verify_with_drift(code, self.class.otp_allowed_drift)
26+
end
27+
28+
def otp_provisioning_uri(account, options = {})
29+
otp_secret = options[:otp_secret] || self.otp_secret
30+
ROTP::TOTP.new(otp_secret, options).provisioning_uri(account)
31+
end
32+
33+
def clean_up_passwords
34+
self.otp_attempt = nil
35+
end
36+
37+
protected
38+
39+
module ClassMethods
40+
Devise::Models.config(self, :otp_secret_length,
41+
:otp_allowed_drift,
42+
:otp_secret_encryption_key)
43+
44+
def generate_otp_secret(otp_secret_length = self.otp_secret_length)
45+
ROTP::Base32.random_base32(otp_secret_length)
46+
end
47+
end
48+
end
49+
end
50+
end

Diff for: lib/devise/models/two_factor_backupable.rb

+64
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
module Devise
2+
module Models
3+
# TwoFactorBackupable allows a user to generate backup codes which
4+
# provide one-time access to their account in the event that they have
5+
# lost access to their two-factor device
6+
module TwoFactorBackupable
7+
extend ActiveSupport::Concern
8+
9+
def self.required_fields(klass)
10+
[:otp_backup_codes]
11+
end
12+
13+
# 1) Invalidates all existing backup codes
14+
# 2) Generates otp_number_of_backup_codes backup codes
15+
# 3) Stores the hashed backup codes in the database
16+
# 4) Returns a plaintext array of the generated backup codes
17+
def generate_otp_backup_codes!
18+
codes = []
19+
number_of_codes = self.class.otp_number_of_backup_codes
20+
code_length = self.class.otp_backup_code_length
21+
22+
number_of_codes.times do
23+
codes << SecureRandom.hex(code_length / 2) # Hexstring has length 2*n
24+
end
25+
26+
hashed_codes = codes.map { |code| Devise.bcrypt self.class, code }
27+
self.otp_backup_codes = hashed_codes
28+
29+
codes
30+
end
31+
32+
# Returns true and invalidates the given code
33+
# iff that code is a valid backup code.
34+
def invalidate_otp_backup_code!(code)
35+
codes = self.otp_backup_codes || []
36+
37+
codes.each do |backup_code|
38+
# We hashed the code with Devise.bcrypt, so if Devise changes that
39+
# method, we'll have to adjust our comparison here to match it
40+
# TODO Fork Devise and encapsulate this logic in a helper
41+
bcrypt = ::BCrypt::Password.new(backup_code)
42+
hashed_code = ::BCrypt::Engine.hash_secret("#{code}#{self.class.pepper}",
43+
bcrypt.salt)
44+
45+
next unless Devise.secure_compare(hashed_code, backup_code)
46+
47+
codes.delete(backup_code)
48+
self.otp_backup_codes = codes
49+
return true
50+
end
51+
52+
false
53+
end
54+
55+
protected
56+
57+
module ClassMethods
58+
Devise::Models.config(self, :otp_backup_code_length,
59+
:otp_number_of_backup_codes,
60+
:pepper)
61+
end
62+
end
63+
end
64+
end

0 commit comments

Comments
 (0)