sorcery-jwt 0.2.0 → 1.0.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.
@@ -0,0 +1,307 @@
1
+ PATH
2
+ remote: ..
3
+ specs:
4
+ sorcery-jwt (0.2.1)
5
+ jwt (>= 1.0, < 3.0)
6
+ sorcery (>= 0.16.5, < 0.19)
7
+
8
+ GEM
9
+ remote: https://rubygems.org/
10
+ specs:
11
+ action_text-trix (2.1.19)
12
+ railties
13
+ actioncable (8.1.3.1)
14
+ actionpack (= 8.1.3.1)
15
+ activesupport (= 8.1.3.1)
16
+ nio4r (~> 2.0)
17
+ websocket-driver (>= 0.6.1)
18
+ zeitwerk (~> 2.6)
19
+ actionmailbox (8.1.3.1)
20
+ actionpack (= 8.1.3.1)
21
+ activejob (= 8.1.3.1)
22
+ activerecord (= 8.1.3.1)
23
+ activestorage (= 8.1.3.1)
24
+ activesupport (= 8.1.3.1)
25
+ mail (>= 2.8.0)
26
+ actionmailer (8.1.3.1)
27
+ actionpack (= 8.1.3.1)
28
+ actionview (= 8.1.3.1)
29
+ activejob (= 8.1.3.1)
30
+ activesupport (= 8.1.3.1)
31
+ mail (>= 2.8.0)
32
+ rails-dom-testing (~> 2.2)
33
+ actionpack (8.1.3.1)
34
+ actionview (= 8.1.3.1)
35
+ activesupport (= 8.1.3.1)
36
+ nokogiri (>= 1.8.5)
37
+ rack (>= 2.2.4)
38
+ rack-session (>= 1.0.1)
39
+ rack-test (>= 0.6.3)
40
+ rails-dom-testing (~> 2.2)
41
+ rails-html-sanitizer (~> 1.6)
42
+ useragent (~> 0.16)
43
+ actiontext (8.1.3.1)
44
+ action_text-trix (~> 2.1.15)
45
+ actionpack (= 8.1.3.1)
46
+ activerecord (= 8.1.3.1)
47
+ activestorage (= 8.1.3.1)
48
+ activesupport (= 8.1.3.1)
49
+ globalid (>= 0.6.0)
50
+ nokogiri (>= 1.8.5)
51
+ actionview (8.1.3.1)
52
+ activesupport (= 8.1.3.1)
53
+ builder (~> 3.1)
54
+ erubi (~> 1.11)
55
+ rails-dom-testing (~> 2.2)
56
+ rails-html-sanitizer (~> 1.6)
57
+ activejob (8.1.3.1)
58
+ activesupport (= 8.1.3.1)
59
+ globalid (>= 0.3.6)
60
+ activemodel (8.1.3.1)
61
+ activesupport (= 8.1.3.1)
62
+ activerecord (8.1.3.1)
63
+ activemodel (= 8.1.3.1)
64
+ activesupport (= 8.1.3.1)
65
+ timeout (>= 0.4.0)
66
+ activestorage (8.1.3.1)
67
+ actionpack (= 8.1.3.1)
68
+ activejob (= 8.1.3.1)
69
+ activerecord (= 8.1.3.1)
70
+ activesupport (= 8.1.3.1)
71
+ marcel (~> 1.0)
72
+ activesupport (8.1.3.1)
73
+ base64
74
+ bigdecimal
75
+ concurrent-ruby (~> 1.0, >= 1.3.1)
76
+ connection_pool (>= 2.2.5)
77
+ drb
78
+ i18n (>= 1.6, < 2)
79
+ json
80
+ logger (>= 1.4.2)
81
+ minitest (>= 5.1)
82
+ securerandom (>= 0.3)
83
+ tzinfo (~> 2.0, >= 2.0.5)
84
+ uri (>= 0.13.1)
85
+ anonymous_loader (0.1.3)
86
+ version_gem (~> 1.1, >= 1.1.14)
87
+ auth-sanitizer (0.2.3)
88
+ version_gem (~> 1.1, >= 1.1.14)
89
+ base64 (0.3.0)
90
+ bcrypt (3.1.22)
91
+ bigdecimal (4.1.2)
92
+ builder (3.3.0)
93
+ concurrent-ruby (1.3.8)
94
+ connection_pool (3.0.2)
95
+ crass (1.0.7)
96
+ date (3.5.1)
97
+ diff-lcs (1.6.2)
98
+ drb (2.2.3)
99
+ erb (6.0.7)
100
+ erubi (1.13.1)
101
+ faraday (2.14.3)
102
+ faraday-net_http (>= 2.0, < 3.5)
103
+ json
104
+ logger
105
+ faraday-net_http (3.4.4)
106
+ net-http (~> 0.5)
107
+ globalid (1.4.0)
108
+ activesupport (>= 6.1)
109
+ hashie (5.1.0)
110
+ logger
111
+ i18n (1.15.2)
112
+ concurrent-ruby (~> 1.0)
113
+ io-console (0.9.2)
114
+ irb (1.18.0)
115
+ pp (>= 0.6.0)
116
+ prism (>= 1.3.0)
117
+ rdoc (>= 4.0.0)
118
+ reline (>= 0.4.2)
119
+ json (2.21.2)
120
+ jwt (2.10.3)
121
+ base64
122
+ logger (1.7.0)
123
+ loofah (2.25.2)
124
+ crass (~> 1.0.2)
125
+ nokogiri (>= 1.12.0)
126
+ mail (2.9.1)
127
+ logger
128
+ mini_mime (>= 0.1.1)
129
+ net-imap
130
+ net-pop
131
+ net-smtp
132
+ marcel (1.2.1)
133
+ mini_mime (1.1.5)
134
+ minitest (6.0.6)
135
+ drb (~> 2.0)
136
+ prism (~> 1.5)
137
+ multi_xml (0.9.1)
138
+ bigdecimal (>= 3.1, < 5)
139
+ net-http (0.9.1)
140
+ uri (>= 0.11.1)
141
+ net-imap (0.6.6)
142
+ date
143
+ net-protocol
144
+ net-pop (0.1.2)
145
+ net-protocol
146
+ net-protocol (0.3.0)
147
+ timeout
148
+ net-smtp (0.5.1)
149
+ net-protocol
150
+ nio4r (2.7.5)
151
+ nokogiri (1.19.4-aarch64-linux-gnu)
152
+ racc (~> 1.4)
153
+ nokogiri (1.19.4-aarch64-linux-musl)
154
+ racc (~> 1.4)
155
+ nokogiri (1.19.4-arm-linux-gnu)
156
+ racc (~> 1.4)
157
+ nokogiri (1.19.4-arm-linux-musl)
158
+ racc (~> 1.4)
159
+ nokogiri (1.19.4-arm64-darwin)
160
+ racc (~> 1.4)
161
+ nokogiri (1.19.4-x86_64-darwin)
162
+ racc (~> 1.4)
163
+ nokogiri (1.19.4-x86_64-linux-gnu)
164
+ racc (~> 1.4)
165
+ nokogiri (1.19.4-x86_64-linux-musl)
166
+ racc (~> 1.4)
167
+ oauth (1.1.8)
168
+ anonymous_loader (~> 0.1, >= 0.1.3)
169
+ auth-sanitizer (~> 0.2, >= 0.2.3)
170
+ base64 (~> 0.1)
171
+ oauth-tty (~> 1.0, >= 1.0.13)
172
+ snaky_hash (~> 2.0, >= 2.0.7)
173
+ version_gem (~> 1.1, >= 1.1.14)
174
+ oauth-tty (1.0.13)
175
+ anonymous_loader (~> 0.1, >= 0.1.3)
176
+ auth-sanitizer (~> 0.2, >= 0.2.3)
177
+ version_gem (~> 1.1, >= 1.1.14)
178
+ oauth2 (2.0.25)
179
+ anonymous_loader (~> 0.1, >= 0.1.3)
180
+ auth-sanitizer (~> 0.2, >= 0.2.3)
181
+ faraday (>= 0.17.3, < 4.0)
182
+ jwt (>= 1.0, < 4.0)
183
+ logger (~> 1.2)
184
+ multi_xml (~> 0.5)
185
+ rack (>= 1.2, < 4)
186
+ snaky_hash (~> 2.0, >= 2.0.7)
187
+ version_gem (~> 1.1, >= 1.1.14)
188
+ pp (0.6.4)
189
+ prettyprint
190
+ prettyprint (0.2.0)
191
+ prism (1.9.0)
192
+ racc (1.8.1)
193
+ rack (3.2.7)
194
+ rack-session (2.1.2)
195
+ base64 (>= 0.1.0)
196
+ rack (>= 3.0.0)
197
+ rack-test (2.2.0)
198
+ rack (>= 1.3)
199
+ rackup (2.3.1)
200
+ rack (>= 3)
201
+ rails (8.1.3.1)
202
+ actioncable (= 8.1.3.1)
203
+ actionmailbox (= 8.1.3.1)
204
+ actionmailer (= 8.1.3.1)
205
+ actionpack (= 8.1.3.1)
206
+ actiontext (= 8.1.3.1)
207
+ actionview (= 8.1.3.1)
208
+ activejob (= 8.1.3.1)
209
+ activemodel (= 8.1.3.1)
210
+ activerecord (= 8.1.3.1)
211
+ activestorage (= 8.1.3.1)
212
+ activesupport (= 8.1.3.1)
213
+ bundler (>= 1.15.0)
214
+ railties (= 8.1.3.1)
215
+ rails-dom-testing (2.3.0)
216
+ activesupport (>= 5.0.0)
217
+ minitest
218
+ nokogiri (>= 1.6)
219
+ rails-html-sanitizer (1.7.1)
220
+ loofah (~> 2.25, >= 2.25.2)
221
+ nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0)
222
+ railties (8.1.3.1)
223
+ actionpack (= 8.1.3.1)
224
+ activesupport (= 8.1.3.1)
225
+ irb (~> 1.13)
226
+ rackup (>= 1.0.0)
227
+ rake (>= 12.2)
228
+ thor (~> 1.0, >= 1.2.2)
229
+ tsort (>= 0.2)
230
+ zeitwerk (~> 2.6)
231
+ rake (13.4.2)
232
+ rbs (4.2.0)
233
+ logger
234
+ prism (>= 1.6.0)
235
+ tsort
236
+ rdoc (8.0.0)
237
+ erb
238
+ prism (>= 1.6.0)
239
+ rbs (>= 4.0.0)
240
+ tsort
241
+ reline (0.7.0)
242
+ io-console (~> 0.5)
243
+ rspec (3.13.2)
244
+ rspec-core (~> 3.13.0)
245
+ rspec-expectations (~> 3.13.0)
246
+ rspec-mocks (~> 3.13.0)
247
+ rspec-core (3.13.6)
248
+ rspec-support (~> 3.13.0)
249
+ rspec-expectations (3.13.5)
250
+ diff-lcs (>= 1.2.0, < 2.0)
251
+ rspec-support (~> 3.13.0)
252
+ rspec-mocks (3.13.8)
253
+ diff-lcs (>= 1.2.0, < 2.0)
254
+ rspec-support (~> 3.13.0)
255
+ rspec-support (3.13.7)
256
+ securerandom (0.4.1)
257
+ snaky_hash (2.0.7)
258
+ hashie (>= 0.1.0, < 6)
259
+ version_gem (~> 1.1, >= 1.1.14)
260
+ sorcery (0.18.0)
261
+ bcrypt (~> 3.1)
262
+ oauth (>= 0.6)
263
+ oauth2 (~> 2.0)
264
+ railties (>= 7.1)
265
+ sqlite3 (2.9.6-aarch64-linux-gnu)
266
+ sqlite3 (2.9.6-aarch64-linux-musl)
267
+ sqlite3 (2.9.6-arm-linux-gnu)
268
+ sqlite3 (2.9.6-arm-linux-musl)
269
+ sqlite3 (2.9.6-arm64-darwin)
270
+ sqlite3 (2.9.6-x86_64-darwin)
271
+ sqlite3 (2.9.6-x86_64-linux-gnu)
272
+ sqlite3 (2.9.6-x86_64-linux-musl)
273
+ thor (1.5.0)
274
+ timeout (0.6.1)
275
+ tsort (0.2.0)
276
+ tzinfo (2.0.6)
277
+ concurrent-ruby (~> 1.0)
278
+ uri (1.1.1)
279
+ useragent (0.16.11)
280
+ version_gem (1.1.15)
281
+ websocket-driver (0.8.2)
282
+ base64
283
+ websocket-extensions (>= 0.1.0)
284
+ websocket-extensions (0.1.5)
285
+ zeitwerk (2.8.3)
286
+
287
+ PLATFORMS
288
+ aarch64-linux-gnu
289
+ aarch64-linux-musl
290
+ arm-linux-gnu
291
+ arm-linux-musl
292
+ arm64-darwin
293
+ x86_64-darwin
294
+ x86_64-linux-gnu
295
+ x86_64-linux-musl
296
+
297
+ DEPENDENCIES
298
+ activerecord (~> 8.0)
299
+ rails (~> 8.0)
300
+ rake (~> 13.0)
301
+ rspec (~> 3.0)
302
+ sorcery (= 0.18.0)
303
+ sorcery-jwt!
304
+ sqlite3 (~> 2.0)
305
+
306
+ BUNDLED WITH
307
+ 2.6.9
@@ -10,6 +10,9 @@ module Sorcery
10
10
  module InstanceMethods
11
11
  protected
12
12
 
13
+ # A sorcery login source: authenticates the request from the
14
+ # Authorization header when it carries a validly signed, unexpired
15
+ # JWT whose id and email claims match a user.
13
16
  def login_from_jwt
14
17
  claims = decoded_token.first.slice("id", "email")
15
18
  return @current_user = nil if claims.empty?
@@ -21,6 +24,10 @@ module Sorcery
21
24
  @current_user = nil
22
25
  end
23
26
 
27
+ # Authenticates credentials via sorcery and, on success, issues a
28
+ # JWT for the user carrying its id and email claims.
29
+ #
30
+ # @return [String, nil] the JWT, or nil when credentials fail
24
31
  def login_and_issue_token(*credentials)
25
32
  return unless (user = user_class.authenticate(*credentials))
26
33
 
@@ -1,5 +1,5 @@
1
1
  module Sorcery
2
2
  module Jwt
3
- VERSION = "0.2.0".freeze
3
+ VERSION = "1.0.0".freeze
4
4
  end
5
5
  end
@@ -15,11 +15,14 @@ module Sorcery
15
15
  attr_accessor :jwt_algorithm
16
16
  # How long the session should be valid for in seconds. Will be set as the exp claim in the token.
17
17
  attr_accessor :session_expiry
18
+ # Clock-skew tolerance in seconds applied when verifying exp. nil disables leeway.
19
+ attr_accessor :exp_leeway
18
20
  end
19
21
 
20
22
  base.sorcery_config.instance_eval do
21
23
  @defaults[:@jwt_algorithm] = "HS256"
22
24
  @defaults[:@session_expiry] = 60 * 60 * 24 * 7 * 2 # 2 weeks
25
+ @defaults[:@exp_leeway] = nil
23
26
 
24
27
  reset!
25
28
  end
@@ -30,15 +33,33 @@ module Sorcery
30
33
  end
31
34
 
32
35
  module ClassMethods
36
+ # Encodes a JWT for a user, adding the exp and iat claims.
37
+ # Any claim in the payload except exp and iat is preserved.
38
+ #
39
+ # @param payload [Hash] claims to encode; must include "id" and "email"
40
+ # for the token to authenticate via the controller submodule
41
+ # @return [String] the signed JWT
33
42
  def issue_token(payload)
34
- exp_payload = payload.merge(exp: Time.now.to_i + @sorcery_config.session_expiry)
43
+ now = Time.now.to_i
44
+ exp_payload = payload.merge(iat: now, exp: now + @sorcery_config.session_expiry)
35
45
  JWT.encode(exp_payload, @sorcery_config.jwt_secret, @sorcery_config.jwt_algorithm)
36
46
  end
37
47
 
48
+ # Decodes and verifies a JWT with the configured secret and algorithm.
49
+ #
50
+ # @param token [String, nil] the JWT
51
+ # @return [Array<(Hash, Hash)>] payload and header, as returned by jwt
52
+ # @raise [JWT::DecodeError] if the signature, algorithm, or exp fail
38
53
  def decode_token(token)
39
- JWT.decode(token, @sorcery_config.jwt_secret, true, algorithm: @sorcery_config.jwt_algorithm)
54
+ options = { algorithm: @sorcery_config.jwt_algorithm }
55
+ options[:exp_leeway] = @sorcery_config.exp_leeway if @sorcery_config.exp_leeway
56
+ JWT.decode(token, @sorcery_config.jwt_secret, true, options)
40
57
  end
41
58
 
59
+ # Returns whether a JWT is validly signed and unexpired.
60
+ #
61
+ # @param token [String, nil]
62
+ # @return [Boolean]
42
63
  def token_valid?(token)
43
64
  !!decode_token(token)
44
65
  rescue JWT::DecodeError, JWT::ExpiredSignature
data/sorcery-jwt.gemspec CHANGED
@@ -16,6 +16,8 @@ Gem::Specification.new do |spec|
16
16
  spec.metadata["homepage_uri"] = spec.homepage
17
17
  spec.metadata["source_code_uri"] = spec.homepage
18
18
  spec.metadata["changelog_uri"] = "#{spec.homepage}/CHANGELOG.md"
19
+ spec.metadata["bug_tracker_uri"] = "#{spec.homepage}/issues"
20
+ spec.metadata["rubygems_mfa_required"] = "true"
19
21
 
20
22
  spec.required_ruby_version = ">= 3.0"
21
23
 
@@ -32,9 +34,12 @@ Gem::Specification.new do |spec|
32
34
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
33
35
  spec.require_paths = ["lib"]
34
36
 
37
+ spec.add_development_dependency "activerecord", "~> 8.0"
38
+ spec.add_development_dependency "rails", "~> 8.0"
35
39
  spec.add_development_dependency "rake", "~> 13.0"
36
40
  spec.add_development_dependency "rspec", "~> 3.0"
41
+ spec.add_development_dependency "sqlite3", "~> 2.0"
37
42
 
38
43
  spec.add_runtime_dependency "jwt", ">= 1.0", "< 3.0"
39
- spec.add_runtime_dependency "sorcery", ">= 0.13", "< 0.17"
44
+ spec.add_runtime_dependency "sorcery", ">= 0.16.5", "< 0.19"
40
45
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sorcery-jwt
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Hayden Luckenbach
@@ -9,6 +9,34 @@ bindir: exe
9
9
  cert_chain: []
10
10
  date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: activerecord
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '8.0'
19
+ type: :development
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: '8.0'
26
+ - !ruby/object:Gem::Dependency
27
+ name: rails
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: '8.0'
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '8.0'
12
40
  - !ruby/object:Gem::Dependency
13
41
  name: rake
14
42
  requirement: !ruby/object:Gem::Requirement
@@ -37,6 +65,20 @@ dependencies:
37
65
  - - "~>"
38
66
  - !ruby/object:Gem::Version
39
67
  version: '3.0'
68
+ - !ruby/object:Gem::Dependency
69
+ name: sqlite3
70
+ requirement: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - "~>"
73
+ - !ruby/object:Gem::Version
74
+ version: '2.0'
75
+ type: :development
76
+ prerelease: false
77
+ version_requirements: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - "~>"
80
+ - !ruby/object:Gem::Version
81
+ version: '2.0'
40
82
  - !ruby/object:Gem::Dependency
41
83
  name: jwt
42
84
  requirement: !ruby/object:Gem::Requirement
@@ -63,20 +105,20 @@ dependencies:
63
105
  requirements:
64
106
  - - ">="
65
107
  - !ruby/object:Gem::Version
66
- version: '0.13'
108
+ version: 0.16.5
67
109
  - - "<"
68
110
  - !ruby/object:Gem::Version
69
- version: '0.17'
111
+ version: '0.19'
70
112
  type: :runtime
71
113
  prerelease: false
72
114
  version_requirements: !ruby/object:Gem::Requirement
73
115
  requirements:
74
116
  - - ">="
75
117
  - !ruby/object:Gem::Version
76
- version: '0.13'
118
+ version: 0.16.5
77
119
  - - "<"
78
120
  - !ruby/object:Gem::Version
79
- version: '0.17'
121
+ version: '0.19'
80
122
  description: Adds JWT issuance and authentication to the Sorcery authentication library,
81
123
  for API-only Rails apps.
82
124
  executables: []
@@ -86,16 +128,22 @@ files:
86
128
  - ".github/workflows/ci.yml"
87
129
  - ".gitignore"
88
130
  - ".rspec"
131
+ - ".ruby-version"
89
132
  - CHANGELOG.md
90
133
  - CODE_OF_CONDUCT.md
91
134
  - Gemfile
92
135
  - Gemfile.lock
93
136
  - LICENSE.txt
94
- - PLAN.md
95
137
  - README.md
96
138
  - Rakefile
97
139
  - bin/console
98
140
  - bin/setup
141
+ - gemfiles/sorcery_0.16.5.gemfile
142
+ - gemfiles/sorcery_0.16.5.gemfile.lock
143
+ - gemfiles/sorcery_0.17.0.gemfile
144
+ - gemfiles/sorcery_0.17.0.gemfile.lock
145
+ - gemfiles/sorcery_0.18.0.gemfile
146
+ - gemfiles/sorcery_0.18.0.gemfile.lock
99
147
  - lib/sorcery/controller/submodules/jwt.rb
100
148
  - lib/sorcery/jwt.rb
101
149
  - lib/sorcery/jwt/version.rb
@@ -109,6 +157,8 @@ metadata:
109
157
  homepage_uri: https://github.com/hayfever/sorcery-jwt
110
158
  source_code_uri: https://github.com/hayfever/sorcery-jwt
111
159
  changelog_uri: https://github.com/hayfever/sorcery-jwt/CHANGELOG.md
160
+ bug_tracker_uri: https://github.com/hayfever/sorcery-jwt/issues
161
+ rubygems_mfa_required: 'true'
112
162
  rdoc_options: []
113
163
  require_paths:
114
164
  - lib
data/PLAN.md DELETED
@@ -1,94 +0,0 @@
1
- # Sorcery::Jwt — Completion plan (1.0.0, no rewrite)
2
-
3
- Goal: ship 1.0.0 = the **existing API**, bug-fixed, tested, CI'd, compatible
4
- with current sorcery. No architectural changes. Everything that would break
5
- the public API is deferred (§5).
6
-
7
- Public API frozen as-is (from `0.1.13`):
8
- `issue_token(payload)` · `decode_token(token)` · `token_valid?(token)` ·
9
- `login_from_jwt` (login source) · `login_and_issue_token(*credentials)` ·
10
- token claims `id` + `email` · config: `jwt_secret`, `jwt_algorithm`,
11
- `session_expiry`.
12
-
13
- Current version `0.1.13`. Target `1.0.0`. Est. **~3 days**.
14
-
15
- ---
16
-
17
- ## Phase 0 — Environment & packaging *(~0.5 day)*
18
-
19
- - [ ] Install Ruby 3.2+ (none on this machine).
20
- - [ ] Refresh `Gemfile.lock` on modern Bundler; drop `spec.add_development_dependency "bundler"`.
21
- - [ ] Fill gemspec: `authors`, `email`, `description`, `metadata[...]`
22
- (`homepage_uri`, `source_code_uri`, `changelog_uri`).
23
- - [ ] Dependency check: gemspec allows `sorcery (>= 0.13, < 0.17)` and
24
- `jwt (>= 1.0, < 3.0)`. Latest sorcery is **0.18.0** (Dec 2025, needs
25
- Ruby ≥ 3.2). Plan: test 0.16.5 / 0.17 / 0.18, widen the range to what
26
- CI proves. Keep `jwt < 3.0` unless 3.x is proven stable.
27
-
28
- ## Phase 1 — Bug fixes, API unchanged *(~0.5 day)*
29
-
30
- All fixes are in-place; no caller-visible changes except corrected behavior.
31
-
32
- - [ ] **Auth bypass** (`login_from_jwt`): `decoded_token.first.slice("id",
33
- "email")` can be `{}` for a validly-signed token → `find_by({})`
34
- returns the first user in the table. Fix: reject empty slice before
35
- lookup (~2 lines). This is the only security-critical item.
36
- - [ ] `token_valid?`: `.present?` is an undeclared ActiveSupport dependency.
37
- Replace with a plain truthy check; add spec proving it works without
38
- ActiveSupport loaded.
39
- - [ ] Failed JWT login sets `@current_user = false` → `nil` (sorcery
40
- convention).
41
- - [ ] Keep the `JWT::ExpiredSignature` rescue clause (redundant on jwt 2.x,
42
- correct on 1.x — harmless either way).
43
-
44
- ## Phase 2 — Test suite *(~1 day, the long pole)*
45
-
46
- - [ ] PORO test doubles, not a dummy Rails app: stub `User#find_by` /
47
- `authenticate`, fake controller with stubbed `request.headers`.
48
- Time-box **half a day**; if sorcery's Model included-hook fights a
49
- PORO, mirror sorcery's own spec helper pattern instead.
50
- - [ ] Specs (all public methods + failure modes):
51
- - issue → decode round-trip; `exp` set and enforced
52
- - wrong secret / wrong algorithm → rejected
53
- - **missing claims → no login** (bypass regression test)
54
- - valid sig, user not in DB → no login
55
- - `token_valid?` without ActiveSupport
56
- - `login_and_issue_token` bad credentials → nil, no `current_user`
57
- - missing/malformed Authorization header → no login, no exception
58
- - [ ] **Ship `0.2.0`** here: tested + security fix for the existing user base
59
- (95K downloads on 0.1.13), zero API break. Don't hold it for 1.0.
60
-
61
- ## Phase 3 — Compat verification + CI *(~0.5 day)*
62
-
63
- - [ ] Replace `.travis.yml` with GitHub Actions: `bundle exec rspec` on
64
- Ruby 3.2 / 3.3 / 3.4 × sorcery 0.16.5 / 0.17 / 0.18 (or appraisal).
65
- - [ ] Fix whatever breaks on sorcery 0.17/0.18 (candidate:
66
- `Config.login_sources` internals, `Time.now` usage).
67
- - [ ] Set final dependency ranges from what CI proves green.
68
-
69
- ## Phase 4 — Docs + release *(~0.5 day)*
70
-
71
- - [ ] README: claim contract (`id` + `email` required in payload), config
72
- reference, security notes (no built-in revocation — link
73
- waiting-for-dev revocation strategies), upstream note (sorcery v1
74
- plans an official JWT plugin).
75
- - [ ] `CHANGELOG.md` 0.1.13 → 1.0.0: framing = same API, bugs fixed, suite +
76
- CI added, current sorcery supported. No breaking changes from 0.1.13.
77
- - [ ] `bundle exec rake build`, install locally, smoke test.
78
- - [ ] Tag `v1.0.0`, `gem push` (hayfever owns the rubygems slot).
79
-
80
- ---
81
-
82
- ## 5. Explicitly deferred (API-breaking — out of scope for 1.0)
83
-
84
- From the earlier review; revisit only if upstream v1 stalls indefinitely:
85
-
86
- - `token_version` revocation (logout / password-change invalidation)
87
- - Refresh tokens with rotation + reuse detection
88
- - `sub` / `iat` / `jti` claims, `exp` leeway, email decoupling
89
- - `authenticate_from_token` encapsulation (decode contract stays as-is)
90
-
91
- Endgame note: upstream sorcery is active (commits through mid-2026, V1
92
- roadmap on main) and plans an official `sorcery-jwt` plugin for v1 — same
93
- gem name we own. After 1.0, talk to maintainers (history in
94
- sorcery-rework#9) about handover vs. parallel.