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,31 @@
|
|
1
|
+
require "zeitwerk"
|
2
|
+
|
3
|
+
module Kingsman
|
4
|
+
class Autoloader
|
5
|
+
class Inflector < Zeitwerk::Inflector
|
6
|
+
def camelize(basename, _abspath)
|
7
|
+
map = { cli: "CLI", version: "VERSION", omniauth: "OmniAuth" }
|
8
|
+
map[basename.to_sym] || super
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
class << self
|
13
|
+
def setup
|
14
|
+
loader = Zeitwerk::Loader.new
|
15
|
+
loader.inflector = Inflector.new
|
16
|
+
lib = File.dirname(__dir__)
|
17
|
+
loader.push_dir(lib) # lib
|
18
|
+
loader.do_not_eager_load("#{lib}/generators")
|
19
|
+
loader.do_not_eager_load("#{lib}/kingsman/models/omniauthable.rb")
|
20
|
+
loader.ignore("#{lib}/kingsman/omniauth.rb")
|
21
|
+
loader.ignore("#{lib}/kingsman/hooks")
|
22
|
+
loader.ignore("#{lib}/kingsman/jets.rb")
|
23
|
+
loader.ignore("#{lib}/kingsman/jets")
|
24
|
+
loader.ignore("#{lib}/kingsman/modules.rb")
|
25
|
+
loader.ignore("#{lib}/kingsman/orm")
|
26
|
+
loader.ignore("#{lib}/kingsman/routes.rb")
|
27
|
+
loader.setup
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,221 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Kingsman
|
4
|
+
module Controllers
|
5
|
+
# Those helpers are convenience methods added to ApplicationController.
|
6
|
+
module Helpers
|
7
|
+
extend ActiveSupport::Concern
|
8
|
+
include Kingsman::Controllers::SignInOut
|
9
|
+
include Kingsman::Controllers::StoreLocation
|
10
|
+
|
11
|
+
included do
|
12
|
+
if respond_to?(:helper_method)
|
13
|
+
helper_method :warden, :signed_in?, :kingsman_controller?
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
# Define authentication filters and accessor helpers based on mappings.
|
18
|
+
# These filters should be used inside the controllers as before_actions,
|
19
|
+
# so you can control the scope of the user who should be signed in to
|
20
|
+
# access that specific controller/action.
|
21
|
+
# Example:
|
22
|
+
#
|
23
|
+
# Roles:
|
24
|
+
# User
|
25
|
+
# Admin
|
26
|
+
#
|
27
|
+
# Generated methods:
|
28
|
+
# authenticate_user! # Signs user in or redirect
|
29
|
+
# authenticate_admin! # Signs admin in or redirect
|
30
|
+
# user_signed_in? # Checks whether there is a user signed in or not
|
31
|
+
# admin_signed_in? # Checks whether there is an admin signed in or not
|
32
|
+
# current_user # Current signed in user
|
33
|
+
# current_admin # Current signed in admin
|
34
|
+
# user_session # Session data available only to the user scope
|
35
|
+
# admin_session # Session data available only to the admin scope
|
36
|
+
#
|
37
|
+
# Use:
|
38
|
+
# before_action :authenticate_user! # Tell kingsman to use :user map
|
39
|
+
# before_action :authenticate_admin! # Tell kingsman to use :admin map
|
40
|
+
#
|
41
|
+
def self.define_helpers(mapping) #:nodoc:
|
42
|
+
mapping = mapping.name
|
43
|
+
|
44
|
+
class_eval <<-METHODS, __FILE__, __LINE__ + 1
|
45
|
+
def authenticate_#{mapping}!(opts = {})
|
46
|
+
opts[:scope] = :#{mapping}
|
47
|
+
warden.authenticate!(opts) if !kingsman_controller? || opts.delete(:force)
|
48
|
+
end
|
49
|
+
|
50
|
+
def #{mapping}_signed_in?
|
51
|
+
!!current_#{mapping}
|
52
|
+
end
|
53
|
+
|
54
|
+
def current_#{mapping}
|
55
|
+
@current_#{mapping} ||= warden.authenticate(scope: :#{mapping})
|
56
|
+
end
|
57
|
+
|
58
|
+
def #{mapping}_session
|
59
|
+
current_#{mapping} && warden.session(:#{mapping})
|
60
|
+
end
|
61
|
+
METHODS
|
62
|
+
|
63
|
+
ActiveSupport.on_load(:jets_controller) do
|
64
|
+
if respond_to?(:helper_method)
|
65
|
+
helper_method "current_#{mapping}", "#{mapping}_signed_in?", "#{mapping}_session"
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# The main accessor for the warden proxy instance
|
71
|
+
def warden
|
72
|
+
request.env['warden'] or raise MissingWarden
|
73
|
+
end
|
74
|
+
|
75
|
+
# Return true if it's a kingsman_controller. false to all controllers unless
|
76
|
+
# the controllers defined inside kingsman. Useful if you want to apply a before
|
77
|
+
# filter to all controllers, except the ones in kingsman:
|
78
|
+
#
|
79
|
+
# before_action :my_filter, unless: :kingsman_controller?
|
80
|
+
def kingsman_controller?
|
81
|
+
is_a?(::KingsmanController)
|
82
|
+
end
|
83
|
+
|
84
|
+
# Set up a param sanitizer to filter parameters using strong_parameters. See
|
85
|
+
# lib/kingsman/parameter_sanitizer.rb for more info. Override this
|
86
|
+
# method in your application controller to use your own parameter sanitizer.
|
87
|
+
def kingsman_parameter_sanitizer
|
88
|
+
@kingsman_parameter_sanitizer ||= Kingsman::ParameterSanitizer.new(resource_class, resource_name, params)
|
89
|
+
end
|
90
|
+
|
91
|
+
# Tell warden that params authentication is allowed for that specific page.
|
92
|
+
def allow_params_authentication!
|
93
|
+
request.env["kingsman.allow_params_authentication"] = true
|
94
|
+
end
|
95
|
+
|
96
|
+
# The scope root url to be used when they're signed in. By default, it first
|
97
|
+
# tries to find a resource_root_path, otherwise it uses the root_path.
|
98
|
+
def signed_in_root_path(resource_or_scope)
|
99
|
+
scope = Kingsman::Mapping.find_scope!(resource_or_scope)
|
100
|
+
router_name = Kingsman.mappings[scope].router_name
|
101
|
+
|
102
|
+
home_path = "#{scope}_root_path"
|
103
|
+
|
104
|
+
context = router_name ? send(router_name) : self
|
105
|
+
if context.respond_to?(home_path, true)
|
106
|
+
context.send(home_path)
|
107
|
+
elsif context.respond_to?(:root_path)
|
108
|
+
context.root_path
|
109
|
+
elsif respond_to?(:root_path)
|
110
|
+
root_path
|
111
|
+
else
|
112
|
+
"/"
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
# The default url to be used after signing in. This is used by all Kingsman
|
117
|
+
# controllers and you can overwrite it in your ApplicationController to
|
118
|
+
# provide a custom hook for a custom resource.
|
119
|
+
#
|
120
|
+
# By default, it first tries to find a valid resource_return_to key in the
|
121
|
+
# session, then it fallbacks to resource_root_path, otherwise it uses the
|
122
|
+
# root path. For a user scope, you can define the default url in
|
123
|
+
# the following way:
|
124
|
+
#
|
125
|
+
# get '/users' => 'users#index', as: :user_root # creates user_root_path
|
126
|
+
#
|
127
|
+
# namespace :user do
|
128
|
+
# root 'users#index' # creates user_root_path
|
129
|
+
# end
|
130
|
+
#
|
131
|
+
# If the resource root path is not defined, root_path is used. However,
|
132
|
+
# if this default is not enough, you can customize it, for example:
|
133
|
+
#
|
134
|
+
# def after_sign_in_path_for(resource)
|
135
|
+
# stored_location_for(resource) ||
|
136
|
+
# if resource.is_a?(User) && resource.can_publish?
|
137
|
+
# publisher_url
|
138
|
+
# else
|
139
|
+
# super
|
140
|
+
# end
|
141
|
+
# end
|
142
|
+
#
|
143
|
+
def after_sign_in_path_for(resource_or_scope)
|
144
|
+
stored_location_for(resource_or_scope) || signed_in_root_path(resource_or_scope)
|
145
|
+
end
|
146
|
+
|
147
|
+
# Method used by sessions controller to sign out a user. You can overwrite
|
148
|
+
# it in your ApplicationController to provide a custom hook for a custom
|
149
|
+
# scope. Notice that differently from +after_sign_in_path_for+ this method
|
150
|
+
# receives a symbol with the scope, and not the resource.
|
151
|
+
#
|
152
|
+
# By default it is the root_path.
|
153
|
+
def after_sign_out_path_for(resource_or_scope)
|
154
|
+
scope = Kingsman::Mapping.find_scope!(resource_or_scope)
|
155
|
+
router_name = Kingsman.mappings[scope].router_name
|
156
|
+
context = router_name ? send(router_name) : self
|
157
|
+
context.respond_to?(:root_path) ? context.root_path : "/"
|
158
|
+
end
|
159
|
+
|
160
|
+
# Sign in a user and tries to redirect first to the stored location and
|
161
|
+
# then to the url specified by after_sign_in_path_for. It accepts the same
|
162
|
+
# parameters as the sign_in method.
|
163
|
+
def sign_in_and_redirect(resource_or_scope, *args)
|
164
|
+
options = args.extract_options!
|
165
|
+
scope = Kingsman::Mapping.find_scope!(resource_or_scope)
|
166
|
+
resource = args.last || resource_or_scope
|
167
|
+
sign_in(scope, resource, options)
|
168
|
+
redirect_to after_sign_in_path_for(resource)
|
169
|
+
end
|
170
|
+
|
171
|
+
# Sign out a user and tries to redirect to the url specified by
|
172
|
+
# after_sign_out_path_for.
|
173
|
+
def sign_out_and_redirect(resource_or_scope)
|
174
|
+
scope = Kingsman::Mapping.find_scope!(resource_or_scope)
|
175
|
+
redirect_path = after_sign_out_path_for(scope)
|
176
|
+
Kingsman.sign_out_all_scopes ? sign_out : sign_out(scope)
|
177
|
+
redirect_to redirect_path
|
178
|
+
end
|
179
|
+
|
180
|
+
# Overwrite Rails' handle unverified request to sign out all scopes,
|
181
|
+
# clear run strategies and remove cached variables.
|
182
|
+
def handle_unverified_request
|
183
|
+
super # call the default behavior which resets/nullifies/raises
|
184
|
+
request.env["kingsman.skip_storage"] = true
|
185
|
+
sign_out_all_scopes(false)
|
186
|
+
end
|
187
|
+
|
188
|
+
def request_format
|
189
|
+
@request_format ||= request.format.try(:ref)
|
190
|
+
end
|
191
|
+
|
192
|
+
def is_navigational_format?
|
193
|
+
Kingsman.navigational_formats.include?(request_format)
|
194
|
+
end
|
195
|
+
|
196
|
+
# Check if flash messages should be emitted. Default is to do it on
|
197
|
+
# navigational formats
|
198
|
+
def is_flashing_format?
|
199
|
+
request.respond_to?(:flash) && is_navigational_format?
|
200
|
+
end
|
201
|
+
|
202
|
+
private
|
203
|
+
|
204
|
+
def expire_data_after_sign_out!
|
205
|
+
Kingsman.mappings.each { |_,m| instance_variable_set("@current_#{m.name}", nil) }
|
206
|
+
super
|
207
|
+
end
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
class MissingWarden < StandardError
|
212
|
+
def initialize
|
213
|
+
super "Kingsman could not find the `Warden::Proxy` instance on your request environment.\n" + \
|
214
|
+
"Make sure that your application is loading Kingsman and Warden as expected and that " + \
|
215
|
+
"the `Warden::Manager` middleware is present in your middleware stack.\n" + \
|
216
|
+
"If you are seeing this on one of your tests, ensure that your tests are either " + \
|
217
|
+
"executing the Rails middleware stack or that your tests are using the `Kingsman::Test::ControllerHelpers` " + \
|
218
|
+
"module to inject the `request.env['warden']` object for you."
|
219
|
+
end
|
220
|
+
end
|
221
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Kingsman
|
4
|
+
module Controllers
|
5
|
+
# A module that may be optionally included in a controller in order
|
6
|
+
# to provide remember me behavior. Useful when signing in is done
|
7
|
+
# through a callback, like in OmniAuth.
|
8
|
+
module Rememberable
|
9
|
+
# Return default cookie values retrieved from session options.
|
10
|
+
def self.cookie_values
|
11
|
+
Jets.config.session_options.slice(:path, :domain, :secure)
|
12
|
+
end
|
13
|
+
|
14
|
+
def remember_me_is_active?(resource)
|
15
|
+
return false unless resource.respond_to?(:remember_me)
|
16
|
+
scope = Kingsman::Mapping.find_scope!(resource)
|
17
|
+
_, token, generated_at = cookies.signed[remember_key(resource, scope)]
|
18
|
+
resource.remember_me?(token, generated_at)
|
19
|
+
end
|
20
|
+
|
21
|
+
# Remembers the given resource by setting up a cookie
|
22
|
+
def remember_me(resource)
|
23
|
+
return if request.env["kingsman.skip_storage"]
|
24
|
+
scope = Kingsman::Mapping.find_scope!(resource)
|
25
|
+
resource.remember_me!
|
26
|
+
cookies.signed[remember_key(resource, scope)] = remember_cookie_values(resource)
|
27
|
+
end
|
28
|
+
|
29
|
+
# Forgets the given resource by deleting a cookie
|
30
|
+
def forget_me(resource)
|
31
|
+
scope = Kingsman::Mapping.find_scope!(resource)
|
32
|
+
resource.forget_me!
|
33
|
+
cookies.delete(remember_key(resource, scope))
|
34
|
+
# cookies.delete(remember_key(resource, scope), forget_cookie_values(resource))
|
35
|
+
end
|
36
|
+
|
37
|
+
protected
|
38
|
+
|
39
|
+
def forget_cookie_values(resource)
|
40
|
+
Kingsman::Controllers::Rememberable.cookie_values.merge!(resource.rememberable_options)
|
41
|
+
end
|
42
|
+
|
43
|
+
def remember_cookie_values(resource)
|
44
|
+
options = { httponly: true }
|
45
|
+
options.merge!(forget_cookie_values(resource))
|
46
|
+
options.merge!(
|
47
|
+
value: resource.class.serialize_into_cookie(resource),
|
48
|
+
expires: resource.remember_expires_at
|
49
|
+
)
|
50
|
+
end
|
51
|
+
|
52
|
+
def remember_key(resource, scope)
|
53
|
+
resource.rememberable_options.fetch(:key, "remember_#{scope}_token")
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Kingsman
|
4
|
+
module Controllers
|
5
|
+
# Custom Responder to configure default statuses that only apply to Kingsman,
|
6
|
+
# and allow to integrate more easily with Hotwire/Turbo.
|
7
|
+
class Responder < Jets::Controller::Responder
|
8
|
+
if respond_to?(:error_status=) && respond_to?(:redirect_status=)
|
9
|
+
self.error_status = :ok
|
10
|
+
self.redirect_status = :found
|
11
|
+
else
|
12
|
+
# TODO: remove this support for older Rails versions, which aren't supported by Turbo
|
13
|
+
# and/or responders. It won't allow configuring a custom response, but it allows Kingsman
|
14
|
+
# to use these methods and defaults across the implementation more easily.
|
15
|
+
def self.error_status
|
16
|
+
:ok
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.redirect_status
|
20
|
+
:found
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.error_status=(*)
|
24
|
+
warn "[KINGSMAN] Setting the error status on the Kingsman responder has no effect with this " \
|
25
|
+
"version of `responders`, please make sure you're using a newer version. Check the changelog for more info."
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.redirect_status=(*)
|
29
|
+
warn "[KINGSMAN] Setting the redirect status on the Kingsman responder has no effect with this " \
|
30
|
+
"version of `responders`, please make sure you're using a newer version. Check the changelog for more info."
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Kingsman
|
4
|
+
module Controllers
|
5
|
+
module ScopedViews
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
module ClassMethods
|
9
|
+
def scoped_views?
|
10
|
+
defined?(@scoped_views) ? @scoped_views : Kingsman.scoped_views
|
11
|
+
end
|
12
|
+
|
13
|
+
def scoped_views=(value)
|
14
|
+
@scoped_views = value
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Kingsman
|
4
|
+
module Controllers
|
5
|
+
# Provide sign in and sign out functionality.
|
6
|
+
# Included by default in all controllers.
|
7
|
+
module SignInOut
|
8
|
+
# Return true if the given scope is signed in session. If no scope given, return
|
9
|
+
# true if any scope is signed in. This will run authentication hooks, which may
|
10
|
+
# cause exceptions to be thrown from this method; if you simply want to check
|
11
|
+
# if a scope has already previously been authenticated without running
|
12
|
+
# authentication hooks, you can directly call `warden.authenticated?(scope: scope)`
|
13
|
+
def signed_in?(scope = nil)
|
14
|
+
[scope || Kingsman.mappings.keys].flatten.any? do |_scope|
|
15
|
+
warden.authenticate?(scope: _scope)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
# Sign in a user that already was authenticated. This helper is useful for logging
|
20
|
+
# users in after sign up. All options given to sign_in is passed forward
|
21
|
+
# to the set_user method in warden.
|
22
|
+
# If you are using a custom warden strategy and the timeoutable module, you have to
|
23
|
+
# set `env["kingsman.skip_timeout"] = true` in the request to use this method, like we do
|
24
|
+
# in the sessions controller: https://github.com/heartcombo/kingsman/blob/main/app/controllers/kingsman/sessions_controller.rb#L7
|
25
|
+
#
|
26
|
+
# Examples:
|
27
|
+
#
|
28
|
+
# sign_in :user, @user # sign_in(scope, resource)
|
29
|
+
# sign_in @user # sign_in(resource)
|
30
|
+
# sign_in @user, event: :authentication # sign_in(resource, options)
|
31
|
+
# sign_in @user, store: false # sign_in(resource, options)
|
32
|
+
#
|
33
|
+
def sign_in(resource_or_scope, *args)
|
34
|
+
options = args.extract_options!
|
35
|
+
scope = Kingsman::Mapping.find_scope!(resource_or_scope)
|
36
|
+
resource = args.last || resource_or_scope
|
37
|
+
|
38
|
+
expire_data_after_sign_in!
|
39
|
+
|
40
|
+
if warden.user(scope) == resource && !options.delete(:force)
|
41
|
+
# Do nothing. User already signed in and we are not forcing it.
|
42
|
+
true
|
43
|
+
else
|
44
|
+
warden.set_user(resource, options.merge!(scope: scope))
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# Sign in a user bypassing the warden callbacks and stores the user
|
49
|
+
# straight in session. This option is useful in cases the user is already
|
50
|
+
# signed in, but we want to refresh the credentials in session.
|
51
|
+
#
|
52
|
+
# Examples:
|
53
|
+
#
|
54
|
+
# bypass_sign_in @user, scope: :user
|
55
|
+
# bypass_sign_in @user
|
56
|
+
def bypass_sign_in(resource, scope: nil)
|
57
|
+
scope ||= Kingsman::Mapping.find_scope!(resource)
|
58
|
+
expire_data_after_sign_in!
|
59
|
+
warden.session_serializer.store(resource, scope)
|
60
|
+
end
|
61
|
+
|
62
|
+
# Sign out a given user or scope. This helper is useful for signing out a user
|
63
|
+
# after deleting accounts. Returns true if there was a logout and false if there
|
64
|
+
# is no user logged in on the referred scope
|
65
|
+
#
|
66
|
+
# Examples:
|
67
|
+
#
|
68
|
+
# sign_out :user # sign_out(scope)
|
69
|
+
# sign_out @user # sign_out(resource)
|
70
|
+
#
|
71
|
+
def sign_out(resource_or_scope = nil)
|
72
|
+
return sign_out_all_scopes unless resource_or_scope
|
73
|
+
scope = Kingsman::Mapping.find_scope!(resource_or_scope)
|
74
|
+
user = warden.user(scope: scope, run_callbacks: false) # If there is no user
|
75
|
+
|
76
|
+
warden.logout(scope)
|
77
|
+
warden.clear_strategies_cache!(scope: scope)
|
78
|
+
instance_variable_set(:"@current_#{scope}", nil)
|
79
|
+
|
80
|
+
!!user
|
81
|
+
end
|
82
|
+
|
83
|
+
# Sign out all active users or scopes. This helper is useful for signing out all roles
|
84
|
+
# in one click. This signs out ALL scopes in warden. Returns true if there was at least one logout
|
85
|
+
# and false if there was no user logged in on all scopes.
|
86
|
+
def sign_out_all_scopes(lock = true)
|
87
|
+
users = Kingsman.mappings.keys.map { |s| warden.user(scope: s, run_callbacks: false) }
|
88
|
+
|
89
|
+
warden.logout
|
90
|
+
expire_data_after_sign_out!
|
91
|
+
warden.clear_strategies_cache!
|
92
|
+
warden.lock! if lock
|
93
|
+
|
94
|
+
users.any?
|
95
|
+
end
|
96
|
+
|
97
|
+
private
|
98
|
+
|
99
|
+
def expire_data_after_sign_in!
|
100
|
+
# TODO: remove once Rails 5.2+ and forward are only supported.
|
101
|
+
# session.keys will return an empty array if the session is not yet loaded.
|
102
|
+
# This is a bug in both Rack and Rails.
|
103
|
+
# A call to #empty? forces the session to be loaded.
|
104
|
+
session.empty?
|
105
|
+
|
106
|
+
session.keys.grep(/^kingsman\./).each { |k| session.delete(k) }
|
107
|
+
end
|
108
|
+
|
109
|
+
alias :expire_data_after_sign_out! :expire_data_after_sign_in!
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "uri"
|
4
|
+
|
5
|
+
module Kingsman
|
6
|
+
module Controllers
|
7
|
+
# Provide the ability to store a location.
|
8
|
+
# Used to redirect back to a desired path after sign in.
|
9
|
+
# Included by default in all controllers.
|
10
|
+
module StoreLocation
|
11
|
+
# Returns and delete (if it's navigational format) the url stored in the session for
|
12
|
+
# the given scope. Useful for giving redirect backs after sign up:
|
13
|
+
#
|
14
|
+
# Example:
|
15
|
+
#
|
16
|
+
# redirect_to stored_location_for(:user) || root_path
|
17
|
+
#
|
18
|
+
def stored_location_for(resource_or_scope)
|
19
|
+
session_key = stored_location_key_for(resource_or_scope)
|
20
|
+
|
21
|
+
if is_navigational_format?
|
22
|
+
session.delete(session_key)
|
23
|
+
else
|
24
|
+
session[session_key]
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# Stores the provided location to redirect the user after signing in.
|
29
|
+
# Useful in combination with the `stored_location_for` helper.
|
30
|
+
#
|
31
|
+
# Example:
|
32
|
+
#
|
33
|
+
# store_location_for(:user, dashboard_path)
|
34
|
+
# redirect_to user_facebook_omniauth_authorize_path
|
35
|
+
#
|
36
|
+
def store_location_for(resource_or_scope, location)
|
37
|
+
session_key = stored_location_key_for(resource_or_scope)
|
38
|
+
|
39
|
+
path = extract_path_from_location(location)
|
40
|
+
session[session_key] = path if path
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def parse_uri(location)
|
46
|
+
location && URI.parse(location)
|
47
|
+
rescue URI::InvalidURIError
|
48
|
+
nil
|
49
|
+
end
|
50
|
+
|
51
|
+
def stored_location_key_for(resource_or_scope)
|
52
|
+
scope = Kingsman::Mapping.find_scope!(resource_or_scope)
|
53
|
+
"#{scope}_return_to"
|
54
|
+
end
|
55
|
+
|
56
|
+
def extract_path_from_location(location)
|
57
|
+
uri = parse_uri(location)
|
58
|
+
|
59
|
+
if uri
|
60
|
+
path = remove_domain_from_uri(uri)
|
61
|
+
path = add_fragment_back_to_path(uri, path)
|
62
|
+
|
63
|
+
path
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def remove_domain_from_uri(uri)
|
68
|
+
[uri.path.sub(/\A\/+/, '/'), uri.query].compact.join('?')
|
69
|
+
end
|
70
|
+
|
71
|
+
def add_fragment_back_to_path(uri, path)
|
72
|
+
[path, uri.fragment].compact.join('#')
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Kingsman
|
4
|
+
module Controllers
|
5
|
+
# Create url helpers to be used with resource/scope configuration. Acts as
|
6
|
+
# proxies to the generated routes created by kingsman.
|
7
|
+
# Resource param can be a string or symbol, a class, or an instance object.
|
8
|
+
# Example using a :user resource:
|
9
|
+
#
|
10
|
+
# new_session_path(:user) => new_user_session_path
|
11
|
+
# session_path(:user) => user_session_path
|
12
|
+
# destroy_session_path(:user) => destroy_user_session_path
|
13
|
+
#
|
14
|
+
# new_password_path(:user) => new_user_password_path
|
15
|
+
# password_path(:user) => user_password_path
|
16
|
+
# edit_password_path(:user) => edit_user_password_path
|
17
|
+
#
|
18
|
+
# new_confirmation_path(:user) => new_user_confirmation_path
|
19
|
+
# confirmation_path(:user) => user_confirmation_path
|
20
|
+
#
|
21
|
+
# Those helpers are included by default to ActionController::Base.
|
22
|
+
#
|
23
|
+
# Keeping interesting note about how Rails routes work.
|
24
|
+
#
|
25
|
+
# In case you want to add such helpers to another class, you can do
|
26
|
+
# that as long as this new class includes both url_helpers and
|
27
|
+
# mounted_helpers. Example:
|
28
|
+
#
|
29
|
+
# include Rails.application.routes.url_helpers
|
30
|
+
# include Rails.application.routes.mounted_helpers
|
31
|
+
#
|
32
|
+
module UrlHelpers
|
33
|
+
def self.remove_helpers!
|
34
|
+
self.instance_methods.map(&:to_s).grep(/_(url|path)$/).each do |method|
|
35
|
+
remove_method method
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.generate_helpers!(routes = nil)
|
40
|
+
routes ||= begin
|
41
|
+
mappings = Kingsman.mappings.values.map(&:used_helpers).flatten.uniq
|
42
|
+
Kingsman::URL_HELPERS.slice(*mappings)
|
43
|
+
end
|
44
|
+
|
45
|
+
routes.each do |module_name, actions|
|
46
|
+
[:path, :url].each do |path_or_url|
|
47
|
+
actions.each do |action|
|
48
|
+
action = action ? "#{action}_" : ""
|
49
|
+
method = :"#{action}#{module_name}_#{path_or_url}"
|
50
|
+
|
51
|
+
define_method method do |resource_or_scope, *args|
|
52
|
+
scope = Kingsman::Mapping.find_scope!(resource_or_scope)
|
53
|
+
router_name = Kingsman.mappings[scope].router_name
|
54
|
+
context = router_name ? send(router_name) : _kingsman_route_context
|
55
|
+
method_name = "#{action}#{scope}_#{module_name}_#{path_or_url}"
|
56
|
+
context.send(method_name, *args)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
generate_helpers!(Kingsman::URL_HELPERS)
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
def _kingsman_route_context
|
68
|
+
@_kingsman_route_context ||= send(Kingsman.available_router_name)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Kingsman
|
4
|
+
# Checks the scope in the given environment and returns the associated failure app.
|
5
|
+
class Delegator
|
6
|
+
def call(env)
|
7
|
+
failure_app(env).call(env)
|
8
|
+
end
|
9
|
+
|
10
|
+
def failure_app(env)
|
11
|
+
app = env["warden.options"] &&
|
12
|
+
(scope = env["warden.options"][:scope]) &&
|
13
|
+
Kingsman.mappings[scope.to_sym].failure_app
|
14
|
+
|
15
|
+
app || Kingsman::FailureApp
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bcrypt'
|
4
|
+
|
5
|
+
module Kingsman
|
6
|
+
module Encryptor
|
7
|
+
def self.digest(password)
|
8
|
+
stretches = 12
|
9
|
+
::BCrypt::Password.create(password, cost: stretches).to_s
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.compare(hashed_password, password)
|
13
|
+
return false if hashed_password.blank?
|
14
|
+
bcrypt = ::BCrypt::Password.new(hashed_password)
|
15
|
+
password = ::BCrypt::Engine.hash_secret(password, bcrypt.salt)
|
16
|
+
Kingsman.secure_compare(password, hashed_password)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|