jwt_sessions 2.5.0 → 2.7.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cb5fdf14070b5d76ee868b427684fba6797cada31600e28b3346c601d7a3079e
4
- data.tar.gz: fb709c41d45a11ea6e5b2d93f991326c0ddd4a562995dc837099af138b86ee08
3
+ metadata.gz: ad42d33af309b3295e9aa6402179df2a9a0ac5e17618d133427e27edafca9578
4
+ data.tar.gz: e67b97b9bf3a2aec4bba3f66cc517d4dad03bcb4ce73269147cce31bcd9d85d4
5
5
  SHA512:
6
- metadata.gz: 742c465fe4f58ca7327cc1257b98e21f658e891c12e5637bde92c8ca48010a284db43ec8af0ff53454f6642927a1de1cb2b5473619d7c5ecdaba706fba6e4527
7
- data.tar.gz: 6247a76e67522e23211a6a6791312d6c50b06598b34743a92f7c32ec841a75928a6eae08d17fc3968fb9cff3ec886efcf7e0a19fe7bea9291dc7b0560fd0a658
6
+ metadata.gz: fe6bc8a993388688a85d6b86ea139ab8d3123b628db77181fa4db6ba68d92c0080ca16fdfcaeb3567be9f2a07896df711cb9edb9c7845d6a226408e6fda00de9
7
+ data.tar.gz: 712ea681644f82710f5d561a96e78df86c1c3e4c225b8535a9d1cd2e03bcb363d93e4b42764847b786461e26f6f9440a3f85b026e02fa557c647a5866eef70dc
data/CHANGELOG.md ADDED
@@ -0,0 +1,55 @@
1
+ ## 2.7.0 (October 05, 2021)
2
+
3
+ Features:
4
+
5
+ - added redis_client setting to JWTSessions::StoreAdapters::RedisStoreAdapter
6
+
7
+ ## 2.6.0 (June 01, 2021)
8
+
9
+ Features:
10
+
11
+ - added support for all Redis settings
12
+
13
+ Support:
14
+
15
+ - updated jwt to '>= 2.2.3'
16
+ - switched to redis scan when looking for keys
17
+ - removed extra gems from gemspec deps
18
+ - updated gems in dummy apps
19
+
20
+ ## 2.5.2 (July 06, 2020)
21
+
22
+ Bugfixes:
23
+
24
+ - fixed `Using the last argument as keyword parameters is deprecated;` warnings
25
+
26
+ ## 2.5.1 (April 20, 2020)
27
+
28
+ Features:
29
+
30
+ - added changelog
31
+
32
+ Bugfixes:
33
+
34
+ - fixed double exp key in payload
35
+
36
+ Support:
37
+
38
+ - moved decode error text to a constant within token class
39
+
40
+ ## 2.5.0 (April 12, 2020)
41
+
42
+ Features:
43
+
44
+ - added new error class `JWTSessions::Errors::Expired`
45
+
46
+ ## 2.4.3 (September 19, 2019)
47
+
48
+ Bugfixes:
49
+
50
+ - fixed lookup for refresh token for namespaced sessions
51
+
52
+ Support:
53
+
54
+ - updated sqlite to ~> 1.4 in `dummy_api`
55
+ - added 2.6.3 Ruby to CI
data/README.md CHANGED
@@ -153,11 +153,11 @@ end
153
153
  ```
154
154
 
155
155
  Specify an encryption key for JSON Web Tokens in `config/initializers/jwt_session.rb` \
156
- It is advisable to store the key itself within the app secrets.
156
+ It is advisable to store the key itself in a secure way, f.e. within app credentials.
157
157
 
158
158
  ```ruby
159
159
  JWTSessions.algorithm = "HS256"
160
- JWTSessions.encryption_key = Rails.application.secrets.secret_jwt_encryption_key
160
+ JWTSessions.encryption_key = Rails.application.credentials.secret_jwt_encryption_key
161
161
  ```
162
162
 
163
163
  Most of the encryption algorithms require private and public keys to sign a token. However, HMAC requires only a single key and you can use the `encryption_key` shortcut to sign the token. For other algorithms you must specify private and public keys separately.
@@ -369,6 +369,21 @@ JWTSessions.token_store = :redis, { redis_url: "redis://localhost:6397" }
369
369
 
370
370
  **NOTE:** if `REDIS_URL` environment variable is set it is used automatically.
371
371
 
372
+ SSL, timeout, reconnect, etc. redis settings are supported:
373
+ ```ruby
374
+ JWTSessions.token_store = :redis, {
375
+ read_timeout: 1.5,
376
+ reconnect_attempts: 10,
377
+ ssl_params: { verify_mode: OpenSSL::SSL::VERIFY_NONE }
378
+ }
379
+ ```
380
+
381
+ If you already have a configured Redis client, you can pass it among the options to reduce opened connections to a Redis server:
382
+
383
+ ```ruby
384
+ JWTSessions.token_store = :redis, {redis_client: Redis.current}
385
+ ```
386
+
372
387
  ##### JWT signature
373
388
 
374
389
  ```ruby
@@ -449,7 +464,7 @@ It is defined globally, but can be overridden on a session level. See `JWTSessio
449
464
  `JWTSessions::Errors::InvalidPayload` - token's payload doesn't contain required keys or they are invalid. \
450
465
  `JWTSessions::Errors::Unauthorized` - token can't be decoded or JWT claims are invalid. \
451
466
  `JWTSessions::Errors::ClaimsVerification` - JWT claims are invalid (inherited from `JWTSessions::Errors::Unauthorized`). \
452
- `JWTSessions::Errors::Expired` - token is expired (inherited from `JWTSessions::Errors::Unauthorized`).
467
+ `JWTSessions::Errors::Expired` - token is expired (inherited from `JWTSessions::Errors::ClaimsVerification`).
453
468
 
454
469
  #### CSRF and cookies
455
470
 
@@ -592,6 +607,13 @@ session = JWTSessions::Session.new(namespace: "ie-sessions")
592
607
  session.flush_namespaced # will flush all sessions which belong to the same namespace
593
608
  ```
594
609
 
610
+ Selectively flush one single session inside a namespace by its access token:
611
+
612
+ ```ruby
613
+ session = JWTSessions::Session.new(namespace: "ie-sessions", payload: payload)
614
+ session.flush_by_access_payload # will flush a specific session which belongs to an existing namespace
615
+ ```
616
+
595
617
  Flush access tokens only:
596
618
 
597
619
  ```ruby
@@ -5,7 +5,7 @@ module JWTSessions
5
5
  class MemoryStoreAdapter < AbstractStoreAdapter
6
6
  attr_reader :storage
7
7
 
8
- def initialize(options)
8
+ def initialize(**options)
9
9
  raise ArgumentError, "Memory store doesn't support any options" if options.any?
10
10
  @storage = Hash.new do |h, k|
11
11
  h[k] = Hash.new { |hh, kk| hh[kk] = {} }
@@ -7,16 +7,20 @@ module JWTSessions
7
7
 
8
8
  REFRESH_KEYS = %i[csrf access_uid access_expiration expiration].freeze
9
9
 
10
- def initialize(token_prefix: JWTSessions.token_prefix, **options)
10
+ def initialize(token_prefix: JWTSessions.token_prefix, redis_client: nil, **options)
11
11
  @prefix = token_prefix
12
12
 
13
- begin
14
- require "redis"
15
- @storage = configure_redis_client(options)
16
- rescue LoadError => e
17
- msg = "Could not load the 'redis' gem, please add it to your gemfile or " \
18
- "configure a different adapter (e.g. JWTSessions.store_adapter = :memory)"
19
- raise e.class, msg, e.backtrace
13
+ if redis_client
14
+ @storage = redis_client
15
+ else
16
+ begin
17
+ require "redis"
18
+ @storage = configure_redis_client(**options)
19
+ rescue LoadError => e
20
+ msg = "Could not load the 'redis' gem, please add it to your gemfile or " \
21
+ "configure a different adapter (e.g. JWTSessions.store_adapter = :memory)"
22
+ raise e.class, msg, e.backtrace
23
+ end
20
24
  end
21
25
  end
22
26
 
@@ -62,7 +66,7 @@ module JWTSessions
62
66
  end
63
67
 
64
68
  def all_refresh_tokens(namespace)
65
- keys_in_namespace = storage.keys(refresh_key("*", namespace))
69
+ keys_in_namespace = scan_keys(refresh_key("*", namespace))
66
70
  (keys_in_namespace || []).each_with_object({}) do |key, acc|
67
71
  uid = uid_from_key(key)
68
72
  acc[uid] = fetch_refresh(uid, namespace)
@@ -80,7 +84,7 @@ module JWTSessions
80
84
 
81
85
  private
82
86
 
83
- def configure_redis_client(redis_url: nil, redis_host: nil, redis_port: nil, redis_db_name: nil)
87
+ def configure_redis_client(redis_url: nil, redis_host: nil, redis_port: nil, redis_db_name: nil, **options)
84
88
  if redis_url && (redis_host || redis_port || redis_db_name)
85
89
  raise ArgumentError, "redis_url cannot be passed along with redis_host, redis_port or redis_db_name options"
86
90
  end
@@ -91,7 +95,7 @@ module JWTSessions
91
95
  redis_db_name: redis_db_name
92
96
  )
93
97
 
94
- Redis.new(url: redis_url)
98
+ Redis.new(options.merge(url: redis_url))
95
99
  end
96
100
 
97
101
  def build_redis_url(redis_host: nil, redis_port: nil, redis_db_name: nil)
@@ -111,7 +115,7 @@ module JWTSessions
111
115
 
112
116
  def first_refresh_key(uid)
113
117
  key = full_refresh_key(uid, "*")
114
- (storage.keys(key) || []).first
118
+ (scan_keys(key) || []).first
115
119
  end
116
120
 
117
121
  def refresh_key(uid, namespace)
@@ -126,6 +130,20 @@ module JWTSessions
126
130
  def uid_from_key(key)
127
131
  key.split("_").last
128
132
  end
133
+
134
+ def scan_keys(key_pattern)
135
+ cursor = 0
136
+ all_keys = []
137
+
138
+ loop do
139
+ cursor, keys = storage.scan(cursor, match: key_pattern, count: 1000)
140
+ all_keys |= keys
141
+
142
+ break if cursor == "0"
143
+ end
144
+
145
+ all_keys
146
+ end
129
147
  end
130
148
  end
131
149
  end
@@ -9,7 +9,7 @@ module JWTSessions
9
9
  def self.build_by_name(adapter, options = nil)
10
10
  camelized_adapter = adapter.to_s.split('_').map(&:capitalize).join
11
11
  adapter_class_name = "#{camelized_adapter}StoreAdapter"
12
- StoreAdapters.const_get(adapter_class_name).new(options || {})
12
+ StoreAdapters.const_get(adapter_class_name).new(**(options || {}))
13
13
  end
14
14
  end
15
15
  end
@@ -4,6 +4,8 @@ require "jwt"
4
4
 
5
5
  module JWTSessions
6
6
  class Token
7
+ DECODE_ERROR = "cannot decode the token"
8
+
7
9
  class << self
8
10
  def encode(payload)
9
11
  exp_payload = meta.merge(payload)
@@ -20,18 +22,18 @@ module JWTSessions
20
22
  rescue JWT::DecodeError => e
21
23
  raise Errors::Unauthorized, e.message
22
24
  rescue StandardError
23
- raise Errors::Unauthorized, "could not decode a token"
25
+ raise Errors::Unauthorized, DECODE_ERROR
24
26
  end
25
27
 
26
28
  def decode!(token)
27
29
  decode_options = { algorithm: JWTSessions.algorithm }
28
30
  JWT.decode(token, JWTSessions.public_key, false, decode_options)
29
31
  rescue StandardError
30
- raise Errors::Unauthorized, "could not decode a token"
32
+ raise Errors::Unauthorized, DECODE_ERROR
31
33
  end
32
34
 
33
35
  def meta
34
- { exp: JWTSessions.access_expiration }
36
+ { "exp" => JWTSessions.access_expiration }
35
37
  end
36
38
  end
37
39
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module JWTSessions
4
- VERSION = "2.5.0"
4
+ VERSION = "2.7.0"
5
5
  end
data/lib/jwt_sessions.rb CHANGED
@@ -155,7 +155,7 @@ module JWTSessions
155
155
  private
156
156
 
157
157
  def supported_algos
158
- algos = JWT::Algos.constants - [:Unsupported]
159
- algos.map { |algo| JWT::Algos.const_get(algo)::SUPPORTED }.flatten + [NONE]
158
+ algos = JWT::Algos::ALGOS - [JWT::Algos::Unsupported]
159
+ algos.map { |algo| algo::SUPPORTED }.flatten + [NONE]
160
160
  end
161
161
  end
@@ -20,13 +20,18 @@ class TestRedisStoreAdapter < Minitest::Test
20
20
  end
21
21
  end
22
22
 
23
- def test_error_on_unknown_option
24
- assert_raises ArgumentError do
25
- JWTSessions::StoreAdapters::RedisStoreAdapter.new(
26
- redis_url: "redis://127.0.0.1:6379/0",
27
- something: "something"
28
- )
29
- end
23
+ def test_support_of_extra_options
24
+ adapter = JWTSessions::StoreAdapters::RedisStoreAdapter.new(
25
+ redis_url: "redis://127.0.0.1:6379",
26
+ ssl_params: { verify_mode: OpenSSL::SSL::VERIFY_NONE },
27
+ reconnect_delay: 2,
28
+ timeout: 8
29
+ )
30
+ options = adapter.storage.instance_variable_get(:@options)
31
+
32
+ assert_equal 8, options[:timeout]
33
+ assert_equal 2, options[:reconnect_delay]
34
+ assert_equal 0, options[:ssl_params][:verify_mode]
30
35
  end
31
36
 
32
37
  def test_default_url
@@ -72,4 +77,10 @@ class TestRedisStoreAdapter < Minitest::Test
72
77
  adapter = JWTSessions::StoreAdapters::RedisStoreAdapter.new
73
78
  assert_equal "redis://127.0.0.2:6322/0", adapter.storage.connection[:id]
74
79
  end
80
+
81
+ def test_configuration_via_redis_client
82
+ client = Object.new
83
+ adapter = JWTSessions::StoreAdapters::RedisStoreAdapter.new(redis_client: client)
84
+ assert_equal client, adapter.storage
85
+ end
75
86
  end
@@ -110,11 +110,11 @@ class TestToken < Minitest::Test
110
110
  def test_token_leeway_decode
111
111
  JWTSessions.encryption_key = "abcdefghijklmnopqrstuvwxyzABCDEF"
112
112
  JWTSessions.jwt_options.leeway = 50
113
- token = JWTSessions::Token.encode(payload.merge(exp: Time.now.to_i - 20))
113
+ token = JWTSessions::Token.encode(payload.merge("exp" => Time.now.to_i - 20))
114
114
  decoded = JWTSessions::Token.decode(token).first
115
115
  assert_equal payload["user_id"], decoded["user_id"]
116
116
  assert_equal payload["secret"], decoded["secret"]
117
- token = JWTSessions::Token.encode(payload.merge(exp: Time.now.to_i - 100))
117
+ token = JWTSessions::Token.encode(payload.merge("exp" => Time.now.to_i - 100))
118
118
  assert_raises JWTSessions::Errors::Unauthorized do
119
119
  JWTSessions::Token.decode(token)
120
120
  end
@@ -141,7 +141,7 @@ class TestToken < Minitest::Test
141
141
  end
142
142
 
143
143
  def test_payload_exp_time
144
- token = JWTSessions::Token.encode(payload.merge(exp: Time.now.to_i - (3600 * 24)))
144
+ token = JWTSessions::Token.encode(payload.merge("exp" => Time.now.to_i - (3600 * 24)))
145
145
  assert_raises JWTSessions::Errors::Expired do
146
146
  JWTSessions::Token.decode(token)
147
147
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jwt_sessions
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.5.0
4
+ version: 2.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yulia Oletskaya
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-04-12 00:00:00.000000000 Z
11
+ date: 2021-10-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: jwt
@@ -16,7 +16,7 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: 2.1.1
19
+ version: 2.2.3
20
20
  - - "<"
21
21
  - !ruby/object:Gem::Version
22
22
  version: '3'
@@ -26,7 +26,7 @@ dependencies:
26
26
  requirements:
27
27
  - - ">="
28
28
  - !ruby/object:Gem::Version
29
- version: 2.1.1
29
+ version: 2.2.3
30
30
  - - "<"
31
31
  - !ruby/object:Gem::Version
32
32
  version: '3'
@@ -44,34 +44,6 @@ dependencies:
44
44
  - - ">="
45
45
  - !ruby/object:Gem::Version
46
46
  version: '1.16'
47
- - !ruby/object:Gem::Dependency
48
- name: minitest
49
- requirement: !ruby/object:Gem::Requirement
50
- requirements:
51
- - - "~>"
52
- - !ruby/object:Gem::Version
53
- version: '5.11'
54
- type: :development
55
- prerelease: false
56
- version_requirements: !ruby/object:Gem::Requirement
57
- requirements:
58
- - - "~>"
59
- - !ruby/object:Gem::Version
60
- version: '5.11'
61
- - !ruby/object:Gem::Dependency
62
- name: pry
63
- requirement: !ruby/object:Gem::Requirement
64
- requirements:
65
- - - "~>"
66
- - !ruby/object:Gem::Version
67
- version: '0.11'
68
- type: :development
69
- prerelease: false
70
- version_requirements: !ruby/object:Gem::Requirement
71
- requirements:
72
- - - "~>"
73
- - !ruby/object:Gem::Version
74
- version: '0.11'
75
47
  - !ruby/object:Gem::Dependency
76
48
  name: rake
77
49
  requirement: !ruby/object:Gem::Requirement
@@ -92,6 +64,7 @@ executables: []
92
64
  extensions: []
93
65
  extra_rdoc_files: []
94
66
  files:
67
+ - CHANGELOG.md
95
68
  - LICENSE
96
69
  - README.md
97
70
  - lib/jwt_sessions.rb
@@ -120,8 +93,12 @@ files:
120
93
  homepage: http://rubygems.org/gems/jwt_sessions
121
94
  licenses:
122
95
  - MIT
123
- metadata: {}
124
- post_install_message:
96
+ metadata:
97
+ homepage_uri: https://github.com/tuwukee/jwt_sessions
98
+ changelog_uri: https://github.com/tuwukee/jwt_sessions/blob/master/CHANGELOG.md
99
+ source_code_uri: https://github.com/tuwukee/jwt_sessions
100
+ bug_tracker_uri: https://github.com/tuwukee/jwt_sessions/issues
101
+ post_install_message:
125
102
  rdoc_options: []
126
103
  require_paths:
127
104
  - lib
@@ -136,8 +113,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
136
113
  - !ruby/object:Gem::Version
137
114
  version: '0'
138
115
  requirements: []
139
- rubygems_version: 3.0.3
140
- signing_key:
116
+ rubygems_version: 3.2.5
117
+ signing_key:
141
118
  specification_version: 4
142
119
  summary: JWT Sessions
143
120
  test_files: