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.
- checksums.yaml +7 -0
- data/LICENSE +674 -0
- data/README.md +28 -0
- data/Rakefile +34 -0
- data/app/actions/linked_rails/auth/access_token_action_list.rb +16 -0
- data/app/actions/linked_rails/auth/confirmation_action_list.rb +17 -0
- data/app/actions/linked_rails/auth/otp_attempt_action_list.rb +13 -0
- data/app/actions/linked_rails/auth/otp_secret_action_list.rb +31 -0
- data/app/actions/linked_rails/auth/password_action_list.rb +25 -0
- data/app/actions/linked_rails/auth/registration_action_list.rb +15 -0
- data/app/actions/linked_rails/auth/session_action_list.rb +22 -0
- data/app/actions/linked_rails/auth/unlock_action_list.rb +17 -0
- data/app/controllers/linked_rails/auth/access_tokens_controller.rb +131 -0
- data/app/controllers/linked_rails/auth/confirmations_controller.rb +87 -0
- data/app/controllers/linked_rails/auth/otp_attempts_controller.rb +21 -0
- data/app/controllers/linked_rails/auth/otp_secrets_controller.rb +40 -0
- data/app/controllers/linked_rails/auth/passwords_controller.rb +63 -0
- data/app/controllers/linked_rails/auth/registrations_controller.rb +33 -0
- data/app/controllers/linked_rails/auth/sessions_controller.rb +55 -0
- data/app/controllers/linked_rails/auth/unlocks_controller.rb +44 -0
- data/app/forms/linked_rails/auth/access_token_form.rb +20 -0
- data/app/forms/linked_rails/auth/confirmation_form.rb +9 -0
- data/app/forms/linked_rails/auth/otp_attempt_form.rb +9 -0
- data/app/forms/linked_rails/auth/otp_secret_form.rb +12 -0
- data/app/forms/linked_rails/auth/password_form.rb +21 -0
- data/app/forms/linked_rails/auth/registration_form.rb +21 -0
- data/app/forms/linked_rails/auth/session_form.rb +13 -0
- data/app/forms/linked_rails/auth/unlock_form.rb +9 -0
- data/app/helpers/linked_rails/auth/otp_helper.rb +30 -0
- data/app/models/linked_rails/auth/access_token.rb +40 -0
- data/app/models/linked_rails/auth/confirmation.rb +70 -0
- data/app/models/linked_rails/auth/guest_user.rb +29 -0
- data/app/models/linked_rails/auth/otp_attempt.rb +41 -0
- data/app/models/linked_rails/auth/otp_base.rb +57 -0
- data/app/models/linked_rails/auth/otp_secret.rb +91 -0
- data/app/models/linked_rails/auth/password.rb +47 -0
- data/app/models/linked_rails/auth/registration.rb +39 -0
- data/app/models/linked_rails/auth/session.rb +46 -0
- data/app/models/linked_rails/auth/unlock.rb +59 -0
- data/app/policies/linked_rails/auth/access_token_policy.rb +17 -0
- data/app/policies/linked_rails/auth/confirmation_policy.rb +17 -0
- data/app/policies/linked_rails/auth/otp_attempt_policy.rb +17 -0
- data/app/policies/linked_rails/auth/otp_secret_policy.rb +37 -0
- data/app/policies/linked_rails/auth/password_policy.rb +23 -0
- data/app/policies/linked_rails/auth/registration_policy.rb +13 -0
- data/app/policies/linked_rails/auth/session_policy.rb +17 -0
- data/app/policies/linked_rails/auth/unlock_policy.rb +21 -0
- data/app/serializers/linked_rails/auth/access_token_serializer.rb +11 -0
- data/app/serializers/linked_rails/auth/confirmation_serializer.rb +10 -0
- data/app/serializers/linked_rails/auth/otp_attempt_serializer.rb +12 -0
- data/app/serializers/linked_rails/auth/otp_secret_serializer.rb +14 -0
- data/app/serializers/linked_rails/auth/password_serializer.rb +18 -0
- data/app/serializers/linked_rails/auth/registration_serializer.rb +12 -0
- data/app/serializers/linked_rails/auth/session_serializer.rb +10 -0
- data/app/serializers/linked_rails/auth/unlock_serializer.rb +9 -0
- data/config/routes.rb +4 -0
- data/lib/generators/linked_rails/auth/install_generator.rb +155 -0
- data/lib/generators/linked_rails/auth/templates/README +2 -0
- data/lib/generators/linked_rails/auth/templates/doorkeeper_jwt_initializer.rb +52 -0
- data/lib/generators/linked_rails/auth/templates/locales.yml +39 -0
- data/lib/generators/linked_rails/auth/templates/migration.rb.erb +20 -0
- data/lib/linked_rails/auth/auth_helper.rb +101 -0
- data/lib/linked_rails/auth/controller/error_handling.rb +17 -0
- data/lib/linked_rails/auth/controller.rb +16 -0
- data/lib/linked_rails/auth/engine.rb +8 -0
- data/lib/linked_rails/auth/errors/account_locked.rb +10 -0
- data/lib/linked_rails/auth/errors/expired.rb +10 -0
- data/lib/linked_rails/auth/errors/unauthorized.rb +10 -0
- data/lib/linked_rails/auth/errors/unknown_email.rb +14 -0
- data/lib/linked_rails/auth/errors/wrong_password.rb +14 -0
- data/lib/linked_rails/auth/errors.rb +7 -0
- data/lib/linked_rails/auth/routes.rb +86 -0
- data/lib/linked_rails/auth/version.rb +7 -0
- data/lib/linked_rails/auth.rb +46 -0
- data/lib/tasks/linked_rails/auth_tasks.rake +6 -0
- 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
|