stytch 2.11.0 → 3.1.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: d9e1c109c231f2dedb872cf4528ad44366517c5bac06adff7139b355e09c0537
4
- data.tar.gz: 1cfd8aa59c94ca621cda81a36a2669ff1352a84919d0ebe304aec47d6463a989
3
+ metadata.gz: 2534e355696eda17e5f4b8dac2b21335fdd54183c29c9ac694ac8886caceccbd
4
+ data.tar.gz: fcf8fea062ec5e2964fb3a32a2154a869b2e8256f6b7331e7aa29f7e52b2d0a8
5
5
  SHA512:
6
- metadata.gz: 21f8bd406c9f5e9135cb93e6f66f1bf7f3b9a4644b6a2e3309afa688302ce466b6cc343d871df5bc295fea07bcf20862a4ccdf0ea8d5eb1df7621a79cb86b6c1
7
- data.tar.gz: 2f7bf4ae7d789858a36d65dea80a9ecd1c6f459748fb301d9eda97e2ce015a125a97f3bd9dda381ec0709d2e962ba268734736ce2aee0dc5e751ace10d7b9f13
6
+ metadata.gz: deb23398868a541f5a0cf46d2314c27e9d50675948d190edc4696457e7dfd79f412a60bcd36140d2cbd2f17267919b1cd46ff60c648abb8a58c12021f27027b4
7
+ data.tar.gz: 5f763a8e124baf69867d99b8f80e76cbbe78bfb6d27199be31a097680187d90e24cb69bf66b97580e523b7287f3d41f97a007f5007d729d28d146721c66e314b
data/lib/stytch/client.rb CHANGED
@@ -26,7 +26,7 @@ module Stytch
26
26
  @magic_links = Stytch::MagicLinks.new(@connection)
27
27
  @oauth = Stytch::OAuth.new(@connection)
28
28
  @otps = Stytch::OTPs.new(@connection)
29
- @sessions = Stytch::Sessions.new(@connection)
29
+ @sessions = Stytch::Sessions.new(@connection, @project_id)
30
30
  @totps = Stytch::TOTPs.new(@connection)
31
31
  @webauthn = Stytch::WebAuthn.new(@connection)
32
32
  @crypto_wallets = Stytch::CryptoWallets.new(@connection)
@@ -32,6 +32,7 @@ module Stytch
32
32
  crypto_wallet_type:,
33
33
  signature:,
34
34
  session_token: nil,
35
+ session_jwt: nil,
35
36
  session_duration_minutes: nil
36
37
  )
37
38
  request = {
@@ -41,6 +42,7 @@ module Stytch
41
42
  }
42
43
 
43
44
  request[:session_token] = session_token unless session_token.nil?
45
+ request[:session_jwt] = session_jwt unless session_jwt.nil?
44
46
  request[:session_duration_minutes] = session_duration_minutes unless session_duration_minutes.nil?
45
47
 
46
48
  post_request("#{PATH}/authenticate", request)
@@ -0,0 +1,25 @@
1
+ module Stytch
2
+ class JWTInvalidIssuerError < StandardError
3
+ def initialize(msg="JWT issuer did not match")
4
+ super
5
+ end
6
+ end
7
+
8
+ class JWTInvalidAudienceError < StandardError
9
+ def initialize(msg="JWT audience did not match")
10
+ super
11
+ end
12
+ end
13
+
14
+ class JWTExpiredSignatureError < StandardError
15
+ def initialize(msg="JWT signature has expired")
16
+ super
17
+ end
18
+ end
19
+
20
+ class JWTIncorrectAlgorithmError < StandardError
21
+ def initialize(msg="JWT algorithm is incorrect")
22
+ super
23
+ end
24
+ end
25
+ end
@@ -36,6 +36,7 @@ module Stytch
36
36
  attributes: {},
37
37
  options: {},
38
38
  session_token: nil,
39
+ session_jwt: nil,
39
40
  session_duration_minutes: nil
40
41
  )
41
42
  request = {
@@ -45,6 +46,7 @@ module Stytch
45
46
  request[:attributes] = attributes if attributes != {}
46
47
  request[:options] = options if options != {}
47
48
  request[:session_token] = session_token unless session_token.nil?
49
+ request[:session_jwt] = session_jwt unless session_jwt.nil?
48
50
  request[:session_duration_minutes] = session_duration_minutes unless session_duration_minutes.nil?
49
51
 
50
52
  post_request("#{PATH}/authenticate", request)
data/lib/stytch/oauth.rb CHANGED
@@ -14,15 +14,15 @@ module Stytch
14
14
 
15
15
  def authenticate(
16
16
  token:,
17
- session_management_type: nil,
18
17
  session_token: nil,
18
+ session_jwt: nil,
19
19
  session_duration_minutes: nil
20
20
  )
21
21
  request = {
22
22
  token: token
23
23
  }
24
- request[:session_management_type] = session_management_type unless session_management_type.nil?
25
24
  request[:session_token] = session_token unless session_token.nil?
25
+ request[:session_jwt] = session_jwt unless session_jwt.nil?
26
26
  request[:session_duration_minutes] = session_duration_minutes unless session_duration_minutes.nil?
27
27
 
28
28
  post_request("#{PATH}/authenticate", request)
data/lib/stytch/otps.rb CHANGED
@@ -24,6 +24,7 @@ module Stytch
24
24
  attributes: {},
25
25
  options: {},
26
26
  session_token: nil,
27
+ session_jwt: nil,
27
28
  session_duration_minutes: nil
28
29
  )
29
30
  request = {
@@ -34,6 +35,7 @@ module Stytch
34
35
  request[:attributes] = attributes if attributes != {}
35
36
  request[:options] = options if options != {}
36
37
  request[:session_token] = session_token unless session_token.nil?
38
+ request[:session_jwt] = session_jwt unless session_jwt.nil?
37
39
  request[:session_duration_minutes] = session_duration_minutes unless session_duration_minutes.nil?
38
40
 
39
41
  post_request("#{PATH}/authenticate", request)
@@ -1,5 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'jwt'
4
+ require 'json/jwt'
5
+
6
+ require_relative 'errors'
3
7
  require_relative 'request_helper'
4
8
 
5
9
  module Stytch
@@ -8,8 +12,12 @@ module Stytch
8
12
 
9
13
  PATH = '/v1/sessions'
10
14
 
11
- def initialize(connection)
15
+ def initialize(connection, project_id)
12
16
  @connection = connection
17
+ @project_id = project_id
18
+ @jwks_loader = ->(options) do
19
+ options[:invalidate] ? jwks(project_id: @project_id) : {}
20
+ end
13
21
  end
14
22
 
15
23
  def get(user_id:)
@@ -23,13 +31,14 @@ module Stytch
23
31
  end
24
32
 
25
33
  def authenticate(
26
- session_token:,
34
+ session_token: nil,
35
+ session_jwt: nil,
27
36
  session_duration_minutes: nil
28
37
  )
29
- request = {
30
- session_token: session_token
31
- }
38
+ request = {}
32
39
 
40
+ request[:session_token] = session_token unless session_token.nil?
41
+ request[:session_jwt] = session_jwt unless session_jwt.nil?
33
42
  request[:session_duration_minutes] = session_duration_minutes unless session_duration_minutes.nil?
34
43
 
35
44
  post_request("#{PATH}/authenticate", request)
@@ -46,5 +55,73 @@ module Stytch
46
55
 
47
56
  post_request("#{PATH}/revoke", request)
48
57
  end
58
+
59
+ def jwks(project_id:)
60
+ request_path = "#{PATH}/jwks/" + project_id
61
+ get_request(request_path)
62
+ end
63
+
64
+ # Parse a JWT and verify the signature. If max_token_age_seconds is unset, call the API directly
65
+ # If max_token_age_seconds is set and the JWT was issued (based on the "iat" claim) less than
66
+ # max_token_age_seconds seconds ago, then just verify locally and don't call the API
67
+ # To force remote validation for all tokens, set max_token_age_seconds to 0 or call authenticate()
68
+ def authenticate_jwt(
69
+ session_jwt,
70
+ max_token_age_seconds: nil,
71
+ session_duration_minutes: nil
72
+ )
73
+ if max_token_age_seconds == 0
74
+ return authenticate(
75
+ session_jwt: session_jwt,
76
+ session_duration_minutes: session_duration_minutes,
77
+ )
78
+ end
79
+
80
+ decoded_jwt = authenticate_jwt_local(session_jwt)
81
+ iat_time = Time.at(decoded_jwt["iat"]).to_datetime
82
+ if iat_time + max_token_age_seconds >= Time.now
83
+ session = marshal_jwt_into_session(decoded_jwt)
84
+ return {"session" => session}
85
+ else
86
+ return authenticate(
87
+ session_jwt: session_jwt,
88
+ session_duration_minutes: session_duration_minutes,
89
+ )
90
+ end
91
+ end
92
+
93
+ # Parse a JWT and verify the signature locally (without calling /authenticate in the API)
94
+ # Uses the cached value to get the JWK but if it is unavailable, it calls the get_jwks()
95
+ # function to get the JWK
96
+ # This method never authenticates a JWT directly with the API
97
+ def authenticate_jwt_local(session_jwt)
98
+ issuer = "stytch.com/" + @project_id
99
+ begin
100
+ decoded_token = JWT.decode session_jwt, nil, true,
101
+ { jwks: @jwks_loader, iss: issuer, verify_iss: true, aud: @project_id, verify_aud: true, algorithms: ["RS256"]}
102
+ return decoded_token[0]
103
+ rescue JWT::InvalidIssuerError
104
+ raise JWTInvalidIssuerError
105
+ rescue JWT::InvalidAudError
106
+ raise JWTInvalidAudienceError
107
+ rescue JWT::ExpiredSignature
108
+ raise JWTExpiredSignatureError
109
+ rescue JWT::IncorrectAlgorithm
110
+ raise JWTIncorrectAlgorithmError
111
+ end
112
+ end
113
+
114
+ def marshal_jwt_into_session(jwt)
115
+ stytch_claim = "https://stytch.com/session"
116
+ return {
117
+ "session_id" => jwt["jti"],
118
+ "user_id" => jwt["sub"],
119
+ "started_at" => jwt[stytch_claim]["started_at"],
120
+ "last_accessed_at" => jwt[stytch_claim]["last_accessed_at"],
121
+ "expires_at" => Time.at(jwt["exp"]).to_datetime.iso8601,
122
+ "attributes" => jwt[stytch_claim]["attributes"],
123
+ "authentication_factors" => jwt[stytch_claim]["authentication_factors"],
124
+ }
125
+ end
49
126
  end
50
127
  end
data/lib/stytch/totps.rb CHANGED
@@ -29,6 +29,7 @@ module Stytch
29
29
  user_id:,
30
30
  totp_code:,
31
31
  session_token: nil,
32
+ session_jwt: nil,
32
33
  session_duration_minutes: nil
33
34
  )
34
35
  request = {
@@ -37,6 +38,7 @@ module Stytch
37
38
  }
38
39
 
39
40
  request[:session_token] = session_token unless session_token.nil?
41
+ request[:session_jwt] = session_jwt unless session_jwt.nil?
40
42
  request[:session_duration_minutes] = session_duration_minutes unless session_duration_minutes.nil?
41
43
 
42
44
  post_request("#{PATH}/authenticate", request)
@@ -56,6 +58,7 @@ module Stytch
56
58
  user_id:,
57
59
  recovery_code:,
58
60
  session_token: nil,
61
+ session_jwt: nil,
59
62
  session_duration_minutes: nil
60
63
  )
61
64
  request = {
@@ -64,6 +67,7 @@ module Stytch
64
67
  }
65
68
 
66
69
  request[:session_token] = session_token unless session_token.nil?
70
+ request[:session_jwt] = session_jwt unless session_jwt.nil?
67
71
  request[:session_duration_minutes] = session_duration_minutes unless session_duration_minutes.nil?
68
72
 
69
73
  post_request("#{PATH}/recover", request)
data/lib/stytch/users.rb CHANGED
@@ -12,6 +12,30 @@ module Stytch
12
12
  @connection = connection
13
13
  end
14
14
 
15
+ def search(query, limit: 100, cursor: nil)
16
+ request = {
17
+ query: query,
18
+ limit: limit
19
+ }
20
+ request[:cursor] = cursor if cursor
21
+ post_request("#{PATH}/search", request)
22
+ end
23
+
24
+ def search_all(query, limit: 100)
25
+ Enumerator.new do |enum|
26
+ cursor = nil
27
+ loop do
28
+ resp = search(query, limit: limit, cursor: cursor)
29
+ resp['results'].each do |user|
30
+ enum << user
31
+ end
32
+
33
+ cursor = resp['results_metadata']['next_cursor']
34
+ break if cursor.nil?
35
+ end
36
+ end
37
+ end
38
+
15
39
  def get(user_id:)
16
40
  get_request("#{PATH}/#{user_id}")
17
41
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Stytch
4
- VERSION = '2.11.0'
4
+ VERSION = '3.1.0'
5
5
  end
@@ -56,6 +56,7 @@ module Stytch
56
56
  def authenticate(
57
57
  public_key_credential:,
58
58
  session_token: nil,
59
+ session_jwt: nil,
59
60
  session_duration_minutes: nil
60
61
  )
61
62
  request = {
@@ -63,6 +64,7 @@ module Stytch
63
64
  }
64
65
 
65
66
  request[:session_token] = session_token unless session_token.nil?
67
+ request[:session_jwt] = session_jwt unless session_jwt.nil?
66
68
  request[:session_duration_minutes] = session_duration_minutes unless session_duration_minutes.nil?
67
69
 
68
70
  post_request("#{PATH}/authenticate", request)
data/stytch.gemspec CHANGED
@@ -27,4 +27,6 @@ Gem::Specification.new do |spec|
27
27
 
28
28
  spec.add_dependency 'faraday', '>= 0.17.0', '< 2.0'
29
29
  spec.add_dependency 'faraday_middleware', '>= 0.14.0', '< 2.0'
30
+ spec.add_dependency 'jwt', '>= 2.3.0'
31
+ spec.add_dependency 'json-jwt', '>=1.13.0'
30
32
  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: 2.11.0
4
+ version: 3.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - stytch
8
- autorequire:
8
+ autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-03-08 00:00:00.000000000 Z
11
+ date: 2022-04-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: faraday
@@ -50,7 +50,35 @@ dependencies:
50
50
  - - "<"
51
51
  - !ruby/object:Gem::Version
52
52
  version: '2.0'
53
- description:
53
+ - !ruby/object:Gem::Dependency
54
+ name: jwt
55
+ requirement: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ version: 2.3.0
60
+ type: :runtime
61
+ prerelease: false
62
+ version_requirements: !ruby/object:Gem::Requirement
63
+ requirements:
64
+ - - ">="
65
+ - !ruby/object:Gem::Version
66
+ version: 2.3.0
67
+ - !ruby/object:Gem::Dependency
68
+ name: json-jwt
69
+ requirement: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - ">="
72
+ - !ruby/object:Gem::Version
73
+ version: 1.13.0
74
+ type: :runtime
75
+ prerelease: false
76
+ version_requirements: !ruby/object:Gem::Requirement
77
+ requirements:
78
+ - - ">="
79
+ - !ruby/object:Gem::Version
80
+ version: 1.13.0
81
+ description:
54
82
  email:
55
83
  - support@stytch.com
56
84
  executables: []
@@ -72,6 +100,7 @@ files:
72
100
  - lib/stytch.rb
73
101
  - lib/stytch/client.rb
74
102
  - lib/stytch/crypto_wallets.rb
103
+ - lib/stytch/errors.rb
75
104
  - lib/stytch/magic_links.rb
76
105
  - lib/stytch/middleware.rb
77
106
  - lib/stytch/oauth.rb
@@ -89,7 +118,7 @@ licenses:
89
118
  metadata:
90
119
  homepage_uri: https://stytch.com
91
120
  source_code_uri: https://github.com/stytchauth/stytch-ruby
92
- post_install_message:
121
+ post_install_message:
93
122
  rdoc_options: []
94
123
  require_paths:
95
124
  - lib
@@ -104,8 +133,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
104
133
  - !ruby/object:Gem::Version
105
134
  version: '0'
106
135
  requirements: []
107
- rubygems_version: 3.1.4
108
- signing_key:
136
+ rubygems_version: 3.0.3.1
137
+ signing_key:
109
138
  specification_version: 4
110
139
  summary: Stytch Ruby Gem
111
140
  test_files: []