orthodox 0.2.4 → 0.3.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/dummy/Gemfile +1 -1
  3. data/dummy/Gemfile.lock +3 -2
  4. data/lib/generators/authentication/USAGE +14 -0
  5. data/lib/generators/authentication/authentication_generator.rb +217 -0
  6. data/lib/generators/authentication/templates/controllers/concerns/authentication.rb.erb +111 -0
  7. data/lib/generators/authentication/templates/controllers/concerns/two_factor_authentication.rb +40 -0
  8. data/lib/generators/authentication/templates/controllers/password_resets_controller.rb.erb +54 -0
  9. data/lib/generators/authentication/templates/controllers/sessions_controller.rb.erb +36 -0
  10. data/lib/generators/authentication/templates/controllers/tfa_sessions_controller.rb.erb +48 -0
  11. data/lib/generators/authentication/templates/controllers/tfas_controller.rb.erb +38 -0
  12. data/lib/generators/authentication/templates/helpers/otp_credentials_helper.rb +33 -0
  13. data/lib/generators/authentication/templates/javascript/tfa_forms.js +19 -0
  14. data/lib/generators/authentication/templates/models/concerns/authenticateable.rb +37 -0
  15. data/lib/generators/authentication/templates/models/concerns/otpable.rb +26 -0
  16. data/lib/generators/authentication/templates/models/concerns/password_resetable.rb +19 -0
  17. data/lib/generators/authentication/templates/models/otp_credential.rb.erb +133 -0
  18. data/lib/generators/authentication/templates/models/password_reset_token.rb +64 -0
  19. data/lib/generators/authentication/templates/models/session.rb.erb +80 -0
  20. data/lib/generators/authentication/templates/models/tfa_session.rb +77 -0
  21. data/lib/generators/authentication/templates/spec/models/otp_credential_spec.rb +215 -0
  22. data/lib/generators/authentication/templates/spec/models/password_reset_token_spec.rb +146 -0
  23. data/lib/generators/authentication/templates/spec/models/session_spec.rb.erb +45 -0
  24. data/lib/generators/authentication/templates/spec/models/tfa_session_spec.rb.erb +115 -0
  25. data/lib/generators/authentication/templates/spec/support/authentication_helpers.rb +18 -0
  26. data/lib/generators/authentication/templates/spec/support/factory_bot.rb +5 -0
  27. data/lib/generators/authentication/templates/spec/system/authentication_spec.rb.erb +25 -0
  28. data/lib/generators/authentication/templates/spec/system/password_resets_spec.rb.erb +73 -0
  29. data/lib/generators/authentication/templates/spec/system/tfa_authentication_spec.rb.erb +38 -0
  30. data/lib/generators/authentication/templates/views/mailers/password_reset_link.html.slim.erb +7 -0
  31. data/lib/generators/authentication/templates/views/password_resets/edit.html.slim.erb +16 -0
  32. data/lib/generators/authentication/templates/views/password_resets/new.html.slim.erb +12 -0
  33. data/lib/generators/authentication/templates/views/sessions/new.html.slim.erb +21 -0
  34. data/lib/generators/authentication/templates/views/tfa_sessions/new.html.slim.erb +26 -0
  35. data/lib/generators/authentication/templates/views/tfas/show.html.slim.erb +9 -0
  36. data/lib/generators/base_controller/USAGE +8 -0
  37. data/lib/generators/base_controller/base_controller_generator.rb +22 -0
  38. data/lib/generators/base_controller/templates/base_controller.rb.erb +7 -0
  39. data/lib/generators/coffeescript/coffeescript_generator.rb +2 -8
  40. data/lib/generators/coffeescript/templates/coffeescript.coffee.erb +3 -14
  41. data/lib/generators/controller/templates/controller.rb.erb +14 -11
  42. data/lib/generators/layout_helper/USAGE +8 -0
  43. data/lib/generators/layout_helper/layout_helper_generator.rb +55 -0
  44. data/lib/orthodox/version.rb +1 -1
  45. data/orthodox.gemspec +1 -1
  46. metadata +42 -5
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f88d03f6e7af9cdf80036cac8d4729085eded3c2fab7397fcf1aa11342614529
4
- data.tar.gz: 3e0cb40bc7890ff8a86e6206ad7d2c9e480aab0913c54e72aa8ff3167f98c912
3
+ metadata.gz: fb77b7c5116298b400bc1dbb3ddaaacb3f0a9ec620a006d50603d27760d012c1
4
+ data.tar.gz: f05694fb1617a6c05d7099b269899a5e97fbf7afd001353325d59ad06cc7bc53
5
5
  SHA512:
6
- metadata.gz: b4286e0501d78936b5e0af3ad5d2005a3f00afe97e5bdbb045743aae8c47baf43573f6be896714e4467f90787561c023ce57f200875707042f157ba68f1619ba
7
- data.tar.gz: 4f4d3f93e1f76cb962183b0cda3ec7c9f9e1cff2a0b776b3fcb2c4e292152f54fbb40d34b8aa3e83d591135f63016cef19d1983695747aafdf013b68ba9b33da
6
+ metadata.gz: df95cde10ea72fd16bb6ec9c40fb8a468a183e0782f101919c65cbcf76401b50258bc61439c17d75edd02b5f108f65369e66dbd3745b1d2f9ba08b4ab6a04506
7
+ data.tar.gz: 7afc8478a2a8476de977c089c3b9292c8122f1abcf922d0e2adcc562f83857fba04186c503fe86eea1beb4884d4b6e58a3fbf66158d9a11149fef8fe2e3f039e
@@ -8,7 +8,7 @@ gem 'rails', '~> 5.2.0'
8
8
  # Use sqlite3 as the database for Active Record
9
9
  gem 'sqlite3'
10
10
  # Use Puma as the app server
11
- gem 'puma', '~> 3.11'
11
+ gem 'puma', '~> 4.3'
12
12
  # Use SCSS for stylesheets
13
13
  gem 'sass-rails', '~> 5.0'
14
14
  # Use Uglifier as compressor for JavaScript assets
@@ -113,7 +113,8 @@ GEM
113
113
  nokogiri (1.10.5)
114
114
  mini_portile2 (~> 2.4.0)
115
115
  public_suffix (3.0.2)
116
- puma (3.11.4)
116
+ puma (4.3.1)
117
+ nio4r (~> 2.0)
117
118
  rack (2.0.6)
118
119
  rack-test (1.1.0)
119
120
  rack (>= 1.0, < 3)
@@ -215,7 +216,7 @@ DEPENDENCIES
215
216
  jbuilder (~> 2.5)
216
217
  listen (>= 3.0.5, < 3.2)
217
218
  orthodox!
218
- puma (~> 3.11)
219
+ puma (~> 4.3)
219
220
  rails (~> 5.2.0)
220
221
  sass-rails (~> 5.0)
221
222
  selenium-webdriver
@@ -0,0 +1,14 @@
1
+ Description:
2
+ Adds boilerplate controllers, models, and views for authenticating a model.
3
+
4
+ Example:
5
+ rails generate authentication Member
6
+
7
+ This will create:
8
+ app/models/concerns/authenticateable.rb
9
+ app/controllers/concerns/authentication.rb
10
+ app/controllers/members/sessions_controller.rb
11
+ app/models/member_session.rb
12
+ app/views/members/sessions/new.html.slim
13
+ spec/system/members/authentication_spec.rb
14
+ spec/models/member_session_spec.rb
@@ -0,0 +1,217 @@
1
+ class AuthenticationGenerator < Rails::Generators::NamedBase
2
+ source_root File.expand_path('templates', __dir__)
3
+
4
+ desc "Creates authentication views, controllers and models for a given Model"
5
+
6
+ class_option :skip_views, type: :boolean, default: false
7
+
8
+ class_option :skip_tests, type: :boolean, default: false
9
+
10
+ class_option :two_factor, type: :boolean, default: false
11
+
12
+ class_option :js, type: :boolean, default: false
13
+
14
+
15
+ def create_controllers
16
+ generate "base_controller", plural_name
17
+ template "controllers/sessions_controller.rb.erb",
18
+ "app/controllers/#{plural_file_name}/sessions_controller.rb"
19
+ template "controllers/password_resets_controller.rb.erb",
20
+ "app/controllers/#{plural_file_name}/password_resets_controller.rb"
21
+
22
+ if options[:two_factor]
23
+ template "controllers/tfa_sessions_controller.rb.erb",
24
+ "app/controllers/#{plural_file_name}/tfa_sessions_controller.rb"
25
+
26
+ template "controllers/tfas_controller.rb.erb",
27
+ "app/controllers/#{plural_file_name}/tfas_controller.rb"
28
+ end
29
+ end
30
+
31
+ def extend_controllers
32
+ inject_into_class "app/controllers/#{plural_name}/base_controller.rb",
33
+ "#{plural_class_name}::BaseController",
34
+ " authenticate_model :#{singular_name}, tfa: #{options[:two_factor]}\n"
35
+ include_module_in_controller("#{plural_name}/base_controller", "Authentication")
36
+
37
+ end
38
+
39
+ def ensure_helpers
40
+ if options[:two_factor]
41
+ copy_file "helpers/otp_credentials_helper.rb", "app/helpers/otp_credentials_helper.rb"
42
+ end
43
+ end
44
+
45
+ def ensure_js
46
+ if options[:two_factor] && options[:js]
47
+ copy_file "javascript/tfa_forms.js", "app/javascript/packs/tfa_forms.js"
48
+ end
49
+ end
50
+
51
+ def create_mailer
52
+ generate "mailer", "#{class_name}Mailer"
53
+ inject_into_class "app/mailers/#{singular_name}_mailer.rb", "#{class_name}Mailer", <<~RUBY
54
+
55
+ def password_reset_link(#{singular_name})
56
+ @#{singular_name} = #{singular_name}
57
+ mail(to: #{singular_name}.email, subject: "Reset your password")
58
+ end
59
+
60
+ RUBY
61
+ template "views/mailers/password_reset_link.html.slim.erb",
62
+ "app/views/#{singular_name}_mailer/password_reset_link.html.slim"
63
+ end
64
+
65
+ def create_models
66
+ copy_file "models/password_reset_token.rb", "app/models/password_reset_token.rb"
67
+ if options[:two_factor]
68
+ template "models/otp_credential.rb.erb", "app/models/otp_credential.rb"
69
+ end
70
+ end
71
+
72
+ def extend_models
73
+ include_module_in_model(class_name, "Authenticateable")
74
+ include_module_in_model(class_name, "PasswordResetable")
75
+ if options[:two_factor]
76
+ include_module_in_model(class_name, "Otpable")
77
+ end
78
+ end
79
+
80
+ def create_form_objects
81
+ template "models/session.rb.erb", "app/models/#{singular_name}_session.rb"
82
+ if options[:two_factor]
83
+ copy_file "models/tfa_session.rb", "app/models/tfa_session.rb"
84
+ end
85
+ end
86
+
87
+ def ensure_concerns
88
+ template "controllers/concerns/authentication.rb.erb",
89
+ "app/controllers/concerns/authentication.rb"
90
+ copy_file "models/concerns/authenticateable.rb",
91
+ "app/models/concerns/authenticateable.rb"
92
+ copy_file "models/concerns/password_resetable.rb",
93
+ "app/models/concerns/password_resetable.rb"
94
+
95
+ if options[:two_factor]
96
+ copy_file "controllers/concerns/two_factor_authentication.rb",
97
+ "app/controllers/concerns/two_factor_authentication.rb"
98
+
99
+ copy_file "models/concerns/otpable.rb", "app/models/concerns/otpable.rb"
100
+ end
101
+ end
102
+
103
+ def create_migrations
104
+ generate "migration", "create_password_reset_tokens secret:token "\
105
+ "expires_at:datetime:index "\
106
+ "resetable:references{polymorphic}"
107
+ if options[:two_factor]
108
+ generate "migration", "create_otp_credentials \
109
+ created_at:datetime \
110
+ last_used_at:datetime \
111
+ secret:string{32} \
112
+ authable:references{polymorphic} \
113
+ recovery_codes:json"
114
+ end
115
+ end
116
+
117
+ def create_view_templates
118
+ return if options[:skip_views]
119
+ template "views/sessions/new.html.slim.erb",
120
+ "app/views/#{plural_name}/sessions/new.html.slim"
121
+
122
+ template "views/password_resets/new.html.slim.erb",
123
+ "app/views/#{plural_name}/password_resets/new.html.slim"
124
+
125
+ template "views/password_resets/edit.html.slim.erb",
126
+ "app/views/#{plural_name}/password_resets/edit.html.slim"
127
+
128
+ if options[:two_factor]
129
+ template "views/tfa_sessions/new.html.slim.erb",
130
+ "app/views/#{plural_name}/tfa_sessions/new.html.slim"
131
+ template "views/tfas/show.html.slim.erb",
132
+ "app/views/#{plural_name}/tfas/show.html.slim"
133
+
134
+ end
135
+ end
136
+
137
+ def create_specs
138
+ return if options[:skip_tests]
139
+
140
+ copy_file "spec/support/factory_bot.rb", "spec/support/factory_bot.rb"
141
+
142
+ template "spec/system/authentication_spec.rb.erb",
143
+ "spec/system/#{plural_name}/authentication_spec.rb"
144
+
145
+ template "spec/system/password_resets_spec.rb.erb",
146
+ "spec/system/#{plural_name}/password_resets_spec.rb"
147
+
148
+ template "spec/models/session_spec.rb.erb",
149
+ "spec/models/#{singular_name}_session_spec.rb"
150
+
151
+ copy_file "spec/models/password_reset_token_spec.rb",
152
+ "spec/models/password_reset_token_spec.rb"
153
+
154
+ if options[:two_factor]
155
+ copy_file "spec/support/authentication_helpers.rb",
156
+ "spec/support/authentication_helpers.rb"
157
+ template "spec/models/tfa_session_spec.rb.erb", "spec/models/tfa_session_spec.rb"
158
+ copy_file "spec/models/otp_credential_spec.rb",
159
+ "spec/models/otp_credential_spec.rb"
160
+ template "spec/system/tfa_authentication_spec.rb.erb",
161
+ "spec/system/#{plural_name}/tfa_authentication_spec.rb"
162
+
163
+ end
164
+ end
165
+
166
+ def create_factories
167
+ generate "factory_bot:model", "otp_credential"
168
+ generate "factory_bot:model", "password_reset_token"
169
+ generate "factory_bot:model", "#{singular_name}_session email:email password:password"
170
+ end
171
+
172
+ def ensure_gems
173
+ gem "validates_email_format_of", version: "~> 1.6"
174
+ gem "slim-rails"
175
+ gem "factory_bot_rails"
176
+ gem "faker"
177
+ if options[:two_factor]
178
+ gem "rotp", version: "~> 5.1.0"
179
+ gem "rqrcode", require: false
180
+ end
181
+ end
182
+
183
+ def create_routes
184
+ route <<~RUBY
185
+ namespace :#{plural_name} do
186
+
187
+ resource :session, only: [:new, :create, :destroy]
188
+
189
+ #{"resource :tfa_session, only: [:new, :create]" if options[:two_factor]}
190
+
191
+ resource :tfa, only: [:create, :show, :destroy]
192
+
193
+ resources :password_resets, only: [:new, :create, :edit, :update], param: :token
194
+
195
+ end
196
+ RUBY
197
+ end
198
+
199
+ private
200
+
201
+ def plural_class_name
202
+ class_name.pluralize
203
+ end
204
+
205
+ def include_module_in_controller(controller_name, module_name)
206
+ class_name = controller_name.classify
207
+ inject_into_class "app/controllers/#{controller_name}.rb", class_name,
208
+ " include #{module_name}\n"
209
+
210
+ end
211
+
212
+ def include_module_in_model(model_name, module_name)
213
+ inject_into_class "app/models/#{model_name.underscore}.rb", model_name,
214
+ " include #{module_name}\n"
215
+
216
+ end
217
+ end
@@ -0,0 +1,111 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Concern added to controllres to provide methods for authentication.
4
+ #
5
+ # Automatically generated by the orthodox gem (https://github.com/katanacode/orthodox)
6
+ # (c) Copyright 2019 Katana Code Ltd. All Rights Reserved.
7
+ module Authentication
8
+
9
+ extend ActiveSupport::Concern
10
+
11
+ protected
12
+
13
+ # Sign in a given record as a given type
14
+ #
15
+ # record - An AppliationRecord subclass instance (e.g. A Member)
16
+ # as - A String or Symbol with the model name (e.g. "member")
17
+ <%- if options[:two_factor] -%>
18
+ # tfa - Mark as Two-Factor authentication authenticated.
19
+ <%- end -%>
20
+ #
21
+ # Returns Integer
22
+ def sign_in(record, as:<%= ", tfa: false" if options[:two_factor] -%>)
23
+ session[:"#{as}_id"] = record.id.to_i
24
+ <%- if options[:two_factor] -%>
25
+ session[:"#{as}_tfa_authenticated"] = tfa
26
+ <%- end -%>
27
+ end
28
+
29
+ # Sign out a given record type.
30
+ #
31
+ # as - A String or Symbol with the model name (e.g. "member")
32
+ def sign_out(as)
33
+ session[:"#{as}_id"] = nil
34
+ <%- if options[:two_factor] -%>
35
+ session[:"#{as}_tfa_authenticated"] = nil
36
+ <%- end -%>
37
+ instance_variable_set("@current_#{as}", nil)
38
+ end
39
+
40
+ module ClassMethods
41
+
42
+ # Create a bunch of authentication methods for a given ActiveRecord model
43
+ def authenticate_model(model_name, **options)
44
+ # Define a current_<model> method to load the currently signed in record, if present
45
+ #
46
+ # Returns ApplicationRecord subclass
47
+ define_method(:"current_#{model_name}") do
48
+ instance_method_name = "@current_#{model_name}"
49
+ if instance_variable_get(instance_method_name).present?
50
+ instance_variable_get(instance_method_name)
51
+ else
52
+ scope = send(:"#{model_name}_auth_scope")
53
+ instance_variable_set(instance_method_name,
54
+ scope.find_by(id: session[:"#{model_name}_id"]))
55
+ end
56
+ end
57
+
58
+ # Define a controller before_action method to authenticate a record for the given
59
+ # model. Redirects to the <model_name>_failed_authentication_url if not passed.
60
+ #
61
+ # Returns nil
62
+ define_method(:"authenticate_#{model_name}") do
63
+ unless send(:"current_#{model_name}?")
64
+ redirect_to send(:"#{model_name}_failed_authentication_url"),
65
+ warn: "You must be signed in to do that"
66
+ end
67
+ end
68
+
69
+ before_action :"authenticate_#{model_name}"
70
+
71
+ # Creates a scope that records are loaded through when being authenticated. Subclass
72
+ # this method to customise the load conditions.
73
+ #
74
+ # Returns ActiveRecord::Relation
75
+ define_method(:"#{model_name}_auth_scope") do
76
+ model_name.to_s.classify.constantize.all
77
+ end
78
+
79
+ # Creates a boolean method to check if a current_<model_name> has been authenticated
80
+ # or not.
81
+ #
82
+ # Returns Boolean
83
+ define_method(:"current_#{model_name}?") { send(:"current_#{model_name}").present? }
84
+
85
+ define_method(:"#{model_name}_signed_in?") { send(:"current_#{model_name}?") }
86
+
87
+ define_method(:"#{model_name}_failed_authentication_url") do
88
+ send(:"new_#{model_name.to_s.pluralize}_session_url")
89
+ end
90
+
91
+ helper_method :"current_#{model_name}"
92
+ helper_method :"current_#{model_name}?"
93
+ helper_method :"#{model_name}_signed_in?"
94
+
95
+ protected :"current_#{model_name}"
96
+ protected :"current_#{model_name}?"
97
+ protected :"#{model_name}_signed_in?"
98
+ protected :"authenticate_#{model_name}"
99
+
100
+ <%- if options[:two_factor] -%>
101
+ # This is included if the authentication generator is run with --two-factor=true
102
+ if options[:tfa] == true
103
+ include TwoFactorAuthentication
104
+ define_tfa_methods(model_name)
105
+ end
106
+ <%- end -%>
107
+ end
108
+
109
+ end
110
+
111
+ end
@@ -0,0 +1,40 @@
1
+ module TwoFactorAuthentication
2
+ extend ActiveSupport::Concern
3
+
4
+ included do
5
+
6
+ end
7
+
8
+ module ClassMethods
9
+
10
+ def define_tfa_methods(model_name)
11
+ define_method :"authenticate_#{model_name}_with_tfa" do
12
+
13
+ send(:"authenticate_#{model_name}_without_tfa")
14
+
15
+ record = send(:"current_#{model_name}")
16
+ return unless record
17
+ if record.tfa? && !send(:"current_#{model_name}_tfa_authenticated?")
18
+ redirect_to send(:"new_#{model_name.to_s.pluralize}_tfa_session_url"),
19
+ warn: "You cannot proceed without authenticating"
20
+ end
21
+
22
+ end
23
+
24
+ define_method :"current_#{model_name}_tfa_authenticated?" do
25
+ session[:"#{model_name}_tfa_authenticated"] == true
26
+ end
27
+
28
+ define_method :"#{model_name}_tfa_success_redirect_url" do
29
+ send(:"#{model_name.to_s.pluralize}_dashboard_url")
30
+ end
31
+
32
+ alias_method :"authenticate_#{model_name}_without_tfa", :"authenticate_#{model_name}"
33
+
34
+ alias_method :"authenticate_#{model_name}", :"authenticate_#{model_name}_with_tfa"
35
+
36
+ end
37
+
38
+ end
39
+
40
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+ class <%= plural_class_name %>::PasswordResetsController < <%= plural_class_name %>::BaseController
3
+
4
+ skip_before_action :authenticate_<%= singular_name %>
5
+
6
+ def new
7
+ end
8
+
9
+ def create
10
+ @<%= singular_name %> = <%= plural_name %>_scope.find_by(email: permitted_params[:email])
11
+ if @<%= singular_name %>
12
+ @<%= singular_name %>.create_password_reset_token
13
+ <%= class_name %>Mailer.password_reset_link(@<%= singular_name %>).deliver_later
14
+ end
15
+ redirect_to new_<%= plural_name %>_session_url,
16
+ notice: "Please check your email for a password reset link"
17
+ end
18
+
19
+ def edit
20
+ @<%= singular_name %> = find_<%= singular_name %>_from_token(params[:token])
21
+ if @<%= singular_name %>.nil? or @<%= singular_name %>.password_reset_token.expired?
22
+ redirect_to new_<%= plural_name %>_session_url,
23
+ error: "The link you followed does not look valid"
24
+ end
25
+ end
26
+
27
+ def update
28
+ @<%= singular_name %> = find_<%= singular_name %>_from_token(params[:token])
29
+ if @<%= singular_name %>.update(password: permitted_params[:password],
30
+ password_confirmation: permitted_params[:password_confirmation])
31
+ @<%= singular_name %>.destroy_password_reset_token
32
+ redirect_to new_<%= plural_name %>_session_url, notice: "Successfully reset your password"
33
+ else
34
+ render :edit
35
+ end
36
+ end
37
+
38
+ private
39
+
40
+ def permitted_params
41
+ params.require(:<%= singular_name %>).permit(:email, :password, :password_confirmation)
42
+ end
43
+
44
+ # Change me to suit the scoping requirements for this project
45
+ def <%= plural_name %>_scope
46
+ <%= class_name %>.all
47
+ end
48
+
49
+ def find_<%= singular_name %>_from_token(token)
50
+ <%= plural_name %>_scope.joins(:password_reset_token)
51
+ .where(password_reset_tokens: { secret: params[:token] }).first
52
+ end
53
+
54
+ end