jwt_sessions 2.5.0 → 2.7.0

Sign up to get free protection for your applications and to get access to all the features.
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: