two_factor_auth 0.1.1

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.
Files changed (103) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +634 -0
  3. data/Rakefile +34 -0
  4. data/app/assets/javascripts/two_factor_auth/application.js +13 -0
  5. data/app/assets/stylesheets/two_factor_auth/application.css +15 -0
  6. data/app/controllers/two_factor_auth/authentications_controller.rb +32 -0
  7. data/app/controllers/two_factor_auth/registrations_controller.rb +29 -0
  8. data/app/controllers/two_factor_auth/trusted_facets_controller.rb +10 -0
  9. data/app/controllers/two_factor_auth/two_factor_auth_controller.rb +19 -0
  10. data/app/helpers/two_factor_auth/application_helper.rb +21 -0
  11. data/app/helpers/two_factor_auth/authentications_helper.rb +17 -0
  12. data/app/helpers/two_factor_auth/registrations_helper.rb +17 -0
  13. data/app/models/two_factor_auth/authentication_client_data.rb +11 -0
  14. data/app/models/two_factor_auth/authentication_request.rb +30 -0
  15. data/app/models/two_factor_auth/authentication_response.rb +49 -0
  16. data/app/models/two_factor_auth/authentication_verifier.rb +68 -0
  17. data/app/models/two_factor_auth/client_data.rb +57 -0
  18. data/app/models/two_factor_auth/registration.rb +18 -0
  19. data/app/models/two_factor_auth/registration_request.rb +33 -0
  20. data/app/models/two_factor_auth/registration_response.rb +91 -0
  21. data/app/models/two_factor_auth/registration_verifier.rb +91 -0
  22. data/app/views/layouts/two_factor_auth/application.html.erb +16 -0
  23. data/app/views/two_factor_auth/authentications/new.html.erb +30 -0
  24. data/app/views/two_factor_auth/registrations/new.html.erb +26 -0
  25. data/config/routes.rb +3 -0
  26. data/lib/generators/templates/README +6 -0
  27. data/lib/generators/templates/initializer.rb +38 -0
  28. data/lib/generators/templates/migration.rb +15 -0
  29. data/lib/generators/two_factor_auth/install_generator.rb +32 -0
  30. data/lib/tasks/two_factor_auth_tasks.rake +13 -0
  31. data/lib/two_factor_auth/authentication_hook.rb +18 -0
  32. data/lib/two_factor_auth/engine.rb +5 -0
  33. data/lib/two_factor_auth/registration_hook.rb +17 -0
  34. data/lib/two_factor_auth/version.rb +3 -0
  35. data/lib/two_factor_auth.rb +155 -0
  36. data/test/controllers/two_factor_auth/authentications_controller_test.rb +70 -0
  37. data/test/controllers/two_factor_auth/registrations_controller_test.rb +57 -0
  38. data/test/controllers/two_factor_auth/trusted_facets_controller_test.rb +17 -0
  39. data/test/dummy/README.rdoc +28 -0
  40. data/test/dummy/Rakefile +6 -0
  41. data/test/dummy/app/assets/javascripts/application.js +13 -0
  42. data/test/dummy/app/assets/stylesheets/application.css +15 -0
  43. data/test/dummy/app/controllers/application_controller.rb +5 -0
  44. data/test/dummy/app/controllers/secrets_controller.rb +3 -0
  45. data/test/dummy/app/helpers/application_helper.rb +2 -0
  46. data/test/dummy/app/models/user.rb +8 -0
  47. data/test/dummy/app/views/layouts/application.html.erb +16 -0
  48. data/test/dummy/app/views/secrets/index.html.erb +10 -0
  49. data/test/dummy/bin/bundle +3 -0
  50. data/test/dummy/bin/rails +4 -0
  51. data/test/dummy/bin/rake +4 -0
  52. data/test/dummy/config/application.rb +24 -0
  53. data/test/dummy/config/boot.rb +5 -0
  54. data/test/dummy/config/database.yml +25 -0
  55. data/test/dummy/config/environment.rb +5 -0
  56. data/test/dummy/config/environments/development.rb +37 -0
  57. data/test/dummy/config/environments/production.rb +78 -0
  58. data/test/dummy/config/environments/test.rb +39 -0
  59. data/test/dummy/config/initializers/assets.rb +8 -0
  60. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  61. data/test/dummy/config/initializers/cookies_serializer.rb +3 -0
  62. data/test/dummy/config/initializers/devise.rb +259 -0
  63. data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  64. data/test/dummy/config/initializers/inflections.rb +16 -0
  65. data/test/dummy/config/initializers/mime_types.rb +4 -0
  66. data/test/dummy/config/initializers/session_store.rb +3 -0
  67. data/test/dummy/config/initializers/two_factor_auth.rb +38 -0
  68. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  69. data/test/dummy/config/locales/devise.en.yml +60 -0
  70. data/test/dummy/config/locales/en.yml +23 -0
  71. data/test/dummy/config/routes.rb +5 -0
  72. data/test/dummy/config/secrets.yml +22 -0
  73. data/test/dummy/config.ru +4 -0
  74. data/test/dummy/db/development.sqlite3 +0 -0
  75. data/test/dummy/db/migrate/20141026231953_devise_create_users.rb +42 -0
  76. data/test/dummy/db/migrate/20141224135949_create_two_factor_auth_registrations.rb +15 -0
  77. data/test/dummy/db/schema.rb +50 -0
  78. data/test/dummy/db/test.sqlite3 +0 -0
  79. data/test/dummy/log/development.log +198 -0
  80. data/test/dummy/log/test.log +3490 -0
  81. data/test/dummy/public/404.html +67 -0
  82. data/test/dummy/public/422.html +67 -0
  83. data/test/dummy/public/500.html +66 -0
  84. data/test/dummy/public/favicon.ico +0 -0
  85. data/test/dummy/tmp/cache/assets/test/sprockets/13fe41fee1fe35b49d145bcc06610705 +0 -0
  86. data/test/dummy/tmp/cache/assets/test/sprockets/2f5173deea6c795b8fdde723bb4b63af +0 -0
  87. data/test/dummy/tmp/cache/assets/test/sprockets/357970feca3ac29060c1e3861e2c0953 +0 -0
  88. data/test/dummy/tmp/cache/assets/test/sprockets/cffd775d018f68ce5dba1ee0d951a994 +0 -0
  89. data/test/dummy/tmp/cache/assets/test/sprockets/d771ace226fc8215a3572e0aa35bb0d6 +0 -0
  90. data/test/dummy/tmp/cache/assets/test/sprockets/f7cbd26ba1d28d48de824f0e94586655 +0 -0
  91. data/test/helpers/two_factor_auth/authentication_helper_test.rb +54 -0
  92. data/test/helpers/two_factor_auth/registrations_helper_test.rb +34 -0
  93. data/test/integration/navigation_test.rb +10 -0
  94. data/test/lib/two_factor_auth_test.rb +169 -0
  95. data/test/models/two_factor_auth/authentication_request_test.rb +35 -0
  96. data/test/models/two_factor_auth/authentication_response_test.rb +44 -0
  97. data/test/models/two_factor_auth/authentication_verifier_test.rb +83 -0
  98. data/test/models/two_factor_auth/client_data_test.rb +79 -0
  99. data/test/models/two_factor_auth/registration_request_test.rb +29 -0
  100. data/test/models/two_factor_auth/registration_response_test.rb +87 -0
  101. data/test/models/two_factor_auth/registration_verifier_test.rb +96 -0
  102. data/test/test_helper.rb +43 -0
  103. metadata +351 -0
@@ -0,0 +1,30 @@
1
+ Touch your two factor authentication device to complete authentication.
2
+
3
+ <script src="chrome-extension://pfboblefjcgdjicmnffhdgionmgcdmne/u2f-api.js"></script>
4
+ <script>
5
+ //console.log("starting", <%= raw authentication_request.serialized %>);
6
+ u2f.sign([<%= raw authentication_request.serialized %>], function (result) {
7
+ //console.log(result);
8
+ // if keyHandle is wrong, it fails with:
9
+ // {errorCode: 1, errorMessage: "device status code: 2"}
10
+ if (result.errorCode) {
11
+ var errors = document.getElementsByClassName('twofactorauth-status')[0];
12
+ if (result.errorCode == 4) {
13
+ errors.innerHTML = "Unable to authenticate. You registered with a different device. Error code: " + result.errorCode;
14
+ } else {
15
+ errors.innerHTML = "Unable to authenticate. Error code: " + result.errorCode;
16
+ }
17
+ return;
18
+ }
19
+ var form = document.getElementById('two-factor-auth-authentication');
20
+ form.elements.keyHandle.value = result.keyHandle;
21
+ form.elements.signatureData.value = result.signatureData;
22
+ form.elements.clientData.value = result.clientData;
23
+ form.submit();
24
+ });
25
+ </script>
26
+ <%= form_tag(two_factor_auth_authentication_path, method: :post, id: 'two-factor-auth-authentication') do %>
27
+ <%= hidden_field_tag :keyHandle %>
28
+ <%= hidden_field_tag :signatureData %>
29
+ <%= hidden_field_tag :clientData %>
30
+ <% end %>
@@ -0,0 +1,26 @@
1
+ <p>
2
+ Touch your two factor authentication device to complete registration.
3
+ </p>
4
+
5
+ <script src="chrome-extension://pfboblefjcgdjicmnffhdgionmgcdmne/u2f-api.js"></script>
6
+ <script>
7
+ console.log("starting", <%= raw registration_request.serialized %>);
8
+ u2f.register([<%= raw registration_request.serialized %>], [], function (result) {
9
+ //console.log("callback", result);
10
+ if (result.errorCode) {
11
+ document.getElementsByClassName('twofactorauth-status')[0]
12
+ .innerHTML = "Failed. Error code: " + result.errorCode;
13
+ return;
14
+ }
15
+ var form = document.getElementById('two-factor-auth-registration');
16
+ form.elements.clientData.value = result.clientData;
17
+ form.elements.registrationData.value = result.registrationData;
18
+ form.elements.challenge.value = result.challenge;
19
+ form.submit();
20
+ });
21
+ </script>
22
+ <%= form_tag(two_factor_auth_registrations_path, method: :post, id: 'two-factor-auth-registration') do %>
23
+ <%= hidden_field_tag :clientData %>
24
+ <%= hidden_field_tag :registrationData %>
25
+ <%= hidden_field_tag :challenge %>
26
+ <% end %>
data/config/routes.rb ADDED
@@ -0,0 +1,3 @@
1
+ TwoFactorAuth::Engine.routes.draw do
2
+ two_factor_auth_for :users
3
+ end
@@ -0,0 +1,6 @@
1
+ ==============================================================================
2
+ TwoFactorAuth gem installed. To integrate with your site, follow the steps in
3
+ the README on GitHub or by running:
4
+
5
+ rake two_factor_auth:readme
6
+ ==============================================================================
@@ -0,0 +1,38 @@
1
+ # Set facet_domain to the domain name your users see in the URL bar
2
+ if Rails.env.production?
3
+ TwoFactorAuth.facet_domain = "https://www.example.com"
4
+
5
+ # Optional: If you have in-depth knowledge of the U2F spec and wnat to
6
+ # generate your own Trusted Facet List, delete the production facet_domain
7
+ # setting above and customize this: (you can still use facet_domain in dev)
8
+ # TwoFactorAuth.trusted_facet_list_url = 'https://www.example.com/ExampleAppId'
9
+ elsif Rails.env.staging?
10
+ TwoFactorAuth.facet_domain = "https://staging.example.com"
11
+ else
12
+ # The standard prohibits "localhost" or "local.dev", add an alias to /etc/hosts and use that
13
+ TwoFactorAuth.facet_domain = "http://local2fa.example.com:3000"
14
+ end
15
+
16
+ # Optional: if you want your users to be able to authenticate against multiple
17
+ # domains names or apps, they will *all* have to be served via https and
18
+ # listed here. Yes, 'www.example.com' and 'example.com' count as different.
19
+ TwoFactorAuth.facets = [
20
+ # 'https://example.com',
21
+ # 'https://www.example.com',
22
+ # 'https://www.example.net',
23
+ # 'https://blog.example.com',
24
+ # 'https://admin.example.com',
25
+ # 'https://staging.example.com',
26
+ # 'https://account.example.com',
27
+
28
+ # Use this format for iOS apps:
29
+ # 'ios:bundle-id:example-ios-bundle-id',
30
+
31
+ # Use this format for Android apps, inserting the sha1 (see below):
32
+ # 'android:apk-key-hash:example-sha1-hash-of-apk-signing-cert',
33
+ # To get the sha1, edit this command to include your keystore path and run
34
+ # it in Linux, BSD, or OS X to export the signing certificate in DER format,
35
+ # hash, base64 encode and trim trailing '=':
36
+ # keytool -exportcert -alias androiddebugkey -keystore <your-path-to-apk-signing-keystore> &>2 /dev/null | openssl sha1 -binary | openssl base64 | sed 's/=//g'
37
+ ]
38
+
@@ -0,0 +1,15 @@
1
+ class CreateTwoFactorAuthRegistrations < ActiveRecord::Migration
2
+ def change
3
+ create_table :two_factor_auth_registrations do |t|
4
+ t.references :login, polymorphic: true, null: false, index: true
5
+ t.binary :key_handle, null: false, limit: 65 # Defined in FIDO spec
6
+ t.binary :public_key, null: false, limit: 10.kilobytes
7
+ t.binary :certificate, null: false, limit: 1.megabyte, default: ""
8
+ t.integer :counter, null: false, limit: 5, default: 0 # limit in bytes; no easy way to get a 32b *unsigned*
9
+ t.timestamp :last_authenticated_at, null: false
10
+ t.timestamps
11
+ end
12
+ add_index :two_factor_auth_registrations, :key_handle
13
+ add_index :two_factor_auth_registrations, :last_authenticated_at
14
+ end
15
+ end
@@ -0,0 +1,32 @@
1
+ require 'rails/generators/base'
2
+ require 'securerandom'
3
+
4
+ module TwoFactorAuth
5
+ module Generators
6
+ class InstallGenerator < Rails::Generators::Base
7
+ source_root File.expand_path("../../templates", __FILE__)
8
+
9
+ desc "Creates a TwoFactorAuth migration"
10
+
11
+ # Rails implies Migration is mixed into Base (false), and there's no
12
+ # explanation for why I must define this method. Clearly I'm missing
13
+ # something stupid because there are no docs for this in Rails 4.
14
+ include Rails::Generators::Migration
15
+ def self.next_migration_number(path)
16
+ Time.now.utc.strftime("%Y%m%d%H%M%S")
17
+ end
18
+
19
+ def copy_migration
20
+ migration_template "migration.rb", "db/migrate/create_two_factor_auth_registrations.rb"
21
+ end
22
+
23
+ def copy_initializer
24
+ copy_file "initializer.rb", "config/initializers/two_factor_auth.rb"
25
+ end
26
+
27
+ def show_readme
28
+ readme "README" if behavior == :invoke
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,13 @@
1
+ namespace :two_factor_auth do
2
+ desc "View the TwoFactorAuth readme"
3
+ task :readme do
4
+ begin
5
+ pager = ENV['PAGER']
6
+ pager = 'less' if pager.blank?
7
+ readme = File.expand_path("../../../README.md", __FILE__)
8
+ exec "#{pager} #{readme}"
9
+ rescue Errno::ENOENT
10
+ puts "Sorry, couldn't automatically print it... here's the readme:", readme
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,18 @@
1
+ module TwoFactorAuth
2
+ module AuthenticationHook
3
+ include TwoFactorAuth::ApplicationHelper
4
+
5
+ before_action :two_factor_auth_authentication
6
+
7
+ private
8
+
9
+ def two_factor_auth_authentication
10
+ if user_signed_in? and !user_two_factor_auth_authenticated?
11
+ redirect_to new_two_factor_auth_authentication_path
12
+ return false
13
+ end
14
+ true
15
+ end
16
+ end
17
+ end
18
+
@@ -0,0 +1,5 @@
1
+ module TwoFactorAuth
2
+ class Engine < ::Rails::Engine
3
+ isolate_namespace TwoFactorAuth
4
+ end
5
+ end
@@ -0,0 +1,17 @@
1
+ module TwoFactorAuth
2
+ module RegistrationHook
3
+ include TwoFactorAuth::ApplicationHelper
4
+
5
+ before_action :two_factor_auth_registration
6
+
7
+ private
8
+
9
+ def two_factor_auth_registration
10
+ if user_signed_in? and !user_two_factor_auth_registered?
11
+ redirect_to new_two_factor_auth_registrations_path
12
+ return false
13
+ end
14
+ true
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,3 @@
1
+ module TwoFactorAuth
2
+ VERSION = "0.1.1"
3
+ end
@@ -0,0 +1,155 @@
1
+ require "two_factor_auth/engine"
2
+
3
+ require 'base64'
4
+ require 'json'
5
+ require 'openssl'
6
+ require 'active_model'
7
+ require 'active_model/validator'
8
+
9
+ module TwoFactorAuth
10
+ U2F_VERSION = 'U2F_V2'
11
+
12
+ class TwoFactorAuthError < RuntimeError ; end
13
+ class CantGenerateRandomNumbers < TwoFactorAuthError ; end
14
+ class InvalidPublicKey < TwoFactorAuthError ; end
15
+ class InvalidFacetDomain < TwoFactorAuthError ; end
16
+
17
+
18
+ def self.facet_domain= facet_domain
19
+ if facet_domain =~ /localhost(:\d+)?\/?$/
20
+ raise InvalidFacetDomain, "Facet domain can't be localhost, edit /etc/hosts to make a custom hostname"
21
+ end
22
+ if facet_domain =~ /\.dev(:\d+)?\/?$/
23
+ raise InvalidFacetDomain, "Facet domain needs a real TLD, not .dev. Edit /etc/hosts to make a custom hostname"
24
+ end
25
+ if facet_domain == "https://www.example.com"
26
+ raise InvalidFacetDomain, "You need to cusomize the facet_domain in config/initializers/two_factor_auth.rb"
27
+ end
28
+
29
+ @facet_domain = facet_domain.sub(/\/$/, '')
30
+ end
31
+
32
+ def self.facet_domain
33
+ @facet_domain
34
+ end
35
+
36
+ def self.trusted_facet_list_url= url
37
+ @trusted_facet_list_url = url
38
+ end
39
+
40
+ def self.trusted_facet_list_url
41
+ @trusted_facet_list_url or "#{facet_domain}/two_factor_auth/trusted_facets"
42
+ end
43
+
44
+ def self.facets= facets
45
+ @facets = facets
46
+ end
47
+
48
+ def self.facets
49
+ if @facets.nil? or @facets.empty?
50
+ [facet_domain]
51
+ else
52
+ @facets
53
+ end
54
+ end
55
+
56
+ def self.websafe_base64_encode str
57
+ # PHP code removes trailing =s, don't know why
58
+ Base64.urlsafe_encode64(str).sub(/=+$/,'')
59
+ end
60
+
61
+ def self.websafe_base64_decode encoded
62
+ # pad back out to decode
63
+ padded = encoded.ljust((encoded.length/4.0).ceil * 4, '=')
64
+ Base64.urlsafe_decode64(padded)
65
+ end
66
+
67
+ def self.random_encoded_challenge
68
+ random = OpenSSL::Random.pseudo_bytes(32)
69
+ TwoFactorAuth::websafe_base64_encode(random)
70
+ rescue OpenSSL::Random::RandomError
71
+ raise CantGenerateRandomNumbers, "Not enough entropy to generate secure challenges"
72
+ end
73
+
74
+ def self.decode_pubkey raw
75
+ bn = OpenSSL::BN.new(raw, 2)
76
+ group = OpenSSL::PKey::EC::Group.new('prime256v1')
77
+ point = OpenSSL::PKey::EC::Point.new(group, bn)
78
+ rescue OpenSSL::PKey::EC::Point::Error => e
79
+ raise InvalidPublicKey, "Invalid public key: #{e.message}"
80
+ end
81
+
82
+ def self.pubkey_valid? raw
83
+ pk = decode_pubkey raw
84
+ pk.on_curve?
85
+ rescue InvalidPublicKey
86
+ false
87
+ end
88
+ end
89
+
90
+ module ActiveModel
91
+ module Validations
92
+ class AssociatedValidator < ActiveModel::EachValidator #:nodoc:
93
+ def validate_each(record, attribute, value)
94
+ if Array.wrap(value).reject {|r| r.valid?}.any?
95
+ record.errors.add(attribute, value.errors.full_messages.join("; "), options.merge(:value => value))
96
+ end
97
+ end
98
+ end
99
+
100
+ module ClassMethods
101
+ # Validates whether the associated object or objects are all valid.
102
+ # Works with any kind of association.
103
+ #
104
+ # class Book < ActiveRecord::Base
105
+ # has_many :pages
106
+ # belongs_to :library
107
+ #
108
+ # validates_associated :pages, :library
109
+ # end
110
+ #
111
+ # WARNING: This validation must not be used on both ends of an association.
112
+ # Doing so will lead to a circular dependency and cause infinite recursion.
113
+ #
114
+ # NOTE: This validation will not fail if the association hasn't been
115
+ # assigned. If you want to ensure that the association is both present and
116
+ # guaranteed to be valid, you also need to use +validates_presence_of+.
117
+ #
118
+ # Configuration options:
119
+ #
120
+ # * <tt>:message</tt> - A custom error message (default is: "is invalid").
121
+ # * <tt>:on</tt> - Specifies when this validation is active. Runs in all
122
+ # validation contexts by default (+nil+), other options are <tt>:create</tt>
123
+ # and <tt>:update</tt>.
124
+ # * <tt>:if</tt> - Specifies a method, proc or string to call to determine
125
+ # if the validation should occur (e.g. <tt>if: :allow_validation</tt>,
126
+ # or <tt>if: Proc.new { |user| user.signup_step > 2 }</tt>). The method,
127
+ # proc or string should return or evaluate to a +true+ or +false+ value.
128
+ # * <tt>:unless</tt> - Specifies a method, proc or string to call to
129
+ # determine if the validation should not occur (e.g. <tt>unless: :skip_validation</tt>,
130
+ # or <tt>unless: Proc.new { |user| user.signup_step <= 2 }</tt>). The
131
+ # method, proc or string should return or evaluate to a +true+ or +false+
132
+ # value.
133
+ def validates_associated(*attr_names)
134
+ validates_with AssociatedValidator, _merge_attributes(attr_names)
135
+ end
136
+ end
137
+ end
138
+ end
139
+
140
+ module ActionDispatch::Routing
141
+ class Mapper
142
+ def two_factor_auth_for resource
143
+ begin
144
+ klass = resource.to_s.classify.constantize
145
+ rescue NameError
146
+ warn "You included two_factor_auth_for #{resource.inspect} in your routes but there is no model defined in your system"
147
+ end
148
+ namespace :two_factor_auth do
149
+ resources(:registrations, only: [:new, :create])
150
+ resource(:authentication, only: [:new, :create])
151
+ resources(:trusted_facets, only: [:index])
152
+ end
153
+ end
154
+ end
155
+ end
@@ -0,0 +1,70 @@
1
+ require "test_helper"
2
+
3
+ module TwoFactorAuth
4
+ describe AuthenticationsController do
5
+ let(:current_user) { User.create!(email: 'user@example.com', password: 'password') }
6
+ let(:registration) { Registration.create!({
7
+ login: current_user,
8
+ key_handle: TwoFactorAuth.websafe_base64_decode("fNKqlc0cHr7CcAScmiwJF3qL5WP5YY9vSZR5i474rPWmg8qjTHIckZA_v2Xioj6RB6BNJqzxUVUwG6wfksKXtA"),
9
+ public_key: TwoFactorAuth.websafe_base64_decode("BDnGR0Pm03VuO2HSBBubLHZr69y1MQgUFgeSjaGpMtdaF7NE331Q2pxn6_03aClOxPLxEBuKx4iaTRdW6r5YFKs"),
10
+ certificate: TwoFactorAuth.websafe_base64_decode("MIICHDCCAQagAwIBAgIEJNurQDALBgkqhkiG9w0BAQswLjEsMCoGA1UEAxMjWXViaWNvIFUyRiBSb290IENBIFNlcmlhbCA0NTcyMDA2MzEwIBcNMTQwODAxMDAwMDAwWhgPMjA1MDA5MDQwMDAwMDBaMCsxKTAnBgNVBAMMIFl1YmljbyBVMkYgRUUgU2VyaWFsIDEzNTAzMjc3ODg4MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEArCUvjR9R3lBxHeOvsXKTe0qR5-qHm_sOa_r3gwgcMtb1L1pyWp447-HUf61eRuN-srClAF1HLFXuXwJ5DkaNqMSMBAwDgYKKwYBBAGCxAoBAQQAMAsGCSqGSIb3DQEBCwOCAQEAo2OuDpg68wu68SyLLfNaWb8cu0obD8toxIRVhJD2hzRYZbjbAmnDRuVTiEwsVgevDqJ7kKyM8e9DH3KsGJ2yHIJJFL8XiKVRGjPQe0yONGR86fYeFRapqbNukApAIGH2mqRuEsUyuZP5Qj76qkz5o7ZUtN3e8pJKVI_VmZVRDdT39Nmk1SGThzxxybh-hoU-ni2nXo8MbSgwU3TU791eFJb4wzkGEHvWi9Y1DarSw3gR7KPKQ7yTC3NAl972nWiNlFUMTPsYqeJLhqLl2I9JmJmgm85bgQxTbK85Dci93pYN8zDKyrwFIaGDI5V__rylnKkLILENCbUjHFjCfrpngw"),
11
+ counter: 3,
12
+ last_authenticated_at: Time.now,
13
+ }) }
14
+ let(:challenge) { "430x3zbNg7tdHBds3_aXoSjp81xWe_2eZoEgR856tv8" }
15
+ let(:clientData) { "eyJ0eXAiOiJuYXZpZ2F0b3IuaWQuZ2V0QXNzZXJ0aW9uIiwiY2hhbGxlbmdlIjoiNDMweDN6Yk5nN3RkSEJkczNfYVhvU2pwODF4V2VfMmVab0VnUjg1NnR2OCIsIm9yaWdpbiI6Imh0dHA6Ly9sb2NhbC5maWRvbG9naW4uY29tOjMwMDAiLCJjaWRfcHVia2V5IjoiIn0" }
16
+ let(:signatureData) { "AQAAABAwRgIhAPueB6u8s63myrtQBT7KNOR3c4CVoNPVAiEkSOB8WGzqAiEA5zYbDQopgsVUl3d3pC947pKFSSIJs00ouC3xn3m7Pxo" }
17
+
18
+ before do
19
+ TwoFactorAuth.trusted_facet_list_url = "http://local.fidologin.com:3000"
20
+ register_as current_user, registration
21
+ end
22
+
23
+ describe "#new" do
24
+ it "has a form linking to create" do
25
+ get :new
26
+ assert_response :success
27
+ assert_select "form[action='/two_factor_auth/authentication']"
28
+ end
29
+
30
+ it "does not prompt for re-authentication if you already have" do
31
+ authenticate_as current_user, registration
32
+ get :new
33
+ assert_response :redirect
34
+ end
35
+ end
36
+
37
+ describe "#create" do
38
+ it "clears the pending challenge" do
39
+ controller.stub(:user_session, { 'pending_authentication_request_challenge' => challenge }) do
40
+ post :create, keyHandle: TwoFactorAuth.websafe_base64_encode(registration.key_handle), clientData: clientData, signatureData: signatureData
41
+ controller.user_session.wont_include 'pending_authentication_request_challenge'
42
+ end
43
+ end
44
+
45
+ describe "success" do
46
+
47
+ it "creates a Registration when the challenge is verified" do
48
+ controller.stub(:user_session, { 'pending_authentication_request_challenge' => challenge }) do
49
+ post :create, keyHandle: TwoFactorAuth.websafe_base64_encode(registration.key_handle), clientData: clientData, signatureData: signatureData
50
+ assert_response :redirect
51
+ assert_redirected_to '/'
52
+ controller.send(:user_two_factor_auth_authenticated?).must_equal true
53
+ end
54
+ end
55
+
56
+ end
57
+
58
+ describe "failure" do
59
+ it "renders an error when challenge is not verified" do
60
+ controller.stub(:user_session, { 'pending_authentication_request_challenge' => 'not matched' }) do
61
+ post :create, keyHandle: TwoFactorAuth.websafe_base64_encode(registration.key_handle), clientData: clientData, signatureData: signatureData
62
+ controller.send(:user_two_factor_auth_authenticated?).must_equal false
63
+ response.status.must_equal 406
64
+ response.body.must_include 'Unable to authenticate'
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,57 @@
1
+ require "test_helper"
2
+
3
+ module TwoFactorAuth
4
+ describe RegistrationsController do
5
+ let(:current_user) { User.create!(email: 'user@example.com', password: 'password') }
6
+ let(:clientData) { "eyJ0eXAiOiJuYXZpZ2F0b3IuaWQuZmluaXNoRW5yb2xsbWVudCIsImNoYWxsZW5nZSI6IjVmeGF6cWFuUkh0ZDdBdEVIQkd4eERwU2o2bWRCRjI2WEY0eGRBOW03SnciLCJvcmlnaW4iOiJodHRwOi8vbG9jYWwuZmlkb2xvZ2luLmNvbTozMDAwIiwiY2lkX3B1YmtleSI6IiJ9" }
7
+ let(:registrationData) { "BQQqdFC3zhANYW9DmErAjFQYZjBExK22PLx-ViMOch04-wZ990aqOcF2gxS5gzSUDKzpPGXpliMk3UoXgYlC2QNuQGbQ4E5v_UrLCzT58SXg902p9JXmLboTF42QkuZXIbdea_97h96lVovJ7xrA-iWTrZiOSRVZoBZsTrCW64XMrUcwggIcMIIBBqADAgECAgQk26tAMAsGCSqGSIb3DQEBCzAuMSwwKgYDVQQDEyNZdWJpY28gVTJGIFJvb3QgQ0EgU2VyaWFsIDQ1NzIwMDYzMTAgFw0xNDA4MDEwMDAwMDBaGA8yMDUwMDkwNDAwMDAwMFowKzEpMCcGA1UEAwwgWXViaWNvIFUyRiBFRSBTZXJpYWwgMTM1MDMyNzc4ODgwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAQCsJS-NH1HeUHEd46-xcpN7SpHn6oeb-w5r-veDCBwy1vUvWnJanjjv4dR_rV5G436ysKUAXUcsVe5fAnkORo2oxIwEDAOBgorBgEEAYLECgEBBAAwCwYJKoZIhvcNAQELA4IBAQCjY64OmDrzC7rxLIst81pZvxy7ShsPy2jEhFWEkPaHNFhluNsCacNG5VOITCxWB68OonuQrIzx70MfcqwYnbIcgkkUvxeIpVEaM9B7TI40ZHzp9h4VFqmps26QCkAgYfaapG4SxTK5k_lCPvqqTPmjtlS03d7ykkpUj9WZlVEN1Pf02aTVIZOHPHHJuH6GhT6eLadejwxtKDBTdNTv3V4UlvjDOQYQe9aL1jUNqtLDeBHso8pDvJMLc0CX3vadaI2UVQxM-xip4kuGouXYj0mYmaCbzluBDFNsrzkNyL3elg3zMMrKvAUhoYMjlX_-vKWcqQsgsQ0JtSMcWMJ-umeDMEUCIQCPkI4L_gHM88JrqJj_ZNRghQyC0gJyCC9RBrnfI2mDTwIgPOuEiD1AOfRaGO_EaHi-z4XyIGDhkG8-BYH-syVY5_o" }
8
+
9
+ before do
10
+ TwoFactorAuth.trusted_facet_list_url = "http://local.fidologin.com:3000"
11
+ sign_in current_user
12
+ end
13
+
14
+ describe "#new" do
15
+ it "has a form linking to create" do
16
+ get :new
17
+ assert_response :success
18
+ assert_select "form[action='/two_factor_auth/registrations']"
19
+ end
20
+ end
21
+
22
+ describe "#create" do
23
+ it "clears the pending challenge" do
24
+ controller.stub(:user_session, { 'pending_registration_request_challenge' => '5fxazqanRHtd7AtEHBGxxDpSj6mdBF26XF4xdA9m7Jw' }) do
25
+ post :create, clientData: clientData, registrationData: registrationData
26
+ controller.user_session.wont_include 'pending_registration_request_challenge'
27
+ end
28
+ end
29
+
30
+ describe "success" do
31
+
32
+ it "creates a Registration when the challenge is verified" do
33
+ controller.stub(:user_session, { 'pending_registration_request_challenge' => '5fxazqanRHtd7AtEHBGxxDpSj6mdBF26XF4xdA9m7Jw' }) do
34
+ Registration.count.must_equal 0
35
+ post :create, clientData: clientData, registrationData: registrationData
36
+ assert_response :redirect
37
+ assert_redirected_to '/'
38
+ current_user.registrations.count.must_equal 1
39
+ end
40
+ end
41
+
42
+ end
43
+
44
+ describe "failure" do
45
+ it "renders an error when challenge is not verified" do
46
+ controller.stub(:user_session, { 'pending_registration_request_challenge' => 'not matched' }) do
47
+ Registration.count.must_equal 0
48
+ post :create, clientData: clientData, registrationData: registrationData
49
+ response.status.must_equal 406
50
+ Registration.count.must_equal 0
51
+ response.body.must_include 'Unable to register'
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,17 @@
1
+ require "test_helper"
2
+
3
+ module TwoFactorAuth
4
+ describe TrustedFacetsController do
5
+ it "returns the list of facets as json" do
6
+ TwoFactorAuth.facets = [ 'https://example.com', 'https://admin.example.com' ]
7
+ get :index
8
+ facets = JSON::parse(response.body)
9
+ facets.must_equal TwoFactorAuth.facets
10
+ end
11
+
12
+ it "has the U2F mimetype" do
13
+ get :index
14
+ response.content_type.must_equal "application/fido.trusted-apps+json"
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,28 @@
1
+ == README
2
+
3
+ This README would normally document whatever steps are necessary to get the
4
+ application up and running.
5
+
6
+ Things you may want to cover:
7
+
8
+ * Ruby version
9
+
10
+ * System dependencies
11
+
12
+ * Configuration
13
+
14
+ * Database creation
15
+
16
+ * Database initialization
17
+
18
+ * How to run the test suite
19
+
20
+ * Services (job queues, cache servers, search engines, etc.)
21
+
22
+ * Deployment instructions
23
+
24
+ * ...
25
+
26
+
27
+ Please feel free to use a different markup language if you do not plan to run
28
+ <tt>rake doc:app</tt>.
@@ -0,0 +1,6 @@
1
+ # Add your own tasks in files placed in lib/tasks ending in .rake,
2
+ # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
3
+
4
+ require File.expand_path('../config/application', __FILE__)
5
+
6
+ Rails.application.load_tasks
@@ -0,0 +1,13 @@
1
+ // This is a manifest file that'll be compiled into application.js, which will include all the files
2
+ // listed below.
3
+ //
4
+ // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
5
+ // or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path.
6
+ //
7
+ // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
8
+ // compiled file.
9
+ //
10
+ // Read Sprockets README (https://github.com/sstephenson/sprockets#sprockets-directives) for details
11
+ // about supported directives.
12
+ //
13
+ //= require_tree .
@@ -0,0 +1,15 @@
1
+ /*
2
+ * This is a manifest file that'll be compiled into application.css, which will include all the files
3
+ * listed below.
4
+ *
5
+ * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
6
+ * or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path.
7
+ *
8
+ * You're free to add application-wide styles to this file and they'll appear at the bottom of the
9
+ * compiled file so the styles you add here take precedence over styles defined in any styles
10
+ * defined in the other CSS/SCSS files in this directory. It is generally better to create a new
11
+ * file per style scope.
12
+ *
13
+ *= require_tree .
14
+ *= require_self
15
+ */
@@ -0,0 +1,5 @@
1
+ class ApplicationController < ActionController::Base
2
+ # Prevent CSRF attacks by raising an exception.
3
+ # For APIs, you may want to use :null_session instead.
4
+ protect_from_forgery with: :exception
5
+ end
@@ -0,0 +1,3 @@
1
+ class SecretsController < ApplicationController
2
+ before_action :authenticate_user!
3
+ end
@@ -0,0 +1,2 @@
1
+ module ApplicationHelper
2
+ end
@@ -0,0 +1,8 @@
1
+ class User < ActiveRecord::Base
2
+ # Include default devise modules. Others available are:
3
+ # :confirmable, :lockable, :timeoutable and :omniauthable
4
+ devise :database_authenticatable, :registerable,
5
+ :recoverable, :rememberable, :trackable, :validatable
6
+
7
+ has_many :registrations, inverse_of: :login, as: :login, dependent: :destroy, class_name: "TwoFactorAuth::Registration"
8
+ end
@@ -0,0 +1,16 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>Dummy</title>
5
+ <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track' => true %>
6
+ <%= javascript_include_tag 'application', 'data-turbolinks-track' => true %>
7
+ <%= csrf_meta_tags %>
8
+ </head>
9
+ <body>
10
+
11
+ <div class="alert twofactorauth-status"><%= alert %></div>
12
+
13
+ <%= yield %>
14
+
15
+ </body>
16
+ </html>
@@ -0,0 +1,10 @@
1
+ <h1>Secrets</h1>
2
+
3
+ <ul>
4
+ <li><a href="#">Startup ideas (NDA required)</a></li>
5
+ <li><a href="#">Stock tips</a></li>
6
+ <li><a href="#">18 and 1/2 minutes of audio tape</a></li>
7
+ <li><a href="#">Great American novel (first draft)</a></li>
8
+ <li><a href="#">Crushes on boys</a></li>
9
+ <li><a href="#">IRS email backup</a></li>
10
+ </ul>
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env ruby
2
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
3
+ load Gem.bin_path('bundler', 'bundle')
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+ APP_PATH = File.expand_path('../../config/application', __FILE__)
3
+ require_relative '../config/boot'
4
+ require 'rails/commands'