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 +4 -4
- data/.rspec +2 -0
- data/.rubocop.yml +3 -0
- data/Gemfile +30 -2
- data/Gemfile.lock +80 -2
- data/README.md +185 -5
- data/Rakefile +3 -7
- data/app/controllers/devise/api/tokens_controller.rb +146 -0
- data/app/services/devise/api/base_service.rb +20 -0
- data/app/services/devise/api/resource_owner_service/authenticate.rb +28 -0
- data/app/services/devise/api/resource_owner_service/sign_in.rb +31 -0
- data/app/services/devise/api/resource_owner_service/sign_up.rb +35 -0
- data/app/services/devise/api/tokens_service/create.rb +45 -0
- data/app/services/devise/api/tokens_service/refresh.rb +26 -0
- data/app/services/devise/api/tokens_service/revoke.rb +19 -0
- data/config/locales/en.yml +22 -0
- data/devise-api.gemspec +12 -4
- data/lib/devise/api/configuration.rb +44 -0
- data/lib/devise/api/controllers/helpers.rb +93 -0
- data/lib/devise/api/generators/install_generator.rb +62 -0
- data/lib/devise/api/generators/templates/migration.rb.erb +16 -0
- data/lib/devise/api/rails/engine.rb +11 -0
- data/lib/devise/api/rails/routes.rb +24 -0
- data/lib/devise/api/responses/error_response.rb +120 -0
- data/lib/devise/api/responses/token_response.rb +76 -0
- data/lib/devise/api/token.rb +88 -0
- data/lib/devise/api/version.rb +1 -1
- data/lib/devise/api.rb +44 -3
- metadata +103 -14
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 35aa72da6fbb296fb21b21ec8b0512f54c4061b253c30c0e4292223fec052d00
|
4
|
+
data.tar.gz: 7f960c887b37beb79bd01f5ecea667d8f19007b865207d21c47881f7e057d104
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fe4d8f38cecf1d6cb38a79caa0e7ac47fca859789bae2f2e8de612757747f11a72220c5465a9d58f73d206d33006eb85ac8352e723a0467a82f4fc7f87efc90a
|
7
|
+
data.tar.gz: 3c91b3f5800f9b6a3e7684841dc29c7ee517058584a9ea15a890b2d1ff2000b5b21b451108d7c68eaf5a88f431ec7b11bf4abf684cbdd25f9c733d7faa785011
|
data/.rspec
ADDED
data/.rubocop.yml
CHANGED
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
|
-
#
|
12
|
-
gem '
|
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.
|
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
|
-
|
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
|
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
|
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
|
-
|
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 '
|
4
|
+
require 'rspec/core/rake_task'
|
5
5
|
|
6
|
-
|
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[
|
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
|