rose_quartz 0.1.0 → 0.9.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 9a4ea455229fd1c679e1f03e3af168e2957c7009
4
- data.tar.gz: 497bd1c54a984d513e7cef6da66e9229487e4de2
3
+ metadata.gz: 715723d37a50e4966319aead3e66cf26168a2302
4
+ data.tar.gz: cbf6814f33a0b4e797f71c13bbfe4deab4c355dc
5
5
  SHA512:
6
- metadata.gz: 8ef130e0e2cddad380778fd8126491e0e74abd27bcefd0f9fa1f47aca7d2ebe25daf23264eb13e85a65516809dae0f9fbefc198d3bbb39d700e49e4fe56911a7
7
- data.tar.gz: f214a522eb35570560431dbd8bfabb7c638aa7d0acae64f4903134a2b8d1087be49297a15aeec66be28eaef67c189673fc10fb5b526b56a20986c2b43133b5a4
6
+ metadata.gz: e4c51661f7211f4150ba8d89eb1410885d5ba01c3cd79ef72a2be5cc411f088aeb383e0f832c7692a52008d1a3698503aaa10366f1388a2a9eb998daf1128b48
7
+ data.tar.gz: 43bdea2ee92e8ba06d0d0a3132f17e932bc202ae9716b64b02c70167b6b204b6a6760aa43d911404258f7ade1a6c011dfe096531fb1a0e914db7399b9c76f39e
@@ -3,3 +3,5 @@ language: ruby
3
3
  rvm:
4
4
  - 2.3.3
5
5
  - 2.4.0
6
+ after_success:
7
+ - bundle exec codeclimate-test-reporter
data/README.md CHANGED
@@ -1,6 +1,7 @@
1
1
  # RoseQuartz
2
2
 
3
3
  [![Build Status](https://travis-ci.org/little-bobby-tables/rose_quartz.svg?branch=master)](https://travis-ci.org/little-bobby-tables/rose_quartz)
4
+ [![Test Coverage](https://codeclimate.com/github/little-bobby-tables/rose_quartz/badges/coverage.svg)](https://codeclimate.com/github/little-bobby-tables/rose_quartz/coverage)
4
5
 
5
6
  A gem that adds two-factor authentication (time-based one-time passwords) to [Devise](https://github.com/plataformatec/devise)
6
7
  using the [rotp](https://github.com/mdp/rotp) library.
@@ -8,10 +9,105 @@ using the [rotp](https://github.com/mdp/rotp) library.
8
9
  It attempts to stay lightweight by making a lot of assumptions — for example, that
9
10
  you have a single authenticatable resource, `User`, and that you're using `ActiveRecord`.
10
11
 
11
- Highlights of *RoseQuartz* are:
12
+ #### Highlights:
12
13
 
13
- * Zero tampering with the `User` model no additional fields, no included modules.
14
- * Separate table that can be updated in future without affecting your codebase and data.
14
+ * Adds optional TOTP (compatible with Google Authenticator) to the sign-in process.
15
+ * Provides a backup code as a fallback option; resets it once it has been used and notifies the user.
16
+ * Does not tamper with the `User` model — no additional fields, no included modules.
17
+ * Employs a separate table that can be updated in future without affecting your codebase and data.
15
18
  * Built with Rails 5 and Devise 4 in mind.
16
19
 
17
- The gem is still in development. All tests for core functionality are passing, but UI integration is not implemented yet.
20
+ #### What it does not do:
21
+
22
+ Use a multiple-page login system (email and password first, two-factor authentication token next).
23
+ This introduces lots of needless complexity, which goes against the purpose of the gem.
24
+
25
+ #### What it should do, but does not (yet):
26
+
27
+ * Encrypt the backup code and the secret used to generate OTP.
28
+
29
+ ## Getting Started
30
+
31
+ First, add *RoseQuartz* to your Gemfile:
32
+
33
+ ```
34
+ gem 'rose_quartz'
35
+ ```
36
+ And run:
37
+ ```
38
+ bundle install
39
+ ```
40
+
41
+ Next, you need to copy initializers, locales, and add a migration:
42
+ ```
43
+ rails g rose_quarts:install
44
+ ```
45
+
46
+ Finally, run the migration:
47
+ ```
48
+ rails db:migrate
49
+ ```
50
+
51
+ ## Adding views
52
+
53
+ #### Signing in
54
+
55
+ You need a special field for one-time password/backup code on the sign-in page (*app/views/devise/sessions/new.html.erb*).
56
+
57
+ Here's an example:
58
+
59
+ ```
60
+ <%# E-mail and password fields %>
61
+
62
+ <div class="field">
63
+ <%= label_tag :otp, 'Two-factor authentication token' %>
64
+ <%= text_field_tag :otp, '', autocomplete: "off" %>
65
+ </div>
66
+
67
+ <%# The rest of the form %>
68
+ ```
69
+
70
+ Note that you must leave the parameter name (`otp`) intact.
71
+
72
+ #### Enabling/disabling two-factor authentication
73
+
74
+ The gem adds a special extension to Devise that allows you to
75
+ include two-factor authentication setup in the account editing page
76
+ (*app/views/devise/registrations/edit.html.erb*).
77
+
78
+ As with other settings there, a password is required to toggle two-factor authentication.
79
+ The user also needs to provide a correct token generated by their TOTP application of choice,
80
+ which ensures that their device clock is in sync with the server.
81
+
82
+ Here's a sample implementation:
83
+
84
+ ```
85
+ <div class="field">
86
+ <%= fields_for :two_factor_authentication do |tfa| %>
87
+ <% if two_factor_authentication_enabled? %>
88
+ <%= tfa.label :disable, 'Disable two-factor authentication' %>
89
+ <%= tfa.check_box :disable %>
90
+ <p>
91
+ Your backup code is <strong><%= two_factor_authentication_backup_code %></strong> -
92
+ save it to access your account if you ever lose your device or don't have it with you.
93
+ </p>
94
+ <%= tfa.label :reset_backup_code %>
95
+ <%= tfa.check_box :reset_backup_code %>
96
+ <% else %>
97
+ <%= tfa.hidden_field :secret, value: two_factor_authentication_secret %>
98
+ <%= image_tag two_factor_authentication_qr_code_uri(size: 200) %>
99
+ <p>
100
+ Scan this QR code with your device and enter the token below:
101
+ </p>
102
+ <%= tfa.label :token, 'Token' %><br />
103
+ <%= tfa.text_field :token, value: '' %>
104
+ <p>
105
+ Tip: to configure authentication on multiple devices, scan the code using each device.
106
+ </p>
107
+ <% end %>
108
+ <% end %>
109
+ </div>
110
+ ```
111
+
112
+ The following helper methods are available in the view: `two_factor_authentication_enabled?`,
113
+ `two_factor_authentication_backup_code`, `two_factor_authentication_qr_code_uri`, `two_factor_authentication_secret`.
@@ -0,0 +1,24 @@
1
+ module RoseQuartz
2
+ class InstallGenerator < Rails::Generators::Base
3
+ include Rails::Generators::Migration
4
+
5
+ source_root File.expand_path('../templates', __FILE__)
6
+
7
+ def copy_initializer
8
+ copy_file 'initializer.rb', 'config/initializers/rose_quartz.rb'
9
+ end
10
+
11
+ def copy_locale
12
+ copy_file 'locale.en.yml', 'config/locales/rose_quartz.en.yml'
13
+ end
14
+
15
+ def copy_migration
16
+ migration_template 'migration.rb', 'db/migrate/add_two_factor_auth_with_rose_quartz.rb'
17
+ end
18
+
19
+ def self.next_migration_number(path)
20
+ next_migration_number = current_migration_number(path) + 1
21
+ ActiveRecord::Migration.next_migration_number(next_migration_number)
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,16 @@
1
+ RoseQuartz.initialize! do |config|
2
+ # Token issuer is used as a title in user applications (e.g. Google Authenticator).
3
+ # It is included in the QR code, and changing it won't have an effect on users
4
+ # that already have two-factor authentication enabled.
5
+ config.issuer = 'My Rails Application'
6
+
7
+ # In addition to +issuer+, client-side applications display an identifier
8
+ # (usually, this is account's email address).
9
+ # This setting needs to refer to an existing attribute or method of the authenticatable model.
10
+ config.user_identifier = :email
11
+
12
+ # Some users may have their devices slightly ahead or behind of the actual time.
13
+ # To counter this, the authenticator will accept tokens that are generated for
14
+ # timestamps withing the time drift window defined below.
15
+ config.time_drift = 2.minutes
16
+ end
@@ -0,0 +1,4 @@
1
+ en:
2
+ rose_quartz:
3
+ backup_code_used: "The backup code you have used to sign in has been reset. Please go to your account settings to copy the new one or temporarily disable two-factor authentication."
4
+ invalid_token_when_enabling_tfa: "Invalid token. Please make sure that your device has the correct time settings."
@@ -0,0 +1,14 @@
1
+ class AddTwoFactorAuthWithRoseQuartz < ActiveRecord::Migration[5.0]
2
+ def up
3
+ create_table :user_authenticators do |t|
4
+ t.integer :user_id
5
+ t.string :secret
6
+ t.string :backup_code
7
+ t.integer :last_authenticated_at
8
+ end
9
+ end
10
+
11
+ def down
12
+ drop_table :user_authenticators
13
+ end
14
+ end
@@ -1,7 +1,11 @@
1
- # frozen_string_literal: truez
1
+ # frozen_string_literal: true
2
2
  require 'devise'
3
3
 
4
4
  require 'rose_quartz/version'
5
+
6
+ require 'rose_quartz/hooks'
5
7
  require 'rose_quartz/configuration'
6
8
  require 'rose_quartz/user_authenticator'
7
9
  require 'rose_quartz/devise/strategies/two_factor_authenticatable'
10
+ require 'rose_quartz/devise/controllers/sessions_controller_extensions'
11
+ require 'rose_quartz/devise/controllers/registrations_controller_extensions'
@@ -8,23 +8,16 @@ module RoseQuartz
8
8
 
9
9
  def self.initialize!
10
10
  yield self.configuration
11
- insert_authentication_strategy!
12
- end
13
-
14
- def self.insert_authentication_strategy!
15
- ::Devise.setup do |c|
16
- c.warden do |manager|
17
- manager.default_strategies(scope: :user).unshift :two_factor_authenticatable
18
- end
19
- end
11
+ Hooks.initialize_hooks!
20
12
  end
21
13
 
22
14
  class Configuration
23
- attr_accessor :issuer, :time_drift
15
+ attr_accessor :issuer, :time_drift, :user_identifier
24
16
 
25
17
  def initialize
26
- @issuer = ''
27
- @time_drift = 60.seconds
18
+ @issuer = 'RoseQuartz'
19
+ @time_drift = 1.minute
20
+ @user_identifier = :email
28
21
  end
29
22
  end
30
23
  end
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+ require 'i18n'
3
+ require 'rqrcode'
4
+
5
+ module Devise
6
+ module RegistrationsControllerExtensions
7
+ def self.prepended(base)
8
+ base.class_eval do
9
+ helper_method :two_factor_authentication_enabled?,
10
+ :two_factor_authentication_backup_code,
11
+ :two_factor_authentication_qr_code_uri,
12
+ :two_factor_authentication_secret
13
+ end
14
+ end
15
+
16
+ def update_resource(resource, _params)
17
+ result = super
18
+ edit_two_factor_authentication(resource) if result
19
+ result
20
+ end
21
+
22
+ protected
23
+
24
+ # View helpers
25
+
26
+ def two_factor_authentication_enabled?(user = resource)
27
+ RoseQuartz::UserAuthenticator.exists? user_id: user.id
28
+ end
29
+
30
+ def two_factor_authentication_backup_code
31
+ authenticator(resource).backup_code
32
+ end
33
+
34
+ def two_factor_authentication_qr_code_uri(size:)
35
+ uri = authenticator.provisioning_uri
36
+ qr = RQRCode::QRCode.new(uri)
37
+ qr.as_png(size: size).to_data_url
38
+ end
39
+
40
+ def two_factor_authentication_secret
41
+ authenticator.secret
42
+ end
43
+
44
+ private
45
+
46
+ # Internal logic
47
+
48
+ def edit_two_factor_authentication(resource)
49
+ if two_factor_authentication_enabled?
50
+ disable_two_factor_authentication!(resource) if form_params[:disable] == '1'
51
+ reset_two_factor_authentication_backup_code!(resource) if form_params[:reset_backup_code] == '1'
52
+ else
53
+ enable_two_factor_authentication!(resource)
54
+ end
55
+ end
56
+
57
+ def disable_two_factor_authentication!(resource)
58
+ authenticator(resource).disable!
59
+ end
60
+
61
+ def reset_two_factor_authentication_backup_code!(resource)
62
+ authenticator(resource).reset_backup_code!
63
+ end
64
+
65
+ def enable_two_factor_authentication!(resource)
66
+ secret, token = form_params.values_at(:secret, :token)
67
+ authenticator = RoseQuartz::UserAuthenticator.new(user: resource, secret: secret)
68
+ token_valid = authenticator.authenticate_otp!(token) rescue false
69
+ if token_valid
70
+ authenticator.save
71
+ else
72
+ resource.errors.add(:base, I18n.t('rose_quartz.invalid_token_when_enabling_tfa'))
73
+ end
74
+ end
75
+
76
+ def form_params
77
+ params.require(:two_factor_authentication).permit(:secret, :token, :disable, :reset_backup_code)
78
+ end
79
+
80
+ def authenticator(existing_user = nil)
81
+ @authenticator ||= if existing_user
82
+ RoseQuartz::UserAuthenticator.find_by(user_id: resource.id)
83
+ else
84
+ RoseQuartz::UserAuthenticator.new(user: resource)
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Devise
4
+ module SessionsControllerExtensions
5
+ def create
6
+ super do |_resource|
7
+ if request.env['rose_quartz.backup_code_used']
8
+ flash[:alert] = t('rose_quartz.backup_code_used')
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -7,17 +7,30 @@ module Devise
7
7
  def authenticate!
8
8
  resource = password.present? && mapping.to.find_for_database_authentication(authentication_hash)
9
9
 
10
- super if validate(resource) { otp_matches?(resource) }
10
+ super if validate(resource) { authenticated?(resource) }
11
11
  end
12
12
 
13
- def otp_matches?(resource)
13
+ def authenticated?(resource)
14
14
  authenticator = RoseQuartz::UserAuthenticator.find_by(user_id: resource.id)
15
- return true if authenticator.nil? # two-factor authentication is disabled
15
+ token = params['otp']
16
16
 
17
- token = params['tf_authentication_token']
17
+ # Two-factor authentication is disabled
18
+ return true if authenticator.nil?
19
+
20
+ # Token is not provided
18
21
  return false if token.nil?
19
22
 
20
- authenticator.authenticate(token)
23
+ # Token is a valid OTP
24
+ return true if authenticator.authenticate_otp!(token)
25
+
26
+ # Token is a valid backup code
27
+ if authenticator.authenticate_backup_code!(token)
28
+ env['rose_quartz.backup_code_used'] = true
29
+ return true
30
+ end
31
+
32
+ # Token is not a valid OTP or backup code
33
+ false
21
34
  end
22
35
  end
23
36
  end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+ module RoseQuartz
3
+ module Hooks
4
+ def self.initialize_hooks!
5
+ ::Devise.setup do |c|
6
+ c.warden do |manager|
7
+ manager.default_strategies(scope: :user).unshift :two_factor_authenticatable
8
+ end
9
+ end
10
+
11
+ ::Devise::SessionsController.prepend Devise::SessionsControllerExtensions
12
+ ::Devise::RegistrationsController.prepend Devise::RegistrationsControllerExtensions
13
+ end
14
+ end
15
+ end
@@ -5,22 +5,52 @@ module RoseQuartz
5
5
  class UserAuthenticator < ::ActiveRecord::Base
6
6
  belongs_to :user
7
7
 
8
- before_create :set_secret
8
+ after_initialize :set_secret_and_backup_code, if: :new_record?
9
9
 
10
- def set_secret
11
- self.secret = ROTP::Base32.random_base32
10
+ def set_secret_and_backup_code
11
+ self.secret ||= ROTP::Base32.random_base32
12
+ self.backup_code ||= generate_backup_code
12
13
  end
13
14
 
14
- def authenticator
15
- @authenticator ||= ROTP::TOTP.new(secret)
15
+ def authenticate_otp!(token)
16
+ authenticated_at = totp.verify_with_drift_and_prior(
17
+ token, RoseQuartz.configuration.time_drift, last_authenticated_at)
18
+ if authenticated_at
19
+ update_columns last_authenticated_at: authenticated_at if persisted?
20
+ true
21
+ else
22
+ false
23
+ end
16
24
  end
17
25
 
18
- def authenticate(token)
19
- authenticated_at = authenticator.verify_with_drift_and_prior(
20
- token, RoseQuartz.configuration.time_drift, last_authenticated_at)
21
- return false unless authenticated_at
22
- update_columns last_authenticated_at: authenticated_at
23
- true
26
+ def authenticate_backup_code!(token)
27
+ if token == backup_code
28
+ reset_backup_code!
29
+ true
30
+ else
31
+ false
32
+ end
33
+ end
34
+
35
+ def reset_backup_code!
36
+ update_columns backup_code: generate_backup_code
37
+ end
38
+
39
+ def totp
40
+ @authenticator ||= ROTP::TOTP.new(secret, issuer: RoseQuartz.configuration.issuer)
41
+ end
42
+
43
+ def provisioning_uri
44
+ totp.provisioning_uri(user.send(RoseQuartz.configuration.user_identifier))
45
+ end
46
+
47
+ alias disable! delete
48
+
49
+ private
50
+
51
+ # Four groups of 4-character base32 strings joined by dashes, e.g. "gs3w-ntpt-hrse-v23t"
52
+ def generate_backup_code
53
+ ROTP::Base32.random_base32(16).scan(/.{1,4}/).join('-')
24
54
  end
25
55
  end
26
56
  end
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module RoseQuartz
3
- VERSION = '0.1.0'
3
+ VERSION = '0.9.0'
4
4
  end
@@ -16,11 +16,15 @@ Gem::Specification.new do |spec|
16
16
  end
17
17
  spec.require_paths = ['lib']
18
18
 
19
- spec.add_runtime_dependency 'rails', '~> 5.0'
20
- spec.add_runtime_dependency 'devise', '~> 4.2'
21
- spec.add_runtime_dependency 'rotp', '~> 3.3'
19
+ spec.add_runtime_dependency 'rails', '>= 5.0'
20
+ spec.add_runtime_dependency 'devise', '>= 4.2'
21
+ spec.add_runtime_dependency 'rotp', '>= 3.3'
22
+ spec.add_runtime_dependency 'rqrcode', '>= 0.10'
22
23
 
23
24
  spec.add_development_dependency 'sqlite3'
25
+ spec.add_development_dependency 'capybara'
24
26
  spec.add_development_dependency 'factory_girl_rails'
25
27
  spec.add_development_dependency 'minitest-reporters'
28
+ spec.add_development_dependency 'simplecov'
29
+ spec.add_development_dependency 'codeclimate-test-reporter'
26
30
  end
metadata CHANGED
@@ -1,57 +1,71 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rose_quartz
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.9.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - little-bobby-tables
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-01-21 00:00:00.000000000 Z
11
+ date: 2017-01-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - "~>"
17
+ - - ">="
18
18
  - !ruby/object:Gem::Version
19
19
  version: '5.0'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - "~>"
24
+ - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: '5.0'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: devise
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - "~>"
31
+ - - ">="
32
32
  - !ruby/object:Gem::Version
33
33
  version: '4.2'
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - "~>"
38
+ - - ">="
39
39
  - !ruby/object:Gem::Version
40
40
  version: '4.2'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: rotp
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
- - - "~>"
45
+ - - ">="
46
46
  - !ruby/object:Gem::Version
47
47
  version: '3.3'
48
48
  type: :runtime
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
- - - "~>"
52
+ - - ">="
53
53
  - !ruby/object:Gem::Version
54
54
  version: '3.3'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rqrcode
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0.10'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0.10'
55
69
  - !ruby/object:Gem::Dependency
56
70
  name: sqlite3
57
71
  requirement: !ruby/object:Gem::Requirement
@@ -66,6 +80,20 @@ dependencies:
66
80
  - - ">="
67
81
  - !ruby/object:Gem::Version
68
82
  version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: capybara
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
69
97
  - !ruby/object:Gem::Dependency
70
98
  name: factory_girl_rails
71
99
  requirement: !ruby/object:Gem::Requirement
@@ -94,6 +122,34 @@ dependencies:
94
122
  - - ">="
95
123
  - !ruby/object:Gem::Version
96
124
  version: '0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: simplecov
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
139
+ - !ruby/object:Gem::Dependency
140
+ name: codeclimate-test-reporter
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: '0'
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - ">="
151
+ - !ruby/object:Gem::Version
152
+ version: '0'
97
153
  description:
98
154
  email:
99
155
  - little-bobby-tables@users.noreply.github.com
@@ -107,9 +163,16 @@ files:
107
163
  - LICENSE
108
164
  - README.md
109
165
  - Rakefile
166
+ - lib/generators/rose_quartz/install_generator.rb
167
+ - lib/generators/rose_quartz/templates/initializer.rb
168
+ - lib/generators/rose_quartz/templates/locale.en.yml
169
+ - lib/generators/rose_quartz/templates/migration.rb
110
170
  - lib/rose_quartz.rb
111
171
  - lib/rose_quartz/configuration.rb
172
+ - lib/rose_quartz/devise/controllers/registrations_controller_extensions.rb
173
+ - lib/rose_quartz/devise/controllers/sessions_controller_extensions.rb
112
174
  - lib/rose_quartz/devise/strategies/two_factor_authenticatable.rb
175
+ - lib/rose_quartz/hooks.rb
113
176
  - lib/rose_quartz/user_authenticator.rb
114
177
  - lib/rose_quartz/version.rb
115
178
  - rose_quartz.gemspec