stytch 2.10.1 → 3.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a7e1189f6fb15a13e7eca082c284fd13e96297000dcd4cad735620289a34ac2f
4
- data.tar.gz: 922aed7238ccb9f2042afad0c506ca0ceeff30d1cc2905947fec5d3513051eb6
3
+ metadata.gz: 2a361942c6f469a3bd03cb666b2740717d4f163ae5668536d292e4c72670b2a3
4
+ data.tar.gz: 865893e5e873829cee7fc43cb9aa9a006fa87487ccf04cc2c41009e657f407e0
5
5
  SHA512:
6
- metadata.gz: f5786234e98901186751ad3c142365144e1587d83aa827c1f13c4bbbec7f2093a165af5e26aacab9a27069d280885f897c03ea245e8c4dfae03dd60716eaa832
7
- data.tar.gz: 32a2c56b4cb0327ed95e0991bbb5dd2db32eb3a3eee4f8c21aba81b540a08a04a14d38931b52b476dfcb4353c7679346c15aafdf8d056532370f02e674bae491
6
+ metadata.gz: 7ab5a3c953f0c7eea8297685e97e408271f00389b563de2ac8583fec5b5c2a95971b00920a42c87232f584afad6fcfdfab6ef09b541a5d35d5df809771d9e925
7
+ data.tar.gz: 274ee8bf41f5cdc48d5d35c5f97e368d4766cb5670c3294afc8f8f5364e9835540d07eb7f02d804414602f8cbca777c6dae42110a4db657fd655289bb649a766
data/README.md CHANGED
@@ -34,6 +34,7 @@ This client library supports all of Stytch's live products:
34
34
  - [x] [Session Management](https://stytch.com/docs/api/sessions-overview)
35
35
  - [x] [WebAuthn (Beta)](https://stytch.com/docs/api/webauthn-overview)
36
36
  - [x] [Time-based one-time passcodes (TOTPs) (Beta)](https://stytch.com/docs/api/totps-overview)
37
+ - [x] [Crypto wallets (Beta)](https://stytch.com/docs/api/crypto-wallet-overview)
37
38
 
38
39
  ### Example usage
39
40
  Create an API client:
@@ -48,16 +49,14 @@ client = Stytch::Client.new(
48
49
  Send a magic link by email:
49
50
  ```ruby
50
51
  client.magic_links.email.login_or_create(
51
- email: "sandbox@stytch.com",
52
- login_magic_link_url: "https://example.com/login",
53
- signup_magic_link_url: "https://example.com/signup",
52
+ email: "sandbox@stytch.com"
54
53
  )
55
54
  ```
56
55
 
57
56
  Authenticate the token from the magic link:
58
57
  ```ruby
59
58
  client.magic_links.authenticate(
60
- token: "SeiGwdj5lKkrEVgcEY3QNJXt6srxS3IK2Nwkar6mXD4=",
59
+ token: "SeiGwdj5lKkrEVgcEY3QNJXt6srxS3IK2Nwkar6mXD4="
61
60
  )
62
61
  ```
63
62
 
data/lib/stytch/client.rb CHANGED
@@ -7,12 +7,13 @@ require_relative 'otps'
7
7
  require_relative 'sessions'
8
8
  require_relative 'totps'
9
9
  require_relative 'webauthn'
10
+ require_relative 'crypto_wallets'
10
11
 
11
12
  module Stytch
12
13
  class Client
13
14
  ENVIRONMENTS = %i[live test].freeze
14
15
 
15
- attr_reader :users, :magic_links, :oauth, :otps, :sessions, :totps, :webauthn
16
+ attr_reader :users, :magic_links, :oauth, :otps, :sessions, :totps, :webauthn, :crypto_wallets
16
17
 
17
18
  def initialize(env:, project_id:, secret:, &block)
18
19
  @api_host = api_host(env)
@@ -25,9 +26,10 @@ module Stytch
25
26
  @magic_links = Stytch::MagicLinks.new(@connection)
26
27
  @oauth = Stytch::OAuth.new(@connection)
27
28
  @otps = Stytch::OTPs.new(@connection)
28
- @sessions = Stytch::Sessions.new(@connection)
29
+ @sessions = Stytch::Sessions.new(@connection, @project_id)
29
30
  @totps = Stytch::TOTPs.new(@connection)
30
31
  @webauthn = Stytch::WebAuthn.new(@connection)
32
+ @crypto_wallets = Stytch::CryptoWallets.new(@connection)
31
33
  end
32
34
 
33
35
  private
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'request_helper'
4
+
5
+ module Stytch
6
+ class CryptoWallets
7
+ include Stytch::RequestHelper
8
+
9
+ PATH = '/v1/crypto_wallets'
10
+
11
+ def initialize(connection)
12
+ @connection = connection
13
+ end
14
+
15
+ def authenticate_start(
16
+ crypto_wallet_address:,
17
+ crypto_wallet_type:,
18
+ user_id: nil
19
+ )
20
+ request = {
21
+ crypto_wallet_address: crypto_wallet_address,
22
+ crypto_wallet_type: crypto_wallet_type
23
+ }
24
+
25
+ request[:user_id] = user_id unless user_id.nil?
26
+
27
+ post_request("#{PATH}/authenticate/start", request)
28
+ end
29
+
30
+ def authenticate(
31
+ crypto_wallet_address:,
32
+ crypto_wallet_type:,
33
+ signature:,
34
+ session_token: nil,
35
+ session_jwt: nil,
36
+ session_duration_minutes: nil
37
+ )
38
+ request = {
39
+ crypto_wallet_address: crypto_wallet_address,
40
+ crypto_wallet_type: crypto_wallet_type,
41
+ signature: signature
42
+ }
43
+
44
+ request[:session_token] = session_token unless session_token.nil?
45
+ request[:session_jwt] = session_jwt unless session_jwt.nil?
46
+ request[:session_duration_minutes] = session_duration_minutes unless session_duration_minutes.nil?
47
+
48
+ post_request("#{PATH}/authenticate", request)
49
+ end
50
+ end
51
+ end
@@ -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
@@ -54,11 +54,13 @@ module Stytch
54
54
  name: {},
55
55
  emails: [],
56
56
  phone_numbers: [],
57
+ crypto_wallets: [],
57
58
  attributes: {}
58
59
  )
59
60
  request = {
60
61
  emails: format_emails(emails),
61
- phone_numbers: format_phone_numbers(phone_numbers)
62
+ phone_numbers: format_phone_numbers(phone_numbers),
63
+ crypto_wallets: crypto_wallets
62
64
  }
63
65
 
64
66
  request[:name] = name if name != {}
@@ -95,6 +97,12 @@ module Stytch
95
97
  delete_request("#{PATH}/totps/#{totp_id}")
96
98
  end
97
99
 
100
+ def delete_crypto_wallet(
101
+ crypto_wallet_id:
102
+ )
103
+ delete_request("#{PATH}/crypto_wallets/#{crypto_wallet_id}")
104
+ end
105
+
98
106
  private
99
107
 
100
108
  def format_emails(emails)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Stytch
4
- VERSION = '2.10.1'
4
+ VERSION = '3.0.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.10.1
4
+ version: 3.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - stytch
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-02-17 00:00:00.000000000 Z
11
+ date: 2022-04-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: faraday
@@ -50,6 +50,34 @@ dependencies:
50
50
  - - "<"
51
51
  - !ruby/object:Gem::Version
52
52
  version: '2.0'
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
53
81
  description:
54
82
  email:
55
83
  - support@stytch.com
@@ -71,6 +99,8 @@ files:
71
99
  - bin/setup
72
100
  - lib/stytch.rb
73
101
  - lib/stytch/client.rb
102
+ - lib/stytch/crypto_wallets.rb
103
+ - lib/stytch/errors.rb
74
104
  - lib/stytch/magic_links.rb
75
105
  - lib/stytch/middleware.rb
76
106
  - lib/stytch/oauth.rb
@@ -103,7 +133,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
103
133
  - !ruby/object:Gem::Version
104
134
  version: '0'
105
135
  requirements: []
106
- rubygems_version: 3.0.3
136
+ rubygems_version: 3.0.3.1
107
137
  signing_key:
108
138
  specification_version: 4
109
139
  summary: Stytch Ruby Gem