devise-api 0.0.0 → 0.1.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: 52cab446a0341887a91a1fc53e0f9ba2d6d70ebe18c7c3ccadf502eccd5bc0f5
4
- data.tar.gz: c633f441ac5967666f9c703812aa126161748d3e7d11ea7060d6c813633c8e3f
3
+ metadata.gz: 35aa72da6fbb296fb21b21ec8b0512f54c4061b253c30c0e4292223fec052d00
4
+ data.tar.gz: 7f960c887b37beb79bd01f5ecea667d8f19007b865207d21c47881f7e057d104
5
5
  SHA512:
6
- metadata.gz: 8a2d7924e04bd2789f73f0aba892fcb3919b17736c2b33c97c007ba2a3d1b619b3eb80e66f600a77b352cc20069a8b82a6b03b0ea9340e2640a8c76ff3203366
7
- data.tar.gz: 4dea11a94ce670c76ef74622f920e42563aa421b3061d52583f44b2709402255045be520e8613e910d5865779f044b56fdb6d0837e48ff8ec916af1f2fab345c
6
+ metadata.gz: fe4d8f38cecf1d6cb38a79caa0e7ac47fca859789bae2f2e8de612757747f11a72220c5465a9d58f73d206d33006eb85ac8352e723a0467a82f4fc7f87efc90a
7
+ data.tar.gz: 3c91b3f5800f9b6a3e7684841dc29c7ee517058584a9ea15a890b2d1ff2000b5b21b451108d7c68eaf5a88f431ec7b11bf4abf684cbdd25f9c733d7faa785011
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
data/.rubocop.yml CHANGED
@@ -27,3 +27,6 @@ Metrics/AbcSize:
27
27
 
28
28
  Metrics/BlockLength:
29
29
  Max: 30
30
+ Exclude:
31
+ - 'spec/**/*_spec.rb'
32
+ - 'spec/dummy/db/**/*'
data/Gemfile CHANGED
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  source 'https://rubygems.org'
4
+ git_source(:github) { |repo| "https://github.com/#{repo}.git" }
4
5
 
5
6
  # Specify your gem's dependencies in devise-api.gemspec
6
7
  gemspec
@@ -8,8 +9,8 @@ gemspec
8
9
  # Rake is a Make-like program implemented in Ruby [https://github.com/ruby/rake]
9
10
  gem 'rake', '~> 13.0'
10
11
 
11
- # Minitest provides a complete suite of testing facilities supporting [https://github.com/minitest/minitest]
12
- gem 'minitest', '~> 5.0'
12
+ # Behaviour Driven Development for Ruby [https://github.com/rspec/rspec-metagem]
13
+ gem 'rspec', '~> 3.0'
13
14
 
14
15
  # RuboCop is a Ruby code style checking and code formatting tool [https://github.com/rubocop/rubocop]
15
16
  gem 'rubocop', '~> 1.21'
@@ -19,3 +20,30 @@ gem 'awesome_print'
19
20
 
20
21
  # Pry is a runtime developer console and IRB alternative with powerful introspection capabilities [https://github.com/pry/pry]
21
22
  gem 'pry', '~> 0.14.1'
23
+
24
+ # The original asset pipeline for Rails [https://github.com/rails/sprockets-rails]
25
+ gem 'sprockets-rails'
26
+
27
+ # Use sqlite3 as the database for Active Record
28
+ gem 'sqlite3', '~> 1.4'
29
+
30
+ # Use the Puma web server [https://github.com/puma/puma]
31
+ gem 'puma', '~> 5.0'
32
+
33
+ # A library for setting up Ruby objects as test data [https://github.com/thoughtbot/factory_bot]
34
+ gem 'factory_bot', '~> 6.2', '>= 6.2.1'
35
+
36
+ # A library for generating fake data such as names, addresses, and phone numbers [https://github.com/faker-ruby/faker]
37
+ gem 'faker', '~> 3.1'
38
+
39
+ # Strategies for cleaning databases in Ruby. Can be used to ensure a clean state for testing [https://github.com/DatabaseCleaner/database_cleaner]
40
+ gem 'database_cleaner', '~> 2.0', '>= 2.0.1'
41
+
42
+ # RSpec for Rails 5+ [https://github.com/rspec/rspec-rails]
43
+ gem 'rspec-rails', '~> 6.0', '>= 6.0.1'
44
+
45
+ # RSpec runner and formatters [https://github.com/rspec/rspec-core]
46
+ gem 'rspec-core'
47
+
48
+ # Common code needed by the other RSpec gems. Not intended for direct use [https://github.com/rspec/rspec-support]
49
+ gem 'rspec-support'
data/Gemfile.lock CHANGED
@@ -3,6 +3,10 @@ PATH
3
3
  specs:
4
4
  devise-api (0.0.0)
5
5
  devise (>= 4.7.2)
6
+ dry-configurable (~> 1.0, >= 1.0.1)
7
+ dry-initializer (>= 3.1.1)
8
+ dry-monads (>= 1.6.0)
9
+ dry-types (>= 1.7.0)
6
10
  rails (>= 6.0.0)
7
11
 
8
12
  GEM
@@ -80,6 +84,12 @@ GEM
80
84
  coderay (1.1.3)
81
85
  concurrent-ruby (1.1.10)
82
86
  crass (1.0.6)
87
+ database_cleaner (2.0.1)
88
+ database_cleaner-active_record (~> 2.0.0)
89
+ database_cleaner-active_record (2.0.1)
90
+ activerecord (>= 5.a)
91
+ database_cleaner-core (~> 2.0.0)
92
+ database_cleaner-core (2.0.1)
83
93
  date (3.3.3)
84
94
  devise (4.8.1)
85
95
  bcrypt (~> 3.0)
@@ -87,7 +97,34 @@ GEM
87
97
  railties (>= 4.1.0)
88
98
  responders
89
99
  warden (~> 1.2.3)
100
+ diff-lcs (1.5.0)
101
+ dry-configurable (1.0.1)
102
+ dry-core (~> 1.0, < 2)
103
+ zeitwerk (~> 2.6)
104
+ dry-core (1.0.0)
105
+ concurrent-ruby (~> 1.0)
106
+ zeitwerk (~> 2.6)
107
+ dry-inflector (1.0.0)
108
+ dry-initializer (3.1.1)
109
+ dry-logic (1.5.0)
110
+ concurrent-ruby (~> 1.0)
111
+ dry-core (~> 1.0, < 2)
112
+ zeitwerk (~> 2.6)
113
+ dry-monads (1.6.0)
114
+ concurrent-ruby (~> 1.0)
115
+ dry-core (~> 1.0, < 2)
116
+ zeitwerk (~> 2.6)
117
+ dry-types (1.7.0)
118
+ concurrent-ruby (~> 1.0)
119
+ dry-core (~> 1.0, < 2)
120
+ dry-inflector (~> 1.0, < 2)
121
+ dry-logic (>= 1.4, < 2)
122
+ zeitwerk (~> 2.6)
90
123
  erubi (1.12.0)
124
+ factory_bot (6.2.1)
125
+ activesupport (>= 5.0.0)
126
+ faker (3.1.0)
127
+ i18n (>= 1.8.11, < 2)
91
128
  globalid (1.0.0)
92
129
  activesupport (>= 5.0)
93
130
  i18n (1.12.0)
@@ -123,9 +160,11 @@ GEM
123
160
  parallel (1.22.1)
124
161
  parser (3.2.0.0)
125
162
  ast (~> 2.4.1)
126
- pry (0.14.1)
163
+ pry (0.14.2)
127
164
  coderay (~> 1.1)
128
165
  method_source (~> 1.0)
166
+ puma (5.6.5)
167
+ nio4r (~> 2.0)
129
168
  racc (1.6.2)
130
169
  rack (2.2.5)
131
170
  rack-test (2.0.2)
@@ -163,6 +202,27 @@ GEM
163
202
  actionpack (>= 5.0)
164
203
  railties (>= 5.0)
165
204
  rexml (3.2.5)
205
+ rspec (3.12.0)
206
+ rspec-core (~> 3.12.0)
207
+ rspec-expectations (~> 3.12.0)
208
+ rspec-mocks (~> 3.12.0)
209
+ rspec-core (3.12.0)
210
+ rspec-support (~> 3.12.0)
211
+ rspec-expectations (3.12.2)
212
+ diff-lcs (>= 1.2.0, < 2.0)
213
+ rspec-support (~> 3.12.0)
214
+ rspec-mocks (3.12.2)
215
+ diff-lcs (>= 1.2.0, < 2.0)
216
+ rspec-support (~> 3.12.0)
217
+ rspec-rails (6.0.1)
218
+ actionpack (>= 6.1)
219
+ activesupport (>= 6.1)
220
+ railties (>= 6.1)
221
+ rspec-core (~> 3.11)
222
+ rspec-expectations (~> 3.11)
223
+ rspec-mocks (~> 3.11)
224
+ rspec-support (~> 3.11)
225
+ rspec-support (3.12.0)
166
226
  rubocop (1.42.0)
167
227
  json (~> 2.3)
168
228
  parallel (~> 1.10)
@@ -176,6 +236,14 @@ GEM
176
236
  rubocop-ast (1.24.1)
177
237
  parser (>= 3.1.1.0)
178
238
  ruby-progressbar (1.11.0)
239
+ sprockets (4.2.0)
240
+ concurrent-ruby (~> 1.0)
241
+ rack (>= 2.2.4, < 4)
242
+ sprockets-rails (3.4.2)
243
+ actionpack (>= 5.2)
244
+ activesupport (>= 5.2)
245
+ sprockets (>= 3.0.0)
246
+ sqlite3 (1.6.0-arm64-darwin)
179
247
  thor (1.2.1)
180
248
  timeout (0.3.1)
181
249
  tzinfo (2.0.5)
@@ -189,15 +257,25 @@ GEM
189
257
  zeitwerk (2.6.6)
190
258
 
191
259
  PLATFORMS
260
+ arm64-darwin-21
192
261
  arm64-darwin-22
193
262
 
194
263
  DEPENDENCIES
195
264
  awesome_print
265
+ database_cleaner (~> 2.0, >= 2.0.1)
196
266
  devise-api!
197
- minitest (~> 5.0)
267
+ factory_bot (~> 6.2, >= 6.2.1)
268
+ faker (~> 3.1)
198
269
  pry (~> 0.14.1)
270
+ puma (~> 5.0)
199
271
  rake (~> 13.0)
272
+ rspec (~> 3.0)
273
+ rspec-core
274
+ rspec-rails (~> 6.0, >= 6.0.1)
275
+ rspec-support
200
276
  rubocop (~> 1.21)
277
+ sprockets-rails
278
+ sqlite3 (~> 1.4)
201
279
 
202
280
  BUNDLED WITH
203
281
  2.4.3
data/README.md CHANGED
@@ -5,17 +5,16 @@
5
5
  ![Ruby Version](https://img.shields.io/badge/ruby_version->=_2.7.0-blue.svg)
6
6
 
7
7
  # Devise API
8
-
9
- The `devise-api` gem is a convenient way to add authentication to your Ruby on Rails API application using the [devise](https://github.com/heartcombo/devise) gem. It provides support for access tokens and refresh tokens, which allow you to authenticate API requests and keep the user's session active for a longer period of time.
8
+ The devise-api gem is a convenient way to add authentication to your Ruby on Rails application using the devise gem. It provides support for access tokens and refresh tokens, which allow you to authenticate API requests and keep the user's session active for a longer period of time on the client side. It can be installed by adding the gem to your Gemfile, running migrations, and adding the :api module to your devise model. The gem is fully configurable, allowing you to set things like token expiration times and token generators.
10
9
 
11
10
  Here's how it works:
12
11
 
13
- - When a user logs in to your API application, the `devise-api` gem generates an access token and a refresh token.
12
+ - When a user logs in to your Rails application, the `devise-api` gem generates an access token and a refresh token.
14
13
  - The access token is included in the API request headers and is used to authenticate the user on each subsequent request.
15
14
  - The refresh token is stored on the client side (e.g. in a browser cookie or on a mobile device) and is used to obtain a new access token when the original access token expires.
16
15
  - This allows the user to remain logged in and make API requests without having to constantly re-enter their login credentials.
17
16
 
18
- Overall, the `devise-api` gem is a useful tool for adding secure authentication to your Ruby on Rails API application.
17
+ Overall, the `devise-api` gem is a useful tool for adding secure authentication to your Ruby on Rails application.
19
18
 
20
19
  ## Installation
21
20
 
@@ -34,9 +33,190 @@ If bundler is not being used to manage dependencies, install the gem by executin
34
33
  gem install devise-api
35
34
  ```
36
35
 
36
+ After that, you need to generate relevant migrations and locales by executing:
37
+ ```bash
38
+ $ rails generate devise_api:install
39
+ ```
40
+
41
+ This will introduce two changes:
42
+ - Locale files in `config/locales/devise_api.en.yml`
43
+ - Migration file in `db/migrate` to create devise api tokens table
44
+
45
+ Now you're ready to run the migrations:
46
+ ```bash
47
+ $ rails db:migrate
48
+ ```
49
+
50
+ Finally, you need to add `:api` module to your devise model. For example:
51
+ ```ruby
52
+ class User < ApplicationRecord
53
+ devise :database_authenticatable,
54
+ :registerable,
55
+ :recoverable,
56
+ :rememberable,
57
+ :validatable,
58
+ :api # <--- Add this module
59
+ end
60
+ ```
61
+
62
+ Your user model is now ready to use `devise-api` gem.
63
+
64
+ ## Configuration
65
+
66
+ `devise-api` is a full configurable gem. You can configure it to your needs. Here is a basic usage example:
67
+
68
+ ```ruby
69
+ # config/initializers/devise.rb
70
+ Devise.setup do |config|
71
+ config.api.configure do |api|
72
+ # Access Token
73
+ api.access_token.expires_in = 1.hour
74
+ api.access_token.expires_in_infinite = ->(_resource_owner) { false }
75
+ api.access_token.generator = ->(_resource_owner) { Devise.friendly_token(60)) }
76
+
77
+
78
+ # Refresh Token
79
+ api.refresh_token.enabled = true
80
+ api.refresh_token.expires_in = 1.week
81
+ api.refresh_token.generator = ->(_resource_owner) { Devise.friendly_token(60)) }
82
+ api.refresh_token.expires_in_infinite = ->(_resource_owner) { false }
83
+
84
+
85
+ # Authorization
86
+ api.authorization.key = 'Authorization'
87
+ api.authorization.scheme = 'Bearer'
88
+ api.authorization.location = :both # :header or :params or :both
89
+ api.authorization.params_key = 'access_token'
90
+
91
+
92
+ # Base classes
93
+ api.base_token_model = 'Devise::Api::Token'
94
+ api.base_controller = '::DeviseController'
95
+
96
+
97
+ # After successful callbacks
98
+ api.after_successful_sign_in = ->(_resource_owner, _token, _request) { }
99
+ api.after_successful_sign_up = ->(_resource_owner, _token, _request) { }
100
+ api.after_successful_refresh = ->(_resource_owner, _token, _request) { }
101
+ api.after_successful_revoke = ->(_resource_owner, _token, _request) { }
102
+
103
+
104
+ # Before callbacks
105
+ api.before_sign_in = ->(_params, _request, _resource_class) { }
106
+ api.before_sign_up = ->(_params, _request, _resource_class) { }
107
+ api.before_refresh = ->(_params, _request, _resource_class) { }
108
+ api.before_revoke = ->(_params, _request, _resource_class) { }
109
+ end
110
+ end
111
+ ```
112
+
113
+ ## Routes
114
+
115
+ You can configure the tokens routes with the orginally `devise_for` method. For example:
116
+ ```ruby
117
+ # config/routes.rb
118
+ Rails.application.routes.draw do
119
+ devise_for :customers,
120
+ controllers: { tokens: 'customers/api/tokens' }
121
+ end
122
+ ```
123
+
37
124
  ## Usage
125
+ `devise-api` module works with `:lockable` and `:confirmable` modules. It also works with `:trackable` module.
126
+
127
+ `devise-api` provides a set of controllers and helpers to help you implement authentication in your Rails application. Here's a quick overview of the available controllers and helpers:
128
+
129
+ - [Devise::Api::TokensController](https://github.com/nejdetkadir/devise-api/tree/main/app/controllers/devise/api/tokens_controller.rb) - This controller is responsible for generating access tokens and refresh tokens. It also provides actions for refreshing access tokens and revoking refresh tokens.
130
+
131
+ - [Devise::Api::Token](https://github.com/nejdetkadir/devise-api/tree/main/lib/devise/api/token.rb) - This model is responsible for storing access tokens and refresh tokens in the database.
132
+
133
+ - [Devise::Api::Responses::ErrorResponse](https://github.com/nejdetkadir/devise-api/tree/main/lib/devise/api/responses/error_response.rb) - This class is responsible for generating error responses. It also provides a set of error types and helpers to help you implement error responses in your Rails application.
134
+
135
+ - [Devise::Api::Responses::TokenResponse](https://github.com/nejdetkadir/devise-api/tree/main/lib/devise/api/responses/token_response.rb) - This class is responsible for generating token responses. It also provides actions for generating access tokens and refresh tokens for each action.
136
+
137
+ ## Overriding Responses
138
+ You can prepend your decorators to the response classes to override the default responses. For example:
139
+ ```ruby
140
+ # app/lib/devise/api/responses/token_response_decorator.rb
141
+ module Devise::Api::Responses::TokenResponseDecorator
142
+ def body
143
+ return default_body.merge({ roles: resource_owner.roles })
144
+ end
145
+ end
146
+ ```
147
+
148
+ Then you need to prepend your decorator to the response class. For example:
149
+
150
+ ```ruby
151
+ # config/initializers/devise.rb
152
+ Devise.setup do |config|
153
+ end
38
154
 
39
- TODO: Write usage instructions here
155
+ Devise::Api::Responses::TokenResponse.prepend Devise::Api::Responses::TokenResponseDecorator
156
+ ```
157
+
158
+ ## Using helpers
159
+ `devise-api` provides a set of helpers to help you implement authentication in your Rails application. Here's a quick overview of the available helpers:
160
+
161
+ Example:
162
+ ```ruby
163
+ # app/controllers/api/v1/orders_controller.rb
164
+ class Api::V1::OrdersController < YourBaseController
165
+ skip_before_action :verify_authenticity_token, raise: false
166
+ before_action :authenticate_devise_api_token!
167
+
168
+ def index
169
+ render json: current_devise_api_user.orders, status: :ok
170
+ end
171
+
172
+ def show
173
+ devise_api_token = current_devise_api_token
174
+ render json: devise_api_token.resource_owner.orders.find(params[:id]), status: :ok
175
+ end
176
+ end
177
+ ```
178
+
179
+ ## Using devise base services
180
+ `devise-api` provides a set of base services to help you implement authentication in your Rails application. Here's a quick overview of the available services:
181
+
182
+ - [Devise::Api::BaseService](https://github.com/nejdetkadir/devise-api/tree/main/app/services/devise/api/base_service.rb) - This service is useful for creating and updating resources. It is inherited by the following gems.
183
+ - [dry-monads](https://dry-rb.org/gems/dry-monads)
184
+ - [dry-types](https://dry-rb.org/gems/dry-types)
185
+ - [dry-initializer](https://dry-rb.org/gems/dry-initializer)
186
+
187
+ You can create a service by inheriting the `Devise::Api::BaseService` class. For example:
188
+ ```ruby
189
+ # app/services/devise/api/tokens_service/v2/create.rb
190
+ module Devise::Api::TokensService::V2
191
+ class Create < Devise::Api::BaseService
192
+ option :params, type: Types::Hash, reader: true
193
+ option :resource_class, type: Types::Class, reader: true
194
+
195
+ def call
196
+ ...
197
+
198
+ Success(resource)
199
+ end
200
+ end
201
+ end
202
+ ```
203
+
204
+ Then you can call the service in your controller. For example:
205
+ ```ruby
206
+ # app/controllers/api/v1/tokens_controller.rb
207
+ class Api::V1::TokensController < YourBaseController
208
+ skip_before_action :verify_authenticity_token, raise: false
209
+
210
+ def create
211
+ service = Devise::Api::TokensService::V2::Create.call(params: params, resource_class: Customer || resource_class)
212
+ if service.success?
213
+ render json: service.success, status: :created
214
+ else
215
+ render json: service.failure, status: :unprocessable_entity
216
+ end
217
+ end
218
+ end
219
+ ```
40
220
 
41
221
  ## Development
42
222
 
data/Rakefile CHANGED
@@ -1,16 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'bundler/gem_tasks'
4
- require 'rake/testtask'
4
+ require 'rspec/core/rake_task'
5
5
 
6
- Rake::TestTask.new(:test) do |t|
7
- t.libs << 'test'
8
- t.libs << 'lib'
9
- t.test_files = FileList['test/**/test_*.rb']
10
- end
6
+ RSpec::Core::RakeTask.new(:rspec)
11
7
 
12
8
  require 'rubocop/rake_task'
13
9
 
14
10
  RuboCop::RakeTask.new
15
11
 
16
- task default: %i[test rubocop]
12
+ task default: %i[rspec rubocop]
@@ -0,0 +1,146 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Devise
4
+ module Api
5
+ class TokensController < Devise.api.config.base_controller.constantize
6
+ skip_before_action :verify_authenticity_token, raise: false
7
+ before_action :authenticate_devise_api_token!, only: %i[info refresh]
8
+
9
+ respond_to :json
10
+
11
+ # rubocop:disable Metrics/AbcSize
12
+ def sign_up
13
+ Devise.api.config.before_sign_up.call(sign_up_params, request, resource_class)
14
+
15
+ service = Devise::Api::ResourceOwnerService::SignUp.new(params: sign_up_params,
16
+ resource_class: resource_class).call
17
+
18
+ if service.success?
19
+ token = service.success
20
+
21
+ call_devise_trackable!(token.resource_owner)
22
+
23
+ token_response = Devise::Api::Responses::TokenResponse.new(request, token: token, action: __method__)
24
+
25
+ Devise.api.config.after_successful_sign_up.call(token.resource_owner, token, request)
26
+
27
+ return render json: token_response.body, status: token_response.status
28
+ end
29
+
30
+ error_response = Devise::Api::Responses::ErrorResponse.new(request,
31
+ resource_class: resource_class,
32
+ **service.failure)
33
+
34
+ render json: error_response.body, status: error_response.status
35
+ end
36
+ # rubocop:enable Metrics/AbcSize
37
+
38
+ # rubocop:disable Metrics/AbcSize
39
+ def sign_in
40
+ Devise.api.config.before_sign_in.call(sign_in_params, request, resource_class)
41
+
42
+ service = Devise::Api::ResourceOwnerService::SignIn.new(params: sign_in_params,
43
+ resource_class: resource_class).call
44
+
45
+ if service.success?
46
+ token = service.success
47
+
48
+ call_devise_trackable!(token.resource_owner)
49
+
50
+ token_response = Devise::Api::Responses::TokenResponse.new(request, token: service.success,
51
+ action: __method__)
52
+
53
+ Devise.api.config.after_successful_sign_in.call(token.resource_owner, token, request)
54
+
55
+ return render json: token_response.body, status: token_response.status
56
+ end
57
+
58
+ error_response = Devise::Api::Responses::ErrorResponse.new(request,
59
+ resource_class: resource_class,
60
+ **service.failure)
61
+
62
+ render json: error_response.body, status: error_response.status
63
+ end
64
+ # rubocop:enable Metrics/AbcSize
65
+
66
+ def info
67
+ token_response = Devise::Api::Responses::TokenResponse.new(request, token: current_devise_api_token,
68
+ action: __method__)
69
+
70
+ render json: token_response.body, status: token_response.status
71
+ end
72
+
73
+ # rubocop:disable Metrics/AbcSize
74
+ def revoke
75
+ Devise.api.config.before_revoke.call(current_devise_api_token, request)
76
+
77
+ service = Devise::Api::TokensService::Revoke.new(devise_api_token: current_devise_api_token).call
78
+
79
+ if service.success?
80
+ token_response = Devise::Api::Responses::TokenResponse.new(request, token: service.success,
81
+ action: __method__)
82
+
83
+ Devise.api.config.after_successful_revoke.call(service.success&.resource_owner, service.success, request)
84
+
85
+ return render json: token_response.body, status: token_response.status
86
+ end
87
+
88
+ error_response = Devise::Api::Responses::ErrorResponse.new(request,
89
+ resource_class: resource_class,
90
+ **service.failure)
91
+
92
+ render json: error_response.body, status: error_response.status
93
+ end
94
+ # rubocop:enable Metrics/AbcSize
95
+
96
+ # rubocop:disable Metrics/AbcSize
97
+ def refresh
98
+ unless Devise.api.config.refresh_token.enabled
99
+ error_response = Devise::Api::Responses::ErrorResponse.new(request,
100
+ resource_class: resource_class,
101
+ error: :refresh_token_disabled)
102
+
103
+ return render json: error_response.body, status: error_response.status
104
+ end
105
+
106
+ Devise.api.config.before_refresh.call(current_devise_api_token, request)
107
+
108
+ service = Devise::Api::TokensService::Refresh.new(devise_api_token: current_devise_api_token).call
109
+
110
+ if service.success?
111
+ token_response = Devise::Api::Responses::TokenResponse.new(request, token: service.success,
112
+ action: __method__)
113
+
114
+ Devise.api.config.after_successful_refresh.call(service.success.resource_owner, service.success, request)
115
+
116
+ return render json: token_response.body, status: token_response.status
117
+ end
118
+
119
+ error_response = Devise::Api::Responses::ErrorResponse.new(request,
120
+ resource_class: resource_class,
121
+ **service.failure)
122
+
123
+ render json: error_response.body, status: error_response.status
124
+ end
125
+ # rubocop:enable Metrics/AbcSize
126
+
127
+ private
128
+
129
+ def sign_up_params
130
+ params.permit(*resource_class.authentication_keys,
131
+ *::Devise::ParameterSanitizer::DEFAULT_PERMITTED_ATTRIBUTES[:sign_up]).to_h
132
+ end
133
+
134
+ def sign_in_params
135
+ params.permit(*resource_class.authentication_keys,
136
+ *::Devise::ParameterSanitizer::DEFAULT_PERMITTED_ATTRIBUTES[:sign_in]).to_h
137
+ end
138
+
139
+ def call_devise_trackable!(resource_owner)
140
+ return unless resource_class.supported_devise_modules.trackable?
141
+
142
+ resource_owner.update_tracked_fields!(request)
143
+ end
144
+ end
145
+ end
146
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'dry/monads/all'
4
+ require 'dry-initializer'
5
+ require 'dry-types'
6
+
7
+ module Types
8
+ include Dry.Types()
9
+ end
10
+
11
+ module Devise
12
+ module Api
13
+ class BaseService
14
+ extend Dry::Initializer
15
+
16
+ include Dry::Monads
17
+ include Dry::Monads::Do
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Devise
4
+ module Api
5
+ module ResourceOwnerService
6
+ class Authenticate < Devise::Api::BaseService
7
+ option :params, type: Types::Hash
8
+ option :resource_class, type: Types::Class
9
+
10
+ def call
11
+ resource = resource_class.find_for_authentication(email: params[:email])
12
+ return Failure(error: :invalid_email, record: nil) if resource.blank?
13
+ return Failure(error: :invalid_authentication, record: resource) unless authenticate!(resource)
14
+
15
+ Success(resource)
16
+ end
17
+
18
+ private
19
+
20
+ def authenticate!(resource)
21
+ resource.valid_for_authentication? do
22
+ resource.valid_password?(params[:password])
23
+ end && resource.active_for_authentication?
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Devise
4
+ module Api
5
+ module ResourceOwnerService
6
+ class SignIn < Devise::Api::BaseService
7
+ option :params, type: Types::Hash
8
+ option :resource_class, type: Types::Class
9
+
10
+ def call
11
+ resource_owner = yield call_authenticate_service
12
+ devise_api_token = yield call_create_devise_api_token_service(resource_owner)
13
+ resource_owner.reset_failed_attempts! if resource_owner.class.supported_devise_modules.lockable?
14
+
15
+ Success(devise_api_token)
16
+ end
17
+
18
+ private
19
+
20
+ def call_authenticate_service
21
+ Devise::Api::ResourceOwnerService::Authenticate.new(params: params,
22
+ resource_class: resource_class).call
23
+ end
24
+
25
+ def call_create_devise_api_token_service(resource_owner)
26
+ Devise::Api::TokensService::Create.new(resource_owner: resource_owner).call
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end