devise-api 0.1.3 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -5,6 +5,12 @@ module Devise
5
5
  module Rails
6
6
  class Engine < ::Rails::Engine
7
7
  isolate_namespace Devise::Api
8
+
9
+ # Keep raw token secrets out of the host app's request logs (tokens can arrive as
10
+ # query/body params when authorization.location is :params or :both)
11
+ initializer 'devise.api.filter_parameters' do |app|
12
+ app.config.filter_parameters |= %i[access_token refresh_token previous_refresh_token]
13
+ end
8
14
  end
9
15
  end
10
16
  end
@@ -15,6 +15,7 @@ module Devise
15
15
  sign_up_disabled
16
16
  invalid_refresh_token
17
17
  invalid_email
18
+ invalid_login
18
19
  invalid_resource_owner
19
20
  resource_owner_create_error
20
21
  devise_api_token_create_error
@@ -22,6 +23,15 @@ module Devise
22
23
  invalid_authentication
23
24
  ].freeze
24
25
 
26
+ UNAUTHORIZED_ERRORS = %i[
27
+ invalid_token expired_token expired_refresh_token revoked_token invalid_authentication
28
+ ].freeze
29
+
30
+ BAD_REQUEST_ERRORS = %i[
31
+ invalid_email invalid_login invalid_refresh_token refresh_token_disabled sign_up_disabled
32
+ invalid_resource_owner
33
+ ].freeze
34
+
25
35
  ERROR_TYPES.each do |error_type|
26
36
  method_name = error_type.end_with?('_error') ? error_type : "#{error_type}_error"
27
37
 
@@ -55,33 +65,34 @@ module Devise
55
65
 
56
66
  private
57
67
 
58
- # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
59
68
  def error_description
60
69
  return [I18n.t("devise.api.error_response.#{error}")] if record.blank?
61
- if invalid_authentication_error? && devise_lockable_info.present? && record.access_locked?
62
- return [I18n.t('devise.api.error_response.lockable.locked')]
63
- end
64
- if invalid_authentication_error? && devise_confirmable_info.present? && !record.confirmed?
65
- return [I18n.t('devise.api.error_response.confirmable.unconfirmed')]
66
- end
67
- return [I18n.t('devise.api.error_response.invalid_authentication')] if invalid_authentication_error?
70
+ return invalid_authentication_description if invalid_authentication_error?
68
71
 
69
72
  record.errors.full_messages
70
73
  end
71
- # rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
72
74
 
73
- def devise_lockable_info
74
- unless resource_class.present? &&
75
- resource_class.supported_devise_modules.lockable? &&
76
- invalid_authentication_error?
77
- return nil
75
+ def invalid_authentication_description
76
+ unless Devise.api.config.paranoid
77
+ return [I18n.t('devise.api.error_response.lockable.locked')] if lockable_record? && record.access_locked?
78
+ if confirmable_record? && !record.confirmed?
79
+ return [I18n.t('devise.api.error_response.confirmable.unconfirmed')]
80
+ end
78
81
  end
79
82
 
83
+ [I18n.t('devise.api.error_response.invalid_authentication')]
84
+ end
85
+
86
+ def devise_lockable_info
87
+ return nil unless verbose_account_state? && lockable_record? && invalid_authentication_error?
88
+
80
89
  unlock_at = record.access_locked? ? record.locked_at + ::Devise.unlock_in : nil
81
90
 
82
91
  {
83
92
  locked: record.access_locked?,
84
93
  max_attempts: ::Devise.maximum_attempts,
94
+ failed_attempts: record.failed_attempts,
95
+ # Deprecated misspelling kept for backward compatibility; will be removed in the next major release
85
96
  failed_attemps: record.failed_attempts,
86
97
  locked_at: record.locked_at,
87
98
  unlock_at: unlock_at
@@ -89,11 +100,7 @@ module Devise
89
100
  end
90
101
 
91
102
  def devise_confirmable_info
92
- unless resource_class.present? &&
93
- resource_class.supported_devise_modules.confirmable? &&
94
- invalid_authentication_error?
95
- return nil
96
- end
103
+ return nil unless verbose_account_state? && confirmable_record? && invalid_authentication_error?
97
104
 
98
105
  {
99
106
  confirmed: record.confirmed?,
@@ -101,20 +108,24 @@ module Devise
101
108
  }.compact
102
109
  end
103
110
 
111
+ def lockable_record?
112
+ record.present? && resource_class.present? && resource_class.supported_devise_modules.lockable?
113
+ end
114
+
115
+ def confirmable_record?
116
+ record.present? && resource_class.present? && resource_class.supported_devise_modules.confirmable?
117
+ end
118
+
119
+ def verbose_account_state?
120
+ !Devise.api.config.paranoid && Devise.api.config.error_response.verbose_account_state
121
+ end
122
+
104
123
  def unauthorized_status?
105
- invalid_token_error? ||
106
- expired_token_error? ||
107
- expired_refresh_token_error? ||
108
- revoked_token_error? ||
109
- invalid_authentication_error?
124
+ UNAUTHORIZED_ERRORS.include?(error)
110
125
  end
111
126
 
112
127
  def bad_request_status?
113
- invalid_email_error? ||
114
- invalid_refresh_token_error? ||
115
- refresh_token_disabled_error? ||
116
- sign_up_disabled_error? ||
117
- invalid_resource_owner_error?
128
+ BAD_REQUEST_ERRORS.include?(error)
118
129
  end
119
130
  end
120
131
  end
@@ -41,15 +41,19 @@ module Devise
41
41
  refresh_token: Devise.api.config.refresh_token.enabled ? token.refresh_token : nil,
42
42
  expires_in: token.expires_in,
43
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
- }
44
+ resource_owner: default_resource_owner
50
45
  }.compact
51
46
  end
52
47
 
48
+ def default_resource_owner
49
+ keys_to_extract = %i[id email created_at updated_at]
50
+ if Devise.api.config.sign_up.extra_fields.present?
51
+ keys_to_extract |= Devise.api.config.sign_up.extra_fields.map(&:to_sym)
52
+ end
53
+
54
+ resource_owner.slice(*keys_to_extract)
55
+ end
56
+
53
57
  def status
54
58
  return :created if sign_up_action?
55
59
  return :no_content if revoke_action?
@@ -62,7 +66,11 @@ module Devise
62
66
  def signed_up_body
63
67
  return default_body unless resource_owner.class.supported_devise_modules.confirmable?
64
68
 
65
- message = resource_owner.confirmed? ? nil : I18n.t('devise.api.registerable.signed_up_but_unconfirmed')
69
+ message = if resource_owner.confirmed?
70
+ nil
71
+ else
72
+ I18n.t('devise.api.error_response.registerable.signed_up_but_unconfirmed')
73
+ end
66
74
 
67
75
  default_body.merge(confirmable: { confirmed: resource_owner.confirmed?, message: message }.compact)
68
76
  end
@@ -7,6 +7,10 @@ module Devise
7
7
  class Token < ::ActiveRecord::Base
8
8
  self.table_name = 'devise_api_tokens'
9
9
 
10
+ # Redact raw token secrets from #inspect / pretty_print output (see also the
11
+ # devise.api.filter_parameters engine initializer for request-log filtering)
12
+ self.filter_attributes += %i[access_token refresh_token previous_refresh_token]
13
+
10
14
  # associations
11
15
  belongs_to :resource_owner,
12
16
  polymorphic: true,
@@ -36,6 +40,21 @@ module Devise
36
40
  revoked_at.present?
37
41
  end
38
42
 
43
+ def revoke!
44
+ return self if revoked?
45
+
46
+ update!(revoked_at: Time.current)
47
+ self
48
+ end
49
+
50
+ # Revokes every token in this token's refresh chain (ancestors and descendants). Used for
51
+ # refresh-token reuse detection when refresh_token.rotation_enabled is on.
52
+ def revoke_family!
53
+ transaction do
54
+ family_tokens.each(&:revoke!)
55
+ end
56
+ end
57
+
39
58
  def active?
40
59
  !inactive?
41
60
  end
@@ -47,13 +66,13 @@ module Devise
47
66
  def expired?
48
67
  return false if Devise.api.config.access_token.expires_in_infinite.call(resource_owner)
49
68
 
50
- !!(expires_in && Time.now.utc > expires_at)
69
+ !!(expires_in && Time.current > expires_at)
51
70
  end
52
71
 
53
72
  def refresh_token_expired?
54
73
  return false if Devise.api.config.refresh_token.expires_in_infinite.call(resource_owner)
55
74
 
56
- Time.now.utc > refresh_token_expires_at
75
+ Time.current > refresh_token_expires_at
57
76
  end
58
77
 
59
78
  def self.generate_uniq_access_token(resource_owner)
@@ -83,6 +102,19 @@ module Devise
83
102
  def refresh_token_expires_at
84
103
  created_at + Devise.api.config.refresh_token.expires_in.seconds
85
104
  end
105
+
106
+ def family_tokens
107
+ root = self
108
+ root = root.previous_refresh while root.previous_refresh.present?
109
+
110
+ tokens = []
111
+ queue = [root]
112
+ while (token = queue.shift)
113
+ tokens << token
114
+ queue.concat(token.refreshes.to_a)
115
+ end
116
+ tokens
117
+ end
86
118
  end
87
119
  end
88
120
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Devise
4
4
  module Api
5
- VERSION = '0.1.3'
5
+ VERSION = '0.3.0'
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: devise-api
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - nejdetkadir
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-08-08 00:00:00.000000000 Z
11
+ date: 2026-08-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: devise
@@ -113,9 +113,13 @@ executables: []
113
113
  extensions: []
114
114
  extra_rdoc_files: []
115
115
  files:
116
+ - ".claude/commands/commit.md"
117
+ - ".claude/commands/create-branch.md"
118
+ - ".claude/commands/create-pr.md"
116
119
  - ".rspec"
117
120
  - ".rubocop.yml"
118
121
  - CHANGELOG.md
122
+ - CLAUDE.md
119
123
  - CODE_OF_CONDUCT.md
120
124
  - Gemfile
121
125
  - Gemfile.lock
@@ -132,6 +136,17 @@ files:
132
136
  - app/services/devise/api/tokens_service/revoke.rb
133
137
  - config/locales/en.yml
134
138
  - devise-api.gemspec
139
+ - docs/README.md
140
+ - docs/analysis/known-issues.md
141
+ - docs/analysis/security-review.md
142
+ - docs/api-reference.md
143
+ - docs/architecture.md
144
+ - docs/configuration.md
145
+ - docs/data-model.md
146
+ - docs/development.md
147
+ - docs/extending.md
148
+ - docs/services.md
149
+ - docs/testing.md
135
150
  - lib/devise/api.rb
136
151
  - lib/devise/api/configuration.rb
137
152
  - lib/devise/api/controllers/helpers.rb
@@ -143,7 +158,6 @@ files:
143
158
  - lib/devise/api/responses/token_response.rb
144
159
  - lib/devise/api/token.rb
145
160
  - lib/devise/api/version.rb
146
- - sig/devise/api.rbs
147
161
  homepage: https://github.com/nejdetkadir/devise-api
148
162
  licenses:
149
163
  - MIT
@@ -166,7 +180,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
166
180
  - !ruby/object:Gem::Version
167
181
  version: '0'
168
182
  requirements: []
169
- rubygems_version: 3.4.12
183
+ rubygems_version: 3.4.19
170
184
  signing_key:
171
185
  specification_version: 4
172
186
  summary: The devise-api gem is a convenient way to add authentication to your Ruby
data/sig/devise/api.rbs DELETED
@@ -1,6 +0,0 @@
1
- module Devise
2
- module Api
3
- VERSION: String
4
- # See the writing guide of rbs: https://github.com/ruby/rbs#guides
5
- end
6
- end