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.
Files changed (76) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +674 -0
  3. data/README.md +28 -0
  4. data/Rakefile +34 -0
  5. data/app/actions/linked_rails/auth/access_token_action_list.rb +16 -0
  6. data/app/actions/linked_rails/auth/confirmation_action_list.rb +17 -0
  7. data/app/actions/linked_rails/auth/otp_attempt_action_list.rb +13 -0
  8. data/app/actions/linked_rails/auth/otp_secret_action_list.rb +31 -0
  9. data/app/actions/linked_rails/auth/password_action_list.rb +25 -0
  10. data/app/actions/linked_rails/auth/registration_action_list.rb +15 -0
  11. data/app/actions/linked_rails/auth/session_action_list.rb +22 -0
  12. data/app/actions/linked_rails/auth/unlock_action_list.rb +17 -0
  13. data/app/controllers/linked_rails/auth/access_tokens_controller.rb +131 -0
  14. data/app/controllers/linked_rails/auth/confirmations_controller.rb +87 -0
  15. data/app/controllers/linked_rails/auth/otp_attempts_controller.rb +21 -0
  16. data/app/controllers/linked_rails/auth/otp_secrets_controller.rb +40 -0
  17. data/app/controllers/linked_rails/auth/passwords_controller.rb +63 -0
  18. data/app/controllers/linked_rails/auth/registrations_controller.rb +33 -0
  19. data/app/controllers/linked_rails/auth/sessions_controller.rb +55 -0
  20. data/app/controllers/linked_rails/auth/unlocks_controller.rb +44 -0
  21. data/app/forms/linked_rails/auth/access_token_form.rb +20 -0
  22. data/app/forms/linked_rails/auth/confirmation_form.rb +9 -0
  23. data/app/forms/linked_rails/auth/otp_attempt_form.rb +9 -0
  24. data/app/forms/linked_rails/auth/otp_secret_form.rb +12 -0
  25. data/app/forms/linked_rails/auth/password_form.rb +21 -0
  26. data/app/forms/linked_rails/auth/registration_form.rb +21 -0
  27. data/app/forms/linked_rails/auth/session_form.rb +13 -0
  28. data/app/forms/linked_rails/auth/unlock_form.rb +9 -0
  29. data/app/helpers/linked_rails/auth/otp_helper.rb +30 -0
  30. data/app/models/linked_rails/auth/access_token.rb +40 -0
  31. data/app/models/linked_rails/auth/confirmation.rb +70 -0
  32. data/app/models/linked_rails/auth/guest_user.rb +29 -0
  33. data/app/models/linked_rails/auth/otp_attempt.rb +41 -0
  34. data/app/models/linked_rails/auth/otp_base.rb +57 -0
  35. data/app/models/linked_rails/auth/otp_secret.rb +91 -0
  36. data/app/models/linked_rails/auth/password.rb +47 -0
  37. data/app/models/linked_rails/auth/registration.rb +39 -0
  38. data/app/models/linked_rails/auth/session.rb +46 -0
  39. data/app/models/linked_rails/auth/unlock.rb +59 -0
  40. data/app/policies/linked_rails/auth/access_token_policy.rb +17 -0
  41. data/app/policies/linked_rails/auth/confirmation_policy.rb +17 -0
  42. data/app/policies/linked_rails/auth/otp_attempt_policy.rb +17 -0
  43. data/app/policies/linked_rails/auth/otp_secret_policy.rb +37 -0
  44. data/app/policies/linked_rails/auth/password_policy.rb +23 -0
  45. data/app/policies/linked_rails/auth/registration_policy.rb +13 -0
  46. data/app/policies/linked_rails/auth/session_policy.rb +17 -0
  47. data/app/policies/linked_rails/auth/unlock_policy.rb +21 -0
  48. data/app/serializers/linked_rails/auth/access_token_serializer.rb +11 -0
  49. data/app/serializers/linked_rails/auth/confirmation_serializer.rb +10 -0
  50. data/app/serializers/linked_rails/auth/otp_attempt_serializer.rb +12 -0
  51. data/app/serializers/linked_rails/auth/otp_secret_serializer.rb +14 -0
  52. data/app/serializers/linked_rails/auth/password_serializer.rb +18 -0
  53. data/app/serializers/linked_rails/auth/registration_serializer.rb +12 -0
  54. data/app/serializers/linked_rails/auth/session_serializer.rb +10 -0
  55. data/app/serializers/linked_rails/auth/unlock_serializer.rb +9 -0
  56. data/config/routes.rb +4 -0
  57. data/lib/generators/linked_rails/auth/install_generator.rb +155 -0
  58. data/lib/generators/linked_rails/auth/templates/README +2 -0
  59. data/lib/generators/linked_rails/auth/templates/doorkeeper_jwt_initializer.rb +52 -0
  60. data/lib/generators/linked_rails/auth/templates/locales.yml +39 -0
  61. data/lib/generators/linked_rails/auth/templates/migration.rb.erb +20 -0
  62. data/lib/linked_rails/auth/auth_helper.rb +101 -0
  63. data/lib/linked_rails/auth/controller/error_handling.rb +17 -0
  64. data/lib/linked_rails/auth/controller.rb +16 -0
  65. data/lib/linked_rails/auth/engine.rb +8 -0
  66. data/lib/linked_rails/auth/errors/account_locked.rb +10 -0
  67. data/lib/linked_rails/auth/errors/expired.rb +10 -0
  68. data/lib/linked_rails/auth/errors/unauthorized.rb +10 -0
  69. data/lib/linked_rails/auth/errors/unknown_email.rb +14 -0
  70. data/lib/linked_rails/auth/errors/wrong_password.rb +14 -0
  71. data/lib/linked_rails/auth/errors.rb +7 -0
  72. data/lib/linked_rails/auth/routes.rb +86 -0
  73. data/lib/linked_rails/auth/version.rb +7 -0
  74. data/lib/linked_rails/auth.rb +46 -0
  75. data/lib/tasks/linked_rails/auth_tasks.rake +6 -0
  76. 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