stytch 10.35.0 → 10.37.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: a21c7819e335e5e1512dd207196f170a535f83abb0cfd2075d1e74c031016e62
4
- data.tar.gz: 7241e6caccaaf5a7380b3dd6acca90fd619d5df457a8c368d1c0d8ef667bce8a
3
+ metadata.gz: 2636f56ed7d78577fb1ff81133a92fc1c0fb2500696f7921dcff85d6e79d7995
4
+ data.tar.gz: 932c0aa9dcef45bfb75a82516508bceac612f89a4b4a55e1555103017f80ba55
5
5
  SHA512:
6
- metadata.gz: e0b5df47cb45208d49f4cb9558066e698438905eec30b11febc43b55623b5a81afe90112d4110dcc385ec483ab6a9335b3bf5e43a04e85ea912ffc64babe5048
7
- data.tar.gz: 7c513da2af700131f700ba74e2319043e677a2ad295bfe93a0cb22ca5b73ff49060781cccff7e9f86783796975bc0dc65f9bed497ee6f45cccce06a74264292d
6
+ metadata.gz: 988ed498a5b30318230b13dd477125149ce76ca35bedab317843ce4b77f9ed0857d375f165e0268eb5d0877ae9ddda12f2fca221f2d82d95d14bb7dcf9f7e8e8
7
+ data.tar.gz: fa274f938a19f3f8241288467d7724e1a63a5c43695fdce8ea7626ce38e005ce76b71a7af799028356660728f8510abfb41d46ec31d3f999049adeea9354b852
@@ -23,6 +23,7 @@ require_relative 'b2b_totps'
23
23
  require_relative 'connected_apps'
24
24
  require_relative 'debug'
25
25
  require_relative 'fraud'
26
+ require_relative 'jwks_cache'
26
27
  require_relative 'm2m'
27
28
  require_relative 'project'
28
29
  require_relative 'rbac_local'
@@ -33,15 +34,18 @@ module StytchB2B
33
34
 
34
35
  attr_reader :connected_app, :debug, :discovery, :fraud, :idp, :impersonation, :m2m, :magic_links, :oauth, :otps, :organizations, :passwords, :project, :rbac, :recovery_codes, :scim, :sso, :sessions, :totps
35
36
 
36
- def initialize(project_id:, secret:, env: nil, fraud_env: nil, &block)
37
+ def initialize(project_id:, secret:, env: nil, fraud_env: nil, timeout: nil, jwks: nil, &block)
37
38
  @api_host = api_host(env, project_id)
38
39
  @fraud_api_host = fraud_api_host(fraud_env)
39
40
  @project_id = project_id
40
41
  @secret = secret
42
+ @timeout = timeout
41
43
  @is_b2b_client = true
42
44
 
43
45
  create_connection(&block)
44
46
 
47
+ @jwks_cache = Stytch::JWKSCache.new(@connection, @project_id, jwks)
48
+
45
49
  rbac = StytchB2B::RBAC.new(@connection)
46
50
  @policy_cache = Stytch::PolicyCache.new(rbac_client: rbac)
47
51
 
@@ -49,9 +53,9 @@ module StytchB2B
49
53
  @debug = Stytch::Debug.new(@connection)
50
54
  @discovery = StytchB2B::Discovery.new(@connection)
51
55
  @fraud = Stytch::Fraud.new(@fraud_connection)
52
- @idp = StytchB2B::IDP.new(@connection, @project_id, @policy_cache)
56
+ @idp = StytchB2B::IDP.new(@connection, @project_id, @jwks_cache, @policy_cache)
53
57
  @impersonation = StytchB2B::Impersonation.new(@connection)
54
- @m2m = Stytch::M2M.new(@connection, @project_id, @is_b2b_client)
58
+ @m2m = Stytch::M2M.new(@connection, @project_id, @is_b2b_client, @jwks_cache)
55
59
  @magic_links = StytchB2B::MagicLinks.new(@connection)
56
60
  @oauth = StytchB2B::OAuth.new(@connection)
57
61
  @otps = StytchB2B::OTPs.new(@connection)
@@ -62,7 +66,7 @@ module StytchB2B
62
66
  @recovery_codes = StytchB2B::RecoveryCodes.new(@connection)
63
67
  @scim = StytchB2B::SCIM.new(@connection)
64
68
  @sso = StytchB2B::SSO.new(@connection)
65
- @sessions = StytchB2B::Sessions.new(@connection, @project_id, @policy_cache)
69
+ @sessions = StytchB2B::Sessions.new(@connection, @project_id, @jwks_cache, @policy_cache)
66
70
  @totps = StytchB2B::TOTPs.new(@connection)
67
71
  end
68
72
 
@@ -108,7 +112,7 @@ module StytchB2B
108
112
  end
109
113
 
110
114
  def build_default_connection(builder)
111
- builder.options[:timeout] = Stytch::Middleware::NETWORK_TIMEOUT
115
+ builder.options[:timeout] = Stytch::Middleware.timeout(@timeout)
112
116
  builder.headers = Stytch::Middleware::NETWORK_HEADERS
113
117
  builder.request :json
114
118
  builder.use Stytch::Middleware
@@ -16,24 +16,13 @@ module StytchB2B
16
16
  include Stytch::RequestHelper
17
17
  attr_reader :oauth
18
18
 
19
- def initialize(connection, project_id, policy_cache)
19
+ def initialize(connection, project_id, jwks_cache, policy_cache)
20
20
  @connection = connection
21
21
 
22
22
  @oauth = StytchB2B::IDP::OAuth.new(@connection)
23
23
  @policy_cache = policy_cache
24
24
  @project_id = project_id
25
- @cache_last_update = 0
26
- @jwks_loader = lambda do |options|
27
- @cached_keys = nil if options[:invalidate] && @cache_last_update < Time.now.to_i - 300
28
- @cached_keys ||= begin
29
- @cache_last_update = Time.now.to_i
30
- keys = []
31
- get_jwks(project_id: @project_id)['keys'].each do |r|
32
- keys << r
33
- end
34
- { keys: keys }
35
- end
36
- end
25
+ @jwks_cache = jwks_cache
37
26
  end
38
27
 
39
28
  # MANUAL(IDP::introspect_token_network)(SERVICE_METHOD)
@@ -199,7 +188,7 @@ module StytchB2B
199
188
  true,
200
189
  {
201
190
  algorithms: ['RS256'],
202
- jwks: @jwks_loader,
191
+ jwks: @jwks_cache.loader,
203
192
  iss: ["stytch.com/#{@project_id}", @connection.url_prefix],
204
193
  aud: @project_id
205
194
  }
@@ -34,23 +34,12 @@ module StytchB2B
34
34
 
35
35
  include Stytch::RequestHelper
36
36
 
37
- def initialize(connection, project_id, policy_cache)
37
+ def initialize(connection, project_id, jwks_cache, policy_cache)
38
38
  @connection = connection
39
39
 
40
40
  @policy_cache = policy_cache
41
41
  @project_id = project_id
42
- @cache_last_update = 0
43
- @jwks_loader = lambda do |options|
44
- @cached_keys = nil if options[:invalidate] && @cache_last_update < Time.now.to_i - 300
45
- @cached_keys ||= begin
46
- @cache_last_update = Time.now.to_i
47
- keys = []
48
- get_jwks(project_id: @project_id)['keys'].each do |r|
49
- keys << r
50
- end
51
- { keys: keys }
52
- end
53
- end
42
+ @jwks_cache = jwks_cache
54
43
  end
55
44
 
56
45
  # Retrieves all active Sessions for a Member.
@@ -706,7 +695,7 @@ module StytchB2B
706
695
 
707
696
  begin
708
697
  decoded_token = JWT.decode session_jwt, nil, true,
709
- { jwks: @jwks_loader, iss: valid_issuers, verify_iss: true, aud: @project_id, verify_aud: true, algorithms: ['RS256'], nbf_leeway: clock_tolerance_seconds }
698
+ { jwks: @jwks_cache.loader, iss: valid_issuers, verify_iss: true, aud: @project_id, verify_aud: true, algorithms: ['RS256'], nbf_leeway: clock_tolerance_seconds }
710
699
 
711
700
  session = decoded_token[0]
712
701
  iat_time = Time.at(session['iat']).to_datetime
data/lib/stytch/client.rb CHANGED
@@ -12,6 +12,7 @@ require_relative 'debug'
12
12
  require_relative 'fraud'
13
13
  require_relative 'idp'
14
14
  require_relative 'impersonation'
15
+ require_relative 'jwks_cache'
15
16
  require_relative 'm2m'
16
17
  require_relative 'magic_links'
17
18
  require_relative 'oauth'
@@ -31,15 +32,18 @@ module Stytch
31
32
 
32
33
  attr_reader :connected_app, :crypto_wallets, :debug, :fraud, :idp, :impersonation, :m2m, :magic_links, :oauth, :otps, :passwords, :project, :rbac, :sessions, :totps, :users, :webauthn
33
34
 
34
- def initialize(project_id:, secret:, env: nil, fraud_env: nil, &block)
35
+ def initialize(project_id:, secret:, env: nil, fraud_env: nil, timeout: nil, jwks: nil, &block)
35
36
  @api_host = api_host(env, project_id)
36
37
  @fraud_api_host = fraud_api_host(fraud_env)
37
38
  @project_id = project_id
38
39
  @secret = secret
40
+ @timeout = timeout
39
41
  @is_b2b_client = false
40
42
 
41
43
  create_connection(&block)
42
44
 
45
+ @jwks_cache = Stytch::JWKSCache.new(@connection, @project_id, jwks)
46
+
43
47
  rbac = Stytch::RBAC.new(@connection)
44
48
  @policy_cache = Stytch::PolicyCache.new(rbac_client: rbac)
45
49
 
@@ -47,16 +51,16 @@ module Stytch
47
51
  @crypto_wallets = Stytch::CryptoWallets.new(@connection)
48
52
  @debug = Stytch::Debug.new(@connection)
49
53
  @fraud = Stytch::Fraud.new(@fraud_connection)
50
- @idp = Stytch::IDP.new(@connection, @project_id, @policy_cache)
54
+ @idp = Stytch::IDP.new(@connection, @project_id, @jwks_cache, @policy_cache)
51
55
  @impersonation = Stytch::Impersonation.new(@connection)
52
- @m2m = Stytch::M2M.new(@connection, @project_id, @is_b2b_client)
56
+ @m2m = Stytch::M2M.new(@connection, @project_id, @is_b2b_client, @jwks_cache)
53
57
  @magic_links = Stytch::MagicLinks.new(@connection)
54
58
  @oauth = Stytch::OAuth.new(@connection)
55
59
  @otps = Stytch::OTPs.new(@connection)
56
60
  @passwords = Stytch::Passwords.new(@connection)
57
61
  @project = Stytch::Project.new(@connection)
58
62
  @rbac = Stytch::RBAC.new(@connection)
59
- @sessions = Stytch::Sessions.new(@connection, @project_id, @policy_cache)
63
+ @sessions = Stytch::Sessions.new(@connection, @project_id, @jwks_cache, @policy_cache)
60
64
  @totps = Stytch::TOTPs.new(@connection)
61
65
  @users = Stytch::Users.new(@connection)
62
66
  @webauthn = Stytch::WebAuthn.new(@connection)
@@ -104,7 +108,7 @@ module Stytch
104
108
  end
105
109
 
106
110
  def build_default_connection(builder)
107
- builder.options[:timeout] = Stytch::Middleware::NETWORK_TIMEOUT
111
+ builder.options[:timeout] = Stytch::Middleware.timeout(@timeout)
108
112
  builder.headers = Stytch::Middleware::NETWORK_HEADERS
109
113
  builder.request :json
110
114
  builder.use Stytch::Middleware
data/lib/stytch/idp.rb CHANGED
@@ -16,24 +16,13 @@ module Stytch
16
16
  include Stytch::RequestHelper
17
17
  attr_reader :oauth
18
18
 
19
- def initialize(connection, project_id, policy_cache)
19
+ def initialize(connection, project_id, jwks_cache, policy_cache)
20
20
  @connection = connection
21
21
 
22
22
  @oauth = Stytch::IDP::OAuth.new(@connection)
23
23
  @policy_cache = policy_cache
24
24
  @project_id = project_id
25
- @cache_last_update = 0
26
- @jwks_loader = lambda do |options|
27
- @cached_keys = nil if options[:invalidate] && @cache_last_update < Time.now.to_i - 300
28
- @cached_keys ||= begin
29
- @cache_last_update = Time.now.to_i
30
- keys = []
31
- get_jwks(project_id: @project_id)['keys'].each do |r|
32
- keys << r
33
- end
34
- { keys: keys }
35
- end
36
- end
25
+ @jwks_cache = jwks_cache
37
26
  end
38
27
 
39
28
  # MANUAL(IDP::introspect_token_network)(SERVICE_METHOD)
@@ -188,7 +177,7 @@ module Stytch
188
177
  true,
189
178
  {
190
179
  algorithms: ['RS256'],
191
- jwks: @jwks_loader,
180
+ jwks: @jwks_cache.loader,
192
181
  iss: ["stytch.com/#{@project_id}", @connection.url_prefix],
193
182
  aud: @project_id
194
183
  }
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'request_helper'
4
+
5
+ module Stytch
6
+ # JWKSCache handles caching and refreshing of JSON Web Key Sets (JWKS)
7
+ # for JWT signature verification. It can be initialized with pre-cached
8
+ # keys or will fetch them on-demand from the Stytch API.
9
+ class JWKSCache
10
+ include Stytch::RequestHelper
11
+
12
+ CACHE_EXPIRY_SECONDS = 300 # 5 minutes
13
+
14
+ def initialize(connection, project_id, jwks = nil)
15
+ @connection = connection
16
+ @project_id = project_id
17
+ @cache_last_update = 0
18
+
19
+ # If jwks are provided during initialization, use them directly
20
+ return unless jwks
21
+
22
+ @cached_keys = { keys: jwks }
23
+ @cache_last_update = Time.now.to_i
24
+ end
25
+
26
+ # Returns a lambda suitable for use with JWT.decode
27
+ def loader
28
+ lambda do |options|
29
+ @cached_keys = nil if options[:invalidate] && @cache_last_update < Time.now.to_i - CACHE_EXPIRY_SECONDS
30
+ @cached_keys ||= begin
31
+ @cache_last_update = Time.now.to_i
32
+ keys = []
33
+ get_jwks(project_id: @project_id)['keys'].each do |r|
34
+ keys << r
35
+ end
36
+ { keys: keys }
37
+ end
38
+ end
39
+ end
40
+
41
+ # Fetches JWKS from the Stytch API
42
+ def get_jwks(project_id:)
43
+ request_with_query_params(
44
+ @connection,
45
+ '/v1/sessions/jwks/%<project_id>s',
46
+ {
47
+ project_id: project_id
48
+ }
49
+ )
50
+ end
51
+ end
52
+ end
data/lib/stytch/m2m.rb CHANGED
@@ -13,24 +13,13 @@ module Stytch
13
13
  include Stytch::RequestHelper
14
14
  attr_reader :clients
15
15
 
16
- def initialize(connection, project_id, is_b2b_client)
16
+ def initialize(connection, project_id, is_b2b_client, jwks_cache)
17
17
  @connection = connection
18
18
 
19
19
  @clients = Stytch::M2M::Clients.new(@connection)
20
20
  @project_id = project_id
21
- @cache_last_update = 0
21
+ @jwks_cache = jwks_cache
22
22
  @is_b2b_client = is_b2b_client
23
- @jwks_loader = lambda do |options|
24
- @cached_keys = nil if options[:invalidate] && @cache_last_update < Time.now.to_i - 300
25
- @cached_keys ||= begin
26
- @cache_last_update = Time.now.to_i
27
- keys = []
28
- get_jwks(project_id: @project_id)['keys'].each do |r|
29
- keys << r
30
- end
31
- { keys: keys }
32
- end
33
- end
34
23
  end
35
24
 
36
25
  # MANUAL(M2M::get_jwks)(SERVICE_METHOD)
@@ -190,7 +179,7 @@ module Stytch
190
179
 
191
180
  begin
192
181
  decoded_token = JWT.decode jwt, nil, true,
193
- { jwks: @jwks_loader, iss: valid_issuers, verify_iss: true, aud: @project_id, verify_aud: true, algorithms: ['RS256'], nbf_leeway: clock_tolerance_seconds }
182
+ { jwks: @jwks_cache.loader, iss: valid_issuers, verify_iss: true, aud: @project_id, verify_aud: true, algorithms: ['RS256'], nbf_leeway: clock_tolerance_seconds }
194
183
  decoded_token[0]
195
184
  rescue JWT::InvalidIssuerError
196
185
  raise JWTInvalidIssuerError
@@ -13,6 +13,10 @@ module Stytch
13
13
  'Content-Type' => 'application/json'
14
14
  }.freeze
15
15
 
16
- NETWORK_TIMEOUT = 300
16
+ NETWORK_TIMEOUT = 31
17
+
18
+ def self.timeout(custom_timeout = nil)
19
+ custom_timeout || NETWORK_TIMEOUT
20
+ end
17
21
  end
18
22
  end
@@ -15,23 +15,12 @@ module Stytch
15
15
  class Sessions
16
16
  include Stytch::RequestHelper
17
17
 
18
- def initialize(connection, project_id, policy_cache)
18
+ def initialize(connection, project_id, jwks_cache, policy_cache)
19
19
  @connection = connection
20
20
 
21
21
  @policy_cache = policy_cache
22
22
  @project_id = project_id
23
- @cache_last_update = 0
24
- @jwks_loader = lambda do |options|
25
- @cached_keys = nil if options[:invalidate] && @cache_last_update < Time.now.to_i - 300
26
- @cached_keys ||= begin
27
- @cache_last_update = Time.now.to_i
28
- keys = []
29
- get_jwks(project_id: @project_id)['keys'].each do |r|
30
- keys << r
31
- end
32
- { keys: keys }
33
- end
34
- end
23
+ @jwks_cache = jwks_cache
35
24
  end
36
25
 
37
26
  # List all active Sessions for a given `user_id`. All timestamps are formatted according to the RFC 3339 standard and are expressed in UTC, e.g. `2021-12-29T12:33:09Z`.
@@ -514,7 +503,7 @@ module Stytch
514
503
 
515
504
  begin
516
505
  decoded_token = JWT.decode session_jwt, nil, true,
517
- { jwks: @jwks_loader, iss: valid_issuers, verify_iss: true, aud: @project_id, verify_aud: true, algorithms: ['RS256'], nbf_leeway: clock_tolerance_seconds }
506
+ { jwks: @jwks_cache.loader, iss: valid_issuers, verify_iss: true, aud: @project_id, verify_aud: true, algorithms: ['RS256'], nbf_leeway: clock_tolerance_seconds }
518
507
 
519
508
  session = decoded_token[0]
520
509
  iat_time = Time.at(session['iat']).to_datetime
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Stytch
4
- VERSION = '10.35.0'
4
+ VERSION = '10.37.0'
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: stytch
3
3
  version: !ruby/object:Gem::Version
4
- version: 10.35.0
4
+ version: 10.37.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - stytch
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2025-09-26 00:00:00.000000000 Z
11
+ date: 2025-09-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: faraday
@@ -147,6 +147,7 @@ files:
147
147
  - lib/stytch/fraud.rb
148
148
  - lib/stytch/idp.rb
149
149
  - lib/stytch/impersonation.rb
150
+ - lib/stytch/jwks_cache.rb
150
151
  - lib/stytch/m2m.rb
151
152
  - lib/stytch/magic_links.rb
152
153
  - lib/stytch/method_options.rb