orthodox 0.2.4 → 0.3.4

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 (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