kingsman 0.0.0.beta → 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +10 -0
- data/README.md +5 -4
- data/app/controllers/kingsman/confirmations_controller.rb +54 -0
- data/app/controllers/kingsman/omniauth_callbacks_controller.rb +36 -0
- data/app/controllers/kingsman/passwords_controller.rb +83 -0
- data/app/controllers/kingsman/registrations_controller.rb +168 -0
- data/app/controllers/kingsman/sessions_controller.rb +83 -0
- data/app/controllers/kingsman/unlocks_controller.rb +52 -0
- data/app/controllers/kingsman_controller.rb +252 -0
- data/app/jobs/application_job.rb +2 -0
- data/app/mailers/kingsman/mailer.rb +30 -0
- data/app/views/kingsman/confirmations/new.html.erb +16 -0
- data/app/views/kingsman/home/index.html.erb +1 -0
- data/app/views/kingsman/mailer/confirmation_instructions.html.erb +5 -0
- data/app/views/kingsman/mailer/email_changed.html.erb +7 -0
- data/app/views/kingsman/mailer/password_change.html.erb +3 -0
- data/app/views/kingsman/mailer/reset_password_instructions.html.erb +8 -0
- data/app/views/kingsman/mailer/unlock_instructions.html.erb +7 -0
- data/app/views/kingsman/passwords/edit.html.erb +25 -0
- data/app/views/kingsman/passwords/new.html.erb +16 -0
- data/app/views/kingsman/registrations/edit.html.erb +42 -0
- data/app/views/kingsman/registrations/new.html.erb +29 -0
- data/app/views/kingsman/sessions/new.html.erb +26 -0
- data/app/views/kingsman/shared/_error_messages.html.erb +15 -0
- data/app/views/kingsman/shared/_links.html.erb +25 -0
- data/app/views/kingsman/unlocks/new.html.erb +16 -0
- data/app/views/kingsman/up/index.html.erb +11 -0
- data/config/application.rb +0 -0
- data/config/locales/en.yml +63 -0
- data/config.ru +6 -0
- data/lib/generators/active_record/kingsman_generator.rb +127 -0
- data/lib/generators/active_record/templates/migration.rb +20 -0
- data/lib/generators/active_record/templates/migration_existing.rb +27 -0
- data/lib/generators/kingsman/controllers_generator.rb +46 -0
- data/lib/generators/kingsman/install_generator.rb +42 -0
- data/lib/generators/kingsman/kingsman_generator.rb +28 -0
- data/lib/generators/kingsman/orm_helpers.rb +40 -0
- data/lib/generators/kingsman/views_generator.rb +145 -0
- data/lib/generators/mongoid/kingsman_generator.rb +57 -0
- data/lib/generators/templates/README +36 -0
- data/lib/generators/templates/controllers/README +14 -0
- data/lib/generators/templates/controllers/confirmations_controller.rb +30 -0
- data/lib/generators/templates/controllers/omniauth_callbacks_controller.rb +30 -0
- data/lib/generators/templates/controllers/passwords_controller.rb +34 -0
- data/lib/generators/templates/controllers/registrations_controller.rb +62 -0
- data/lib/generators/templates/controllers/sessions_controller.rb +27 -0
- data/lib/generators/templates/controllers/unlocks_controller.rb +30 -0
- data/lib/generators/templates/kingsman.rb +313 -0
- data/lib/generators/templates/markerb/confirmation_instructions.markerb +5 -0
- data/lib/generators/templates/markerb/email_changed.markerb +7 -0
- data/lib/generators/templates/markerb/password_change.markerb +3 -0
- data/lib/generators/templates/markerb/reset_password_instructions.markerb +8 -0
- data/lib/generators/templates/markerb/unlock_instructions.markerb +7 -0
- data/lib/generators/templates/simple_form_for/confirmations/new.html.erb +20 -0
- data/lib/generators/templates/simple_form_for/passwords/edit.html.erb +27 -0
- data/lib/generators/templates/simple_form_for/passwords/new.html.erb +18 -0
- data/lib/generators/templates/simple_form_for/registrations/edit.html.erb +35 -0
- data/lib/generators/templates/simple_form_for/registrations/new.html.erb +25 -0
- data/lib/generators/templates/simple_form_for/sessions/new.html.erb +20 -0
- data/lib/generators/templates/simple_form_for/unlocks/new.html.erb +19 -0
- data/lib/kingsman/autoloader.rb +31 -0
- data/lib/kingsman/controllers/helpers.rb +221 -0
- data/lib/kingsman/controllers/rememberable.rb +57 -0
- data/lib/kingsman/controllers/responder.rb +35 -0
- data/lib/kingsman/controllers/scoped_views.rb +19 -0
- data/lib/kingsman/controllers/sign_in_out.rb +112 -0
- data/lib/kingsman/controllers/store_location.rb +76 -0
- data/lib/kingsman/controllers/url_helpers.rb +72 -0
- data/lib/kingsman/delegator.rb +18 -0
- data/lib/kingsman/encryptor.rb +19 -0
- data/lib/kingsman/failure_app.rb +280 -0
- data/lib/kingsman/hooks/activatable.rb +12 -0
- data/lib/kingsman/hooks/csrf_cleaner.rb +14 -0
- data/lib/kingsman/hooks/forgetable.rb +11 -0
- data/lib/kingsman/hooks/lockable.rb +9 -0
- data/lib/kingsman/hooks/proxy.rb +23 -0
- data/lib/kingsman/hooks/rememberable.rb +9 -0
- data/lib/kingsman/hooks/timeoutable.rb +35 -0
- data/lib/kingsman/hooks/trackable.rb +11 -0
- data/lib/kingsman/hooks.rb +6 -0
- data/lib/kingsman/jets/routes.rb +195 -0
- data/lib/kingsman/jets/warden_compat.rb +15 -0
- data/lib/kingsman/jets.rb +39 -0
- data/lib/kingsman/mailers/helpers.rb +93 -0
- data/lib/kingsman/mapping.rb +148 -0
- data/lib/kingsman/models/authenticatable.rb +310 -0
- data/lib/kingsman/models/confirmable.rb +369 -0
- data/lib/kingsman/models/database_authenticatable.rb +206 -0
- data/lib/kingsman/models/lockable.rb +214 -0
- data/lib/kingsman/models/omniauthable.rb +29 -0
- data/lib/kingsman/models/recoverable.rb +156 -0
- data/lib/kingsman/models/registerable.rb +29 -0
- data/lib/kingsman/models/rememberable.rb +158 -0
- data/lib/kingsman/models/timeoutable.rb +45 -0
- data/lib/kingsman/models/trackable.rb +51 -0
- data/lib/kingsman/models/validatable.rb +68 -0
- data/lib/kingsman/models.rb +122 -0
- data/lib/kingsman/modules.rb +33 -0
- data/lib/kingsman/omniauth/config.rb +47 -0
- data/lib/kingsman/omniauth/url_helpers.rb +28 -0
- data/lib/kingsman/omniauth.rb +20 -0
- data/lib/kingsman/orm/active_record.rb +13 -0
- data/lib/kingsman/orm/mongoid.rb +8 -0
- data/lib/kingsman/orm.rb +37 -0
- data/lib/kingsman/parameter_filter.rb +44 -0
- data/lib/kingsman/parameter_sanitizer.rb +173 -0
- data/lib/kingsman/secret_key_finder.rb +27 -0
- data/lib/kingsman/strategies/authenticatable.rb +178 -0
- data/lib/kingsman/strategies/base.rb +22 -0
- data/lib/kingsman/strategies/database_authenticatable.rb +31 -0
- data/lib/kingsman/strategies/rememberable.rb +67 -0
- data/lib/kingsman/token_generator.rb +32 -0
- data/lib/kingsman/version.rb +1 -1
- data/lib/kingsman.rb +427 -3
- metadata +304 -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,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,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
|