kingsman 0.0.0.beta → 0.1.1

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 (116) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +10 -0
  3. data/README.md +5 -4
  4. data/app/controllers/kingsman/confirmations_controller.rb +54 -0
  5. data/app/controllers/kingsman/omniauth_callbacks_controller.rb +36 -0
  6. data/app/controllers/kingsman/passwords_controller.rb +83 -0
  7. data/app/controllers/kingsman/registrations_controller.rb +168 -0
  8. data/app/controllers/kingsman/sessions_controller.rb +83 -0
  9. data/app/controllers/kingsman/unlocks_controller.rb +52 -0
  10. data/app/controllers/kingsman_controller.rb +252 -0
  11. data/app/jobs/application_job.rb +2 -0
  12. data/app/mailers/kingsman/mailer.rb +30 -0
  13. data/app/views/kingsman/confirmations/new.html.erb +16 -0
  14. data/app/views/kingsman/home/index.html.erb +1 -0
  15. data/app/views/kingsman/mailer/confirmation_instructions.html.erb +5 -0
  16. data/app/views/kingsman/mailer/email_changed.html.erb +7 -0
  17. data/app/views/kingsman/mailer/password_change.html.erb +3 -0
  18. data/app/views/kingsman/mailer/reset_password_instructions.html.erb +8 -0
  19. data/app/views/kingsman/mailer/unlock_instructions.html.erb +7 -0
  20. data/app/views/kingsman/passwords/edit.html.erb +25 -0
  21. data/app/views/kingsman/passwords/new.html.erb +16 -0
  22. data/app/views/kingsman/registrations/edit.html.erb +42 -0
  23. data/app/views/kingsman/registrations/new.html.erb +29 -0
  24. data/app/views/kingsman/sessions/new.html.erb +26 -0
  25. data/app/views/kingsman/shared/_error_messages.html.erb +15 -0
  26. data/app/views/kingsman/shared/_links.html.erb +25 -0
  27. data/app/views/kingsman/unlocks/new.html.erb +16 -0
  28. data/app/views/kingsman/up/index.html.erb +11 -0
  29. data/config/application.rb +0 -0
  30. data/config/locales/en.yml +63 -0
  31. data/config.ru +6 -0
  32. data/lib/generators/active_record/kingsman_generator.rb +127 -0
  33. data/lib/generators/active_record/templates/migration.rb +20 -0
  34. data/lib/generators/active_record/templates/migration_existing.rb +27 -0
  35. data/lib/generators/kingsman/controllers_generator.rb +46 -0
  36. data/lib/generators/kingsman/install_generator.rb +42 -0
  37. data/lib/generators/kingsman/kingsman_generator.rb +28 -0
  38. data/lib/generators/kingsman/orm_helpers.rb +40 -0
  39. data/lib/generators/kingsman/views_generator.rb +145 -0
  40. data/lib/generators/mongoid/kingsman_generator.rb +57 -0
  41. data/lib/generators/templates/README +36 -0
  42. data/lib/generators/templates/controllers/README +14 -0
  43. data/lib/generators/templates/controllers/confirmations_controller.rb +30 -0
  44. data/lib/generators/templates/controllers/omniauth_callbacks_controller.rb +30 -0
  45. data/lib/generators/templates/controllers/passwords_controller.rb +34 -0
  46. data/lib/generators/templates/controllers/registrations_controller.rb +62 -0
  47. data/lib/generators/templates/controllers/sessions_controller.rb +27 -0
  48. data/lib/generators/templates/controllers/unlocks_controller.rb +30 -0
  49. data/lib/generators/templates/kingsman.rb +313 -0
  50. data/lib/generators/templates/markerb/confirmation_instructions.markerb +5 -0
  51. data/lib/generators/templates/markerb/email_changed.markerb +7 -0
  52. data/lib/generators/templates/markerb/password_change.markerb +3 -0
  53. data/lib/generators/templates/markerb/reset_password_instructions.markerb +8 -0
  54. data/lib/generators/templates/markerb/unlock_instructions.markerb +7 -0
  55. data/lib/generators/templates/simple_form_for/confirmations/new.html.erb +20 -0
  56. data/lib/generators/templates/simple_form_for/passwords/edit.html.erb +27 -0
  57. data/lib/generators/templates/simple_form_for/passwords/new.html.erb +18 -0
  58. data/lib/generators/templates/simple_form_for/registrations/edit.html.erb +35 -0
  59. data/lib/generators/templates/simple_form_for/registrations/new.html.erb +25 -0
  60. data/lib/generators/templates/simple_form_for/sessions/new.html.erb +20 -0
  61. data/lib/generators/templates/simple_form_for/unlocks/new.html.erb +19 -0
  62. data/lib/kingsman/autoloader.rb +31 -0
  63. data/lib/kingsman/controllers/helpers.rb +221 -0
  64. data/lib/kingsman/controllers/rememberable.rb +57 -0
  65. data/lib/kingsman/controllers/responder.rb +35 -0
  66. data/lib/kingsman/controllers/scoped_views.rb +19 -0
  67. data/lib/kingsman/controllers/sign_in_out.rb +112 -0
  68. data/lib/kingsman/controllers/store_location.rb +76 -0
  69. data/lib/kingsman/controllers/url_helpers.rb +72 -0
  70. data/lib/kingsman/delegator.rb +18 -0
  71. data/lib/kingsman/encryptor.rb +19 -0
  72. data/lib/kingsman/failure_app.rb +280 -0
  73. data/lib/kingsman/hooks/activatable.rb +12 -0
  74. data/lib/kingsman/hooks/csrf_cleaner.rb +14 -0
  75. data/lib/kingsman/hooks/forgetable.rb +11 -0
  76. data/lib/kingsman/hooks/lockable.rb +9 -0
  77. data/lib/kingsman/hooks/proxy.rb +23 -0
  78. data/lib/kingsman/hooks/rememberable.rb +9 -0
  79. data/lib/kingsman/hooks/timeoutable.rb +35 -0
  80. data/lib/kingsman/hooks/trackable.rb +11 -0
  81. data/lib/kingsman/hooks.rb +6 -0
  82. data/lib/kingsman/jets/routes.rb +195 -0
  83. data/lib/kingsman/jets/warden_compat.rb +15 -0
  84. data/lib/kingsman/jets.rb +39 -0
  85. data/lib/kingsman/mailers/helpers.rb +93 -0
  86. data/lib/kingsman/mapping.rb +148 -0
  87. data/lib/kingsman/models/authenticatable.rb +310 -0
  88. data/lib/kingsman/models/confirmable.rb +369 -0
  89. data/lib/kingsman/models/database_authenticatable.rb +206 -0
  90. data/lib/kingsman/models/lockable.rb +214 -0
  91. data/lib/kingsman/models/omniauthable.rb +29 -0
  92. data/lib/kingsman/models/recoverable.rb +156 -0
  93. data/lib/kingsman/models/registerable.rb +29 -0
  94. data/lib/kingsman/models/rememberable.rb +158 -0
  95. data/lib/kingsman/models/timeoutable.rb +45 -0
  96. data/lib/kingsman/models/trackable.rb +51 -0
  97. data/lib/kingsman/models/validatable.rb +68 -0
  98. data/lib/kingsman/models.rb +122 -0
  99. data/lib/kingsman/modules.rb +33 -0
  100. data/lib/kingsman/omniauth/config.rb +47 -0
  101. data/lib/kingsman/omniauth/url_helpers.rb +28 -0
  102. data/lib/kingsman/omniauth.rb +20 -0
  103. data/lib/kingsman/orm/active_record.rb +13 -0
  104. data/lib/kingsman/orm/mongoid.rb +8 -0
  105. data/lib/kingsman/orm.rb +37 -0
  106. data/lib/kingsman/parameter_filter.rb +44 -0
  107. data/lib/kingsman/parameter_sanitizer.rb +173 -0
  108. data/lib/kingsman/secret_key_finder.rb +27 -0
  109. data/lib/kingsman/strategies/authenticatable.rb +178 -0
  110. data/lib/kingsman/strategies/base.rb +22 -0
  111. data/lib/kingsman/strategies/database_authenticatable.rb +31 -0
  112. data/lib/kingsman/strategies/rememberable.rb +67 -0
  113. data/lib/kingsman/token_generator.rb +32 -0
  114. data/lib/kingsman/version.rb +1 -1
  115. data/lib/kingsman.rb +427 -3
  116. 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