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
data/README.md ADDED
@@ -0,0 +1,28 @@
1
+ # LinkedRails::Auth
2
+ Short description and motivation.
3
+
4
+ ## Usage
5
+ How to use my plugin.
6
+
7
+ ## Installation
8
+ Add this line to your application's Gemfile:
9
+
10
+ ```ruby
11
+ gem 'linked_rails-auth'
12
+ ```
13
+
14
+ And then execute:
15
+ ```bash
16
+ $ bundle
17
+ ```
18
+
19
+ Or install it yourself as:
20
+ ```bash
21
+ $ gem install linked_rails-auth
22
+ ```
23
+
24
+ ## Contributing
25
+ Contribution directions go here.
26
+
27
+ ## License
28
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ begin
4
+ require 'bundler/setup'
5
+ rescue LoadError
6
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
7
+ end
8
+
9
+ require 'rdoc/task'
10
+
11
+ RDoc::Task.new(:rdoc) do |rdoc|
12
+ rdoc.rdoc_dir = 'rdoc'
13
+ rdoc.title = 'LinkedRails::Auth'
14
+ rdoc.options << '--line-numbers'
15
+ rdoc.rdoc_files.include('README.md')
16
+ rdoc.rdoc_files.include('lib/**/*.rb')
17
+ end
18
+
19
+ APP_RAKEFILE = File.expand_path('test/dummy/Rakefile', __dir__)
20
+ load 'rails/tasks/engine.rake'
21
+
22
+ load 'rails/tasks/statistics.rake'
23
+
24
+ require 'bundler/gem_tasks'
25
+
26
+ require 'rake/testtask'
27
+
28
+ Rake::TestTask.new(:test) do |t|
29
+ t.libs << 'test'
30
+ t.pattern = 'test/**/*_test.rb'
31
+ t.verbose = false
32
+ end
33
+
34
+ task default: :test
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LinkedRails
4
+ module Auth
5
+ class AccessTokenActionList < LinkedRails.action_list_parent_class
6
+ def self.actionable_class
7
+ LinkedRails.access_token_class
8
+ end
9
+
10
+ has_singular_create_action(
11
+ type: [Vocab.ontola['Create::Auth::AccessToken'], Vocab.schema.CreateAction],
12
+ url: -> { LinkedRails.iri(path: '/login') }
13
+ )
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LinkedRails
4
+ module Auth
5
+ class ConfirmationActionList < LinkedRails.action_list_parent_class
6
+ def self.actionable_class
7
+ LinkedRails.confirmation_class
8
+ end
9
+
10
+ has_singular_create_action(
11
+ form: -> { resource.class.try(:form_class) },
12
+ type: [Vocab.ontola['Create::Auth::Confirmation'], Vocab.schema.CreateAction]
13
+ )
14
+ has_singular_update_action(form: -> { nil })
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LinkedRails
4
+ module Auth
5
+ class OtpAttemptActionList < LinkedRails.action_list_parent_class
6
+ def self.actionable_class
7
+ LinkedRails.otp_attempt_class
8
+ end
9
+
10
+ has_singular_create_action(type: Vocab.schema[:CreateAction])
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LinkedRails
4
+ module Auth
5
+ class OtpSecretActionList < LinkedRails.action_list_parent_class
6
+ def self.actionable_class
7
+ LinkedRails.otp_secret_class
8
+ end
9
+
10
+ has_singular_create_action(
11
+ type: Vocab.schema[:CreateAction],
12
+ url: lambda {
13
+ LinkedRails.iri(
14
+ path: 'u/otp_secret',
15
+ query: {session: resource.encoded_session}.compact.to_param.presence
16
+ )
17
+ },
18
+ root_relative_iri: lambda {
19
+ RDF::URI(
20
+ path: '/u/otp_secret/new',
21
+ query: {session: resource.encoded_session}.compact.to_param.presence
22
+ )
23
+ }
24
+ )
25
+
26
+ has_singular_destroy_action(
27
+ description: -> { I18n.t('actions.otp_secrets.destroy.description', name: resource.owner.display_name) }
28
+ )
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LinkedRails
4
+ module Auth
5
+ class PasswordActionList < LinkedRails.action_list_parent_class
6
+ def self.actionable_class
7
+ LinkedRails.password_class
8
+ end
9
+
10
+ has_singular_create_action(
11
+ type: [Vocab.ontola['Create::Auth::Password'], Vocab.schema.CreateAction]
12
+ )
13
+
14
+ has_singular_update_action(
15
+ label: nil,
16
+ root_relative_iri: lambda {
17
+ RDF::URI(
18
+ path: '/u/password/edit',
19
+ query: {reset_password_token: resource.reset_password_token}.compact.to_param.presence
20
+ )
21
+ }
22
+ )
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LinkedRails
4
+ module Auth
5
+ class RegistrationActionList < LinkedRails.action_list_parent_class
6
+ def self.actionable_class
7
+ LinkedRails.registration_class
8
+ end
9
+
10
+ has_singular_create_action(
11
+ root_relative_iri: '/u/registration/new'
12
+ )
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LinkedRails
4
+ module Auth
5
+ class SessionActionList < LinkedRails.action_list_parent_class
6
+ def self.actionable_class
7
+ LinkedRails.session_class
8
+ end
9
+
10
+ has_singular_create_action(
11
+ root_relative_iri: lambda {
12
+ uri = resource.root_relative_iri.dup
13
+ uri.path ||= ''
14
+ uri.path += '/new'
15
+ uri.query = {redirect_url: resource.redirect_url}.compact.to_param.presence
16
+ uri.to_s
17
+ },
18
+ type: [Vocab.ontola['Create::Auth::Session'], Vocab.schema.CreateAction]
19
+ )
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LinkedRails
4
+ module Auth
5
+ class UnlockActionList < LinkedRails.action_list_parent_class
6
+ def self.actionable_class
7
+ LinkedRails.unlock_class
8
+ end
9
+
10
+ has_singular_create_action(
11
+ form: -> { resource.class.try(:form_class) }
12
+ )
13
+
14
+ has_singular_update_action
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,131 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LinkedRails
4
+ module Auth
5
+ class AccessTokensController < Doorkeeper::TokensController # rubocop:disable Metrics/ClassLength
6
+ def create
7
+ headers.merge!(authorize_response.headers)
8
+
9
+ create_success_effects if authorize_response.status == :ok
10
+
11
+ render json: authorize_response.body, status: authorize_response.status
12
+ rescue Doorkeeper::Errors::DoorkeeperError => e
13
+ handle_token_exception(e)
14
+ end
15
+
16
+ private
17
+
18
+ def create_success_effects
19
+ if enter_otp? && !strategy.is_a?(Doorkeeper::Request::RefreshToken)
20
+ redirect_to_otp_attempt
21
+ elsif !otp_activated? && otp_setup_required? && !strategy.is_a?(Doorkeeper::Request::RefreshToken)
22
+ redirect_to_otp_secret
23
+ else
24
+ handle_new_token
25
+ end
26
+ end
27
+
28
+ def enter_otp?
29
+ otp_activated?
30
+ end
31
+
32
+ def handle_new_token
33
+ update_oauth_token(authorize_response.token)
34
+ response.headers['Location'] = redirect_url_param if redirect_url_param
35
+ end
36
+
37
+ def handle_token_exception(exception)
38
+ active_response_block do
39
+ case active_response_type
40
+ when :json
41
+ handle_token_exception_json(exception)
42
+ else
43
+ respond_with_invalid_resource(resource: token_with_errors(exception))
44
+ end
45
+ end
46
+ end
47
+
48
+ def handle_token_exception_json(exception) # rubocop:disable Metrics/AbcSize
49
+ error = get_error_response_from_exception(exception)
50
+ headers.merge!(error.headers)
51
+ Bugsnag.notify(exception)
52
+ Rails.logger.info(error.body.merge(class: exception.class.name).to_json)
53
+ self.response_body = error.body.merge(class: exception.class.name).to_json
54
+ self.status = error.status
55
+ end
56
+
57
+ def otp_activated?
58
+ @otp_activated ||= LinkedRails.otp_secret_class.activated?(authorize_response.token.resource_owner_id)
59
+ end
60
+
61
+ def otp_attempt_form_iri
62
+ LinkedRails.iri(path: 'u/otp_attempt/new', query: {session: session_param}.to_param)
63
+ end
64
+
65
+ def otp_secret_form_iri
66
+ LinkedRails.iri(path: 'u/otp_secret/new', query: {session: session_param}.to_param)
67
+ end
68
+
69
+ def otp_setup_required?
70
+ false
71
+ end
72
+
73
+ def redirect_to_otp_attempt
74
+ response.headers['Location'] = otp_attempt_form_iri.to_s
75
+ @authorize_response = Doorkeeper::OAuth::TokenResponse.new(Doorkeeper::AccessToken.new)
76
+ end
77
+
78
+ def redirect_to_otp_secret
79
+ response.headers['Location'] = otp_secret_form_iri.to_s
80
+ @authorize_response = Doorkeeper::OAuth::TokenResponse.new(Doorkeeper::AccessToken.new)
81
+ end
82
+
83
+ def session_param
84
+ sign_payload(
85
+ exp: 10.minutes.from_now.to_i,
86
+ user_id: authorize_response.token.resource_owner_id,
87
+ redirect_uri: redirect_url_param
88
+ )
89
+ end
90
+
91
+ def sign_payload(payload)
92
+ JWT.encode(
93
+ payload,
94
+ Doorkeeper::JWT.configuration.secret_key,
95
+ Doorkeeper::JWT.configuration.encryption_method.to_s.upcase
96
+ )
97
+ end
98
+
99
+ def raise_login_error(request) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
100
+ raise(
101
+ if LinkedRails.user_class.find_by(email: request.params[:user][:email]).nil?
102
+ LinkedRails::Auth::Errors::UnknownEmail.new
103
+ elsif request.env['warden'].message == :invalid
104
+ LinkedRails::Auth::Errors::WrongPassword.new
105
+ elsif request.env['warden'].message == :not_found_in_database
106
+ LinkedRails::Auth::Errors::WrongPassword.new
107
+ else
108
+ "unhandled login state #{request.env['warden'].message}"
109
+ end
110
+ )
111
+ end
112
+
113
+ def redirect_url_param
114
+ params[:access_token].try(:[], :redirect_url) || params[:redirect_url]
115
+ end
116
+
117
+ def token_with_errors(exception)
118
+ token_with_errors = AccessToken.new
119
+ field = [LinkedRails::Auth::Errors::WrongPassword].include?(exception.class) ? :password : :email
120
+ token_with_errors.errors.add(field, exception.message)
121
+ token_with_errors
122
+ end
123
+
124
+ class << self
125
+ def controller_class
126
+ LinkedRails.access_token_class
127
+ end
128
+ end
129
+ end
130
+ end
131
+ end
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LinkedRails
4
+ module Auth
5
+ class ConfirmationsController < Devise::ConfirmationsController
6
+ active_response :show, :update
7
+
8
+ private
9
+
10
+ def after_confirmation_path_for(_resource_name, _resource)
11
+ if current_user.guest?
12
+ LinkedRails.iri(path: '/u/session/new').path
13
+ else
14
+ LinkedRails.iri.path
15
+ end
16
+ end
17
+
18
+ def after_resending_confirmation_instructions_path_for(_resource_name)
19
+ LinkedRails.iri(path: '/u/session/new').path
20
+ end
21
+
22
+ def already_confirmed_notice
23
+ I18n.t('errors.messages.already_confirmed')
24
+ end
25
+
26
+ def create_failure_message
27
+ current_resource!.errors.full_messages.join("\n")
28
+ end
29
+
30
+ def create_execute
31
+ @current_resource = resource_class.send_confirmation_instructions(resource_params)
32
+
33
+ successfully_sent?(current_resource!)
34
+ end
35
+
36
+ def create_success_location
37
+ after_resending_confirmation_instructions_path_for(resource_name)
38
+ end
39
+
40
+ def create_success_message
41
+ find_message(:send_instructions)
42
+ end
43
+
44
+ def original_token
45
+ @original_token ||= params[:confirmation_token]
46
+ end
47
+
48
+ def resource_params
49
+ params.fetch(resource_name, nil) ||
50
+ params.fetch(controller_name.singularize, {})
51
+ end
52
+
53
+ def show_success
54
+ return super unless current_resource!.confirmed?
55
+
56
+ add_exec_action_header(response.headers, ontola_redirect_action(current_resource!.redirect_url))
57
+ add_exec_action_header(response.headers, ontola_snackbar_action(already_confirmed_notice))
58
+
59
+ super
60
+ end
61
+
62
+ def update_execute
63
+ current_resource!.confirm!
64
+ end
65
+
66
+ def update_failure
67
+ respond_with_redirect(
68
+ location: after_confirmation_path_for(resource_name, current_resource!),
69
+ notice: user_by_token.errors.full_messages.first
70
+ )
71
+ end
72
+
73
+ def update_success
74
+ respond_with_redirect(
75
+ location: after_confirmation_path_for(resource_name, current_resource!),
76
+ notice: find_message(:confirmed)
77
+ )
78
+ end
79
+
80
+ class << self
81
+ def controller_class
82
+ LinkedRails.confirmation_class
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end