api-blocks 0.2.1 → 0.4.0
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 +4 -4
- data/lib/api_blocks/controller.rb +19 -4
- data/lib/api_blocks/doorkeeper/invitations/application.rb +17 -0
- data/lib/api_blocks/doorkeeper/invitations/controller.rb +102 -0
- data/lib/api_blocks/doorkeeper/invitations/migration_generator.rb +34 -0
- data/lib/api_blocks/doorkeeper/invitations.rb +9 -0
- data/lib/api_blocks/doorkeeper/passwords/controller.rb +14 -13
- data/lib/api_blocks/doorkeeper.rb +1 -0
- data/lib/api_blocks/railtie.rb +17 -0
- data/lib/api_blocks/responder.rb +23 -3
- data/lib/api_blocks/version.rb +1 -1
- metadata +19 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 66461c9b8a8937195f5481be3ecf649923d9a9534e7cb4d1194d0b31e1325b92
|
4
|
+
data.tar.gz: 81ac3b5a4c0f1d5c4ed3b0653d57e1aa6e343cd03dbdf17a3f400248ddbff840
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0d0d11a13c33b6fbd07a0e4c54e4a36b89cc3680443908d4db4ec76600c4735a822e9f12ed3e081e5d860cb121ae3c7d30cd3fd8849508ca6ddad180c3fbbc8d
|
7
|
+
data.tar.gz: b828665074662b5a2c91739efeb3728d502a22c215688ddd0061cce3b0656370c8d539610d103f522112b160bc922a931934fc4993104cf91cdd9425ce7b1d02
|
@@ -45,17 +45,32 @@ module ApiBlocks::Controller
|
|
45
45
|
def authorize(record, query = nil)
|
46
46
|
super(self.class.pundit_api_scope + [record], query)
|
47
47
|
end
|
48
|
+
|
49
|
+
handle_api_error Pundit::NotAuthorizedError do |error|
|
50
|
+
[{ detail: error.message }, :forbidden]
|
51
|
+
end
|
52
|
+
|
53
|
+
mattr_accessor :pundit_api_scope, default: []
|
48
54
|
end
|
49
55
|
|
50
56
|
class_methods do
|
51
57
|
# Provide a default scope to pundit's `PolicyFinder`.
|
52
58
|
def pundit_scope(*scope)
|
53
|
-
|
59
|
+
self.pundit_api_scope = scope
|
54
60
|
end
|
55
61
|
|
56
|
-
#
|
57
|
-
def
|
58
|
-
|
62
|
+
# Defines a error handler that returns
|
63
|
+
def handle_api_error(error_class)
|
64
|
+
rescue_from error_class do |ex|
|
65
|
+
problem, status =
|
66
|
+
if block_given?
|
67
|
+
yield ex
|
68
|
+
else
|
69
|
+
[{ detail: ex.message }, :ok]
|
70
|
+
end
|
71
|
+
|
72
|
+
render problem: problem, status: status
|
73
|
+
end
|
59
74
|
end
|
60
75
|
end
|
61
76
|
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# ApiBlocks::Doorkeeper::Invitations::Application adds `invitation_uri`
|
4
|
+
# validation to `Doorkeeper::Application`.
|
5
|
+
#
|
6
|
+
# This module is automatically included on rails application startup if the
|
7
|
+
# invitations migrations have been ran.
|
8
|
+
#
|
9
|
+
# @private
|
10
|
+
#
|
11
|
+
module ApiBlocks::Doorkeeper::Invitations::Application
|
12
|
+
extend ActiveSupport::Concern
|
13
|
+
|
14
|
+
included do
|
15
|
+
validates :invitation_uri, "doorkeeper/redirect_uri": true
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# ApiBlocks::Doorkeeper::Invitations::Controller implements a devise invitable
|
4
|
+
# API controller.
|
5
|
+
module ApiBlocks::Doorkeeper::Invitations::Controller
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
included do # rubocop:disable Metrics/BlockLength
|
9
|
+
skip_after_action :verify_authorized
|
10
|
+
skip_after_action :verify_policy_scoped
|
11
|
+
|
12
|
+
# Initialize a new invitation.
|
13
|
+
def create
|
14
|
+
user = user_model.invite!(
|
15
|
+
create_params, current_user, application: oauth_application,
|
16
|
+
)
|
17
|
+
|
18
|
+
return render(status: :no_content) if user.errors.empty?
|
19
|
+
|
20
|
+
respond_with(user)
|
21
|
+
end
|
22
|
+
|
23
|
+
# Renders informations about the invited user.
|
24
|
+
def show
|
25
|
+
user = user_model.find_by_invitation_token(params[:invitation_token], false)
|
26
|
+
|
27
|
+
if user.nil? || !user.persisted?
|
28
|
+
return render(
|
29
|
+
problem: { details: "invalid invitation token" },
|
30
|
+
status: :bad_request
|
31
|
+
)
|
32
|
+
end
|
33
|
+
|
34
|
+
respond_with(user)
|
35
|
+
end
|
36
|
+
|
37
|
+
# Redirects to the application's redirect uri.
|
38
|
+
def callback
|
39
|
+
query = {
|
40
|
+
invitation_token: params[:invitation_token]
|
41
|
+
}.to_query
|
42
|
+
|
43
|
+
redirect_to("#{oauth_application.invitation_uri}?#{query}")
|
44
|
+
end
|
45
|
+
|
46
|
+
# Finalize the invitation.
|
47
|
+
def update
|
48
|
+
user = user_model.accept_invitation!(update_params)
|
49
|
+
|
50
|
+
return respond_with(user) unless user.errors.empty?
|
51
|
+
|
52
|
+
user.unlock_access! if unlockable?(user)
|
53
|
+
|
54
|
+
respond_with(Doorkeeper::OAuth::TokenResponse.new(
|
55
|
+
access_token(oauth_application, user)
|
56
|
+
).body)
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
def create_params
|
62
|
+
params.require(:user).permit(:email)
|
63
|
+
end
|
64
|
+
|
65
|
+
def update_params
|
66
|
+
params.require(:user).permit(
|
67
|
+
:invitation_token, :password, :password_confirmation
|
68
|
+
)
|
69
|
+
end
|
70
|
+
|
71
|
+
# Copied over from devise base controller in order to determine wether a ser
|
72
|
+
# is unlockable or not.
|
73
|
+
def unlockable?(resource)
|
74
|
+
resource.respond_to?(:unlock_access!) &&
|
75
|
+
resource.respond_to?(:unlock_strategy_enabled?) &&
|
76
|
+
resource.unlock_strategy_enabled?(:email)
|
77
|
+
end
|
78
|
+
|
79
|
+
# Returns a new access token for this user.
|
80
|
+
def access_token(application, user)
|
81
|
+
Doorkeeper::AccessToken.find_or_create_for(
|
82
|
+
application,
|
83
|
+
user.id,
|
84
|
+
Doorkeeper.configuration.default_scopes,
|
85
|
+
Doorkeeper.configuration.access_token_expires_in,
|
86
|
+
true
|
87
|
+
)
|
88
|
+
end
|
89
|
+
|
90
|
+
def oauth_application
|
91
|
+
@oauth_application ||= Doorkeeper::Application.find_by!(
|
92
|
+
uid: params[:client_id]
|
93
|
+
)
|
94
|
+
end
|
95
|
+
|
96
|
+
|
97
|
+
# Returns the user model class.
|
98
|
+
def user_model
|
99
|
+
raise 'the method `user_model` must be implemented on your password controller' # rubocop:disable Metrics/LineLength
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rails/generators'
|
4
|
+
require 'rails/generators/active_record'
|
5
|
+
|
6
|
+
# ApiBlocks::Doorkeeper::Invitations::MigrationGenerator implements the Rails
|
7
|
+
# generator for doorkeeper invitations api migrations.
|
8
|
+
#
|
9
|
+
# @private
|
10
|
+
#
|
11
|
+
class ApiBlocks::Doorkeeper::Invitations::MigrationGenerator < ::Rails::Generators::Base # rubocop:disable Metrics/LineLength
|
12
|
+
include ::Rails::Generators::Migration
|
13
|
+
|
14
|
+
source_root File.expand_path('templates', __dir__)
|
15
|
+
desc 'Installs doorkeeper invitations api migrations'
|
16
|
+
|
17
|
+
def install
|
18
|
+
migration_template(
|
19
|
+
'migration.rb.erb',
|
20
|
+
'db/migrate/add_invitation_uri_to_doorkeeper_applications.rb',
|
21
|
+
migration_version: migration_version
|
22
|
+
)
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.next_migration_number(dirname)
|
26
|
+
ActiveRecord::Generators::Base.next_migration_number(dirname)
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def migration_version
|
32
|
+
"[#{ActiveRecord::VERSION::MAJOR}.#{ActiveRecord::VERSION::MINOR}]"
|
33
|
+
end
|
34
|
+
end
|
@@ -65,10 +65,8 @@ module ApiBlocks::Doorkeeper::Passwords::Controller
|
|
65
65
|
# Initialize the reset password workflow, sends a reset password email to
|
66
66
|
# the user.
|
67
67
|
def create
|
68
|
-
application = Doorkeeper::Application.find_by!(uid: params[:client_id])
|
69
|
-
|
70
68
|
user = user_model.send_reset_password_instructions(
|
71
|
-
create_params, application:
|
69
|
+
create_params, application: oauth_application
|
72
70
|
)
|
73
71
|
|
74
72
|
if successfully_sent?(user)
|
@@ -81,29 +79,26 @@ module ApiBlocks::Doorkeeper::Passwords::Controller
|
|
81
79
|
# Handles the redirection from the email towards the application's
|
82
80
|
# `redirect_uri`.
|
83
81
|
def callback
|
84
|
-
application = Doorkeeper::Application.find_by!(uid: params[:client_id])
|
85
|
-
|
86
82
|
query = {
|
87
83
|
reset_password_token: params[:reset_password_token]
|
88
84
|
}.to_query
|
89
85
|
|
90
86
|
redirect_to(
|
91
|
-
"#{
|
87
|
+
"#{oauth_application.reset_password_uri}?#{query}"
|
92
88
|
)
|
93
89
|
end
|
94
90
|
|
95
91
|
# Updates the user password and returns a new Doorkeeper::AccessToken.
|
96
92
|
def update
|
97
|
-
application = Doorkeeper::Application.find_by!(uid: params[:client_id])
|
98
93
|
user = user_model.reset_password_by_token(update_params)
|
99
94
|
|
100
|
-
|
101
|
-
user.unlock_access! if unlockable?(user)
|
95
|
+
return respond_with(user) unless user.errors.empty?
|
102
96
|
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
97
|
+
user.unlock_access! if unlockable?(user)
|
98
|
+
|
99
|
+
respond_with(Doorkeeper::OAuth::TokenResponse.new(
|
100
|
+
access_token(oauth_application, user)
|
101
|
+
).body)
|
107
102
|
end
|
108
103
|
|
109
104
|
private
|
@@ -150,6 +145,12 @@ module ApiBlocks::Doorkeeper::Passwords::Controller
|
|
150
145
|
)
|
151
146
|
end
|
152
147
|
|
148
|
+
def oauth_application
|
149
|
+
@oauth_application ||= Doorkeeper::Application.find_by!(
|
150
|
+
uid: params[:client_id]
|
151
|
+
)
|
152
|
+
end
|
153
|
+
|
153
154
|
# Returns the user model class.
|
154
155
|
def user_model
|
155
156
|
raise 'the method `user_model` must be implemented on your password controller' # rubocop:disable Metrics/LineLength
|
data/lib/api_blocks/railtie.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "problem_details-rails"
|
4
|
+
|
3
5
|
# ApiBlocks::Railtie implements the Rails integration for ApiBlocks.
|
4
6
|
#
|
5
7
|
# @private
|
@@ -21,9 +23,24 @@ class ApiBlocks::Railtie < Rails::Railtie
|
|
21
23
|
ApiBlocks::Doorkeeper::Passwords::Application
|
22
24
|
)
|
23
25
|
end
|
26
|
+
|
27
|
+
ActiveSupport.on_load(:active_record) do
|
28
|
+
# do not load the Doorkeeper::Application extensions if migrations have
|
29
|
+
# not been setup.
|
30
|
+
invitation_uri = Doorkeeper::Application.columns.find do |col|
|
31
|
+
col.name == 'invitation_uri'
|
32
|
+
end
|
33
|
+
|
34
|
+
next unless invitation_uri
|
35
|
+
|
36
|
+
Doorkeeper::Application.include(
|
37
|
+
ApiBlocks::Doorkeeper::Invitations::Application
|
38
|
+
)
|
39
|
+
end
|
24
40
|
end
|
25
41
|
|
26
42
|
generators do
|
27
43
|
require_relative 'doorkeeper/passwords/migration_generator'
|
44
|
+
require_relative 'doorkeeper/invitations/migration_generator'
|
28
45
|
end
|
29
46
|
end
|
data/lib/api_blocks/responder.rb
CHANGED
@@ -10,13 +10,15 @@ require 'dry/monads/result'
|
|
10
10
|
class ApiBlocks::Responder < ActionController::Responder
|
11
11
|
include Responders::HttpCacheResponder
|
12
12
|
|
13
|
-
# Override resource_errors to handle more error kinds
|
13
|
+
# Override resource_errors to handle more error kinds and return a status
|
14
|
+
# code.
|
15
|
+
#
|
14
16
|
def resource_errors
|
15
17
|
case @resource
|
16
18
|
when ApplicationRecord
|
17
|
-
{ errors: @resource.errors }
|
19
|
+
[{ errors: @resource.errors }, :unprocessable_entity]
|
18
20
|
when ActiveRecord::RecordInvalid
|
19
|
-
{ errors: @resource.record.errors }
|
21
|
+
[{ errors: @resource.record.errors }, :unprocessable_entity]
|
20
22
|
else
|
21
23
|
# propagate the error so it can be handled through the standard rails
|
22
24
|
# error handlers.
|
@@ -24,6 +26,24 @@ class ApiBlocks::Responder < ActionController::Responder
|
|
24
26
|
end
|
25
27
|
end
|
26
28
|
|
29
|
+
# Display is just a shortcut to render a resource's errors with the current
|
30
|
+
# format using `problem_details` when format is set to JSON.
|
31
|
+
#
|
32
|
+
def display_errors
|
33
|
+
return super unless format == :json
|
34
|
+
|
35
|
+
errors, status = resource_errors
|
36
|
+
|
37
|
+
controller.render problem: errors, status: status
|
38
|
+
end
|
39
|
+
|
40
|
+
# All other formats follow the procedure below. First we try to render a
|
41
|
+
# template, if the template is not available, we verify if the resource
|
42
|
+
# responds to :to_format and display it.
|
43
|
+
#
|
44
|
+
# In addition, if the resource is a Dry::Monads::Result we unwrap it and
|
45
|
+
# assign the failure instead.
|
46
|
+
#
|
27
47
|
def to_format
|
28
48
|
return super unless resource.is_a?(Dry::Monads::Result)
|
29
49
|
|
data/lib/api_blocks/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: api-blocks
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Paul d'Hubert
|
@@ -66,6 +66,20 @@ dependencies:
|
|
66
66
|
- - "~>"
|
67
67
|
- !ruby/object:Gem::Version
|
68
68
|
version: '1.3'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: problem_details-rails
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0.2'
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0.2'
|
69
83
|
- !ruby/object:Gem::Dependency
|
70
84
|
name: pundit
|
71
85
|
requirement: !ruby/object:Gem::Requirement
|
@@ -244,6 +258,10 @@ files:
|
|
244
258
|
- lib/api_blocks.rb
|
245
259
|
- lib/api_blocks/controller.rb
|
246
260
|
- lib/api_blocks/doorkeeper.rb
|
261
|
+
- lib/api_blocks/doorkeeper/invitations.rb
|
262
|
+
- lib/api_blocks/doorkeeper/invitations/application.rb
|
263
|
+
- lib/api_blocks/doorkeeper/invitations/controller.rb
|
264
|
+
- lib/api_blocks/doorkeeper/invitations/migration_generator.rb
|
247
265
|
- lib/api_blocks/doorkeeper/passwords.rb
|
248
266
|
- lib/api_blocks/doorkeeper/passwords/application.rb
|
249
267
|
- lib/api_blocks/doorkeeper/passwords/controller.rb
|