api-blocks 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2e93230922c56b224b46fc9e1dd0c907cae145922f1d33279381b09bcbef3dc0
4
- data.tar.gz: 0e14903ac71f1f4b013065e09a27072e6269ceddf4b065dc12a4b9bd1002796f
3
+ metadata.gz: 6612a52d3c9bd211ec55b833406c4fd53196d257161945ad0ddaad976f4b5ac6
4
+ data.tar.gz: 5f959035fe3f550d408efe650051ccb5869595cce7a999377b5cae688e752c51
5
5
  SHA512:
6
- metadata.gz: f326dc96844750dca45d10408fb9ec5ff9c1f1692b24d20895f84584aae1882eba07374d5c8be6566aff28ce94900a8c8683b42ed934b5be52f6c6a00a19bc52
7
- data.tar.gz: afc90a22ee62c2b3f75e523edf5bd854af9ccac15f916808ea08e8389e7429b9001c6403b7be829719d3ce93f897e385bb6697ea7888f7e943e91d87deb808ac
6
+ metadata.gz: 5a5496696e2e9176e444c0001a3061f71aadede9e8d0b9c8ac86e74d37d62c75a689a42b1e3009d92381b83c73883c91dcc310fb0e1a9afb487ff4a1d68d00df
7
+ data.tar.gz: 3db65f293c0adda35b590e5b49d85ffd04dc4ce1d02c63acb1f0587e5d0324231d7c150857573afaa0b2397d98c9494e0b5778c050fbf8d7e41b239d03d04514
data/lib/api-blocks.rb ADDED
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'api_blocks'
@@ -1,11 +1,14 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # frozen_string_litreal: true
2
4
 
3
- require "pundit"
5
+ require 'pundit'
4
6
 
5
7
  # ApiBlocks::Controller provides a set of default configurations for
6
8
  # Ruby on Rails api controllers.
7
9
  #
8
- # It sets up `ApiBlocks::Responder` as a responder, `Pundit` and controller defaults.
10
+ # It sets up `ApiBlocks::Responder` as a responder, `Pundit` and controller
11
+ # defaults.
9
12
  #
10
13
  # @example
11
14
  #
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ # ApiBlocks::Doorkeeper::Passwords::Application adds `reset_password_uri`
4
+ # validation to `Doorkeeper::Application`.
5
+ #
6
+ # This module is automatically included on rails application startup if the
7
+ # passwords migrations have been ran.
8
+ #
9
+ # @private
10
+ #
11
+ module ApiBlocks::Doorkeeper::Passwords::Application
12
+ extend ActiveSupport::Concern
13
+
14
+ included do
15
+ validates :reset_password_uri, "doorkeeper/redirect_uri": true
16
+ end
17
+ end
@@ -0,0 +1,158 @@
1
+ # frozen_string_literal: true
2
+
3
+ # ApiBlocks::Doorkeeper::Passwords::Controller implements an API passwords reset
4
+ # workflow.
5
+ #
6
+ # @example
7
+ # # app/controllers/api/v1/passwords_controller.rb
8
+ # class Api::V1::PasswordsController < Api::V1::ApplicationController
9
+ # include ApiBlocks::Doorkeeper::Passwords::Controller
10
+ #
11
+ # private
12
+ #
13
+ # def user_model
14
+ # User
15
+ # end
16
+ # end
17
+ #
18
+ # @example
19
+ # # config/routes.rb
20
+ # Rails.application.routes.draw do
21
+ # scope module: :api do
22
+ # namespace :v1 do
23
+ # resources :passwords, only: %i[create] do
24
+ # get :callback, on: :collection
25
+ # put :update, on: :collection
26
+ # end
27
+ # end
28
+ # end
29
+ # end
30
+ #
31
+ # @example
32
+ # # app/models/user.rb
33
+ # class User < ApplicationRecord
34
+ # include ApiBlocks::Doorkeeper::Passwords::User
35
+ # end
36
+ #
37
+ # @example
38
+ # # config/initializers/devise.rb
39
+ # Devise.setup do |config|
40
+ # # Configure the class responsible to send e-mails.
41
+ # config.mailer = "DeviseMailer"
42
+ # end
43
+ #
44
+ # @example
45
+ # # app/mailers/devise_mailer.rb
46
+ #
47
+ # class DeviseMailer < Devise::Mailer
48
+ # def reset_password_instructions(
49
+ # record, token, application = nil, _opts = {}
50
+ # )
51
+ # @token = token
52
+ # @application = application
53
+ # end
54
+ # end
55
+ #
56
+ module ApiBlocks::Doorkeeper::Passwords::Controller
57
+ extend ActiveSupport::Concern
58
+
59
+ included do # rubocop:disable Metrics/BlockLength
60
+ # Skip pundit after action hooks because there is no authorization to
61
+ # perform.
62
+ skip_after_action :verify_authorized
63
+ skip_after_action :verify_policy_scoped
64
+
65
+ # Initialize the reset password workflow, sends a reset password email to
66
+ # the user.
67
+ def create
68
+ application = Doorkeeper::Application.find_by!(uid: params[:client_id])
69
+
70
+ user = user_model.send_reset_password_instructions(
71
+ create_params, application: application
72
+ )
73
+
74
+ if successfully_sent?(user)
75
+ render(status: :no_content)
76
+ else
77
+ respond_with(user)
78
+ end
79
+ end
80
+
81
+ # Handles the redirection from the email towards the application's
82
+ # `redirect_uri`.
83
+ def callback
84
+ application = Doorkeeper::Application.find_by!(uid: params[:client_id])
85
+
86
+ query = {
87
+ reset_password_token: params[:reset_password_token]
88
+ }.to_query
89
+
90
+ redirect_to(
91
+ "#{application.reset_password_uri}?#{query}"
92
+ )
93
+ end
94
+
95
+ # Updates the user password and returns a new Doorkeeper::AccessToken.
96
+ def update
97
+ application = Doorkeeper::Application.find_by!(uid: params[:client_id])
98
+ user = user_model.reset_password_by_token(update_params)
99
+
100
+ if user.errors.empty?
101
+ user.unlock_access! if unlockable?(user)
102
+
103
+ render json: access_token(application, user)
104
+ else
105
+ respond_with(user)
106
+ end
107
+ end
108
+
109
+ private
110
+
111
+ # Create permitted parameters
112
+ def create_params
113
+ params.require(:user).permit(:email)
114
+ end
115
+
116
+ # Update permitted parameters
117
+ def update_params
118
+ params.require(:user).permit(
119
+ :reset_password_token, :password
120
+ )
121
+ end
122
+
123
+ # Copied over from devise base controller in order to clear user errors if
124
+ # `Devise.paranoid` is active.
125
+ def successfully_sent?(user)
126
+ if Devise.paranoid
127
+ user.errors.clear
128
+ true
129
+ elsif user.errors.empty?
130
+ true
131
+ end
132
+ end
133
+
134
+ # Copied over from devise base controller in order to determine wether a ser
135
+ # is unlockable or not.
136
+ def unlockable?(resource)
137
+ resource.respond_to?(:unlock_access!) &&
138
+ resource.respond_to?(:unlock_strategy_enabled?) &&
139
+ resource.unlock_strategy_enabled?(:email)
140
+ end
141
+
142
+ # Returns a new access token for this user.
143
+ def access_token(application, user)
144
+ Doorkeeper::AccessToken.find_or_create_for(
145
+ application,
146
+ user.id,
147
+ Doorkeeper.configuration.default_scopes,
148
+ Doorkeeper.configuration.access_token_expires_in,
149
+ true
150
+ )
151
+ end
152
+
153
+ # Returns the user model class.
154
+ def user_model
155
+ raise 'the method `user_model` must be implemented on your password controller' # rubocop:disable Metrics/LineLength
156
+ end
157
+ end
158
+ 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::Passwords::MigrationGenerator implements the Rails
7
+ # generator for doorkeeper passwords api migrations.
8
+ #
9
+ # @private
10
+ #
11
+ class ApiBlocks::Doorkeeper::Passwords::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 passwords api migrations'
16
+
17
+ def install
18
+ migration_template(
19
+ 'migration.rb.erb',
20
+ 'db/migrate/add_reset_password_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
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ # ApiBlocks::Doorkeeper::Passwords::User overrides some methods from devise
4
+ # recoverable module to add the dorkeeper application to the mailer.
5
+ #
6
+ # @example
7
+ # # app/models/user.rb
8
+ # class User < ApplicationRecord
9
+ # include ApiBlocks::Doorkeeper::Passwords::User
10
+ # end
11
+ #
12
+ module ApiBlocks::Doorkeeper::Passwords::User
13
+ extend ActiveSupport::Concern
14
+
15
+ included do
16
+ # Resets reset password token and send reset password instructions by email.
17
+ # Returns the token sent in the e-mail.
18
+ def send_reset_password_instructions(application = nil)
19
+ token = set_reset_password_token
20
+ puts token
21
+ send_reset_password_instructions_notification(token, application)
22
+
23
+ token
24
+ end
25
+
26
+ protected
27
+
28
+ def send_reset_password_instructions_notification(token, application = nil)
29
+ send_devise_notification(
30
+ :reset_password_instructions, token, application
31
+ )
32
+ end
33
+ end
34
+
35
+ class_methods do
36
+ # Attempt to find a user by its email. If a record is found, send new
37
+ # password instructions to it. If user is not found, returns a new user
38
+ # with an email not found error.
39
+ # Attributes must contain the user's email
40
+ def send_reset_password_instructions(attributes = {}, application: nil)
41
+ recoverable = find_or_initialize_with_errors(
42
+ reset_password_keys, attributes, :not_found
43
+ )
44
+
45
+ if recoverable.persisted?
46
+ recoverable.send_reset_password_instructions(application)
47
+ end
48
+ recoverable
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ # ApiBlocks::Doorkeeper::Passwords implements an API reset password workflow.
4
+ module ApiBlocks::Doorkeeper::Passwords
5
+ extend ActiveSupport::Autoload
6
+
7
+ autoload :Controller
8
+ autoload :User
9
+ autoload :Application
10
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ # ApiBlocks::Doorkeeper implements API extensions for doorkeeper.
4
+ module ApiBlocks::Doorkeeper
5
+ extend ActiveSupport::Autoload
6
+
7
+ autoload :Passwords
8
+ end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "dry/transaction"
4
- require "dry/validation"
3
+ require 'dry/transaction'
4
+ require 'dry/validation'
5
5
 
6
6
  # ApiBlocks::Interactor implements a base interactor class.
7
7
  #
@@ -10,7 +10,7 @@ require "dry/validation"
10
10
  #
11
11
  # @example
12
12
  #
13
- # class InviteUser < ApplicationInteractor
13
+ # class InviteUser < ApiBlocks::Interactor
14
14
  # input do
15
15
  # schema do
16
16
  # required(:email).filled
@@ -48,7 +48,7 @@ class ApiBlocks::Interactor
48
48
  #
49
49
  # @example
50
50
  #
51
- # class FooInteractor < ApplicationInteractor
51
+ # class FooInteractor < ApiBlocks::Interactor
52
52
  # input do
53
53
  # schema do
54
54
  # required(:bar).filled
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ # ApiBlocks::Railtie implements the Rails integration for ApiBlocks.
4
+ #
5
+ # @private
6
+ #
7
+ class ApiBlocks::Railtie < Rails::Railtie
8
+ config.after_initialize do
9
+ next unless defined?(Doorkeeper)
10
+
11
+ ActiveSupport.on_load(:active_record) do
12
+ # do not load the Doorkeeper::Application extensions if migrations have
13
+ # not been setup.
14
+ has_reset_password_uri = Doorkeeper::Application.columns.find do |col|
15
+ col.name == 'reset_password_uri'
16
+ end
17
+
18
+ next unless has_reset_password_uri
19
+
20
+ Doorkeeper::Application.include(
21
+ ApiBlocks::Doorkeeper::Passwords::Application
22
+ )
23
+ end
24
+ end
25
+
26
+ generators do
27
+ require_relative 'doorkeeper/passwords/migration_generator'
28
+ end
29
+ end
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "action_controller/responder"
4
- require "responders"
5
- require "dry/monads"
3
+ require 'action_controller/responder'
4
+ require 'responders'
5
+ require 'dry/monads/result'
6
6
 
7
7
  # ApiBlocks::Responder provides a responder with better error handling and
8
8
  # `ApiBlocks::Interactor` through `Dry::Monads::Result` support.
@@ -27,7 +27,8 @@ class ApiBlocks::Responder < ActionController::Responder
27
27
  def to_format
28
28
  return super unless resource.is_a?(Dry::Monads::Result)
29
29
 
30
- # unwrap the result monad so it can be processed by ActionController::Responder
30
+ # unwrap the result monad so it can be processed by
31
+ # ActionController::Responder
31
32
  resource.fmap { |result| @resource = result }.or do |failure|
32
33
  @resource = failure
33
34
  @failure = true
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
- #
2
+
3
3
  module ApiBlocks
4
4
  # Current version of ApiBlocks
5
- VERSION = '0.1.1'.freeze
5
+ VERSION = '0.2.0'
6
6
  end
data/lib/api_blocks.rb CHANGED
@@ -7,7 +7,6 @@ require 'api_blocks/version'
7
7
  require 'active_support/concern'
8
8
  require 'active_support/dependencies/autoload'
9
9
 
10
-
11
10
  # ApiBlocks provides simple and consistent rails api extensions.
12
11
  module ApiBlocks
13
12
  extend ActiveSupport::Autoload
@@ -15,4 +14,7 @@ module ApiBlocks
15
14
  autoload :Controller
16
15
  autoload :Responder
17
16
  autoload :Interactor
17
+ autoload :Doorkeeper
18
18
  end
19
+
20
+ require 'api_blocks/railtie' if defined?(Rails)
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.1.1
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Paul d'Hubert
@@ -25,35 +25,35 @@ dependencies:
25
25
  - !ruby/object:Gem::Version
26
26
  version: 3.0.0
27
27
  - !ruby/object:Gem::Dependency
28
- name: rails
28
+ name: dry-monads
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - ">="
31
+ - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: 6.0.0
33
+ version: '1.3'
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - ">="
38
+ - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: 6.0.0
40
+ version: '1.3'
41
41
  - !ruby/object:Gem::Dependency
42
- name: pundit
42
+ name: dry-transaction
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
45
  - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: '2.1'
47
+ version: '0.13'
48
48
  type: :runtime
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
- version: '2.1'
54
+ version: '0.13'
55
55
  - !ruby/object:Gem::Dependency
56
- name: dry-monads
56
+ name: dry-validation
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
59
  - - "~>"
@@ -67,33 +67,33 @@ dependencies:
67
67
  - !ruby/object:Gem::Version
68
68
  version: '1.3'
69
69
  - !ruby/object:Gem::Dependency
70
- name: dry-transaction
70
+ name: pundit
71
71
  requirement: !ruby/object:Gem::Requirement
72
72
  requirements:
73
73
  - - "~>"
74
74
  - !ruby/object:Gem::Version
75
- version: '0.13'
75
+ version: '2.1'
76
76
  type: :runtime
77
77
  prerelease: false
78
78
  version_requirements: !ruby/object:Gem::Requirement
79
79
  requirements:
80
80
  - - "~>"
81
81
  - !ruby/object:Gem::Version
82
- version: '0.13'
82
+ version: '2.1'
83
83
  - !ruby/object:Gem::Dependency
84
- name: dry-validation
84
+ name: rails
85
85
  requirement: !ruby/object:Gem::Requirement
86
86
  requirements:
87
- - - "~>"
87
+ - - ">="
88
88
  - !ruby/object:Gem::Version
89
- version: '1.3'
89
+ version: 6.0.0
90
90
  type: :runtime
91
91
  prerelease: false
92
92
  version_requirements: !ruby/object:Gem::Requirement
93
93
  requirements:
94
- - - "~>"
94
+ - - ">="
95
95
  - !ruby/object:Gem::Version
96
- version: '1.3'
96
+ version: 6.0.0
97
97
  - !ruby/object:Gem::Dependency
98
98
  name: responders
99
99
  requirement: !ruby/object:Gem::Requirement
@@ -109,21 +109,21 @@ dependencies:
109
109
  - !ruby/object:Gem::Version
110
110
  version: 3.0.0
111
111
  - !ruby/object:Gem::Dependency
112
- name: rails
112
+ name: bundler
113
113
  requirement: !ruby/object:Gem::Requirement
114
114
  requirements:
115
- - - "~>"
115
+ - - ">="
116
116
  - !ruby/object:Gem::Version
117
- version: '6.0'
117
+ version: '0'
118
118
  type: :development
119
119
  prerelease: false
120
120
  version_requirements: !ruby/object:Gem::Requirement
121
121
  requirements:
122
- - - "~>"
122
+ - - ">="
123
123
  - !ruby/object:Gem::Version
124
- version: '6.0'
124
+ version: '0'
125
125
  - !ruby/object:Gem::Dependency
126
- name: bundler
126
+ name: mocha
127
127
  requirement: !ruby/object:Gem::Requirement
128
128
  requirements:
129
129
  - - ">="
@@ -150,6 +150,20 @@ dependencies:
150
150
  - - ">="
151
151
  - !ruby/object:Gem::Version
152
152
  version: '0'
153
+ - !ruby/object:Gem::Dependency
154
+ name: rails
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - "~>"
158
+ - !ruby/object:Gem::Version
159
+ version: '6.0'
160
+ type: :development
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - "~>"
165
+ - !ruby/object:Gem::Version
166
+ version: '6.0'
153
167
  - !ruby/object:Gem::Dependency
154
168
  name: rake
155
169
  requirement: !ruby/object:Gem::Requirement
@@ -220,29 +234,23 @@ dependencies:
220
234
  - - ">="
221
235
  - !ruby/object:Gem::Version
222
236
  version: '0'
223
- - !ruby/object:Gem::Dependency
224
- name: mocha
225
- requirement: !ruby/object:Gem::Requirement
226
- requirements:
227
- - - ">="
228
- - !ruby/object:Gem::Version
229
- version: '0'
230
- type: :development
231
- prerelease: false
232
- version_requirements: !ruby/object:Gem::Requirement
233
- requirements:
234
- - - ">="
235
- - !ruby/object:Gem::Version
236
- version: '0'
237
237
  description: Simple and consistent rails api extensions
238
238
  email: dev@tymate.com
239
239
  executables: []
240
240
  extensions: []
241
241
  extra_rdoc_files: []
242
242
  files:
243
+ - lib/api-blocks.rb
243
244
  - lib/api_blocks.rb
244
245
  - lib/api_blocks/controller.rb
246
+ - lib/api_blocks/doorkeeper.rb
247
+ - lib/api_blocks/doorkeeper/passwords.rb
248
+ - lib/api_blocks/doorkeeper/passwords/application.rb
249
+ - lib/api_blocks/doorkeeper/passwords/controller.rb
250
+ - lib/api_blocks/doorkeeper/passwords/migration_generator.rb
251
+ - lib/api_blocks/doorkeeper/passwords/user.rb
245
252
  - lib/api_blocks/interactor.rb
253
+ - lib/api_blocks/railtie.rb
246
254
  - lib/api_blocks/responder.rb
247
255
  - lib/api_blocks/version.rb
248
256
  homepage: https://github.com/tymate/api-blocks