kingsman 0.0.0.beta → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (115) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +5 -4
  3. data/app/controllers/kingsman/confirmations_controller.rb +54 -0
  4. data/app/controllers/kingsman/omniauth_callbacks_controller.rb +36 -0
  5. data/app/controllers/kingsman/passwords_controller.rb +83 -0
  6. data/app/controllers/kingsman/registrations_controller.rb +168 -0
  7. data/app/controllers/kingsman/sessions_controller.rb +83 -0
  8. data/app/controllers/kingsman/unlocks_controller.rb +52 -0
  9. data/app/controllers/kingsman_controller.rb +252 -0
  10. data/app/jobs/application_job.rb +2 -0
  11. data/app/mailers/kingsman/mailer.rb +30 -0
  12. data/app/views/kingsman/confirmations/new.html.erb +16 -0
  13. data/app/views/kingsman/home/index.html.erb +1 -0
  14. data/app/views/kingsman/mailer/confirmation_instructions.html.erb +5 -0
  15. data/app/views/kingsman/mailer/email_changed.html.erb +7 -0
  16. data/app/views/kingsman/mailer/password_change.html.erb +3 -0
  17. data/app/views/kingsman/mailer/reset_password_instructions.html.erb +8 -0
  18. data/app/views/kingsman/mailer/unlock_instructions.html.erb +7 -0
  19. data/app/views/kingsman/passwords/edit.html.erb +25 -0
  20. data/app/views/kingsman/passwords/new.html.erb +16 -0
  21. data/app/views/kingsman/registrations/edit.html.erb +42 -0
  22. data/app/views/kingsman/registrations/new.html.erb +29 -0
  23. data/app/views/kingsman/sessions/new.html.erb +26 -0
  24. data/app/views/kingsman/shared/_error_messages.html.erb +15 -0
  25. data/app/views/kingsman/shared/_links.html.erb +25 -0
  26. data/app/views/kingsman/unlocks/new.html.erb +16 -0
  27. data/app/views/kingsman/up/index.html.erb +11 -0
  28. data/config/application.rb +0 -0
  29. data/config/locales/en.yml +63 -0
  30. data/config.ru +6 -0
  31. data/lib/generators/active_record/kingsman_generator.rb +127 -0
  32. data/lib/generators/active_record/templates/migration.rb +20 -0
  33. data/lib/generators/active_record/templates/migration_existing.rb +27 -0
  34. data/lib/generators/kingsman/controllers_generator.rb +46 -0
  35. data/lib/generators/kingsman/install_generator.rb +42 -0
  36. data/lib/generators/kingsman/kingsman_generator.rb +28 -0
  37. data/lib/generators/kingsman/orm_helpers.rb +40 -0
  38. data/lib/generators/kingsman/views_generator.rb +145 -0
  39. data/lib/generators/mongoid/kingsman_generator.rb +57 -0
  40. data/lib/generators/templates/README +36 -0
  41. data/lib/generators/templates/controllers/README +14 -0
  42. data/lib/generators/templates/controllers/confirmations_controller.rb +30 -0
  43. data/lib/generators/templates/controllers/omniauth_callbacks_controller.rb +30 -0
  44. data/lib/generators/templates/controllers/passwords_controller.rb +34 -0
  45. data/lib/generators/templates/controllers/registrations_controller.rb +62 -0
  46. data/lib/generators/templates/controllers/sessions_controller.rb +27 -0
  47. data/lib/generators/templates/controllers/unlocks_controller.rb +30 -0
  48. data/lib/generators/templates/kingsman.rb +313 -0
  49. data/lib/generators/templates/markerb/confirmation_instructions.markerb +5 -0
  50. data/lib/generators/templates/markerb/email_changed.markerb +7 -0
  51. data/lib/generators/templates/markerb/password_change.markerb +3 -0
  52. data/lib/generators/templates/markerb/reset_password_instructions.markerb +8 -0
  53. data/lib/generators/templates/markerb/unlock_instructions.markerb +7 -0
  54. data/lib/generators/templates/simple_form_for/confirmations/new.html.erb +20 -0
  55. data/lib/generators/templates/simple_form_for/passwords/edit.html.erb +27 -0
  56. data/lib/generators/templates/simple_form_for/passwords/new.html.erb +18 -0
  57. data/lib/generators/templates/simple_form_for/registrations/edit.html.erb +35 -0
  58. data/lib/generators/templates/simple_form_for/registrations/new.html.erb +25 -0
  59. data/lib/generators/templates/simple_form_for/sessions/new.html.erb +20 -0
  60. data/lib/generators/templates/simple_form_for/unlocks/new.html.erb +19 -0
  61. data/lib/kingsman/autoloader.rb +31 -0
  62. data/lib/kingsman/controllers/helpers.rb +221 -0
  63. data/lib/kingsman/controllers/rememberable.rb +57 -0
  64. data/lib/kingsman/controllers/responder.rb +35 -0
  65. data/lib/kingsman/controllers/scoped_views.rb +19 -0
  66. data/lib/kingsman/controllers/sign_in_out.rb +112 -0
  67. data/lib/kingsman/controllers/store_location.rb +76 -0
  68. data/lib/kingsman/controllers/url_helpers.rb +72 -0
  69. data/lib/kingsman/delegator.rb +18 -0
  70. data/lib/kingsman/encryptor.rb +19 -0
  71. data/lib/kingsman/failure_app.rb +280 -0
  72. data/lib/kingsman/hooks/activatable.rb +12 -0
  73. data/lib/kingsman/hooks/csrf_cleaner.rb +14 -0
  74. data/lib/kingsman/hooks/forgetable.rb +11 -0
  75. data/lib/kingsman/hooks/lockable.rb +9 -0
  76. data/lib/kingsman/hooks/proxy.rb +23 -0
  77. data/lib/kingsman/hooks/rememberable.rb +9 -0
  78. data/lib/kingsman/hooks/timeoutable.rb +35 -0
  79. data/lib/kingsman/hooks/trackable.rb +11 -0
  80. data/lib/kingsman/hooks.rb +6 -0
  81. data/lib/kingsman/jets/routes.rb +195 -0
  82. data/lib/kingsman/jets/warden_compat.rb +15 -0
  83. data/lib/kingsman/jets.rb +39 -0
  84. data/lib/kingsman/mailers/helpers.rb +93 -0
  85. data/lib/kingsman/mapping.rb +148 -0
  86. data/lib/kingsman/models/authenticatable.rb +310 -0
  87. data/lib/kingsman/models/confirmable.rb +369 -0
  88. data/lib/kingsman/models/database_authenticatable.rb +206 -0
  89. data/lib/kingsman/models/lockable.rb +214 -0
  90. data/lib/kingsman/models/omniauthable.rb +29 -0
  91. data/lib/kingsman/models/recoverable.rb +156 -0
  92. data/lib/kingsman/models/registerable.rb +29 -0
  93. data/lib/kingsman/models/rememberable.rb +158 -0
  94. data/lib/kingsman/models/timeoutable.rb +45 -0
  95. data/lib/kingsman/models/trackable.rb +51 -0
  96. data/lib/kingsman/models/validatable.rb +68 -0
  97. data/lib/kingsman/models.rb +122 -0
  98. data/lib/kingsman/modules.rb +33 -0
  99. data/lib/kingsman/omniauth/config.rb +47 -0
  100. data/lib/kingsman/omniauth/url_helpers.rb +28 -0
  101. data/lib/kingsman/omniauth.rb +20 -0
  102. data/lib/kingsman/orm/active_record.rb +13 -0
  103. data/lib/kingsman/orm/mongoid.rb +8 -0
  104. data/lib/kingsman/orm.rb +37 -0
  105. data/lib/kingsman/parameter_filter.rb +44 -0
  106. data/lib/kingsman/parameter_sanitizer.rb +173 -0
  107. data/lib/kingsman/secret_key_finder.rb +27 -0
  108. data/lib/kingsman/strategies/authenticatable.rb +178 -0
  109. data/lib/kingsman/strategies/base.rb +22 -0
  110. data/lib/kingsman/strategies/database_authenticatable.rb +31 -0
  111. data/lib/kingsman/strategies/rememberable.rb +67 -0
  112. data/lib/kingsman/token_generator.rb +32 -0
  113. data/lib/kingsman/version.rb +1 -1
  114. data/lib/kingsman.rb +427 -3
  115. metadata +289 -11
@@ -0,0 +1,280 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "action_controller/metal"
4
+
5
+ module Kingsman
6
+ # Failure application that will be called every time :warden is thrown from
7
+ # any strategy or hook. It is responsible for redirecting the user to the sign
8
+ # in page based on current scope and mapping. If no scope is given, it
9
+ # redirects to the default_url.
10
+ class FailureApp < Jets::BareController
11
+ # include ActionController::UrlFor
12
+ # include ActionController::Redirecting
13
+
14
+ include Jets.application.routes.url_helpers
15
+ include Jets.application.routes.mounted_helpers
16
+
17
+ # include Kingsman::Controllers::StoreLocation
18
+
19
+ # delegate :flash, to: :request
20
+
21
+ def self.call(env)
22
+ @respond ||= action(:respond)
23
+ @respond.call(env)
24
+ end
25
+
26
+ # Try retrieving the URL options from the parent controller (usually
27
+ # ApplicationController). Instance methods are not supported at the moment,
28
+ # so only the class-level attribute is used.
29
+ def self.default_url_options(*args)
30
+ # if defined?(Kingsman.parent_controller.constantize)
31
+ # Kingsman.parent_controller.constantize.try(:default_url_options) || {}
32
+ # else
33
+ {}
34
+ # end
35
+ end
36
+
37
+ def respond
38
+ if http_auth?
39
+ http_auth
40
+ elsif warden_options[:recall]
41
+ recall
42
+ else
43
+ redirect
44
+ end
45
+ end
46
+
47
+ def http_auth
48
+ self.status = 401
49
+ self.headers["WWW-Authenticate"] = %(Basic realm=#{Kingsman.http_authentication_realm.inspect}) if http_auth_header?
50
+ self.content_type = request.format.to_s
51
+ self.response_body = http_auth_body
52
+ end
53
+
54
+ def recall
55
+ header_info = if relative_url_root?
56
+ base_path = Pathname.new(relative_url_root)
57
+ full_path = Pathname.new(attempted_path)
58
+
59
+ { "SCRIPT_NAME" => relative_url_root,
60
+ "PATH_INFO" => '/' + full_path.relative_path_from(base_path).to_s }
61
+ else
62
+ { "PATH_INFO" => attempted_path }
63
+ end
64
+
65
+ header_info.each do | var, value|
66
+ if request.respond_to?(:set_header)
67
+ request.set_header(var, value)
68
+ else
69
+ request.env[var] = value
70
+ end
71
+ end
72
+
73
+ flash.now[:alert] = i18n_message(:invalid) if is_flashing_format?
74
+ self.response = recall_app(warden_options[:recall]).call(request.env).tap { |response|
75
+ response[0] = Rack::Utils.status_code(
76
+ response[0].in?(300..399) ? Kingsman.responder.redirect_status : Kingsman.responder.error_status
77
+ )
78
+ }
79
+ end
80
+
81
+ def redirect
82
+ store_location!
83
+ if is_flashing_format?
84
+ if flash[:timedout] && flash[:alert]
85
+ flash.keep(:timedout)
86
+ flash.keep(:alert)
87
+ else
88
+ flash[:alert] = i18n_message
89
+ end
90
+ end
91
+ redirect_to redirect_url
92
+ end
93
+
94
+ protected
95
+
96
+ def i18n_options(options)
97
+ options
98
+ end
99
+
100
+ def i18n_message(default = nil)
101
+ message = warden_message || default || :unauthenticated
102
+
103
+ if message.is_a?(Symbol)
104
+ options = {}
105
+ options[:resource_name] = scope
106
+ options[:scope] = "kingsman.failure"
107
+ options[:default] = [message]
108
+ auth_keys = scope_class.authentication_keys
109
+ keys = (auth_keys.respond_to?(:keys) ? auth_keys.keys : auth_keys).map { |key| scope_class.human_attribute_name(key) }
110
+ options[:authentication_keys] = keys.join(I18n.translate(:"support.array.words_connector"))
111
+ options = i18n_options(options)
112
+
113
+ I18n.t(:"#{scope}.#{message}", **options)
114
+ else
115
+ message.to_s
116
+ end
117
+ end
118
+
119
+ def redirect_url
120
+ if warden_message == :timeout
121
+ flash[:timedout] = true if is_flashing_format?
122
+
123
+ path = if request.get?
124
+ attempted_path
125
+ else
126
+ request.referrer
127
+ end
128
+
129
+ path || scope_url
130
+ else
131
+ scope_url
132
+ end
133
+ end
134
+
135
+ def route(scope)
136
+ :"new_#{scope}_session_url"
137
+ end
138
+
139
+ def scope_url
140
+ opts = {}
141
+
142
+ # Initialize script_name with nil to prevent infinite loops in
143
+ # authenticated mounted engines in rails 4.2 and 5.0
144
+ opts[:script_name] = nil
145
+
146
+ route = route(scope)
147
+
148
+ opts[:format] = request_format unless skip_format?
149
+
150
+ router_name = Kingsman.mappings[scope].router_name || Kingsman.available_router_name
151
+ context = send(router_name)
152
+
153
+ if context.respond_to?(route)
154
+ context.send(route)
155
+ elsif respond_to?(:root_url)
156
+ root_url
157
+ else
158
+ "/"
159
+ end
160
+
161
+ # if relative_url_root?
162
+ # opts[:script_name] = relative_url_root
163
+ # end
164
+
165
+ # if context.respond_to?(route)
166
+ # context.send(route, opts)
167
+ # elsif respond_to?(:root_url)
168
+ # root_url(opts)
169
+ # else
170
+ # "/"
171
+ # end
172
+ end
173
+
174
+ def skip_format?
175
+ %w(html */* turbo_stream).include? request_format.to_s
176
+ end
177
+
178
+ # Choose whether we should respond in an HTTP authentication fashion,
179
+ # including 401 and optional headers.
180
+ #
181
+ # This method allows the user to explicitly disable HTTP authentication
182
+ # on AJAX requests in case they want to redirect on failures instead of
183
+ # handling the errors on their own. This is useful in case your AJAX API
184
+ # is the same as your public API and uses a format like JSON (so you
185
+ # cannot mark JSON as a navigational format).
186
+ def http_auth?
187
+ if request.xhr?
188
+ Kingsman.http_authenticatable_on_xhr
189
+ else
190
+ !(request_format && is_navigational_format?)
191
+ end
192
+ end
193
+
194
+ # It doesn't make sense to send authenticate headers in AJAX requests
195
+ # or if the user disabled them.
196
+ def http_auth_header?
197
+ scope_class.http_authenticatable && !request.xhr?
198
+ end
199
+
200
+ def http_auth_body
201
+ return i18n_message unless request_format
202
+ method = "to_#{request_format}"
203
+ if method == "to_xml"
204
+ { error: i18n_message }.to_xml(root: "errors")
205
+ elsif {}.respond_to?(method)
206
+ { error: i18n_message }.send(method)
207
+ else
208
+ i18n_message
209
+ end
210
+ end
211
+
212
+ def recall_app(app)
213
+ controller, action = app.split("#")
214
+ controller_name = ActiveSupport::Inflector.camelize(controller)
215
+ controller_klass = ActiveSupport::Inflector.constantize("#{controller_name}Controller")
216
+ controller_klass.action(action)
217
+ end
218
+
219
+ def warden
220
+ request.respond_to?(:get_header) ? request.get_header("warden") : request.env["warden"]
221
+ end
222
+
223
+ def warden_options
224
+ request.respond_to?(:get_header) ? request.get_header("warden.options") : request.env["warden.options"]
225
+ end
226
+
227
+ def warden_message
228
+ @message ||= warden.message || warden_options[:message]
229
+ end
230
+
231
+ def scope
232
+ @scope ||= warden_options[:scope] || Kingsman.default_scope
233
+ end
234
+
235
+ def scope_class
236
+ @scope_class ||= Kingsman.mappings[scope].to
237
+ end
238
+
239
+ def attempted_path
240
+ warden_options[:attempted_path]
241
+ end
242
+
243
+ # Stores requested URI to redirect the user after signing in. We can't use
244
+ # the scoped session provided by warden here, since the user is not
245
+ # authenticated yet, but we still need to store the URI based on scope, so
246
+ # different scopes would never use the same URI to redirect.
247
+ def store_location!
248
+ store_location_for(scope, attempted_path) if request.get? && !http_auth?
249
+ end
250
+
251
+ def is_navigational_format?
252
+ Kingsman.navigational_formats.include?(request_format)
253
+ end
254
+
255
+ # Check if flash messages should be emitted. Default is to do it on
256
+ # navigational formats
257
+ def is_flashing_format?
258
+ request.respond_to?(:flash) && is_navigational_format?
259
+ end
260
+
261
+ def request_format
262
+ @request_format ||= request.format.try(:ref)
263
+ end
264
+
265
+ def relative_url_root
266
+ nil
267
+ # @relative_url_root ||= begin
268
+ # config = Rails.application.config
269
+
270
+ # config.try(:relative_url_root) || config.action_controller.try(:relative_url_root)
271
+ # end
272
+ end
273
+
274
+ def relative_url_root?
275
+ relative_url_root.present?
276
+ end
277
+
278
+ ActiveSupport.run_load_hooks(:kingsman_failure_app, self)
279
+ end
280
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Deny user access whenever their account is not active yet.
4
+ # We need this as hook to validate the user activity on each request
5
+ # and in case the user is using other strategies beside Kingsman ones.
6
+ Warden::Manager.after_set_user do |record, warden, options|
7
+ if record && record.respond_to?(:active_for_authentication?) && !record.active_for_authentication?
8
+ scope = options[:scope]
9
+ warden.logout(scope)
10
+ throw :warden, scope: scope, message: record.inactive_message
11
+ end
12
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ Warden::Manager.after_authentication do |record, warden, options|
4
+ clean_up_for_winning_strategy = !warden.winning_strategy.respond_to?(:clean_up_csrf?) ||
5
+ warden.winning_strategy.clean_up_csrf?
6
+ if Kingsman.clean_up_csrf_token_on_authentication && clean_up_for_winning_strategy
7
+ if warden.request.respond_to?(:reset_csrf_token)
8
+ # Rails 7.1+
9
+ warden.request.reset_csrf_token
10
+ else
11
+ warden.request.session.try(:delete, :_csrf_token)
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Before logout hook to forget the user in the given scope, if it responds
4
+ # to forget_me! Also clear remember token to ensure the user won't be
5
+ # remembered again. Notice that we forget the user unless the record is not persisted.
6
+ # This avoids forgetting deleted users.
7
+ Warden::Manager.before_logout do |record, warden, options|
8
+ if record.respond_to?(:forget_me!)
9
+ Kingsman::Hooks::Proxy.new(warden).forget_me(record)
10
+ end
11
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ # After each sign in, if resource responds to failed_attempts, sets it to 0
4
+ # This is only triggered when the user is explicitly set (with set_user)
5
+ Warden::Manager.after_set_user except: :fetch do |record, warden, options|
6
+ if record.respond_to?(:reset_failed_attempts!) && warden.authenticated?(options[:scope])
7
+ record.reset_failed_attempts!
8
+ end
9
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kingsman
4
+ module Hooks
5
+ # A small warden proxy so we can remember, forget and
6
+ # sign out users from hooks.
7
+ class Proxy #:nodoc:
8
+ include Kingsman::Controllers::Rememberable
9
+ include Kingsman::Controllers::SignInOut
10
+
11
+ attr_reader :warden
12
+ delegate :cookies, :request, to: :warden
13
+
14
+ def initialize(warden)
15
+ @warden = warden
16
+ end
17
+
18
+ def session
19
+ warden.request.session
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ Warden::Manager.after_set_user except: :fetch do |record, warden, options|
4
+ scope = options[:scope]
5
+ if record.respond_to?(:remember_me) && options[:store] != false &&
6
+ record.remember_me && warden.authenticated?(scope)
7
+ Kingsman::Hooks::Proxy.new(warden).remember_me(record)
8
+ end
9
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Each time a record is set we check whether its session has already timed out
4
+ # or not, based on last request time. If so, the record is logged out and
5
+ # redirected to the sign in page. Also, each time the request comes and the
6
+ # record is set, we set the last request time inside its scoped session to
7
+ # verify timeout in the following request.
8
+ Warden::Manager.after_set_user do |record, warden, options|
9
+ scope = options[:scope]
10
+ env = warden.request.env
11
+
12
+ if record && record.respond_to?(:timedout?) && warden.authenticated?(scope) &&
13
+ options[:store] != false && !env['kingsman.skip_timeoutable']
14
+ last_request_at = warden.session(scope)['last_request_at']
15
+
16
+ if last_request_at.is_a? Integer
17
+ last_request_at = Time.at(last_request_at).utc
18
+ elsif last_request_at.is_a? String
19
+ last_request_at = Time.parse(last_request_at)
20
+ end
21
+
22
+ proxy = Kingsman::Hooks::Proxy.new(warden)
23
+
24
+ if !env['kingsman.skip_timeout'] &&
25
+ record.timedout?(last_request_at) &&
26
+ !proxy.remember_me_is_active?(record)
27
+ Kingsman.sign_out_all_scopes ? proxy.sign_out : proxy.sign_out(scope)
28
+ throw :warden, scope: scope, message: :timeout
29
+ end
30
+
31
+ unless env['kingsman.skip_trackable']
32
+ warden.session(scope)['last_request_at'] = Time.now.utc.to_i
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ # After each sign in, update sign in time, sign in count and sign in IP.
4
+ # This is only triggered when the user is explicitly set (with set_user)
5
+ # and on authentication. Retrieving the user from session (:fetch) does
6
+ # not trigger it.
7
+ Warden::Manager.after_set_user except: :fetch do |record, warden, options|
8
+ if record.respond_to?(:update_tracked_fields!) && warden.authenticated?(options[:scope]) && !warden.request.env['kingsman.skip_trackable']
9
+ record.update_tracked_fields!(warden.request)
10
+ end
11
+ end
@@ -0,0 +1,6 @@
1
+ module Kingsman
2
+ module Hooks
3
+ end
4
+ end
5
+
6
+ require "kingsman/hooks/proxy"
@@ -0,0 +1,195 @@
1
+ require "jets/router/dsl"
2
+
3
+ module Kingsman
4
+ module Router
5
+ def finalize!
6
+ result = super
7
+ Kingsman.configure_warden!
8
+ result
9
+ end
10
+ end
11
+ end
12
+
13
+ module Jets
14
+ module Router
15
+ class RouteSet
16
+ # Ensure Kingsman modules are included only after loading routes, because we
17
+ # need kingsman_for mappings already declared to create filters and helpers.
18
+ prepend Kingsman::Router
19
+ end
20
+ end
21
+ end
22
+
23
+ module Jets::Router
24
+ module Dsl
25
+ def kingsman_for(*resources)
26
+ options = resources.extract_options!
27
+
28
+ # options[:as] ||= @scope[:as] if @scope[:as].present?
29
+ # options[:module] ||= @scope[:module] if @scope[:module].present?
30
+ # options[:path_prefix] ||= @scope[:path] if @scope[:path].present?
31
+ # options[:path_names] = (@scope[:path_names] || {}).merge(options[:path_names] || {})
32
+ # options[:constraints] = (@scope[:constraints] || {}).merge(options[:constraints] || {})
33
+ # options[:defaults] = (@scope[:defaults] || {}).merge(options[:defaults] || {})
34
+ # options[:options] = @scope[:options] || {}
35
+ # options[:options][:format] = false if options[:format] == false
36
+
37
+ resources.map!(&:to_sym)
38
+
39
+ resources.each do |resource|
40
+ mapping = Kingsman.add_mapping(resource, options)
41
+
42
+ if options[:controllers] && options[:controllers][:omniauth_callbacks]
43
+ unless mapping.omniauthable?
44
+ raise ArgumentError, "Mapping omniauth_callbacks on a resource that is not omniauthable\n" \
45
+ "Please add `kingsman :omniauthable` to the `#{mapping.class_name}` model"
46
+ end
47
+ end
48
+
49
+ routes = mapping.used_routes
50
+
51
+ kingsman_scope mapping.name do
52
+ with_kingsman_exclusive_scope mapping.fullpath, mapping.name, options do
53
+ routes.each { |mod| send("kingsman_#{mod}", mapping, mapping.controllers) }
54
+ end
55
+ end
56
+ end
57
+ end
58
+
59
+ def kingsman_session(mapping, controllers) #:nodoc:
60
+ resource :session, only: [], controller: controllers[:sessions], path: "" do
61
+ get :new, path: mapping.path_names[:sign_in], as: "new"
62
+ post :create, path: mapping.path_names[:sign_in]
63
+ match :destroy, path: mapping.path_names[:sign_out], as: "destroy", via: mapping.sign_out_via
64
+ end
65
+ end
66
+
67
+ def kingsman_password(mapping, controllers) #:nodoc:
68
+ resource :password, only: [:new, :create, :edit, :update],
69
+ path: mapping.path_names[:password], controller: controllers[:passwords]
70
+ end
71
+
72
+ def kingsman_confirmation(mapping, controllers) #:nodoc:
73
+ resource :confirmation, only: [:new, :create, :show],
74
+ path: mapping.path_names[:confirmation], controller: controllers[:confirmations]
75
+ end
76
+
77
+ def kingsman_unlock(mapping, controllers) #:nodoc:
78
+ if mapping.to.unlock_strategy_enabled?(:email)
79
+ resource :unlock, only: [:new, :create, :show],
80
+ path: mapping.path_names[:unlock], controller: controllers[:unlocks]
81
+ end
82
+ end
83
+
84
+ def kingsman_registration(mapping, controllers) #:nodoc:
85
+ path_names = {
86
+ new: mapping.path_names[:sign_up],
87
+ edit: mapping.path_names[:edit],
88
+ cancel: mapping.path_names[:cancel]
89
+ }
90
+
91
+ options = {
92
+ only: [:new, :create, :edit, :update, :destroy],
93
+ path: mapping.path_names[:registration],
94
+ path_names: path_names,
95
+ controller: controllers[:registrations]
96
+ }
97
+
98
+ resource :registration, options do
99
+ get :cancel
100
+ end
101
+ end
102
+
103
+ def kingsman_omniauth_callback(mapping, controllers) #:nodoc:
104
+ if mapping.fullpath =~ /:[a-zA-Z_]/
105
+ raise <<-ERROR
106
+ Kingsman does not support scoping OmniAuth callbacks under a dynamic segment
107
+ and you have set #{mapping.fullpath.inspect}. You can work around by passing
108
+ `skip: :omniauth_callbacks` to the `kingsman_for` call and extract omniauth
109
+ options to another `kingsman_for` call outside the scope. Here is an example:
110
+
111
+ kingsman_for :users, only: :omniauth_callbacks, controllers: {omniauth_callbacks: 'users/omniauth_callbacks'}
112
+
113
+ scope '/(:locale)', locale: /ru|en/ do
114
+ kingsman_for :users, skip: :omniauth_callbacks
115
+ end
116
+ ERROR
117
+ end
118
+
119
+ current_scope = @scope.dup
120
+
121
+ # Jets routes are all very lazily evaluated. This is a problem for Kingsman
122
+ # because devise sets the scope[:path] to nil and expects the call to match
123
+ # to use it as it's evaluated. And then it restores it to the previous value.
124
+ # This is a problem for Jets because Jets routes are lazily evaluated and
125
+ # none of this will matter. Unsure how to handle at the moment.
126
+
127
+ if @scope.respond_to? :new
128
+ @scope = @scope.new path: nil
129
+ else
130
+ @scope[:path] = nil
131
+ end
132
+
133
+ path_prefix = Kingsman.omniauth_path_prefix || "/#{mapping.fullpath}/auth".squeeze("/")
134
+ set_omniauth_path_prefix!(path_prefix)
135
+ path_prefix = "/auth" # HACK
136
+
137
+ mapping.to.omniauth_providers.each do |provider|
138
+ match "#{path_prefix}/#{provider}",
139
+ to: "#{controllers[:omniauth_callbacks]}#passthru",
140
+ as: "#{provider}_omniauth_authorize",
141
+ via: OmniAuth.config.allowed_request_methods
142
+ # via: [:get, :post]
143
+
144
+ match "#{path_prefix}/#{provider}/callback",
145
+ to: "#{controllers[:omniauth_callbacks]}##{provider}",
146
+ as: "#{provider}_omniauth_callback",
147
+ via: [:get, :post]
148
+ end
149
+ ensure
150
+ @scope = current_scope
151
+ end
152
+
153
+ def set_omniauth_path_prefix!(path_prefix) #:nodoc:
154
+ if ::OmniAuth.config.path_prefix && ::OmniAuth.config.path_prefix != path_prefix
155
+ raise "Wrong OmniAuth configuration. If you are getting this exception, it means that either:\n\n" \
156
+ "1) You are manually setting OmniAuth.config.path_prefix and it doesn't match the Devise one\n" \
157
+ "2) You are setting :omniauthable in more than one model\n" \
158
+ "3) You changed your Devise routes/OmniAuth setting and haven't restarted your server"
159
+ else
160
+ ::OmniAuth.config.path_prefix = path_prefix
161
+ end
162
+ end
163
+
164
+
165
+ def kingsman_scope(scope)
166
+ constraint = lambda do |request|
167
+ request.env["kingsman.mapping"] = Kingsman.mappings[scope]
168
+ true
169
+ end
170
+
171
+ constraints(constraint) do
172
+ yield
173
+ end
174
+ end
175
+ alias :as :kingsman_scope
176
+
177
+ def with_kingsman_exclusive_scope(new_path, new_as, options) #:nodoc:
178
+ current_scope = @scope.dup
179
+
180
+ exclusive = { as: new_as, path: new_path, module: nil }
181
+ exclusive.merge!(options.slice(:constraints, :defaults, :options))
182
+
183
+ if @scope.respond_to? :new
184
+ @scope = @scope.new exclusive
185
+ else
186
+ exclusive.each_pair { |key, value| @scope[key] = value }
187
+ end
188
+
189
+ yield
190
+ ensure
191
+ @scope = current_scope
192
+ end
193
+
194
+ end
195
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Warden::Mixins::Common
4
+ def request
5
+ env['jets.controller'].request
6
+ end
7
+
8
+ def reset_session!
9
+ request.reset_session
10
+ end
11
+
12
+ def cookies
13
+ request.cookie_jar
14
+ end
15
+ end
@@ -0,0 +1,39 @@
1
+ require "kingsman/jets/routes"
2
+ require "kingsman/jets/warden_compat"
3
+
4
+ module Kingsman
5
+ class Engine < ::Jets::Engine
6
+ config.kingsman = ActiveSupport::OrderedOptions.new
7
+
8
+ config.app_middleware.use Warden::Manager do |manager|
9
+ Kingsman.warden_config = manager
10
+ end
11
+
12
+ initializer "kingsman.url_helpers" do
13
+ Kingsman.include_helpers(Kingsman::Controllers)
14
+ end
15
+
16
+ initializer "kingsman.omniauth", after: :load_config_initializers, before: :build_middleware_stack do |app|
17
+ Kingsman.omniauth_configs.each do |provider, config|
18
+ app.middleware.use config.strategy_class, *config.args do |strategy|
19
+ config.strategy = strategy
20
+ end
21
+ end
22
+
23
+ if Kingsman.omniauth_configs.any?
24
+ Kingsman.include_helpers(Kingsman::OmniAuth)
25
+ end
26
+ end
27
+
28
+ initializer "kingsman.secret_key" do |app|
29
+ Kingsman.secret_key ||= Kingsman::SecretKeyFinder.new(app).find
30
+
31
+ Kingsman.token_generator ||=
32
+ if secret_key = Kingsman.secret_key
33
+ Kingsman::TokenGenerator.new(
34
+ ActiveSupport::CachingKeyGenerator.new(ActiveSupport::KeyGenerator.new(secret_key))
35
+ )
36
+ end
37
+ end
38
+ end
39
+ end