linked_rails-auth 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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