devise-api 0.2.0 → 0.3.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.
@@ -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
@@ -66,7 +66,11 @@ module Devise
66
66
  def signed_up_body
67
67
  return default_body unless resource_owner.class.supported_devise_modules.confirmable?
68
68
 
69
- 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
70
74
 
71
75
  default_body.merge(confirmable: { confirmed: resource_owner.confirmed?, message: message }.compact)
72
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.2.0'
5
+ VERSION = '0.3.1'
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: devise-api
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - nejdetkadir
8
- autorequire:
9
8
  bindir: exe
10
9
  cert_chain: []
11
- date: 2024-09-27 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: devise
@@ -113,9 +112,13 @@ executables: []
113
112
  extensions: []
114
113
  extra_rdoc_files: []
115
114
  files:
115
+ - ".claude/commands/commit.md"
116
+ - ".claude/commands/create-branch.md"
117
+ - ".claude/commands/create-pr.md"
116
118
  - ".rspec"
117
119
  - ".rubocop.yml"
118
120
  - CHANGELOG.md
121
+ - CLAUDE.md
119
122
  - CODE_OF_CONDUCT.md
120
123
  - Gemfile
121
124
  - Gemfile.lock
@@ -132,6 +135,17 @@ files:
132
135
  - app/services/devise/api/tokens_service/revoke.rb
133
136
  - config/locales/en.yml
134
137
  - devise-api.gemspec
138
+ - docs/README.md
139
+ - docs/analysis/known-issues.md
140
+ - docs/analysis/security-review.md
141
+ - docs/api-reference.md
142
+ - docs/architecture.md
143
+ - docs/configuration.md
144
+ - docs/data-model.md
145
+ - docs/development.md
146
+ - docs/extending.md
147
+ - docs/services.md
148
+ - docs/testing.md
135
149
  - lib/devise/api.rb
136
150
  - lib/devise/api/configuration.rb
137
151
  - lib/devise/api/controllers/helpers.rb
@@ -143,7 +157,6 @@ files:
143
157
  - lib/devise/api/responses/token_response.rb
144
158
  - lib/devise/api/token.rb
145
159
  - lib/devise/api/version.rb
146
- - sig/devise/api.rbs
147
160
  homepage: https://github.com/nejdetkadir/devise-api
148
161
  licenses:
149
162
  - MIT
@@ -151,7 +164,6 @@ metadata:
151
164
  homepage_uri: https://github.com/nejdetkadir/devise-api
152
165
  source_code_uri: https://github.com/nejdetkadir/devise-api
153
166
  changelog_uri: https://github.com/nejdetkadir/devise-api/blob/main/CHANGELOG.md
154
- post_install_message:
155
167
  rdoc_options: []
156
168
  require_paths:
157
169
  - lib
@@ -166,8 +178,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
166
178
  - !ruby/object:Gem::Version
167
179
  version: '0'
168
180
  requirements: []
169
- rubygems_version: 3.5.18
170
- signing_key:
181
+ rubygems_version: 4.0.6
171
182
  specification_version: 4
172
183
  summary: The devise-api gem is a convenient way to add authentication to your Ruby
173
184
  on Rails application using the devise gem. It provides support for access tokens
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