devise_token_auth_multi_email 0.9.0
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.
- checksums.yaml +7 -0
- data/LICENSE +13 -0
- data/README.md +97 -0
- data/Rakefile +42 -0
- data/app/controllers/devise_token_auth/application_controller.rb +100 -0
- data/app/controllers/devise_token_auth/concerns/resource_finder.rb +68 -0
- data/app/controllers/devise_token_auth/concerns/set_user_by_token.rb +199 -0
- data/app/controllers/devise_token_auth/confirmations_controller.rb +89 -0
- data/app/controllers/devise_token_auth/omniauth_callbacks_controller.rb +284 -0
- data/app/controllers/devise_token_auth/passwords_controller.rb +216 -0
- data/app/controllers/devise_token_auth/registrations_controller.rb +205 -0
- data/app/controllers/devise_token_auth/sessions_controller.rb +153 -0
- data/app/controllers/devise_token_auth/token_validations_controller.rb +31 -0
- data/app/controllers/devise_token_auth/unlocks_controller.rb +94 -0
- data/app/models/devise_token_auth/concerns/active_record_support.rb +18 -0
- data/app/models/devise_token_auth/concerns/confirmable_support.rb +28 -0
- data/app/models/devise_token_auth/concerns/mongoid_support.rb +19 -0
- data/app/models/devise_token_auth/concerns/tokens_serialization.rb +31 -0
- data/app/models/devise_token_auth/concerns/user.rb +282 -0
- data/app/models/devise_token_auth/concerns/user_omniauth_callbacks.rb +39 -0
- data/app/validators/devise_token_auth_email_validator.rb +31 -0
- data/app/views/devise/mailer/confirmation_instructions.html.erb +5 -0
- data/app/views/devise/mailer/reset_password_instructions.html.erb +8 -0
- data/app/views/devise/mailer/unlock_instructions.html.erb +7 -0
- data/app/views/devise_token_auth/omniauth_external_window.html.erb +38 -0
- data/config/locales/da-DK.yml +52 -0
- data/config/locales/de.yml +51 -0
- data/config/locales/en.yml +60 -0
- data/config/locales/es.yml +51 -0
- data/config/locales/fa.yml +60 -0
- data/config/locales/fr.yml +51 -0
- data/config/locales/he.yml +52 -0
- data/config/locales/it.yml +48 -0
- data/config/locales/ja.yml +60 -0
- data/config/locales/ko.yml +51 -0
- data/config/locales/nl.yml +32 -0
- data/config/locales/pl.yml +51 -0
- data/config/locales/pt-BR.yml +48 -0
- data/config/locales/pt.yml +51 -0
- data/config/locales/ro.yml +48 -0
- data/config/locales/ru.yml +52 -0
- data/config/locales/sq.yml +48 -0
- data/config/locales/sv.yml +52 -0
- data/config/locales/uk.yml +61 -0
- data/config/locales/vi.yml +52 -0
- data/config/locales/zh-CN.yml +48 -0
- data/config/locales/zh-HK.yml +50 -0
- data/config/locales/zh-TW.yml +50 -0
- data/lib/devise_token_auth/blacklist.rb +6 -0
- data/lib/devise_token_auth/controllers/helpers.rb +157 -0
- data/lib/devise_token_auth/controllers/url_helpers.rb +10 -0
- data/lib/devise_token_auth/engine.rb +105 -0
- data/lib/devise_token_auth/errors.rb +8 -0
- data/lib/devise_token_auth/rails/routes.rb +122 -0
- data/lib/devise_token_auth/token_factory.rb +126 -0
- data/lib/devise_token_auth/url.rb +44 -0
- data/lib/devise_token_auth/version.rb +5 -0
- data/lib/devise_token_auth.rb +14 -0
- data/lib/generators/devise_token_auth/USAGE +31 -0
- data/lib/generators/devise_token_auth/install_generator.rb +91 -0
- data/lib/generators/devise_token_auth/install_generator_helpers.rb +98 -0
- data/lib/generators/devise_token_auth/install_mongoid_generator.rb +46 -0
- data/lib/generators/devise_token_auth/install_views_generator.rb +18 -0
- data/lib/generators/devise_token_auth/templates/devise_token_auth.rb +66 -0
- data/lib/generators/devise_token_auth/templates/devise_token_auth_create_users.rb.erb +49 -0
- data/lib/generators/devise_token_auth/templates/user.rb.erb +9 -0
- data/lib/generators/devise_token_auth/templates/user_mongoid.rb.erb +56 -0
- data/lib/tasks/devise_token_auth_tasks.rake +6 -0
- data/test/controllers/custom/custom_confirmations_controller_test.rb +25 -0
- data/test/controllers/custom/custom_omniauth_callbacks_controller_test.rb +33 -0
- data/test/controllers/custom/custom_passwords_controller_test.rb +79 -0
- data/test/controllers/custom/custom_registrations_controller_test.rb +63 -0
- data/test/controllers/custom/custom_sessions_controller_test.rb +39 -0
- data/test/controllers/custom/custom_token_validations_controller_test.rb +42 -0
- data/test/controllers/demo_group_controller_test.rb +151 -0
- data/test/controllers/demo_mang_controller_test.rb +313 -0
- data/test/controllers/demo_user_controller_test.rb +658 -0
- data/test/controllers/devise_token_auth/confirmations_controller_test.rb +275 -0
- data/test/controllers/devise_token_auth/omniauth_callbacks_controller_test.rb +438 -0
- data/test/controllers/devise_token_auth/passwords_controller_test.rb +893 -0
- data/test/controllers/devise_token_auth/registrations_controller_test.rb +920 -0
- data/test/controllers/devise_token_auth/sessions_controller_test.rb +605 -0
- data/test/controllers/devise_token_auth/token_validations_controller_test.rb +142 -0
- data/test/controllers/devise_token_auth/unlocks_controller_test.rb +235 -0
- data/test/controllers/overrides/confirmations_controller_test.rb +47 -0
- data/test/controllers/overrides/omniauth_callbacks_controller_test.rb +53 -0
- data/test/controllers/overrides/passwords_controller_test.rb +64 -0
- data/test/controllers/overrides/registrations_controller_test.rb +46 -0
- data/test/controllers/overrides/sessions_controller_test.rb +35 -0
- data/test/controllers/overrides/token_validations_controller_test.rb +43 -0
- data/test/dummy/README.rdoc +28 -0
- data/test/dummy/app/active_record/confirmable_user.rb +11 -0
- data/test/dummy/app/active_record/lockable_user.rb +7 -0
- data/test/dummy/app/active_record/mang.rb +5 -0
- data/test/dummy/app/active_record/only_email_user.rb +7 -0
- data/test/dummy/app/active_record/scoped_user.rb +9 -0
- data/test/dummy/app/active_record/unconfirmable_user.rb +9 -0
- data/test/dummy/app/active_record/unregisterable_user.rb +9 -0
- data/test/dummy/app/active_record/user.rb +6 -0
- data/test/dummy/app/controllers/application_controller.rb +14 -0
- data/test/dummy/app/controllers/auth_origin_controller.rb +7 -0
- data/test/dummy/app/controllers/custom/confirmations_controller.rb +13 -0
- data/test/dummy/app/controllers/custom/omniauth_callbacks_controller.rb +13 -0
- data/test/dummy/app/controllers/custom/passwords_controller.rb +39 -0
- data/test/dummy/app/controllers/custom/registrations_controller.rb +39 -0
- data/test/dummy/app/controllers/custom/sessions_controller.rb +29 -0
- data/test/dummy/app/controllers/custom/token_validations_controller.rb +19 -0
- data/test/dummy/app/controllers/demo_group_controller.rb +15 -0
- data/test/dummy/app/controllers/demo_mang_controller.rb +14 -0
- data/test/dummy/app/controllers/demo_user_controller.rb +27 -0
- data/test/dummy/app/controllers/overrides/confirmations_controller.rb +29 -0
- data/test/dummy/app/controllers/overrides/omniauth_callbacks_controller.rb +16 -0
- data/test/dummy/app/controllers/overrides/passwords_controller.rb +36 -0
- data/test/dummy/app/controllers/overrides/registrations_controller.rb +29 -0
- data/test/dummy/app/controllers/overrides/sessions_controller.rb +36 -0
- data/test/dummy/app/controllers/overrides/token_validations_controller.rb +23 -0
- data/test/dummy/app/helpers/application_helper.rb +1058 -0
- data/test/dummy/app/models/concerns/favorite_color.rb +19 -0
- data/test/dummy/app/mongoid/confirmable_user.rb +52 -0
- data/test/dummy/app/mongoid/lockable_user.rb +38 -0
- data/test/dummy/app/mongoid/mang.rb +46 -0
- data/test/dummy/app/mongoid/only_email_user.rb +33 -0
- data/test/dummy/app/mongoid/scoped_user.rb +50 -0
- data/test/dummy/app/mongoid/unconfirmable_user.rb +44 -0
- data/test/dummy/app/mongoid/unregisterable_user.rb +47 -0
- data/test/dummy/app/mongoid/user.rb +49 -0
- data/test/dummy/app/views/layouts/application.html.erb +12 -0
- data/test/dummy/config/application.rb +50 -0
- data/test/dummy/config/application.yml.bk +0 -0
- data/test/dummy/config/boot.rb +11 -0
- data/test/dummy/config/environment.rb +7 -0
- data/test/dummy/config/environments/development.rb +36 -0
- data/test/dummy/config/environments/production.rb +68 -0
- data/test/dummy/config/environments/test.rb +58 -0
- data/test/dummy/config/initializers/backtrace_silencers.rb +9 -0
- data/test/dummy/config/initializers/cookies_serializer.rb +5 -0
- data/test/dummy/config/initializers/devise.rb +290 -0
- data/test/dummy/config/initializers/devise_token_auth.rb +55 -0
- data/test/dummy/config/initializers/figaro.rb +3 -0
- data/test/dummy/config/initializers/filter_parameter_logging.rb +6 -0
- data/test/dummy/config/initializers/inflections.rb +18 -0
- data/test/dummy/config/initializers/mime_types.rb +6 -0
- data/test/dummy/config/initializers/omniauth.rb +11 -0
- data/test/dummy/config/initializers/session_store.rb +5 -0
- data/test/dummy/config/initializers/wrap_parameters.rb +16 -0
- data/test/dummy/config/routes.rb +57 -0
- data/test/dummy/config/spring.rb +3 -0
- data/test/dummy/config.ru +18 -0
- data/test/dummy/db/migrate/20140715061447_devise_token_auth_create_users.rb +58 -0
- data/test/dummy/db/migrate/20140715061805_devise_token_auth_create_mangs.rb +57 -0
- data/test/dummy/db/migrate/20140829044006_add_operating_thetan_to_user.rb +8 -0
- data/test/dummy/db/migrate/20140916224624_add_favorite_color_to_mangs.rb +7 -0
- data/test/dummy/db/migrate/20141222035835_devise_token_auth_create_only_email_users.rb +55 -0
- data/test/dummy/db/migrate/20141222053502_devise_token_auth_create_unregisterable_users.rb +56 -0
- data/test/dummy/db/migrate/20150708104536_devise_token_auth_create_unconfirmable_users.rb +56 -0
- data/test/dummy/db/migrate/20160103235141_devise_token_auth_create_scoped_users.rb +56 -0
- data/test/dummy/db/migrate/20160629184441_devise_token_auth_create_lockable_users.rb +56 -0
- data/test/dummy/db/migrate/20190924101113_devise_token_auth_create_confirmable_users.rb +49 -0
- data/test/dummy/db/schema.rb +198 -0
- data/test/dummy/lib/migration_database_helper.rb +43 -0
- data/test/dummy/tmp/generators/app/models/mang.rb +9 -0
- data/test/dummy/tmp/generators/app/models/user.rb +9 -0
- data/test/dummy/tmp/generators/config/initializers/devise_token_auth.rb +60 -0
- data/test/dummy/tmp/generators/config/routes.rb +9 -0
- data/test/dummy/tmp/generators/db/migrate/20210305040222_devise_token_auth_create_mangs.rb +49 -0
- data/test/dummy/tmp/generators/db/migrate/20210305040222_devise_token_auth_create_users.rb +49 -0
- data/test/factories/users.rb +41 -0
- data/test/lib/devise_token_auth/blacklist_test.rb +19 -0
- data/test/lib/devise_token_auth/rails/custom_routes_test.rb +29 -0
- data/test/lib/devise_token_auth/rails/routes_test.rb +87 -0
- data/test/lib/devise_token_auth/token_factory_test.rb +191 -0
- data/test/lib/devise_token_auth/url_test.rb +26 -0
- data/test/lib/generators/devise_token_auth/install_generator_test.rb +217 -0
- data/test/lib/generators/devise_token_auth/install_generator_with_namespace_test.rb +222 -0
- data/test/lib/generators/devise_token_auth/install_views_generator_test.rb +25 -0
- data/test/models/concerns/mongoid_support_test.rb +31 -0
- data/test/models/concerns/tokens_serialization_test.rb +104 -0
- data/test/models/confirmable_user_test.rb +35 -0
- data/test/models/only_email_user_test.rb +29 -0
- data/test/models/user_test.rb +224 -0
- data/test/support/controllers/routes.rb +43 -0
- data/test/test_helper.rb +134 -0
- metadata +502 -0
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# see http://www.emilsoman.com/blog/2013/05/18/building-a-tested/
|
|
4
|
+
module DeviseTokenAuth
|
|
5
|
+
class SessionsController < DeviseTokenAuth::ApplicationController
|
|
6
|
+
before_action :set_user_by_token, only: [:destroy]
|
|
7
|
+
after_action :reset_session, only: [:destroy]
|
|
8
|
+
|
|
9
|
+
def new
|
|
10
|
+
render_new_error
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def create
|
|
14
|
+
if field = (resource_params.keys.map(&:to_sym) & resource_class.authentication_keys).first
|
|
15
|
+
q_value = get_case_insensitive_field_from_resource_params(field)
|
|
16
|
+
|
|
17
|
+
@resource = find_resource(field, q_value)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
if @resource && valid_params?(field, q_value) && (!@resource.respond_to?(:active_for_authentication?) || @resource.active_for_authentication?)
|
|
21
|
+
valid_password = @resource.valid_password?(resource_params[:password])
|
|
22
|
+
if (@resource.respond_to?(:valid_for_authentication?) && !@resource.valid_for_authentication? { valid_password }) || !valid_password
|
|
23
|
+
return render_create_error_bad_credentials
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
create_and_assign_token
|
|
27
|
+
|
|
28
|
+
sign_in(@resource, scope: :user, store: false, bypass: false)
|
|
29
|
+
|
|
30
|
+
yield @resource if block_given?
|
|
31
|
+
|
|
32
|
+
render_create_success
|
|
33
|
+
elsif @resource && !Devise.paranoid && !(!@resource.respond_to?(:active_for_authentication?) || @resource.active_for_authentication?)
|
|
34
|
+
if @resource.respond_to?(:locked_at) && @resource.locked_at
|
|
35
|
+
render_create_error_account_locked
|
|
36
|
+
else
|
|
37
|
+
render_create_error_not_confirmed
|
|
38
|
+
end
|
|
39
|
+
else
|
|
40
|
+
hash_password_in_paranoid_mode
|
|
41
|
+
render_create_error_bad_credentials
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def destroy
|
|
46
|
+
# remove auth instance variables so that after_action does not run
|
|
47
|
+
user = remove_instance_variable(:@resource) if @resource
|
|
48
|
+
client = @token.client
|
|
49
|
+
@token.clear!
|
|
50
|
+
|
|
51
|
+
if user && client && user.tokens[client]
|
|
52
|
+
user.tokens.delete(client)
|
|
53
|
+
user.save!
|
|
54
|
+
|
|
55
|
+
if DeviseTokenAuth.cookie_enabled
|
|
56
|
+
# If a cookie is set with a domain specified then it must be deleted with that domain specified
|
|
57
|
+
# See https://api.rubyonrails.org/classes/ActionDispatch/Cookies.html
|
|
58
|
+
cookies.delete(DeviseTokenAuth.cookie_name, domain: DeviseTokenAuth.cookie_attributes[:domain])
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
yield user if block_given?
|
|
62
|
+
|
|
63
|
+
render_destroy_success
|
|
64
|
+
else
|
|
65
|
+
render_destroy_error
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
protected
|
|
70
|
+
|
|
71
|
+
def valid_params?(key, val)
|
|
72
|
+
resource_params[:password] && key && val
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def get_auth_params
|
|
76
|
+
auth_key = nil
|
|
77
|
+
auth_val = nil
|
|
78
|
+
# iterate thru allowed auth keys, use first found
|
|
79
|
+
resource_class.authentication_keys.each do |k|
|
|
80
|
+
if resource_params[k]
|
|
81
|
+
auth_val = resource_params[k]
|
|
82
|
+
auth_key = k
|
|
83
|
+
break
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# honor devise configuration for case_insensitive_keys
|
|
88
|
+
if resource_class.case_insensitive_keys.include?(auth_key)
|
|
89
|
+
auth_val.downcase!
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
{ key: auth_key, val: auth_val }
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def render_new_error
|
|
96
|
+
render_error(405, I18n.t('devise_token_auth.sessions.not_supported'))
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def render_create_success
|
|
100
|
+
render json: {
|
|
101
|
+
data: resource_data(resource_json: @resource.token_validation_response)
|
|
102
|
+
}
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def render_create_error_not_confirmed
|
|
106
|
+
render_error(401, I18n.t('devise_token_auth.sessions.not_confirmed', email: @resource.email))
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def render_create_error_account_locked
|
|
110
|
+
render_error(401, I18n.t('devise.mailer.unlock_instructions.account_lock_msg'))
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def render_create_error_bad_credentials
|
|
114
|
+
render_error(401, I18n.t('devise_token_auth.sessions.bad_credentials'))
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def render_destroy_success
|
|
118
|
+
render json: {
|
|
119
|
+
success:true
|
|
120
|
+
}, status: 200
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def render_destroy_error
|
|
124
|
+
render_error(404, I18n.t('devise_token_auth.sessions.user_not_found'))
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
private
|
|
128
|
+
|
|
129
|
+
def resource_params
|
|
130
|
+
params.permit(*params_for_resource(:sign_in))
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def create_and_assign_token
|
|
134
|
+
if @resource.respond_to?(:with_lock) && !@resource.changed?
|
|
135
|
+
@resource.with_lock do
|
|
136
|
+
@token = @resource.create_token
|
|
137
|
+
@resource.save!
|
|
138
|
+
end
|
|
139
|
+
else
|
|
140
|
+
@token = @resource.create_token
|
|
141
|
+
@resource.save!
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def hash_password_in_paranoid_mode
|
|
146
|
+
# In order to avoid timing attacks in paranoid mode, we want the password hash to be
|
|
147
|
+
# calculated even if no resource has been found. Devise's DatabaseAuthenticatable warden
|
|
148
|
+
# strategy handles this case similarly:
|
|
149
|
+
# https://github.com/heartcombo/devise/blob/main/lib/devise/strategies/database_authenticatable.rb
|
|
150
|
+
resource_class.new.password = resource_params[:password] if Devise.paranoid
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DeviseTokenAuth
|
|
4
|
+
class TokenValidationsController < DeviseTokenAuth::ApplicationController
|
|
5
|
+
skip_before_action :assert_is_devise_resource!, only: [:validate_token]
|
|
6
|
+
before_action :set_user_by_token, only: [:validate_token]
|
|
7
|
+
|
|
8
|
+
def validate_token
|
|
9
|
+
# @resource will have been set by set_user_by_token concern
|
|
10
|
+
if @resource
|
|
11
|
+
yield @resource if block_given?
|
|
12
|
+
render_validate_token_success
|
|
13
|
+
else
|
|
14
|
+
render_validate_token_error
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
protected
|
|
19
|
+
|
|
20
|
+
def render_validate_token_success
|
|
21
|
+
render json: {
|
|
22
|
+
success: true,
|
|
23
|
+
data: resource_data(resource_json: @resource.token_validation_response)
|
|
24
|
+
}
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def render_validate_token_error
|
|
28
|
+
render_error(401, I18n.t('devise_token_auth.token_validations.invalid'))
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DeviseTokenAuth
|
|
4
|
+
class UnlocksController < DeviseTokenAuth::ApplicationController
|
|
5
|
+
skip_after_action :update_auth_header, only: [:create, :show]
|
|
6
|
+
|
|
7
|
+
# this action is responsible for generating unlock tokens and
|
|
8
|
+
# sending emails
|
|
9
|
+
def create
|
|
10
|
+
return render_create_error_missing_email unless resource_params[:email]
|
|
11
|
+
|
|
12
|
+
@email = get_case_insensitive_field_from_resource_params(:email)
|
|
13
|
+
@resource = find_resource(:email, @email)
|
|
14
|
+
|
|
15
|
+
if @resource
|
|
16
|
+
yield @resource if block_given?
|
|
17
|
+
|
|
18
|
+
@resource.send_unlock_instructions(
|
|
19
|
+
email: @email,
|
|
20
|
+
provider: 'email',
|
|
21
|
+
client_config: params[:config_name]
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
if @resource.errors.empty?
|
|
25
|
+
return render_create_success
|
|
26
|
+
else
|
|
27
|
+
render_create_error @resource.errors
|
|
28
|
+
end
|
|
29
|
+
else
|
|
30
|
+
render_not_found_error
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def show
|
|
35
|
+
@resource = resource_class.unlock_access_by_token(params[:unlock_token])
|
|
36
|
+
|
|
37
|
+
if @resource.persisted?
|
|
38
|
+
token = @resource.create_token
|
|
39
|
+
@resource.save!
|
|
40
|
+
yield @resource if block_given?
|
|
41
|
+
|
|
42
|
+
redirect_header_options = { unlock: true }
|
|
43
|
+
redirect_headers = build_redirect_headers(token.token,
|
|
44
|
+
token.client,
|
|
45
|
+
redirect_header_options)
|
|
46
|
+
redirect_to(@resource.build_auth_url(after_unlock_path_for(@resource),
|
|
47
|
+
redirect_headers),
|
|
48
|
+
redirect_options)
|
|
49
|
+
else
|
|
50
|
+
render_show_error
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
private
|
|
55
|
+
def after_unlock_path_for(resource)
|
|
56
|
+
#TODO: This should probably be a configuration option at the very least.
|
|
57
|
+
'/'
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def render_create_error_missing_email
|
|
61
|
+
render_error(401, I18n.t('devise_token_auth.unlocks.missing_email'))
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def render_create_success
|
|
65
|
+
render json: {
|
|
66
|
+
success: true,
|
|
67
|
+
message: success_message('unlocks', @email)
|
|
68
|
+
}
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def render_create_error(errors)
|
|
72
|
+
render json: {
|
|
73
|
+
success: false,
|
|
74
|
+
errors: errors
|
|
75
|
+
}, status: 400
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def render_show_error
|
|
79
|
+
raise ActionController::RoutingError, 'Not Found'
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def render_not_found_error
|
|
83
|
+
if Devise.paranoid
|
|
84
|
+
render_create_success
|
|
85
|
+
else
|
|
86
|
+
render_error(404, I18n.t('devise_token_auth.unlocks.user_not_found', email: @email))
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def resource_params
|
|
91
|
+
params.permit(:email, :unlock_token, :config)
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
module DeviseTokenAuth::Concerns::ActiveRecordSupport
|
|
2
|
+
extend ActiveSupport::Concern
|
|
3
|
+
|
|
4
|
+
included do
|
|
5
|
+
if Rails.gem_version >= Gem::Version.new("7.1.0.a")
|
|
6
|
+
serialize :tokens, coder: DeviseTokenAuth::Concerns::TokensSerialization
|
|
7
|
+
else
|
|
8
|
+
serialize :tokens, DeviseTokenAuth::Concerns::TokensSerialization
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
class_methods do
|
|
13
|
+
# It's abstract replacement .find_by
|
|
14
|
+
def dta_find_by(attrs = {})
|
|
15
|
+
find_by(attrs)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
module DeviseTokenAuth::Concerns::ConfirmableSupport
|
|
2
|
+
extend ActiveSupport::Concern
|
|
3
|
+
|
|
4
|
+
included do
|
|
5
|
+
# Override standard devise `postpone_email_change?` method
|
|
6
|
+
# for not to use `devise_will_save_change_to_email?` methods.
|
|
7
|
+
def postpone_email_change?
|
|
8
|
+
postpone = self.class.reconfirmable &&
|
|
9
|
+
email_value_in_database != email &&
|
|
10
|
+
!@bypass_confirmation_postpone &&
|
|
11
|
+
self.email.present? &&
|
|
12
|
+
(!@skip_reconfirmation_in_callback || !email_value_in_database.nil?)
|
|
13
|
+
@bypass_confirmation_postpone = false
|
|
14
|
+
postpone
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
protected
|
|
19
|
+
|
|
20
|
+
def email_value_in_database
|
|
21
|
+
rails51 = Rails.gem_version >= Gem::Version.new("5.1.x")
|
|
22
|
+
if rails51 && respond_to?(:email_in_database)
|
|
23
|
+
email_in_database
|
|
24
|
+
else
|
|
25
|
+
email_was
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
module DeviseTokenAuth::Concerns::MongoidSupport
|
|
2
|
+
extend ActiveSupport::Concern
|
|
3
|
+
|
|
4
|
+
def as_json(options = {})
|
|
5
|
+
options[:except] = (options[:except] || []) + [:_id]
|
|
6
|
+
hash = super(options)
|
|
7
|
+
hash['id'] = to_param
|
|
8
|
+
hash
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
class_methods do
|
|
12
|
+
# It's abstract replacement .find_by
|
|
13
|
+
def dta_find_by(attrs = {})
|
|
14
|
+
find_by(attrs)
|
|
15
|
+
rescue Mongoid::Errors::DocumentNotFound
|
|
16
|
+
nil
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
module DeviseTokenAuth::Concerns::TokensSerialization
|
|
2
|
+
extend self
|
|
3
|
+
# Serialization hash to json
|
|
4
|
+
def dump(object)
|
|
5
|
+
JSON.generate(object && object.transform_values do |token|
|
|
6
|
+
serialize_updated_at(token).compact
|
|
7
|
+
end.compact)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
# Deserialization json to hash
|
|
11
|
+
def load(json)
|
|
12
|
+
case json
|
|
13
|
+
when String
|
|
14
|
+
JSON.parse(json)
|
|
15
|
+
when NilClass
|
|
16
|
+
{}
|
|
17
|
+
else
|
|
18
|
+
json
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
private
|
|
23
|
+
|
|
24
|
+
def serialize_updated_at(token)
|
|
25
|
+
updated_at_key = ['updated_at', :updated_at].find(&token.method(:[]))
|
|
26
|
+
|
|
27
|
+
return token unless token[updated_at_key].respond_to?(:iso8601)
|
|
28
|
+
|
|
29
|
+
token.merge updated_at_key => token[updated_at_key].iso8601
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DeviseTokenAuth::Concerns::User
|
|
4
|
+
extend ActiveSupport::Concern
|
|
5
|
+
|
|
6
|
+
def self.tokens_match?(token_hash, token)
|
|
7
|
+
@token_equality_cache ||= {}
|
|
8
|
+
|
|
9
|
+
key = "#{token_hash}/#{token}"
|
|
10
|
+
result = @token_equality_cache[key] ||= DeviseTokenAuth::TokenFactory.token_hash_is_token?(token_hash, token)
|
|
11
|
+
@token_equality_cache = {} if @token_equality_cache.size > 10000
|
|
12
|
+
result
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
included do
|
|
16
|
+
# Hack to check if devise is already enabled
|
|
17
|
+
if method_defined?(:devise_modules)
|
|
18
|
+
devise_modules.delete(:omniauthable)
|
|
19
|
+
else
|
|
20
|
+
devise :database_authenticatable, :registerable,
|
|
21
|
+
:recoverable, :validatable, :confirmable
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
if const_defined?('ActiveRecord') && ancestors.include?(ActiveRecord::Base)
|
|
25
|
+
include DeviseTokenAuth::Concerns::ActiveRecordSupport
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
if const_defined?('Mongoid') && ancestors.include?(Mongoid::Document)
|
|
29
|
+
include DeviseTokenAuth::Concerns::MongoidSupport
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
if DeviseTokenAuth.default_callbacks
|
|
33
|
+
include DeviseTokenAuth::Concerns::UserOmniauthCallbacks
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# get rid of dead tokens
|
|
37
|
+
before_save :destroy_expired_tokens
|
|
38
|
+
|
|
39
|
+
# remove old tokens if password has changed
|
|
40
|
+
before_save :remove_tokens_after_password_reset
|
|
41
|
+
|
|
42
|
+
# don't use default devise email validation
|
|
43
|
+
def email_required?; false; end
|
|
44
|
+
def devise_will_save_change_to_email?; false; end
|
|
45
|
+
|
|
46
|
+
if DeviseTokenAuth.send_confirmation_email && devise_modules.include?(:confirmable)
|
|
47
|
+
include DeviseTokenAuth::Concerns::ConfirmableSupport
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def password_required?
|
|
51
|
+
return false unless provider == 'email'
|
|
52
|
+
super
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# override devise method to include additional info as opts hash
|
|
56
|
+
def send_confirmation_instructions(opts = {})
|
|
57
|
+
generate_confirmation_token! unless @raw_confirmation_token
|
|
58
|
+
|
|
59
|
+
# fall back to "default" config name
|
|
60
|
+
opts[:client_config] ||= 'default'
|
|
61
|
+
opts[:to] = unconfirmed_email if pending_reconfirmation?
|
|
62
|
+
opts[:redirect_url] ||= DeviseTokenAuth.default_confirm_success_url
|
|
63
|
+
|
|
64
|
+
send_devise_notification(:confirmation_instructions, @raw_confirmation_token, opts)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# override devise method to include additional info as opts hash
|
|
68
|
+
def send_reset_password_instructions(opts = {})
|
|
69
|
+
token = set_reset_password_token
|
|
70
|
+
|
|
71
|
+
# fall back to "default" config name
|
|
72
|
+
opts[:client_config] ||= 'default'
|
|
73
|
+
|
|
74
|
+
send_devise_notification(:reset_password_instructions, token, opts)
|
|
75
|
+
token
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# override devise method to include additional info as opts hash
|
|
79
|
+
def send_unlock_instructions(opts = {})
|
|
80
|
+
raw, enc = Devise.token_generator.generate(self.class, :unlock_token)
|
|
81
|
+
self.unlock_token = enc
|
|
82
|
+
save(validate: false)
|
|
83
|
+
|
|
84
|
+
# fall back to "default" config name
|
|
85
|
+
opts[:client_config] ||= 'default'
|
|
86
|
+
|
|
87
|
+
send_devise_notification(:unlock_instructions, raw, opts)
|
|
88
|
+
raw
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def create_token(client: nil, lifespan: nil, cost: nil, **token_extras)
|
|
92
|
+
token = DeviseTokenAuth::TokenFactory.create(client: client, lifespan: lifespan, cost: cost)
|
|
93
|
+
|
|
94
|
+
tokens[token.client] = {
|
|
95
|
+
token: token.token_hash,
|
|
96
|
+
expiry: token.expiry
|
|
97
|
+
}.merge!(token_extras)
|
|
98
|
+
|
|
99
|
+
clean_old_tokens
|
|
100
|
+
|
|
101
|
+
token
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def valid_token?(token, client = 'default')
|
|
106
|
+
return false unless tokens[client]
|
|
107
|
+
return true if token_is_current?(token, client)
|
|
108
|
+
return true if token_can_be_reused?(token, client)
|
|
109
|
+
|
|
110
|
+
# return false if none of the above conditions are met
|
|
111
|
+
false
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# this must be done from the controller so that additional params
|
|
115
|
+
# can be passed on from the client
|
|
116
|
+
def send_confirmation_notification?; false; end
|
|
117
|
+
|
|
118
|
+
def token_is_current?(token, client)
|
|
119
|
+
# ghetto HashWithIndifferentAccess
|
|
120
|
+
expiry = tokens[client]['expiry'] || tokens[client][:expiry]
|
|
121
|
+
token_hash = tokens[client]['token'] || tokens[client][:token]
|
|
122
|
+
previous_token_hash = tokens[client]['previous_token'] || tokens[client][:previous_token]
|
|
123
|
+
|
|
124
|
+
return true if (
|
|
125
|
+
# ensure that expiry and token are set
|
|
126
|
+
expiry && token &&
|
|
127
|
+
|
|
128
|
+
# ensure that the token has not yet expired
|
|
129
|
+
DateTime.strptime(expiry.to_s, '%s') > Time.zone.now &&
|
|
130
|
+
|
|
131
|
+
# ensure that the token is valid
|
|
132
|
+
(
|
|
133
|
+
# check if the latest token matches
|
|
134
|
+
does_token_match?(token_hash, token) ||
|
|
135
|
+
|
|
136
|
+
# check if the previous token matches
|
|
137
|
+
does_token_match?(previous_token_hash, token)
|
|
138
|
+
)
|
|
139
|
+
)
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# check if the hash of received token matches the stored token
|
|
143
|
+
def does_token_match?(token_hash, token)
|
|
144
|
+
return false if token_hash.nil?
|
|
145
|
+
|
|
146
|
+
DeviseTokenAuth::Concerns::User.tokens_match?(token_hash, token)
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# allow batch requests to use the last token
|
|
150
|
+
def token_can_be_reused?(token, client)
|
|
151
|
+
# ghetto HashWithIndifferentAccess
|
|
152
|
+
updated_at = tokens[client]['updated_at'] || tokens[client][:updated_at]
|
|
153
|
+
last_token_hash = tokens[client]['last_token'] || tokens[client][:last_token]
|
|
154
|
+
|
|
155
|
+
return true if (
|
|
156
|
+
# ensure that the last token and its creation time exist
|
|
157
|
+
updated_at && last_token_hash &&
|
|
158
|
+
|
|
159
|
+
# ensure that last token falls within the batch buffer throttle time of the last request
|
|
160
|
+
updated_at.to_time > Time.zone.now - DeviseTokenAuth.batch_request_buffer_throttle &&
|
|
161
|
+
|
|
162
|
+
# ensure that the token is valid
|
|
163
|
+
DeviseTokenAuth::TokenFactory.token_hash_is_token?(last_token_hash, token)
|
|
164
|
+
)
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# update user's auth token (should happen on each request)
|
|
168
|
+
def create_new_auth_token(client = nil)
|
|
169
|
+
now = Time.zone.now
|
|
170
|
+
|
|
171
|
+
token = create_token(
|
|
172
|
+
client: client,
|
|
173
|
+
previous_token: tokens.fetch(client, {})['token'],
|
|
174
|
+
last_token: tokens.fetch(client, {})['previous_token'],
|
|
175
|
+
updated_at: now
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
update_auth_headers(token.token, token.client)
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def build_auth_headers(token, client = 'default')
|
|
182
|
+
# client may use expiry to prevent validation request if expired
|
|
183
|
+
# must be cast as string or headers will break
|
|
184
|
+
expiry = tokens[client]['expiry'] || tokens[client][:expiry]
|
|
185
|
+
headers = {
|
|
186
|
+
DeviseTokenAuth.headers_names[:"access-token"] => token,
|
|
187
|
+
DeviseTokenAuth.headers_names[:"token-type"] => 'Bearer',
|
|
188
|
+
DeviseTokenAuth.headers_names[:"client"] => client,
|
|
189
|
+
DeviseTokenAuth.headers_names[:"expiry"] => expiry.to_s,
|
|
190
|
+
DeviseTokenAuth.headers_names[:"uid"] => uid
|
|
191
|
+
}
|
|
192
|
+
headers.merge(build_bearer_token(headers))
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
def build_bearer_token(auth)
|
|
196
|
+
return {} if DeviseTokenAuth.cookie_enabled # There is no need for the bearer token if it is using cookies
|
|
197
|
+
|
|
198
|
+
encoded_token = Base64.strict_encode64(auth.to_json)
|
|
199
|
+
bearer_token = "Bearer #{encoded_token}"
|
|
200
|
+
{ DeviseTokenAuth.headers_names[:"authorization"] => bearer_token }
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
def update_auth_headers(token, client = 'default')
|
|
204
|
+
headers = build_auth_headers(token, client)
|
|
205
|
+
clean_old_tokens
|
|
206
|
+
save!
|
|
207
|
+
|
|
208
|
+
headers
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
def build_auth_url(base_url, args)
|
|
212
|
+
args[:uid] = uid
|
|
213
|
+
args[:expiry] = tokens[args[:client_id]]['expiry']
|
|
214
|
+
|
|
215
|
+
DeviseTokenAuth::Url.generate(base_url, args)
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
def extend_batch_buffer(token, client)
|
|
219
|
+
tokens[client]['updated_at'] = Time.zone.now
|
|
220
|
+
update_auth_headers(token, client)
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
def confirmed?
|
|
224
|
+
devise_modules.exclude?(:confirmable) || super
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
def token_validation_response
|
|
228
|
+
as_json(except: %i[tokens created_at updated_at])
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
protected
|
|
232
|
+
|
|
233
|
+
def destroy_expired_tokens
|
|
234
|
+
if tokens
|
|
235
|
+
tokens.delete_if do |cid, v|
|
|
236
|
+
expiry = v[:expiry] || v['expiry']
|
|
237
|
+
DateTime.strptime(expiry.to_s, '%s') < Time.zone.now
|
|
238
|
+
end
|
|
239
|
+
end
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
def should_remove_tokens_after_password_reset?
|
|
243
|
+
DeviseTokenAuth.remove_tokens_after_password_reset &&
|
|
244
|
+
(respond_to?(:encrypted_password_changed?) && encrypted_password_changed?)
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
def remove_tokens_after_password_reset
|
|
248
|
+
return unless should_remove_tokens_after_password_reset?
|
|
249
|
+
|
|
250
|
+
if tokens.present? && tokens.many?
|
|
251
|
+
client, token_data = tokens.max_by { |cid, v| v[:expiry] || v['expiry'] }
|
|
252
|
+
self.tokens = { client => token_data }
|
|
253
|
+
end
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
def max_client_tokens_exceeded?
|
|
257
|
+
tokens.length > DeviseTokenAuth.max_number_of_devices
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
def clean_old_tokens
|
|
261
|
+
return if tokens.blank? || !max_client_tokens_exceeded?
|
|
262
|
+
|
|
263
|
+
# First, remove any tokens with expiry greater than current max allowed lifespan
|
|
264
|
+
# this handles the case where token lifespan was reduced and old tokens exist
|
|
265
|
+
max_lifespan_expiry = Time.now.to_i + DeviseTokenAuth.token_lifespan.to_i
|
|
266
|
+
tokens_to_keep = tokens.select do |_cid, v|
|
|
267
|
+
expiry = (v[:expiry] || v['expiry']).to_i
|
|
268
|
+
expiry <= max_lifespan_expiry
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
# Using Enumerable#sort_by on a Hash will typecast it into an associative
|
|
272
|
+
# Array (i.e. an Array of key-value Array pairs). However, since Hashes
|
|
273
|
+
# have an internal order in Ruby 1.9+, the resulting sorted associative
|
|
274
|
+
# Array can be converted back into a Hash, while maintaining the sorted
|
|
275
|
+
# order.
|
|
276
|
+
self.tokens = tokens_to_keep.sort_by { |_cid, v| v[:expiry] || v['expiry'] }.to_h
|
|
277
|
+
|
|
278
|
+
# Since the tokens are sorted by expiry, shift the oldest client token
|
|
279
|
+
# off the Hash until it no longer exceeds the maximum number of clients
|
|
280
|
+
tokens.shift while max_client_tokens_exceeded?
|
|
281
|
+
end
|
|
282
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DeviseTokenAuth::Concerns::UserOmniauthCallbacks
|
|
4
|
+
extend ActiveSupport::Concern
|
|
5
|
+
|
|
6
|
+
included do
|
|
7
|
+
validates :email, presence: true, if: lambda { uid_and_provider_defined? && email_provider? }
|
|
8
|
+
validates :email, :devise_token_auth_email => true, allow_nil: true, allow_blank: true, if: lambda { uid_and_provider_defined? && email_provider? }
|
|
9
|
+
validates_presence_of :uid, if: lambda { uid_and_provider_defined? && !email_provider? }
|
|
10
|
+
|
|
11
|
+
# Provide support for devise-multi_email - they implement case_sensitive
|
|
12
|
+
# and maintain uniqueness themselves.
|
|
13
|
+
unless Gem.loaded_specs[ 'devise-multi_email' ]
|
|
14
|
+
# only validate unique emails among email registration users
|
|
15
|
+
validates :email, uniqueness: { case_sensitive: false, scope: :provider }, on: :create, if: lambda { uid_and_provider_defined? && email_provider? }
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# keep uid in sync with email
|
|
19
|
+
before_save :sync_uid
|
|
20
|
+
before_create :sync_uid
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
protected
|
|
24
|
+
|
|
25
|
+
def uid_and_provider_defined?
|
|
26
|
+
defined?(provider) && defined?(uid)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def email_provider?
|
|
30
|
+
provider == 'email'
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def sync_uid
|
|
34
|
+
unless self.new_record?
|
|
35
|
+
return if devise_modules.include?(:confirmable) && !@bypass_confirmation_postpone && postpone_email_change?
|
|
36
|
+
end
|
|
37
|
+
self.uid = email if uid_and_provider_defined? && email_provider?
|
|
38
|
+
end
|
|
39
|
+
end
|