stytch 2.10.1 → 3.0.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: 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