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,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LinkedRails
4
+ module Auth
5
+ class AccessTokenPolicy < LinkedRails.policy_parent_class
6
+ permit_attributes %i[email password redirect_url]
7
+
8
+ def create?
9
+ true
10
+ end
11
+
12
+ def show?
13
+ true
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LinkedRails
4
+ module Auth
5
+ class ConfirmationPolicy < LinkedRails.policy_parent_class
6
+ permit_attributes %i[email]
7
+
8
+ def create?
9
+ true
10
+ end
11
+
12
+ def show?
13
+ true
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LinkedRails
4
+ module Auth
5
+ class OtpAttemptPolicy < LinkedRails.policy_parent_class
6
+ permit_attributes %i[otp_attempt]
7
+
8
+ def show?
9
+ user_context.guest?
10
+ end
11
+
12
+ def create?
13
+ user_context.guest? && record.active?
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LinkedRails
4
+ module Auth
5
+ class OtpSecretPolicy < LinkedRails.policy_parent_class
6
+ permit_attributes %i[otp_attempt]
7
+
8
+ def show?
9
+ user_context.guest? || current_user? || administrate_otp?
10
+ end
11
+
12
+ def create?
13
+ return forbid_with_message(I18n.t('messages.otp_secrets.already_exists')) if user_context.otp_active?
14
+
15
+ user_context.guest? || current_user?
16
+ end
17
+
18
+ def destroy?
19
+ raise(ActiveRecord::RecordNotFound) unless administrate_otp? || current_user?
20
+
21
+ return forbid_with_message(I18n.t('messages.otp_secrets.not_activated')) unless record.active?
22
+
23
+ current_user? || administrate_otp?
24
+ end
25
+
26
+ private
27
+
28
+ def current_user?
29
+ record.owner_id == user_context.id
30
+ end
31
+
32
+ def administrate_otp?
33
+ false
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LinkedRails
4
+ module Auth
5
+ class PasswordPolicy < LinkedRails.policy_parent_class
6
+ permit_attributes %i[password password_confirmation reset_password_token],
7
+ has_properties: {reset_password_token: true}
8
+ permit_attributes %i[email], has_properties: {reset_password_token: false}
9
+
10
+ def create?
11
+ true
12
+ end
13
+
14
+ def update?
15
+ true
16
+ end
17
+
18
+ def show?
19
+ true
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LinkedRails
4
+ module Auth
5
+ class RegistrationPolicy < Pundit::PolicyFinder.new(LinkedRails.user_class).policy || LinkedRails.policy_parent_class
6
+ permit_attributes %i[email password password_confirmation redirect_url]
7
+
8
+ def create?
9
+ true
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LinkedRails
4
+ module Auth
5
+ class SessionPolicy < LinkedRails.policy_parent_class
6
+ permit_attributes %i[email redirect_url]
7
+
8
+ def create?
9
+ true
10
+ end
11
+
12
+ def show?
13
+ true
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LinkedRails
4
+ module Auth
5
+ class UnlockPolicy < LinkedRails.policy_parent_class
6
+ permit_attributes %i[email]
7
+
8
+ def create?
9
+ true
10
+ end
11
+
12
+ def update?
13
+ true
14
+ end
15
+
16
+ def show?
17
+ true
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LinkedRails
4
+ module Auth
5
+ class AccessTokenSerializer < LinkedRails.serializer_parent_class
6
+ attribute :email, predicate: Vocab.schema.email, datatype: RDF::XSD[:string]
7
+ attribute :password, predicate: Vocab.ontola[:password], datatype: RDF::XSD[:string]
8
+ attribute :redirect_url, predicate: Vocab.ontola[:redirectUrl], datatype: RDF::XSD[:string]
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LinkedRails
4
+ module Auth
5
+ class ConfirmationSerializer < LinkedRails.serializer_parent_class
6
+ attribute :email, predicate: Vocab.schema.email, datatype: RDF::XSD[:string]
7
+ attribute :redirect_url, predicate: Vocab.ontola[:redirectUrl]
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LinkedRails
4
+ module Auth
5
+ class OtpAttemptSerializer < LinkedRails.serializer_parent_class
6
+ attribute :otp_attempt,
7
+ predicate: LinkedRails.app_ns[:otp],
8
+ datatype: RDF::XSD[:integer],
9
+ if: method(:never)
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LinkedRails
4
+ module Auth
5
+ class OtpSecretSerializer < LinkedRails.serializer_parent_class
6
+ attribute :otp_attempt,
7
+ predicate: LinkedRails.app_ns[:otp],
8
+ datatype: RDF::XSD[:integer],
9
+ if: method(:never)
10
+ attribute :active, predicate: LinkedRails.app_ns[:otpActive]
11
+ has_one :image, predicate: Vocab.schema.image
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LinkedRails
4
+ module Auth
5
+ class PasswordSerializer < LinkedRails.serializer_parent_class
6
+ attribute :email, predicate: Vocab.schema.email, datatype: RDF::XSD[:string]
7
+ attribute :password,
8
+ predicate: Vocab.ontola[:password],
9
+ datatype: Vocab.ontola['datatype/password'],
10
+ if: method(:never)
11
+ attribute :password_confirmation,
12
+ predicate: Vocab.ontola[:passwordConfirmation],
13
+ datatype: Vocab.ontola['datatype/password'],
14
+ if: method(:never)
15
+ attribute :reset_password_token, predicate: Vocab.ontola[:resetPasswordToken], datatype: RDF::XSD[:string]
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LinkedRails
4
+ module Auth
5
+ class RegistrationSerializer < RDF::Serializers.serializer_for(LinkedRails.user_class) || LinkedRails.serializer_parent_class
6
+ attribute :email, predicate: Vocab.schema.email, datatype: RDF::XSD[:string]
7
+ attribute :password, predicate: Vocab.ontola[:password], datatype: RDF::XSD[:string]
8
+ attribute :password_confirmation, predicate: Vocab.ontola[:passwordConfirmation], datatype: RDF::XSD[:string]
9
+ attribute :redirect_url, predicate: Vocab.ontola[:redirectUrl], datatype: RDF::XSD[:string]
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LinkedRails
4
+ module Auth
5
+ class SessionSerializer < LinkedRails.serializer_parent_class
6
+ attribute :email, predicate: Vocab.schema.email, datatype: RDF::XSD[:string]
7
+ attribute :redirect_url, predicate: Vocab.ontola[:redirectUrl], datatype: RDF::XSD[:string]
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LinkedRails
4
+ module Auth
5
+ class UnlockSerializer < LinkedRails.serializer_parent_class
6
+ attribute :email, predicate: Vocab.schema.email, datatype: RDF::XSD[:string]
7
+ end
8
+ end
9
+ end
data/config/routes.rb ADDED
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ Rails.application.routes.draw do
4
+ end
@@ -0,0 +1,155 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators"
4
+ require "rails/generators/active_record"
5
+
6
+ module LinkedRails
7
+ module Auth
8
+ class InstallGenerator < ::Rails::Generators::Base
9
+ include ::Rails::Generators::Migration
10
+ source_root File.expand_path("templates", __dir__)
11
+ desc "Installs LinkedRails Auth."
12
+
13
+ def install
14
+ template "doorkeeper_jwt_initializer.rb", "config/initializers/doorkeeper_jwt.rb"
15
+ template "locales.yml", "config/locales/linked_rails_auth.en.yml"
16
+ route "use_linked_rails_auth"
17
+
18
+ migration_template(
19
+ "migration.rb.erb",
20
+ "db/migrate/install_linked_rails_auth.rb",
21
+ migration_version: migration_version
22
+ )
23
+ update_user_model
24
+ insert_doorkeeper
25
+ create_doorkeeper_app
26
+
27
+ readme "README"
28
+ end
29
+
30
+ private
31
+
32
+ def create_doorkeeper_app
33
+ if Doorkeeper::Application.any?
34
+ Rails.logger.info('Skipping Doorkeeper app creation, already exists')
35
+
36
+ return
37
+ end
38
+ client_id = ask('Enter the client_id of your doorkeeper app', default: 'client_id')
39
+ client_secret = ask('Enter the client_secret of your doorkeeper app', default: 'client_secret')
40
+ client_token = ask('Enter the client_token of your doorkeeper app. Leave empty to create new.')
41
+
42
+ libro_app = Doorkeeper::Application.find_or_initialize_by(uid: client_id) do |app|
43
+ app.name = 'Libro'
44
+ app.redirect_uri = 'http://example.com/'
45
+ app.scopes = 'guest user'
46
+ app.secret = client_secret
47
+ end
48
+ libro_app.save!(validate: false)
49
+ # rubocop:disable Rails/SkipsModelValidations
50
+ libro_app.update_columns(uid: client_id, secret: client_secret)
51
+ # rubocop:enable Rails/SkipsModelValidations
52
+
53
+ ActiveRecord::Base.connection.try(:reset_pk_sequence!, Doorkeeper::Application.table_name)
54
+
55
+ token = Doorkeeper::AccessToken.find_or_create_for(
56
+ application: libro_app,
57
+ scopes: 'service',
58
+ expires_in: 10.years.to_i,
59
+ resource_owner: nil,
60
+ use_refresh_token: true
61
+ )
62
+
63
+ if client_token.present?
64
+ token.update(token: client_token)
65
+ else
66
+ Rails.logger.info("Generated client token: #{token.token}")
67
+ end
68
+ end
69
+
70
+ def insert_doorkeeper
71
+ file = 'config/initializers/doorkeeper.rb'
72
+ data = "api_only\n"\
73
+ "base_controller 'ApplicationController'\n"\
74
+ "base_metal_controller 'ApplicationController'\n"
75
+ inject_into_file file, optimize_indentation(data, 2), after: "orm :active_record\n", verbose: false
76
+ uncomment_lines file, "access_token_generator '::Doorkeeper::JWT'"
77
+ uncomment_lines file, 'use_refresh_token'
78
+
79
+ replace_doorkeeper_line(
80
+ '# default_scopes :public',
81
+ 'default_scopes :guest'
82
+ )
83
+ replace_doorkeeper_line(
84
+ '# optional_scopes :write, :update',
85
+ 'optional_scopes :user'
86
+ )
87
+ replace_doorkeeper_line(
88
+ '# grant_flows %w[authorization_code client_credentials]',
89
+ 'grant_flows %w[client_credentials authorization_code password]'
90
+ )
91
+ replace_doorkeeper_line("resource_owner_authenticator do\n(.*?)end\n", authentication, true)
92
+ end
93
+
94
+ def authentication
95
+ <<-FOO
96
+ resource_owner_authenticator do
97
+ if doorkeeper_token&.acceptable?('user')
98
+ User.find_by(id: doorkeeper_token.resource_owner_id)
99
+ elsif doorkeeper_token&.acceptable?('guest') && doorkeeper_token_payload['user']
100
+ LinkedRails.guest_user_class.new(id: doorkeeper_token.resource_owner_id)
101
+ end
102
+ end
103
+
104
+ resource_owner_from_credentials do
105
+ request.params[:user] = request.params[:access_token] || {}
106
+ request.params[:user][:email] ||= (request.params[:username] || request.params[:email])&.downcase
107
+ request.params[:user][:password] ||= request.params[:token] || request.params[:password]
108
+ request.env['devise.allow_params_authentication'] = true
109
+ user =
110
+ if request.params[:scope] == 'guest'
111
+ LinkedRails.guest_user_class.new
112
+ else
113
+ request.env['warden'].authenticate(scope: :user, store: false)
114
+ end
115
+ raise_login_error(request) if user.blank?
116
+ request.env['warden'].logout
117
+ user
118
+ end
119
+ FOO
120
+ end
121
+
122
+ def inject_controller_include
123
+ sentinel = /LinkedRails::Controller\n/m
124
+ in_root do
125
+ inject_into_file(
126
+ "app/controllers/application_controller.rb",
127
+ optimize_indentation('include LinkedRails::Auth::AuthHelper', 2),
128
+ after: sentinel
129
+ )
130
+ end
131
+ end
132
+
133
+ def migration_version
134
+ "[#{ActiveRecord::VERSION::MAJOR}.#{ActiveRecord::VERSION::MINOR}]"
135
+ end
136
+
137
+ def replace_doorkeeper_line(old, new, multi_line_sentinel = false)
138
+ sentinel = multi_line_sentinel ? /^(\s*)#{old}/m : /^(\s*)#[[:blank:]]*(.*#{old})/
139
+ gsub_file('config/initializers/doorkeeper.rb', sentinel, new)
140
+ end
141
+
142
+ def update_user_model
143
+ file = 'app/models/user.rb'
144
+ no_guest = "\ndef guest?\n false\nend"
145
+ inject_into_file file, optimize_indentation(no_guest, 2), after: ":recoverable, :rememberable, :validatable\n", verbose: false
146
+ end
147
+
148
+ class << self
149
+ def next_migration_number(dirname)
150
+ ActiveRecord::Generators::Base.next_migration_number(dirname)
151
+ end
152
+ end
153
+ end
154
+ end
155
+ end