devise-api 0.0.0 → 0.1.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 +4 -4
- data/.rspec +2 -0
- data/.rubocop.yml +3 -0
- data/Gemfile +30 -2
- data/Gemfile.lock +80 -2
- data/README.md +193 -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 +104 -15
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Devise
|
|
4
|
+
module Api
|
|
5
|
+
module ResourceOwnerService
|
|
6
|
+
class SignUp < Devise::Api::BaseService
|
|
7
|
+
option :params, type: Types::Hash
|
|
8
|
+
option :resource_class, type: Types::Class
|
|
9
|
+
|
|
10
|
+
def call
|
|
11
|
+
ActiveRecord::Base.transaction do
|
|
12
|
+
resource_owner = yield create_resource_owner
|
|
13
|
+
devise_api_token = yield call_create_devise_api_token_service(resource_owner)
|
|
14
|
+
|
|
15
|
+
Success(devise_api_token)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
|
|
21
|
+
def create_resource_owner
|
|
22
|
+
resource_owner = resource_class.new(params)
|
|
23
|
+
|
|
24
|
+
return Success(resource_owner) if resource_owner.save
|
|
25
|
+
|
|
26
|
+
Failure(error: :resource_owner_create_error, record: resource_owner)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def call_create_devise_api_token_service(resource_owner)
|
|
30
|
+
Devise::Api::TokensService::Create.new(resource_owner: resource_owner).call
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Devise
|
|
4
|
+
module Api
|
|
5
|
+
module TokensService
|
|
6
|
+
class Create < Devise::Api::BaseService
|
|
7
|
+
option :resource_owner
|
|
8
|
+
option :previous_refresh_token, type: Types::String | Types::Nil, default: proc { nil }
|
|
9
|
+
|
|
10
|
+
def call
|
|
11
|
+
return Failure(:invalid_resource_owner) unless resource_owner.respond_to?(:access_tokens)
|
|
12
|
+
|
|
13
|
+
devise_api_token = yield create_devise_api_token
|
|
14
|
+
|
|
15
|
+
Success(devise_api_token)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
|
|
20
|
+
def authenticate_service
|
|
21
|
+
Devise::Api::ResourceOwnerService::Authenticate.new(params: params,
|
|
22
|
+
resource_class: resource_class).call
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def create_devise_api_token
|
|
26
|
+
devise_api_token = resource_owner.access_tokens.new(params)
|
|
27
|
+
|
|
28
|
+
return Success(devise_api_token) if devise_api_token.save
|
|
29
|
+
|
|
30
|
+
Failure(error: :devise_api_token_create_error, record: devise_api_token)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def params
|
|
34
|
+
{
|
|
35
|
+
access_token: Devise.api.config.base_token_model.constantize.generate_uniq_access_token(resource_owner),
|
|
36
|
+
refresh_token: Devise.api.config.base_token_model.constantize.generate_uniq_refresh_token(resource_owner),
|
|
37
|
+
expires_in: Devise.api.config.access_token.expires_in,
|
|
38
|
+
revoked_at: nil,
|
|
39
|
+
previous_refresh_token: previous_refresh_token
|
|
40
|
+
}
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Devise
|
|
4
|
+
module Api
|
|
5
|
+
module TokensService
|
|
6
|
+
class Refresh < Devise::Api::BaseService
|
|
7
|
+
option :devise_api_token, type: Types.Instance(Devise.api.base_token_model.constantize)
|
|
8
|
+
option :resource_owner, default: proc { devise_api_token.resource_owner }
|
|
9
|
+
|
|
10
|
+
def call
|
|
11
|
+
return Failure(:expired_refresh_token) if devise_api_token.refresh_token_expired?
|
|
12
|
+
|
|
13
|
+
devise_api_token = yield create_devise_api_token
|
|
14
|
+
Success(devise_api_token)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
private
|
|
18
|
+
|
|
19
|
+
def create_devise_api_token
|
|
20
|
+
Devise::Api::TokensService::Create.new(resource_owner: resource_owner,
|
|
21
|
+
previous_refresh_token: devise_api_token.refresh_token).call
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Devise
|
|
4
|
+
module Api
|
|
5
|
+
module TokensService
|
|
6
|
+
class Revoke < Devise::Api::BaseService
|
|
7
|
+
option :devise_api_token, optional: true
|
|
8
|
+
|
|
9
|
+
def call
|
|
10
|
+
return Success(devise_api_token) if devise_api_token.blank?
|
|
11
|
+
return Success(devise_api_token) if devise_api_token.revoked? || devise_api_token.expired?
|
|
12
|
+
return Success(devise_api_token) if devise_api_token.update(revoked_at: Time.zone.now)
|
|
13
|
+
|
|
14
|
+
Failure(error: :devise_api_token_revoke_error, record: devise_api_token)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
en:
|
|
2
|
+
devise:
|
|
3
|
+
api:
|
|
4
|
+
error_response:
|
|
5
|
+
invalid_authentication: "Email or password is invalid"
|
|
6
|
+
invalid_token: "Invalid token"
|
|
7
|
+
expired_token: "Token has expired"
|
|
8
|
+
expired_refresh_token: "Refresh token has expired"
|
|
9
|
+
revoked_token: "Token has been revoked"
|
|
10
|
+
refresh_token_disabled: "Refresh token is disabled for this application"
|
|
11
|
+
invalid_refresh_token: "Refresh token is invalid"
|
|
12
|
+
invalid_email: "Email is invalid"
|
|
13
|
+
invalid_resource_owner: "Resource owner is invalid"
|
|
14
|
+
resource_owner_create_error: "Resource owner could not be created"
|
|
15
|
+
devise_api_token_create_error: "Token could not be created"
|
|
16
|
+
devise_api_token_revoke_error: "Token could not be revoked"
|
|
17
|
+
lockable:
|
|
18
|
+
locked: "Your account is locked"
|
|
19
|
+
confirmable:
|
|
20
|
+
unconfirmed: "You have to confirm your account before continuing"
|
|
21
|
+
registerable:
|
|
22
|
+
signed_up_but_unconfirmed: "A message with a confirmation link has been sent to your email address. Please follow the link to activate your account"
|
data/devise-api.gemspec
CHANGED
|
@@ -2,15 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
require_relative 'lib/devise/api/version'
|
|
4
4
|
|
|
5
|
+
# rubocop:disable Layout/LineLength
|
|
5
6
|
Gem::Specification.new do |spec|
|
|
6
7
|
spec.name = 'devise-api'
|
|
7
8
|
spec.version = Devise::Api::VERSION
|
|
8
9
|
spec.authors = ['nejdetkadir']
|
|
9
10
|
spec.email = ['nejdetkadir.550@gmail.com']
|
|
10
11
|
|
|
11
|
-
spec.summary = "
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
spec.summary = "The devise-api gem is a convenient way to add authentication to your Ruby on Rails application using the devise gem.
|
|
13
|
+
It provides support for access tokens and refresh tokens, which allow you to authenticate API requests and
|
|
14
|
+
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,
|
|
15
|
+
running migrations, and adding the :api module to your devise model. The gem is fully configurable,
|
|
16
|
+
allowing you to set things like token expiration times and token generators."
|
|
14
17
|
|
|
15
18
|
spec.description = spec.summary
|
|
16
19
|
spec.homepage = "https://github.com/nejdetkadir/#{spec.name}"
|
|
@@ -33,9 +36,14 @@ Gem::Specification.new do |spec|
|
|
|
33
36
|
spec.require_paths = ['lib']
|
|
34
37
|
|
|
35
38
|
# Uncomment to register a new dependency of your gem
|
|
36
|
-
spec.add_dependency 'rails', '>= 6.0.0'
|
|
37
39
|
spec.add_dependency 'devise', '>= 4.7.2'
|
|
40
|
+
spec.add_dependency 'dry-configurable', '~> 1.0', '>= 1.0.1'
|
|
41
|
+
spec.add_dependency 'dry-initializer', '>= 3.1.1'
|
|
42
|
+
spec.add_dependency 'dry-monads', '>= 1.6.0'
|
|
43
|
+
spec.add_dependency 'dry-types', '>= 1.7.0'
|
|
44
|
+
spec.add_dependency 'rails', '>= 6.0.0'
|
|
38
45
|
|
|
39
46
|
# For more information and examples about making a new gem, check out our
|
|
40
47
|
# guide at: https://bundler.io/guides/creating_gem.html
|
|
41
48
|
end
|
|
49
|
+
# rubocop:enable Layout/LineLength
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'dry-configurable'
|
|
4
|
+
|
|
5
|
+
module Devise
|
|
6
|
+
module Api
|
|
7
|
+
class Configuration
|
|
8
|
+
include Dry::Configurable
|
|
9
|
+
|
|
10
|
+
setting :access_token, reader: true do
|
|
11
|
+
setting :expires_in, default: 1.hour, reader: true
|
|
12
|
+
setting :expires_in_infinite, default: proc { |_resource_owner| false }, reader: true
|
|
13
|
+
setting :generator, default: proc { |_resource_owner| ::Devise.friendly_token(60) }, reader: true
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
setting :refresh_token, reader: true do
|
|
17
|
+
setting :enabled, default: true, reader: true
|
|
18
|
+
setting :expires_in, default: 1.week, reader: true
|
|
19
|
+
setting :generator, default: proc { |_resource_owner| ::Devise.friendly_token(60) }, reader: true
|
|
20
|
+
setting :expires_in_infinite, default: proc { |_resource_owner| false }, reader: true
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
setting :authorization, reader: true do
|
|
24
|
+
setting :key, default: 'Authorization', reader: true
|
|
25
|
+
setting :scheme, default: 'Bearer', reader: true
|
|
26
|
+
setting :location, default: :both, reader: true # :header or :params or :both
|
|
27
|
+
setting :params_key, default: 'access_token', reader: true
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
setting :base_token_model, default: 'Devise::Api::Token', reader: true
|
|
31
|
+
setting :base_controller, default: '::DeviseController', reader: true
|
|
32
|
+
|
|
33
|
+
setting :after_successful_sign_in, default: proc { |_resource_owner, _token, _request| }, reader: true
|
|
34
|
+
setting :after_successful_sign_up, default: proc { |_resource_owner, _token, _request| }, reader: true
|
|
35
|
+
setting :after_successful_refresh, default: proc { |_resource_owner, _token, _request| }, reader: true
|
|
36
|
+
setting :after_successful_revoke, default: proc { |_resource_owner, _token, _request| }, reader: true
|
|
37
|
+
|
|
38
|
+
setting :before_sign_in, default: proc { |_params, _request, _resource_class| }, reader: true
|
|
39
|
+
setting :before_sign_up, default: proc { |_params, _request, _resource_class| }, reader: true
|
|
40
|
+
setting :before_refresh, default: proc { |_token, _request| }, reader: true
|
|
41
|
+
setting :before_revoke, default: proc { |_token, _request| }, reader: true
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'active_support/concern'
|
|
4
|
+
|
|
5
|
+
module Devise
|
|
6
|
+
module Api
|
|
7
|
+
module Controllers
|
|
8
|
+
module Helpers
|
|
9
|
+
extend ActiveSupport::Concern
|
|
10
|
+
|
|
11
|
+
def authenticate_devise_api_token!
|
|
12
|
+
if current_devise_api_token.blank?
|
|
13
|
+
error_response = Devise::Api::Responses::ErrorResponse.new(request, error: :invalid_token,
|
|
14
|
+
resource_class: resource_class)
|
|
15
|
+
|
|
16
|
+
return render json: error_response.body, status: error_response.status
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
if current_devise_api_token.expired?
|
|
20
|
+
error_response = Devise::Api::Responses::ErrorResponse.new(request, error: :expired_token,
|
|
21
|
+
resource_class: resource_class)
|
|
22
|
+
|
|
23
|
+
return render json: error_response.body, status: error_response.status
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
return unless current_devise_api_token.revoked?
|
|
27
|
+
|
|
28
|
+
error_response = Devise::Api::Responses::ErrorResponse.new(request, error: :revoked_token,
|
|
29
|
+
resource_class: resource_class)
|
|
30
|
+
|
|
31
|
+
render json: error_response.body, status: error_response.status
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def current_devise_api_refresh_token
|
|
35
|
+
token = find_devise_api_token
|
|
36
|
+
|
|
37
|
+
Devise.api.config.base_token_model.constantize.find_by(refresh_token: token)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def current_devise_api_token
|
|
41
|
+
token = find_devise_api_token
|
|
42
|
+
|
|
43
|
+
devise_api_token_model = Devise.api.config.base_token_model.constantize
|
|
44
|
+
|
|
45
|
+
if Devise.api.config.refresh_token.enabled
|
|
46
|
+
return devise_api_token_model
|
|
47
|
+
.where(access_token: token)
|
|
48
|
+
.or(devise_api_token_model.where(refresh_token: token))
|
|
49
|
+
&.first
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
devise_api_token_model.find_by(access_token: token)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def current_devise_api_user
|
|
56
|
+
current_devise_api_token&.resource_owner
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
private
|
|
60
|
+
|
|
61
|
+
def extract_devise_api_token_from_params
|
|
62
|
+
params[Devise.api.config.authorization.params_key]
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def extract_devise_api_token_from_headers
|
|
66
|
+
token = request.headers[Devise.api.config.authorization.key]
|
|
67
|
+
unless token.blank?
|
|
68
|
+
token = begin
|
|
69
|
+
token.gsub(/^#{Devise.api.config.authorization.scheme} /,
|
|
70
|
+
'')
|
|
71
|
+
rescue StandardError
|
|
72
|
+
token
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
token
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def find_devise_api_token
|
|
79
|
+
case Devise.api.config.authorization.location
|
|
80
|
+
when :header
|
|
81
|
+
extract_devise_api_token_from_headers
|
|
82
|
+
when :params
|
|
83
|
+
extract_devise_api_token_from_params
|
|
84
|
+
when :both
|
|
85
|
+
extract_devise_api_token_from_params || extract_devise_api_token_from_headers
|
|
86
|
+
else
|
|
87
|
+
raise ArgumentError, 'Invalid authorization location, must be :header, :params or :both'
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'rails/generators'
|
|
4
|
+
require 'rails/generators/active_record'
|
|
5
|
+
require 'rails/generators/active_model'
|
|
6
|
+
|
|
7
|
+
module Devise
|
|
8
|
+
module Api
|
|
9
|
+
module Generators
|
|
10
|
+
class InstallGenerator < ::Rails::Generators::Base
|
|
11
|
+
include ::Rails::Generators::Migration
|
|
12
|
+
source_root File.expand_path('templates', __dir__)
|
|
13
|
+
desc 'Generates a migration to add the required fields to the your devise model'
|
|
14
|
+
namespace 'devise_api:install'
|
|
15
|
+
|
|
16
|
+
def install
|
|
17
|
+
migration_template(
|
|
18
|
+
'migration.rb.erb',
|
|
19
|
+
'db/migrate/create_devise_api_tables.rb',
|
|
20
|
+
migration_version: migration_version
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
copy_file locale_source, locale_destination
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def self.next_migration_number(path)
|
|
27
|
+
ActiveRecord::Generators::Base.next_migration_number(path)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
def locale_source
|
|
33
|
+
File.expand_path('../../../../config/locales/en.yml', __dir__)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def locale_destination
|
|
37
|
+
'config/locales/devise_api.en.yml'
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def migration_version
|
|
41
|
+
"[#{ActiveRecord::VERSION::MAJOR}.#{ActiveRecord::VERSION::MINOR}]"
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def primary_key_type
|
|
45
|
+
fallback = :integer
|
|
46
|
+
|
|
47
|
+
begin
|
|
48
|
+
ActiveRecord::Base.connection.supports_pgcrypto_uuid? ? :uuid : fallback
|
|
49
|
+
rescue StandardError
|
|
50
|
+
fallback
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def table_defaults_for_primary_key_type
|
|
55
|
+
return ', type: :uuid' if primary_key_type == :uuid
|
|
56
|
+
|
|
57
|
+
''
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class CreateDeviseApiTables < ActiveRecord::Migration<%= migration_version %>
|
|
4
|
+
def change
|
|
5
|
+
create_table :devise_api_tokens<%= table_defaults_for_primary_key_type %> do |t|
|
|
6
|
+
t.belongs_to :resource_owner, null: false<%= table_defaults_for_primary_key_type %>, polymorphic: true, index: true
|
|
7
|
+
t.string :access_token, null: false, index: true
|
|
8
|
+
t.string :refresh_token, null: true, index: true
|
|
9
|
+
t.integer :expires_in, null: false
|
|
10
|
+
t.datetime :revoked_at, null: true
|
|
11
|
+
t.string :previous_refresh_token, null: true, index: true
|
|
12
|
+
|
|
13
|
+
t.timestamps
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActionDispatch
|
|
4
|
+
module Routing
|
|
5
|
+
class Mapper
|
|
6
|
+
protected
|
|
7
|
+
|
|
8
|
+
def devise_api(mapping, controllers)
|
|
9
|
+
controller = controllers.fetch(:tokens, 'devise/api/tokens')
|
|
10
|
+
path = mapping.path_names.fetch(:tokens, 'tokens')
|
|
11
|
+
|
|
12
|
+
resource :tokens, only: [], controller: controller, path: path do
|
|
13
|
+
collection do
|
|
14
|
+
post :revoke, as: :revoke
|
|
15
|
+
post :refresh, as: :refresh
|
|
16
|
+
post :sign_up, as: :sign_up
|
|
17
|
+
post :sign_in, as: :sign_in
|
|
18
|
+
get :info, as: :info
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Devise
|
|
4
|
+
module Api
|
|
5
|
+
module Responses
|
|
6
|
+
class ErrorResponse
|
|
7
|
+
attr_reader :request, :error, :record, :resource_class
|
|
8
|
+
|
|
9
|
+
ERROR_TYPES = %i[
|
|
10
|
+
invalid_token
|
|
11
|
+
expired_token
|
|
12
|
+
expired_refresh_token
|
|
13
|
+
revoked_token
|
|
14
|
+
refresh_token_disabled
|
|
15
|
+
invalid_refresh_token
|
|
16
|
+
invalid_email
|
|
17
|
+
invalid_resource_owner
|
|
18
|
+
resource_owner_create_error
|
|
19
|
+
devise_api_token_create_error
|
|
20
|
+
devise_api_token_revoke_error
|
|
21
|
+
invalid_authentication
|
|
22
|
+
].freeze
|
|
23
|
+
|
|
24
|
+
ERROR_TYPES.each do |error_type|
|
|
25
|
+
method_name = error_type.end_with?('_error') ? error_type : "#{error_type}_error"
|
|
26
|
+
|
|
27
|
+
define_method("#{method_name}?") do
|
|
28
|
+
error.eql?(error_type)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def initialize(request, error:, record: nil, resource_class: nil)
|
|
33
|
+
@request = request
|
|
34
|
+
@error = error
|
|
35
|
+
@record = record
|
|
36
|
+
@resource_class = resource_class
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def body
|
|
40
|
+
{
|
|
41
|
+
error: error,
|
|
42
|
+
error_description: error_description,
|
|
43
|
+
lockable: devise_lockable_info,
|
|
44
|
+
confirmable: devise_confirmable_info
|
|
45
|
+
}.compact
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def status
|
|
49
|
+
return :unauthorized if unauthorized_status?
|
|
50
|
+
return :bad_request if bad_request_status?
|
|
51
|
+
|
|
52
|
+
:unprocessable_entity
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
private
|
|
56
|
+
|
|
57
|
+
# rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
58
|
+
def error_description
|
|
59
|
+
return [I18n.t("devise.api.error_response.#{error}")] if record.blank?
|
|
60
|
+
if invalid_authentication_error? && devise_lockable_info.present? && record.access_locked?
|
|
61
|
+
return [I18n.t('devise.api.error_response.lockable.locked')]
|
|
62
|
+
end
|
|
63
|
+
if invalid_authentication_error? && devise_confirmable_info.present? && !record.confirmed?
|
|
64
|
+
return [I18n.t('devise.api.error_response.confirmable.unconfirmed')]
|
|
65
|
+
end
|
|
66
|
+
return [I18n.t('devise.api.error_response.invalid_authentication')] if invalid_authentication_error?
|
|
67
|
+
|
|
68
|
+
record.errors.full_messages
|
|
69
|
+
end
|
|
70
|
+
# rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
71
|
+
|
|
72
|
+
def devise_lockable_info
|
|
73
|
+
unless resource_class.present? &&
|
|
74
|
+
resource_class.supported_devise_modules.lockable? &&
|
|
75
|
+
invalid_authentication_error?
|
|
76
|
+
return nil
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
unlock_at = record.access_locked? ? record.locked_at + ::Devise.unlock_in : nil
|
|
80
|
+
|
|
81
|
+
{
|
|
82
|
+
locked: record.access_locked?,
|
|
83
|
+
max_attempts: ::Devise.maximum_attempts,
|
|
84
|
+
failed_attemps: record.failed_attempts,
|
|
85
|
+
locked_at: record.locked_at,
|
|
86
|
+
unlock_at: unlock_at
|
|
87
|
+
}.compact
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def devise_confirmable_info
|
|
91
|
+
unless resource_class.present? &&
|
|
92
|
+
resource_class.supported_devise_modules.confirmable? &&
|
|
93
|
+
invalid_authentication_error?
|
|
94
|
+
return nil
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
{
|
|
98
|
+
confirmed: record.confirmed?,
|
|
99
|
+
confirmation_sent_at: record.confirmed? ? nil : record.confirmation_sent_at
|
|
100
|
+
}.compact
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def unauthorized_status?
|
|
104
|
+
invalid_token_error? ||
|
|
105
|
+
expired_token_error? ||
|
|
106
|
+
expired_refresh_token_error? ||
|
|
107
|
+
revoked_token_error? ||
|
|
108
|
+
invalid_authentication_error?
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def bad_request_status?
|
|
112
|
+
invalid_email_error? ||
|
|
113
|
+
invalid_refresh_token_error? ||
|
|
114
|
+
refresh_token_disabled_error? ||
|
|
115
|
+
invalid_resource_owner_error?
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Devise
|
|
4
|
+
module Api
|
|
5
|
+
module Responses
|
|
6
|
+
class TokenResponse
|
|
7
|
+
attr_reader :request, :token, :action, :resource_owner
|
|
8
|
+
|
|
9
|
+
ACTIONS = %i[
|
|
10
|
+
sign_in
|
|
11
|
+
sign_up
|
|
12
|
+
refresh
|
|
13
|
+
revoke
|
|
14
|
+
info
|
|
15
|
+
].freeze
|
|
16
|
+
|
|
17
|
+
ACTIONS.each do |act|
|
|
18
|
+
define_method("#{act}_action?") do
|
|
19
|
+
action.eql?(act)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def initialize(request, token:, action:)
|
|
24
|
+
@request = request
|
|
25
|
+
@token = token
|
|
26
|
+
@action = action
|
|
27
|
+
@resource_owner = token&.resource_owner
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def body
|
|
31
|
+
return {} if revoke_action?
|
|
32
|
+
return signed_up_body if sign_up_action?
|
|
33
|
+
return info_body if info_action?
|
|
34
|
+
|
|
35
|
+
default_body
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def default_body
|
|
39
|
+
{
|
|
40
|
+
token: token.access_token,
|
|
41
|
+
refresh_token: Devise.api.config.refresh_token.enabled ? token.refresh_token : nil,
|
|
42
|
+
expires_in: token.expires_in,
|
|
43
|
+
token_type: ::Devise.api.config.authorization.scheme,
|
|
44
|
+
resource_owner: {
|
|
45
|
+
id: resource_owner.id,
|
|
46
|
+
email: resource_owner.email,
|
|
47
|
+
created_at: resource_owner.created_at,
|
|
48
|
+
updated_at: resource_owner.updated_at
|
|
49
|
+
}
|
|
50
|
+
}.compact
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def status
|
|
54
|
+
return :created if sign_up_action?
|
|
55
|
+
return :no_content if revoke_action?
|
|
56
|
+
|
|
57
|
+
:ok
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
private
|
|
61
|
+
|
|
62
|
+
def signed_up_body
|
|
63
|
+
return default_body unless resource_owner.class.supported_devise_modules.confirmable?
|
|
64
|
+
|
|
65
|
+
message = resource_owner.confirmed? ? nil : I18n.t('devise.api.registerable.signed_up_but_unconfirmed')
|
|
66
|
+
|
|
67
|
+
default_body.merge(confirmable: { confirmed: resource_owner.confirmed?, message: message }.compact)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def info_body
|
|
71
|
+
default_body[:resource_owner]
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|