linked_rails-auth 0.0.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.
- checksums.yaml +7 -0
- data/LICENSE +674 -0
- data/README.md +28 -0
- data/Rakefile +34 -0
- data/app/actions/linked_rails/auth/access_token_action_list.rb +16 -0
- data/app/actions/linked_rails/auth/confirmation_action_list.rb +17 -0
- data/app/actions/linked_rails/auth/otp_attempt_action_list.rb +13 -0
- data/app/actions/linked_rails/auth/otp_secret_action_list.rb +31 -0
- data/app/actions/linked_rails/auth/password_action_list.rb +25 -0
- data/app/actions/linked_rails/auth/registration_action_list.rb +15 -0
- data/app/actions/linked_rails/auth/session_action_list.rb +22 -0
- data/app/actions/linked_rails/auth/unlock_action_list.rb +17 -0
- data/app/controllers/linked_rails/auth/access_tokens_controller.rb +131 -0
- data/app/controllers/linked_rails/auth/confirmations_controller.rb +87 -0
- data/app/controllers/linked_rails/auth/otp_attempts_controller.rb +21 -0
- data/app/controllers/linked_rails/auth/otp_secrets_controller.rb +40 -0
- data/app/controllers/linked_rails/auth/passwords_controller.rb +63 -0
- data/app/controllers/linked_rails/auth/registrations_controller.rb +33 -0
- data/app/controllers/linked_rails/auth/sessions_controller.rb +55 -0
- data/app/controllers/linked_rails/auth/unlocks_controller.rb +44 -0
- data/app/forms/linked_rails/auth/access_token_form.rb +20 -0
- data/app/forms/linked_rails/auth/confirmation_form.rb +9 -0
- data/app/forms/linked_rails/auth/otp_attempt_form.rb +9 -0
- data/app/forms/linked_rails/auth/otp_secret_form.rb +12 -0
- data/app/forms/linked_rails/auth/password_form.rb +21 -0
- data/app/forms/linked_rails/auth/registration_form.rb +21 -0
- data/app/forms/linked_rails/auth/session_form.rb +13 -0
- data/app/forms/linked_rails/auth/unlock_form.rb +9 -0
- data/app/helpers/linked_rails/auth/otp_helper.rb +30 -0
- data/app/models/linked_rails/auth/access_token.rb +40 -0
- data/app/models/linked_rails/auth/confirmation.rb +70 -0
- data/app/models/linked_rails/auth/guest_user.rb +29 -0
- data/app/models/linked_rails/auth/otp_attempt.rb +41 -0
- data/app/models/linked_rails/auth/otp_base.rb +57 -0
- data/app/models/linked_rails/auth/otp_secret.rb +91 -0
- data/app/models/linked_rails/auth/password.rb +47 -0
- data/app/models/linked_rails/auth/registration.rb +39 -0
- data/app/models/linked_rails/auth/session.rb +46 -0
- data/app/models/linked_rails/auth/unlock.rb +59 -0
- data/app/policies/linked_rails/auth/access_token_policy.rb +17 -0
- data/app/policies/linked_rails/auth/confirmation_policy.rb +17 -0
- data/app/policies/linked_rails/auth/otp_attempt_policy.rb +17 -0
- data/app/policies/linked_rails/auth/otp_secret_policy.rb +37 -0
- data/app/policies/linked_rails/auth/password_policy.rb +23 -0
- data/app/policies/linked_rails/auth/registration_policy.rb +13 -0
- data/app/policies/linked_rails/auth/session_policy.rb +17 -0
- data/app/policies/linked_rails/auth/unlock_policy.rb +21 -0
- data/app/serializers/linked_rails/auth/access_token_serializer.rb +11 -0
- data/app/serializers/linked_rails/auth/confirmation_serializer.rb +10 -0
- data/app/serializers/linked_rails/auth/otp_attempt_serializer.rb +12 -0
- data/app/serializers/linked_rails/auth/otp_secret_serializer.rb +14 -0
- data/app/serializers/linked_rails/auth/password_serializer.rb +18 -0
- data/app/serializers/linked_rails/auth/registration_serializer.rb +12 -0
- data/app/serializers/linked_rails/auth/session_serializer.rb +10 -0
- data/app/serializers/linked_rails/auth/unlock_serializer.rb +9 -0
- data/config/routes.rb +4 -0
- data/lib/generators/linked_rails/auth/install_generator.rb +155 -0
- data/lib/generators/linked_rails/auth/templates/README +2 -0
- data/lib/generators/linked_rails/auth/templates/doorkeeper_jwt_initializer.rb +52 -0
- data/lib/generators/linked_rails/auth/templates/locales.yml +39 -0
- data/lib/generators/linked_rails/auth/templates/migration.rb.erb +20 -0
- data/lib/linked_rails/auth/auth_helper.rb +101 -0
- data/lib/linked_rails/auth/controller/error_handling.rb +17 -0
- data/lib/linked_rails/auth/controller.rb +16 -0
- data/lib/linked_rails/auth/engine.rb +8 -0
- data/lib/linked_rails/auth/errors/account_locked.rb +10 -0
- data/lib/linked_rails/auth/errors/expired.rb +10 -0
- data/lib/linked_rails/auth/errors/unauthorized.rb +10 -0
- data/lib/linked_rails/auth/errors/unknown_email.rb +14 -0
- data/lib/linked_rails/auth/errors/wrong_password.rb +14 -0
- data/lib/linked_rails/auth/errors.rb +7 -0
- data/lib/linked_rails/auth/routes.rb +86 -0
- data/lib/linked_rails/auth/version.rb +7 -0
- data/lib/linked_rails/auth.rb +46 -0
- data/lib/tasks/linked_rails/auth_tasks.rake +6 -0
- metadata +257 -0
@@ -0,0 +1,70 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module LinkedRails
|
4
|
+
module Auth
|
5
|
+
class Confirmation < LinkedRails::Resource
|
6
|
+
enhance LinkedRails::Enhancements::Actionable
|
7
|
+
enhance LinkedRails::Enhancements::Creatable
|
8
|
+
enhance LinkedRails::Enhancements::Updatable
|
9
|
+
enhance LinkedRails::Enhancements::Singularable
|
10
|
+
attr_accessor :confirmation_token, :email, :user, :password_token
|
11
|
+
alias root_relative_iri root_relative_singular_iri
|
12
|
+
|
13
|
+
def anonymous_iri?
|
14
|
+
confirmation_token.blank?
|
15
|
+
end
|
16
|
+
|
17
|
+
def confirm!
|
18
|
+
owner!.confirm
|
19
|
+
end
|
20
|
+
|
21
|
+
delegate :confirmed?, to: :owner!
|
22
|
+
|
23
|
+
def singular_iri_opts
|
24
|
+
{confirmation_token: confirmation_token}
|
25
|
+
end
|
26
|
+
|
27
|
+
def redirect_url
|
28
|
+
LinkedRails.iri
|
29
|
+
end
|
30
|
+
|
31
|
+
def owner!
|
32
|
+
owner || raise(ActiveRecord::RecordNotFound)
|
33
|
+
end
|
34
|
+
|
35
|
+
class << self
|
36
|
+
def action_list
|
37
|
+
LinkedRails.confirmation_action_list_class
|
38
|
+
end
|
39
|
+
|
40
|
+
def form_class
|
41
|
+
LinkedRails.confirmation_form_class
|
42
|
+
end
|
43
|
+
|
44
|
+
def iri_namespace
|
45
|
+
Vocab.ontola
|
46
|
+
end
|
47
|
+
|
48
|
+
def singular_iri_template
|
49
|
+
@singular_iri_template ||= URITemplate.new("/#{singular_route_key}{?confirmation_token}")
|
50
|
+
end
|
51
|
+
|
52
|
+
def requested_singular_resource(params, _user_context)
|
53
|
+
return new unless params.key?(:confirmation_token)
|
54
|
+
|
55
|
+
user_by_token = LinkedRails.user_class.find_by(confirmation_token: params[:confirmation_token])
|
56
|
+
return if user_by_token.blank?
|
57
|
+
|
58
|
+
new(
|
59
|
+
confirmation_token: params[:confirmation_token],
|
60
|
+
user: user_by_token
|
61
|
+
)
|
62
|
+
end
|
63
|
+
|
64
|
+
def singular_route_key
|
65
|
+
'u/confirmation'
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module LinkedRails
|
4
|
+
module Auth
|
5
|
+
class GuestUser
|
6
|
+
include ActiveModel::Model
|
7
|
+
include LinkedRails::Model
|
8
|
+
attr_writer :id
|
9
|
+
|
10
|
+
def email; end
|
11
|
+
|
12
|
+
def guest?
|
13
|
+
true
|
14
|
+
end
|
15
|
+
|
16
|
+
def id
|
17
|
+
@id ||= SecureRandom.hex
|
18
|
+
end
|
19
|
+
|
20
|
+
def iri_opts
|
21
|
+
{id: id}
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.iri
|
25
|
+
Vocab.ontola[:GuestUser]
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module LinkedRails
|
4
|
+
module Auth
|
5
|
+
class OtpAttempt < OtpBase
|
6
|
+
def raise_on_persisting(_opts = {})
|
7
|
+
raise "#{self.class.name} should not be persisted"
|
8
|
+
end
|
9
|
+
ActiveRecord::Persistence.instance_methods.each do |method|
|
10
|
+
alias_method method, :raise_on_persisting unless method.to_s.include?('?')
|
11
|
+
end
|
12
|
+
|
13
|
+
alias root_relative_iri root_relative_singular_iri
|
14
|
+
|
15
|
+
def save
|
16
|
+
validate_otp_attempt
|
17
|
+
|
18
|
+
errors.empty?
|
19
|
+
end
|
20
|
+
|
21
|
+
class << self
|
22
|
+
def form_class
|
23
|
+
LinkedRails.otp_attempt_form_class
|
24
|
+
end
|
25
|
+
|
26
|
+
def singular_route_key
|
27
|
+
'u/otp_attempt'
|
28
|
+
end
|
29
|
+
|
30
|
+
def requested_singular_resource(params, user_context)
|
31
|
+
owner = owner_for_otp(params, user_context)
|
32
|
+
return if owner.blank?
|
33
|
+
|
34
|
+
attempt = LinkedRails.otp_attempt_class.find_by(owner: owner) || LinkedRails.otp_attempt_class.new
|
35
|
+
attempt.encoded_session = params[:session]
|
36
|
+
attempt
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module LinkedRails
|
4
|
+
module Auth
|
5
|
+
class OtpBase < ApplicationRecord
|
6
|
+
self.table_name = 'otp_secrets'
|
7
|
+
self.abstract_class = true
|
8
|
+
|
9
|
+
enhance LinkedRails::Enhancements::Actionable
|
10
|
+
enhance LinkedRails::Enhancements::Creatable
|
11
|
+
enhance LinkedRails::Enhancements::Singularable
|
12
|
+
|
13
|
+
extend OtpHelper
|
14
|
+
include OtpHelper
|
15
|
+
|
16
|
+
has_one_time_password
|
17
|
+
belongs_to :owner, class_name: LinkedRails.otp_owner_class.to_s
|
18
|
+
validates :owner, presence: true
|
19
|
+
|
20
|
+
attr_accessor :encoded_session, :otp_attempt
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def decoded_session
|
25
|
+
@decoded_session ||= session_from_param(encoded_session)
|
26
|
+
end
|
27
|
+
|
28
|
+
def singular_iri_opts
|
29
|
+
{session: encoded_session}
|
30
|
+
end
|
31
|
+
|
32
|
+
def validate_otp_attempt
|
33
|
+
return if persisted? && authenticate_otp(otp_attempt, drift: LinkedRails::Auth.otp_drift)
|
34
|
+
|
35
|
+
errors.add(:otp_attempt, I18n.t('messages.otp_secrets.invalid'))
|
36
|
+
end
|
37
|
+
|
38
|
+
class << self
|
39
|
+
def iri_template
|
40
|
+
@iri_template ||= URITemplate.new("/#{route_key}{/id}{?session}{#fragment}")
|
41
|
+
end
|
42
|
+
|
43
|
+
def singular_iri_template
|
44
|
+
@singular_iri_template ||= URITemplate.new("{/parent_iri*}/#{singular_route_key}{?session}{#fragment}")
|
45
|
+
end
|
46
|
+
|
47
|
+
def owner_for_otp(params, user_context)
|
48
|
+
if params.key?(:session)
|
49
|
+
owner_from_session(params[:session])
|
50
|
+
else
|
51
|
+
user_context unless user_context.guest?
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rqrcode'
|
4
|
+
|
5
|
+
module LinkedRails
|
6
|
+
module Auth
|
7
|
+
class OtpSecret < OtpBase
|
8
|
+
enhance LinkedRails::Enhancements::Destroyable
|
9
|
+
|
10
|
+
validate :validate_otp_attempt, on: %i[update]
|
11
|
+
|
12
|
+
def image
|
13
|
+
return if active? || !persisted?
|
14
|
+
|
15
|
+
@image ||=
|
16
|
+
LinkedRails::MediaObject.new(
|
17
|
+
content_url: data_url,
|
18
|
+
content_type: 'image/png',
|
19
|
+
iri: LinkedRails.iri(path: root_relative_iri, fragment: 'image')
|
20
|
+
)
|
21
|
+
end
|
22
|
+
|
23
|
+
def iri_opts
|
24
|
+
{id: id}
|
25
|
+
end
|
26
|
+
|
27
|
+
def redirect_url
|
28
|
+
decoded_session['redirect_uri'] || LinkedRails.iri.to_s
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def data_url
|
34
|
+
[
|
35
|
+
'data:image/svg+xml;base64,',
|
36
|
+
RQRCode::QRCode.new(provisioning_uri(owner.email, issuer: issuer)).as_svg(module_size: 4).to_s
|
37
|
+
].pack('A*m').delete("\n")
|
38
|
+
end
|
39
|
+
|
40
|
+
def issuer
|
41
|
+
return issuer_name if Rails.env.production?
|
42
|
+
|
43
|
+
"#{issuer_name} #{Rails.env}"
|
44
|
+
end
|
45
|
+
|
46
|
+
def issuer_name
|
47
|
+
Rails.application.railtie_name.chomp('_application').humanize
|
48
|
+
end
|
49
|
+
|
50
|
+
class << self
|
51
|
+
def activated?(owner_id)
|
52
|
+
LinkedRails.otp_secret_class.exists?(
|
53
|
+
owner_id: owner_id,
|
54
|
+
active: true
|
55
|
+
)
|
56
|
+
end
|
57
|
+
|
58
|
+
def form_class
|
59
|
+
LinkedRails.otp_secret_form_class
|
60
|
+
end
|
61
|
+
|
62
|
+
def preview_includes
|
63
|
+
%i[image]
|
64
|
+
end
|
65
|
+
|
66
|
+
def requested_singular_resource(params, user_context)
|
67
|
+
owner = owner_for_otp(params, user_context)
|
68
|
+
return if owner.blank?
|
69
|
+
|
70
|
+
secret = LinkedRails.otp_secret_class.find_or_create_by!(owner: owner)
|
71
|
+
secret.encoded_session = params[:session]
|
72
|
+
secret
|
73
|
+
rescue ActiveRecord::RecordNotUnique
|
74
|
+
requested_singular_resource(params, user_context)
|
75
|
+
end
|
76
|
+
|
77
|
+
def requested_single_resource(params, _user_context)
|
78
|
+
LinkedRails.otp_secret_class.find_by(id: params[:id])
|
79
|
+
end
|
80
|
+
|
81
|
+
def route_key
|
82
|
+
'u/otp_secrets'
|
83
|
+
end
|
84
|
+
|
85
|
+
def singular_route_key
|
86
|
+
'u/otp_secret'
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module LinkedRails
|
4
|
+
module Auth
|
5
|
+
class Password < LinkedRails::Resource
|
6
|
+
enhance LinkedRails::Enhancements::Actionable
|
7
|
+
enhance LinkedRails::Enhancements::Creatable
|
8
|
+
enhance LinkedRails::Enhancements::Updatable
|
9
|
+
enhance LinkedRails::Enhancements::Singularable
|
10
|
+
attr_accessor :email, :password, :password_confirmation, :user, :reset_password_token
|
11
|
+
alias root_relative_iri root_relative_singular_iri
|
12
|
+
|
13
|
+
class << self
|
14
|
+
def action_list
|
15
|
+
LinkedRails.password_action_list_class
|
16
|
+
end
|
17
|
+
|
18
|
+
def decrypt_token(token)
|
19
|
+
Devise.token_generator.digest(self, :reset_password_token, token)
|
20
|
+
end
|
21
|
+
|
22
|
+
def form_class
|
23
|
+
LinkedRails.password_form_class
|
24
|
+
end
|
25
|
+
|
26
|
+
def iri_namespace
|
27
|
+
Vocab.ontola
|
28
|
+
end
|
29
|
+
|
30
|
+
def requested_singular_resource(params, _user_context)
|
31
|
+
reset_password_token = decrypt_token(params[:reset_password_token])
|
32
|
+
user_by_token ||= LinkedRails.user_class.find_by(reset_password_token: reset_password_token)
|
33
|
+
return new(reset_password_token: params[:reset_password_token]) if user_by_token.blank?
|
34
|
+
|
35
|
+
new(
|
36
|
+
reset_password_token: params[:reset_password_token],
|
37
|
+
user: user_by_token
|
38
|
+
)
|
39
|
+
end
|
40
|
+
|
41
|
+
def singular_route_key
|
42
|
+
'u/password'
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module LinkedRails
|
4
|
+
module Auth
|
5
|
+
class Registration < LinkedRails.user_class
|
6
|
+
enhance LinkedRails::Enhancements::Actionable
|
7
|
+
enhance LinkedRails::Enhancements::Creatable
|
8
|
+
enhance LinkedRails::Enhancements::Singularable
|
9
|
+
|
10
|
+
attr_accessor :redirect_url
|
11
|
+
|
12
|
+
class << self
|
13
|
+
def action_list
|
14
|
+
LinkedRails.registration_action_list_class
|
15
|
+
end
|
16
|
+
|
17
|
+
def form_class
|
18
|
+
LinkedRails.registration_form_class
|
19
|
+
end
|
20
|
+
|
21
|
+
def iri_namespace
|
22
|
+
Vocab.ontola
|
23
|
+
end
|
24
|
+
|
25
|
+
def iri_template
|
26
|
+
LinkedRails.user_class.iri_template
|
27
|
+
end
|
28
|
+
|
29
|
+
def requested_singular_resource(_params, user_context)
|
30
|
+
build_new(user_context: user_context)
|
31
|
+
end
|
32
|
+
|
33
|
+
def singular_route_key
|
34
|
+
'u/registration'
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module LinkedRails
|
4
|
+
module Auth
|
5
|
+
class Session < LinkedRails::Resource
|
6
|
+
enhance LinkedRails::Enhancements::Actionable
|
7
|
+
enhance LinkedRails::Enhancements::Creatable
|
8
|
+
enhance LinkedRails::Enhancements::Destroyable
|
9
|
+
enhance LinkedRails::Enhancements::Singularable
|
10
|
+
alias root_relative_iri root_relative_singular_iri
|
11
|
+
|
12
|
+
attr_accessor :email, :redirect_url
|
13
|
+
|
14
|
+
def singular_iri_opts
|
15
|
+
{redirect_url: redirect_url}
|
16
|
+
end
|
17
|
+
|
18
|
+
class << self
|
19
|
+
def action_list
|
20
|
+
LinkedRails.session_action_list_class
|
21
|
+
end
|
22
|
+
|
23
|
+
def form_class
|
24
|
+
LinkedRails.session_form_class
|
25
|
+
end
|
26
|
+
|
27
|
+
def iri_namespace
|
28
|
+
Vocab.ontola
|
29
|
+
end
|
30
|
+
|
31
|
+
def singular_iri_template
|
32
|
+
@singular_iri_template ||= URITemplate.new("/#{singular_route_key}{?redirect_url}")
|
33
|
+
end
|
34
|
+
alias iri_template singular_iri_template
|
35
|
+
|
36
|
+
def requested_singular_resource(params, _user_context)
|
37
|
+
new(redirect_url: params[:redirect_url])
|
38
|
+
end
|
39
|
+
|
40
|
+
def singular_route_key
|
41
|
+
'u/session'
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module LinkedRails
|
4
|
+
module Auth
|
5
|
+
class Unlock < LinkedRails::Resource
|
6
|
+
enhance LinkedRails::Enhancements::Actionable
|
7
|
+
enhance LinkedRails::Enhancements::Creatable
|
8
|
+
enhance LinkedRails::Enhancements::Updatable, except: %i[Serializer]
|
9
|
+
enhance LinkedRails::Enhancements::Singularable
|
10
|
+
attr_accessor :email, :unlock_token, :user
|
11
|
+
alias root_relative_iri root_relative_singular_iri
|
12
|
+
|
13
|
+
def anonymous_iri?
|
14
|
+
unlock_token.blank?
|
15
|
+
end
|
16
|
+
|
17
|
+
def singular_iri_opts
|
18
|
+
{unlock_token: unlock_token}
|
19
|
+
end
|
20
|
+
|
21
|
+
class << self
|
22
|
+
def action_list
|
23
|
+
LinkedRails.unlock_action_list_class
|
24
|
+
end
|
25
|
+
|
26
|
+
def decrypt_token(token)
|
27
|
+
Devise.token_generator.digest(self, :unlock_token, token)
|
28
|
+
end
|
29
|
+
|
30
|
+
def form_class
|
31
|
+
LinkedRails.unlock_form_class
|
32
|
+
end
|
33
|
+
|
34
|
+
def iri_namespace
|
35
|
+
Vocab.ontola
|
36
|
+
end
|
37
|
+
|
38
|
+
def singular_iri_template
|
39
|
+
@singular_iri_template ||= URITemplate.new("/#{singular_route_key}{?unlock_token}")
|
40
|
+
end
|
41
|
+
|
42
|
+
def singular_route_key
|
43
|
+
'u/unlock'
|
44
|
+
end
|
45
|
+
|
46
|
+
def requested_singular_resource(params, _user_context)
|
47
|
+
token = decrypt_token(token)
|
48
|
+
user_by_token ||= LinkedRails.user_class.find_by(unlock_token: token)
|
49
|
+
return new(unlock_token: params[:unlock_token]) if user_by_token.blank?
|
50
|
+
|
51
|
+
new(
|
52
|
+
unlock_token: params[:unlock_token],
|
53
|
+
user: user_by_token
|
54
|
+
)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|