stytch 2.10.0 → 2.12.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: c2b973d686af6ce37a2f1d51825d075f0b361ad75eb06ee0d74df0ba343543bc
4
- data.tar.gz: 4649dc2a86ddcb0013bfe8f1cee480fa8f0533fa52eabe4531fad4bc8acf0432
3
+ metadata.gz: b507432445cb9a02301a537e29ffd6286dca4a9daa1629a6344bee6c073751e1
4
+ data.tar.gz: 48e01f84c824c94a987baf00221a142e45c5b5f976a25801cc04846f14658a7d
5
5
  SHA512:
6
- metadata.gz: 1e34d2f40645f2393c8573ce667c36fdff565c4918133b4bedc800b70c5e406822b33f85a5a95981bda8ceeaf4196ae3816ce863c20ac04b2f847b00be6b10c5
7
- data.tar.gz: 97ad61637feec32db2b4e20927385c248b566c2c32ccc47e32be27a33d93363e04449e314357b30ed42fdaf645beab9e7464377d7b7b45be9df36ff37b03272c
6
+ metadata.gz: '093757bb72ff4b992e94e875df54c7b781a8c3d1bbf54c5f3be79f3d8df00b0ec7a70cc25a0477977122c6f498533cc47881e0ad2b51fa1fb352a89e23f35387'
7
+ data.tar.gz: a53dcc14f57a8a141f048232c004b886c3b2ad291c70fd0ebd595b883e2301f0e96a52146a5255a359ecb8cb1204dd92c50b2e54c1412cf2185b533e50d97a1c
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: "DOYoip3rvIMMW5lgItikFK-Ak1CfMsgjuiCyI7uuU94=",
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
@@ -37,7 +39,7 @@ module Stytch
37
39
  when :live
38
40
  'https://api.stytch.com'
39
41
  when :test
40
- 'https://test.nikhil.dev.stytch.com'
42
+ 'https://test.stytch.com'
41
43
  else
42
44
  raise ArgumentError, "Invalid value for env (#{@env}): should be live or test"
43
45
  end
@@ -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
@@ -16,6 +16,7 @@ module Stytch
16
16
  token:,
17
17
  session_management_type: nil,
18
18
  session_token: nil,
19
+ session_jwt: nil,
19
20
  session_duration_minutes: nil
20
21
  )
21
22
  request = {
@@ -23,6 +24,7 @@ module Stytch
23
24
  }
24
25
  request[:session_management_type] = session_management_type unless session_management_type.nil?
25
26
  request[:session_token] = session_token unless session_token.nil?
27
+ request[:session_jwt] = session_jwt unless session_jwt.nil?
26
28
  request[:session_duration_minutes] = session_duration_minutes unless session_duration_minutes.nil?
27
29
 
28
30
  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.0'
4
+ VERSION = '2.12.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/lib/stytch.rb CHANGED
@@ -8,16 +8,3 @@ require_relative 'stytch/middleware'
8
8
  require_relative 'stytch/version'
9
9
 
10
10
  module Stytch end
11
-
12
- client = Stytch::Client.new(
13
- env: :test,
14
- project_id: "project-test-2a934f44-a014-425d-aa1d-76f2d0344db7",
15
- secret: "secret-test-z5kbR31dkcTnocolLJT77gkyX9KJQeU9umg="
16
- )
17
-
18
- resp = client.totps.authenticate(
19
- user_id: "user-test-fa9e1ba8-6786-4a7c-923f-a76a2b82e6a5",
20
- totp_code: "021363",
21
- session_token: "HLr0NGPZkyn7xA9Vc9XFjOIDjUafuTumOT6au3-PqM83",
22
- )
23
- puts resp
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.0
4
+ version: 2.12.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-01-20 00:00:00.000000000 Z
11
+ date: 2022-04-08 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