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
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