kingsman 0.0.0.beta → 0.1.0

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