statelydb 0.13.0 → 0.15.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: 1518fe47ef69d7e171862c88f5b8a9425116dc1390623724dc95e285e960b728
4
- data.tar.gz: f5e9faad6e851892a695a47590e7430908e419b44c2f94a60087fec4c316c808
3
+ metadata.gz: f1d7563d7f40d84bdc1ef3dacff41707ed1d915b638223d33b488ca4c7de531f
4
+ data.tar.gz: dd682b92fbc100505e15820c5415acadc9defc6bc2f2d0dad5b5797deddbf24b
5
5
  SHA512:
6
- metadata.gz: acc4be2621242c08236efdf8e1f9fa48090070c3c4ad12583481bc2a2df4ee5638fbb665059a0138a7bf53270bed8d6bf0f5290843dd5d488b71274f2a3284b8
7
- data.tar.gz: ddccab6882a70b7eab33c56893fd9cae614ee67c5343b0953f7af33d3fe487418eb00c3300d3a24ae6e14df88f5cf49609b9e1f4a966db10d646ab184c00a685
6
+ metadata.gz: c0f27aa770fdf0c7b3386ac37862226cb4b5215766d265e47c82323b2157966e6ce0d87b5e4081d8d9dcd549f45cd0f6527203b0025b0e6c62ce0780c81dae74
7
+ data.tar.gz: de86db0a2a0227e6bec231d11c378bffff42fa7911ccc0874bd3ed97fae7f73d0ac29ce55b31817f75bfa82c0b63daa79dabd28276a66e17e2748770450f6243
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+ # Generated by the protocol buffer compiler. DO NOT EDIT!
3
+ # source: auth/get_auth_token.proto
4
+
5
+ require 'google/protobuf'
6
+
7
+
8
+ descriptor_data = "\n\x19\x61uth/get_auth_token.proto\x12\x0cstately.auth\"B\n\x13GetAuthTokenRequest\x12\x1f\n\naccess_key\x18\x01 \x01(\tH\x00R\taccessKeyB\n\n\x08identity\"W\n\x14GetAuthTokenResponse\x12\x1d\n\nauth_token\x18\x01 \x01(\tR\tauthToken\x12 \n\x0c\x65xpires_in_s\x18\x02 \x01(\x04R\nexpiresInSBv\n\x10\x63om.stately.authB\x11GetAuthTokenProtoP\x01\xa2\x02\x03SAX\xaa\x02\x0cStately.Auth\xca\x02\x0cStately\\Auth\xe2\x02\x18Stately\\Auth\\GPBMetadata\xea\x02\rStately::Authb\x06proto3"
9
+
10
+ pool = Google::Protobuf::DescriptorPool.generated_pool
11
+ pool.add_serialized_file(descriptor_data)
12
+
13
+ module Stately
14
+ module Auth
15
+ GetAuthTokenRequest = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("stately.auth.GetAuthTokenRequest").msgclass
16
+ GetAuthTokenResponse = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("stately.auth.GetAuthTokenResponse").msgclass
17
+ end
18
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+ # Generated by the protocol buffer compiler. DO NOT EDIT!
3
+ # source: auth/service.proto
4
+
5
+ require 'google/protobuf'
6
+
7
+ require 'api/auth/get_auth_token_pb'
8
+
9
+
10
+ descriptor_data = "\n\x12\x61uth/service.proto\x12\x0cstately.auth\x1a\x19\x61uth/get_auth_token.proto2i\n\x0b\x41uthService\x12Z\n\x0cGetAuthToken\x12!.stately.auth.GetAuthTokenRequest\x1a\".stately.auth.GetAuthTokenResponse\"\x03\x90\x02\x01\x42q\n\x10\x63om.stately.authB\x0cServiceProtoP\x01\xa2\x02\x03SAX\xaa\x02\x0cStately.Auth\xca\x02\x0cStately\\Auth\xe2\x02\x18Stately\\Auth\\GPBMetadata\xea\x02\rStately::Authb\x06proto3"
11
+
12
+ pool = Google::Protobuf::DescriptorPool.generated_pool
13
+ pool.add_serialized_file(descriptor_data)
14
+
15
+ module Stately
16
+ module Auth
17
+ end
18
+ end
@@ -0,0 +1,29 @@
1
+ # Generated by the protocol buffer compiler. DO NOT EDIT!
2
+ # Source: auth/service.proto for package 'Stately.Auth'
3
+
4
+ require 'grpc'
5
+ require 'api/auth/service_pb'
6
+
7
+ module Stately
8
+ module Auth
9
+ module AuthService
10
+ # AuthService is the service for vending access tokens used to connect to
11
+ # StatelyDB. This API is meant to be used from SDKs. Access Keys are created
12
+ # and managed from the stately.dbmanagement.UserService.
13
+ class Service
14
+
15
+ include ::GRPC::GenericService
16
+
17
+ self.marshal_class_method = :encode
18
+ self.unmarshal_class_method = :decode
19
+ self.service_name = 'stately.auth.AuthService'
20
+
21
+ # GetAuthToken returns a short-lived access token from some proof of
22
+ # identity. This operation will fail if the identity cannot be verified.
23
+ rpc :GetAuthToken, ::Stately::Auth::GetAuthTokenRequest, ::Stately::Auth::GetAuthTokenResponse
24
+ end
25
+
26
+ Stub = Service.rpc_stub_class
27
+ end
28
+ end
29
+ end
@@ -6,8 +6,10 @@ require "async/http/internet"
6
6
  require "async/semaphore"
7
7
  require "json"
8
8
  require "logger"
9
- require "weakref"
9
+ require "grpc"
10
10
  require_relative "token_provider"
11
+ require_relative "token_fetcher"
12
+ require_relative "../../error"
11
13
 
12
14
  LOGGER = Logger.new($stdout)
13
15
  LOGGER.level = Logger::WARN
@@ -22,20 +24,25 @@ module StatelyDB
22
24
  # which vends tokens from auth0 with the given client_id and client_secret.
23
25
  # It will default to using the values of `STATELY_CLIENT_ID` and `STATELY_CLIENT_SECRET` if
24
26
  # no credentials are explicitly passed and will throw an error if none are found.
25
- class Auth0TokenProvider < TokenProvider
27
+ class AuthTokenProvider < TokenProvider
26
28
  # @param [String] origin The origin of the OAuth server
27
29
  # @param [String] audience The OAuth Audience for the token
28
30
  # @param [String] client_secret The StatelyDB client secret credential
29
31
  # @param [String] client_id The StatelyDB client ID credential
32
+ # @param [String] access_key The StatelyDB access key credential
33
+ # @param [Float] base_retry_backoff_secs The base retry backoff in seconds
30
34
  def initialize(
31
35
  origin: "https://oauth.stately.cloud",
32
36
  audience: "api.stately.cloud",
33
- client_secret: ENV.fetch("STATELY_CLIENT_SECRET"),
34
- client_id: ENV.fetch("STATELY_CLIENT_ID")
37
+ client_secret: ENV.fetch("STATELY_CLIENT_SECRET", nil),
38
+ client_id: ENV.fetch("STATELY_CLIENT_ID", nil),
39
+ access_key: ENV.fetch("STATELY_ACCESS_KEY", nil),
40
+ base_retry_backoff_secs: 1
35
41
  )
36
42
  super()
37
43
  @actor = Async::Actor.new(Actor.new(origin: origin, audience: audience,
38
- client_secret: client_secret, client_id: client_id))
44
+ client_secret: client_secret, client_id: client_id, access_key: access_key,
45
+ base_retry_backoff_secs: base_retry_backoff_secs))
39
46
  # this initialization cannot happen in the constructor because it is async and must run on the event loop
40
47
  # which is not available in the constructor
41
48
  @actor.init
@@ -60,33 +67,50 @@ module StatelyDB
60
67
  # @param [String] audience The OAuth Audience for the token
61
68
  # @param [String] client_secret The StatelyDB client secret credential
62
69
  # @param [String] client_id The StatelyDB client ID credential
70
+ # @param [String] access_key The StatelyDB access key credential
71
+ # @param [Float] base_retry_backoff_secs The base retry backoff in seconds
63
72
  def initialize(
64
73
  origin:,
65
74
  audience:,
66
75
  client_secret:,
67
- client_id:
76
+ client_id:,
77
+ access_key:,
78
+ base_retry_backoff_secs:
68
79
  )
69
80
  super()
70
- @client = Async::HTTP::Client.new(Async::HTTP::Endpoint.parse(origin))
71
- @client_id = client_id
72
- @client_secret = client_secret
73
- @audience = audience
74
81
 
75
- @access_token = nil
76
- @expires_at_unix_secs = nil
82
+ @token_fetcher = nil
83
+ if !access_key.nil?
84
+ @token_fetcher = StatelyDB::Common::Auth::StatelyAccessTokenFetcher.new(
85
+ origin: origin, access_key: access_key, base_retry_backoff_secs: base_retry_backoff_secs
86
+ )
87
+ elsif !client_secret.nil? && !client_id.nil?
88
+ @token_fetcher = StatelyDB::Common::Auth::Auth0TokenFetcher.new(origin: origin, audience: audience,
89
+ client_secret: client_secret, client_id: client_id)
90
+ else
91
+ raise StatelyDB::Error.new("unable to find client credentials in STATELY_ACCESS_KEY or STATELY_CLIENT_ID and " \
92
+ "STATELY_CLIENT_SECRET environment variables. Either pass your credentials in " \
93
+ "explicitly or set these environment variables",
94
+ code: GRPC::Core::StatusCodes::UNAUTHENTICATED,
95
+ stately_code: "Unauthenticated")
96
+ end
97
+
98
+ @token_state = nil
77
99
  @pending_refresh = nil
78
100
  end
79
101
 
80
102
  # Initialize the actor. This runs on the actor thread which means
81
103
  # we can dispatch async operations here.
82
104
  def init
105
+ # disable the async lib logger. We do our own error handling and propagation
106
+ Console.logger.disable(Async::Task)
83
107
  refresh_token
84
108
  end
85
109
 
86
110
  # Close the token provider and kill any background operations
87
111
  def close
88
112
  @scheduled&.stop
89
- @client&.close
113
+ @token_fetcher&.close
90
114
  end
91
115
 
92
116
  # Get the current access token
@@ -94,8 +118,7 @@ module StatelyDB
94
118
  # @return [String] The current access token
95
119
  def get_token(force: false)
96
120
  if force
97
- @access_token = nil
98
- @expires_at_unix_secs = nil
121
+ @token_state = nil
99
122
  else
100
123
  token, ok = valid_access_token
101
124
  return token if ok
@@ -107,11 +130,10 @@ module StatelyDB
107
130
  # Get the current access token and whether it is valid
108
131
  # @return [Array] The current access token and whether it is valid
109
132
  def valid_access_token
110
- return "", false if @access_token.nil?
111
- return "", false if @expires_at_unix_secs.nil?
112
- return "", false if @expires_at_unix_secs < Time.now.to_i
133
+ return "", false if @token_state.nil?
134
+ return "", false if @token_state.expires_at_unix_secs < Time.now.to_i
113
135
 
114
- [@access_token, true]
136
+ [@token_state.token, true]
115
137
  end
116
138
 
117
139
  # Refresh the access token
@@ -151,19 +173,16 @@ module StatelyDB
151
173
  # @return [String] The new access token
152
174
  def refresh_token_impl
153
175
  Sync do
154
- resp_data = make_auth0_request
155
-
156
- new_access_token = resp_data["access_token"]
157
- new_expires_in_secs = resp_data["expires_in"]
176
+ token_result = @token_fetcher.fetch
177
+ new_expires_in_secs = token_result.expires_in_secs
158
178
  new_expires_at_unix_secs = Time.now.to_i + new_expires_in_secs
159
- if @expires_at_unix_secs.nil? || new_expires_at_unix_secs > @expires_at_unix_secs
160
179
 
161
- @access_token = new_access_token
162
- @expires_at_unix_secs = new_expires_at_unix_secs
180
+ # only update the token state if the new expiry is later than the current one
181
+ if @token_state.nil? || new_expires_at_unix_secs > @token_state.expires_at_unix_secs
182
+ @token_state = TokenState.new(token: token_result.token, expires_at_unix_secs: new_expires_at_unix_secs)
163
183
  else
164
-
165
- new_access_token = @access_token
166
- new_expires_in_secs = @expires_at_unix_secs - Time.now.to_i
184
+ # otherwise use the existing expiry time for scheduling the refresh
185
+ new_expires_in_secs = @token_state.expires_at_unix_secs - Time.now.to_i
167
186
  end
168
187
 
169
188
  # Schedule a refresh of the token ahead of the expiry time
@@ -182,24 +201,21 @@ module StatelyDB
182
201
  @scheduled = nil
183
202
  end
184
203
 
185
- new_access_token
204
+ @token_state.token
186
205
  end
187
206
  end
207
+ end
188
208
 
189
- def make_auth0_request
190
- headers = [["content-type", "application/json"]]
191
- body = JSON.dump({ "client_id" => @client_id, client_secret: @client_secret, audience: @audience,
192
- grant_type: DEFAULT_GRANT_TYPE })
193
- Sync do
194
- # TODO: Wrap this in a retry loop and parse errors like we
195
- # do in the Go SDK.
196
- response = @client.post("/oauth/token", headers, body)
197
- raise "Auth request failed" if response.status != 200
198
-
199
- JSON.parse(response.read)
200
- ensure
201
- response&.close
202
- end
209
+ # Persistent state for the token provider
210
+ class TokenState
211
+ attr_reader :token, :expires_at_unix_secs
212
+
213
+ # Create a new TokenState
214
+ # @param [String] token The access token
215
+ # @param [Integer] expires_at_unix_secs The unix timestamp when the token expires
216
+ def initialize(token:, expires_at_unix_secs:)
217
+ @token = token
218
+ @expires_at_unix_secs = expires_at_unix_secs
203
219
  end
204
220
  end
205
221
  end
@@ -11,7 +11,7 @@ module StatelyDB
11
11
  class Interceptor < GRPC::ClientInterceptor
12
12
  # @param [TokenProvider] token_provider The token provider to use for authentication
13
13
  def initialize(
14
- token_provider: Auth0TokenProvider.new
14
+ token_provider: AuthTokenProvider.new
15
15
  )
16
16
  super()
17
17
  @token_provider = token_provider
@@ -0,0 +1,144 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../net/conn"
4
+ require_relative "../error_interceptor"
5
+ require_relative "../../api/auth/service_services_pb"
6
+ require "grpc"
7
+
8
+ module StatelyDB
9
+ module Common
10
+ # A module for Stately Cloud auth code
11
+ module Auth
12
+ # Result from a token fetch operation
13
+ class TokenResult
14
+ attr_reader :token, :expires_in_secs
15
+
16
+ # Create a new TokenResult
17
+ # @param [String] token The access token
18
+ # @param [Integer] expires_in_secs The number of seconds until the token expires
19
+ def initialize(token:, expires_in_secs:)
20
+ @token = token
21
+ @expires_in_secs = expires_in_secs
22
+ end
23
+ end
24
+
25
+ # TokenFetcher is an abstract base class that should be extended
26
+ # for individual token fetcher implementations
27
+ class TokenFetcher
28
+ # Get the current access token
29
+ # @return [TokenResult] The fetched TokenResult
30
+ def fetch
31
+ raise "Not Implemented"
32
+ end
33
+
34
+ # Close the token provider and kill any background operations
35
+ def close
36
+ raise "Not Implemented"
37
+ end
38
+ end
39
+
40
+ # Auth0TokenFetcher is a TokenFetcher that fetches tokens from an Auth0 server
41
+ class Auth0TokenFetcher < TokenFetcher
42
+ # @param [String] origin The origin of the OAuth server
43
+ # @param [String] audience The OAuth Audience for the token
44
+ # @param [String] client_secret The StatelyDB client secret credential
45
+ # @param [String] client_id The StatelyDB client ID credential
46
+ def initialize(origin:, audience:, client_secret:, client_id:)
47
+ super()
48
+ @client = Async::HTTP::Client.new(Async::HTTP::Endpoint.parse(origin))
49
+ @audience = audience
50
+ @client_secret = client_secret
51
+ @client_id = client_id
52
+ end
53
+
54
+ # Fetch a new token from auth0
55
+ # @return [TokenResult] The fetched TokenResult
56
+ def fetch
57
+ headers = [["content-type", "application/json"]]
58
+ body = JSON.dump({ "client_id" => @client_id, client_secret: @client_secret, audience: @audience,
59
+ grant_type: DEFAULT_GRANT_TYPE })
60
+ Sync do
61
+ response = @client.post("/oauth/token", headers, body)
62
+ raise "Auth request failed" if response.status != 200
63
+
64
+ resp_data = JSON.parse(response.read)
65
+ TokenResult.new(token: resp_data["access_token"], expires_in_secs: resp_data["expires_in"])
66
+ ensure
67
+ response&.close
68
+ end
69
+ end
70
+
71
+ def close
72
+ @client&.close
73
+ end
74
+ end
75
+
76
+ # StatelyAccessTokenFetcher is a TokenFetcher that fetches tokens from the StatelyDB API
77
+ class StatelyAccessTokenFetcher < TokenFetcher
78
+ NON_RETRYABLE_ERRORS = [
79
+ GRPC::Core::StatusCodes::UNAUTHENTICATED,
80
+ GRPC::Core::StatusCodes::PERMISSION_DENIED,
81
+ GRPC::Core::StatusCodes::NOT_FOUND,
82
+ GRPC::Core::StatusCodes::UNIMPLEMENTED,
83
+ GRPC::Core::StatusCodes::INVALID_ARGUMENT
84
+ ].freeze
85
+ RETRY_ATTEMPTS = 10
86
+
87
+ # @param [String] origin The origin of the OAuth server
88
+ # @param [String] access_key The StatelyDB access key credential
89
+ # @param [Float] base_retry_backoff_secs The base backoff time in seconds
90
+ def initialize(origin:, access_key:, base_retry_backoff_secs:)
91
+ super()
92
+ @access_key = access_key
93
+ @base_retry_backoff_secs = base_retry_backoff_secs
94
+ @channel = Common::Net.new_channel(endpoint: origin)
95
+ error_interceptor = Common::ErrorInterceptor.new
96
+ @stub = Stately::Auth::AuthService::Stub.new(nil, nil, channel_override: @channel,
97
+ interceptors: [error_interceptor])
98
+ end
99
+
100
+ # Fetch a new token from the StatelyDB API
101
+ # @return [TokenResult] The fetched TokenResult
102
+ def fetch
103
+ RETRY_ATTEMPTS.times do |i|
104
+ resp = @stub.get_auth_token(Stately::Auth::GetAuthTokenRequest.new(access_key: @access_key))
105
+ return TokenResult.new(token: resp.auth_token, expires_in_secs: resp.expires_in_s)
106
+ rescue StatelyDB::Error => e
107
+ # raise if it's the final attempt or if the error is not retryable
108
+ raise e unless self.class.retryable_error?(e) && i < RETRY_ATTEMPTS - 1
109
+
110
+ # exponential backoff
111
+ sleep(backoff(i, @base_retry_backoff_secs))
112
+ end
113
+ end
114
+
115
+ def close
116
+ @channel&.close
117
+ end
118
+
119
+ # Check if an error is retryable
120
+ # @param [StatelyDB::Error] err The error to check
121
+ # @return [Boolean] True if the error is retryable
122
+ def self.retryable_error?(err)
123
+ !NON_RETRYABLE_ERRORS.include?(err.code)
124
+ end
125
+ end
126
+ end
127
+ end
128
+ end
129
+
130
+ # backoff returns a duration to wait before retrying a request. `attempt` is
131
+ # the current attempt number, starting from 0 (e.g. the first attempt is 0,
132
+ # then 1, then 2...).
133
+ #
134
+ # @param [Integer] attempt The current attempt number
135
+ # @param [Float] base_backoff The base backoff time in seconds
136
+ # @return [Float] The duration in seconds to wait before retrying
137
+ def backoff(attempt, base_backoff)
138
+ # Double the base backoff time per attempt, starting with 1
139
+ exp = 2**attempt
140
+ # Add a full jitter to the backoff time, from no wait to 100% of the exponential backoff.
141
+ # See https://aws.amazon.com/blogs/architecture/exponential-backoff-and-jitter/
142
+ jitter = rand
143
+ (exp * jitter * base_backoff)
144
+ end
data/lib/statelydb.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "api/db/service_services_pb"
4
- require "common/auth/auth0_token_provider"
4
+ require "common/auth/auth_token_provider"
5
5
  require "common/auth/interceptor"
6
6
  require "common/net/conn"
7
7
  require "common/error_interceptor"
@@ -30,7 +30,7 @@ module StatelyDB
30
30
  # @param region [String] the region to connect to.
31
31
  def initialize(store_id:,
32
32
  schema:,
33
- token_provider: Common::Auth::Auth0TokenProvider.new,
33
+ token_provider: Common::Auth::AuthTokenProvider.new,
34
34
  endpoint: nil,
35
35
  region: nil)
36
36
  if store_id.nil?
data/sig/statelydb.rbi CHANGED
@@ -213,7 +213,7 @@ module StatelyDB
213
213
  region: T.nilable(String)
214
214
  ).void
215
215
  end
216
- def initialize(store_id:, schema:, token_provider: Common::Auth::Auth0TokenProvider.new, endpoint: nil, region: nil); end
216
+ def initialize(store_id:, schema:, token_provider: Common::Auth::AuthTokenProvider.new, endpoint: nil, region: nil); end
217
217
 
218
218
  # _@return_ — nil
219
219
  sig { void }
@@ -479,7 +479,7 @@ module StatelyDB
479
479
  class Interceptor < GRPC::ClientInterceptor
480
480
  # _@param_ `token_provider` — The token provider to use for authentication
481
481
  sig { params(token_provider: TokenProvider).void }
482
- def initialize(token_provider: Auth0TokenProvider.new); end
482
+ def initialize(token_provider: AuthTokenProvider.new); end
483
483
 
484
484
  # gRPC client unary interceptor
485
485
  #
@@ -572,6 +572,105 @@ module StatelyDB
572
572
  def add_jwt_to_grpc_request(metadata:); end
573
573
  end
574
574
 
575
+ # Result from a token fetch operation
576
+ class TokenResult
577
+ # Create a new TokenResult
578
+ #
579
+ # _@param_ `token` — The access token
580
+ #
581
+ # _@param_ `expires_in_secs` — The number of seconds until the token expires
582
+ sig { params(token: String, expires_in_secs: Integer).void }
583
+ def initialize(token:, expires_in_secs:); end
584
+
585
+ # Returns the value of attribute token.
586
+ sig { returns(T.untyped) }
587
+ attr_reader :token
588
+
589
+ # Returns the value of attribute expires_in_secs.
590
+ sig { returns(T.untyped) }
591
+ attr_reader :expires_in_secs
592
+ end
593
+
594
+ # TokenFetcher is an abstract base class that should be extended
595
+ # for individual token fetcher implementations
596
+ class TokenFetcher
597
+ # Get the current access token
598
+ #
599
+ # _@return_ — The fetched TokenResult
600
+ sig { returns(TokenResult) }
601
+ def fetch; end
602
+
603
+ # Close the token provider and kill any background operations
604
+ sig { returns(T.untyped) }
605
+ def close; end
606
+ end
607
+
608
+ # Auth0TokenFetcher is a TokenFetcher that fetches tokens from an Auth0 server
609
+ class Auth0TokenFetcher < StatelyDB::Common::Auth::TokenFetcher
610
+ # _@param_ `origin` — The origin of the OAuth server
611
+ #
612
+ # _@param_ `audience` — The OAuth Audience for the token
613
+ #
614
+ # _@param_ `client_secret` — The StatelyDB client secret credential
615
+ #
616
+ # _@param_ `client_id` — The StatelyDB client ID credential
617
+ sig do
618
+ params(
619
+ origin: String,
620
+ audience: String,
621
+ client_secret: String,
622
+ client_id: String
623
+ ).void
624
+ end
625
+ def initialize(origin:, audience:, client_secret:, client_id:); end
626
+
627
+ # Fetch a new token from auth0
628
+ #
629
+ # _@return_ — The fetched TokenResult
630
+ sig { returns(TokenResult) }
631
+ def fetch; end
632
+
633
+ sig { returns(T.untyped) }
634
+ def close; end
635
+ end
636
+
637
+ # StatelyAccessTokenFetcher is a TokenFetcher that fetches tokens from the StatelyDB API
638
+ class StatelyAccessTokenFetcher < StatelyDB::Common::Auth::TokenFetcher
639
+ NON_RETRYABLE_ERRORS = T.let([
640
+ GRPC::Core::StatusCodes::UNAUTHENTICATED,
641
+ GRPC::Core::StatusCodes::PERMISSION_DENIED,
642
+ GRPC::Core::StatusCodes::NOT_FOUND,
643
+ GRPC::Core::StatusCodes::UNIMPLEMENTED,
644
+ GRPC::Core::StatusCodes::INVALID_ARGUMENT
645
+ ].freeze, T.untyped)
646
+ RETRY_ATTEMPTS = T.let(10, T.untyped)
647
+
648
+ # _@param_ `origin` — The origin of the OAuth server
649
+ #
650
+ # _@param_ `access_key` — The StatelyDB access key credential
651
+ #
652
+ # _@param_ `base_retry_backoff_secs` — The base backoff time in seconds
653
+ sig { params(origin: String, access_key: String, base_retry_backoff_secs: Float).void }
654
+ def initialize(origin:, access_key:, base_retry_backoff_secs:); end
655
+
656
+ # Fetch a new token from the StatelyDB API
657
+ #
658
+ # _@return_ — The fetched TokenResult
659
+ sig { returns(TokenResult) }
660
+ def fetch; end
661
+
662
+ sig { returns(T.untyped) }
663
+ def close; end
664
+
665
+ # Check if an error is retryable
666
+ #
667
+ # _@param_ `err` — The error to check
668
+ #
669
+ # _@return_ — True if the error is retryable
670
+ sig { params(err: StatelyDB::Error).returns(T::Boolean) }
671
+ def self.retryable_error?(err); end
672
+ end
673
+
575
674
  # TokenProvider is an abstract base class that should be extended
576
675
  # for individual token provider implementations
577
676
  class TokenProvider
@@ -592,7 +691,7 @@ module StatelyDB
592
691
  # which vends tokens from auth0 with the given client_id and client_secret.
593
692
  # It will default to using the values of `STATELY_CLIENT_ID` and `STATELY_CLIENT_SECRET` if
594
693
  # no credentials are explicitly passed and will throw an error if none are found.
595
- class Auth0TokenProvider < StatelyDB::Common::Auth::TokenProvider
694
+ class AuthTokenProvider < StatelyDB::Common::Auth::TokenProvider
596
695
  # _@param_ `origin` — The origin of the OAuth server
597
696
  #
598
697
  # _@param_ `audience` — The OAuth Audience for the token
@@ -600,15 +699,21 @@ module StatelyDB
600
699
  # _@param_ `client_secret` — The StatelyDB client secret credential
601
700
  #
602
701
  # _@param_ `client_id` — The StatelyDB client ID credential
702
+ #
703
+ # _@param_ `access_key` — The StatelyDB access key credential
704
+ #
705
+ # _@param_ `base_retry_backoff_secs` — The base retry backoff in seconds
603
706
  sig do
604
707
  params(
605
708
  origin: String,
606
709
  audience: String,
607
710
  client_secret: String,
608
- client_id: String
711
+ client_id: String,
712
+ access_key: String,
713
+ base_retry_backoff_secs: Float
609
714
  ).void
610
715
  end
611
- def initialize(origin: "https://oauth.stately.cloud", audience: "api.stately.cloud", client_secret: ENV.fetch("STATELY_CLIENT_SECRET"), client_id: ENV.fetch("STATELY_CLIENT_ID")); end
716
+ def initialize(origin: "https://oauth.stately.cloud", audience: "api.stately.cloud", client_secret: ENV.fetch("STATELY_CLIENT_SECRET", nil), client_id: ENV.fetch("STATELY_CLIENT_ID", nil), access_key: ENV.fetch("STATELY_ACCESS_KEY", nil), base_retry_backoff_secs: 1); end
612
717
 
613
718
  # Close the token provider and kill any background operations
614
719
  # This just invokes the close method on the actor which should do the cleanup
@@ -631,15 +736,21 @@ module StatelyDB
631
736
  # _@param_ `client_secret` — The StatelyDB client secret credential
632
737
  #
633
738
  # _@param_ `client_id` — The StatelyDB client ID credential
739
+ #
740
+ # _@param_ `access_key` — The StatelyDB access key credential
741
+ #
742
+ # _@param_ `base_retry_backoff_secs` — The base retry backoff in seconds
634
743
  sig do
635
744
  params(
636
745
  origin: String,
637
746
  audience: String,
638
747
  client_secret: String,
639
- client_id: String
748
+ client_id: String,
749
+ access_key: String,
750
+ base_retry_backoff_secs: Float
640
751
  ).void
641
752
  end
642
- def initialize(origin:, audience:, client_secret:, client_id:); end
753
+ def initialize(origin:, audience:, client_secret:, client_id:, access_key:, base_retry_backoff_secs:); end
643
754
 
644
755
  # Initialize the actor. This runs on the actor thread which means
645
756
  # we can dispatch async operations here.
@@ -675,9 +786,25 @@ module StatelyDB
675
786
  # _@return_ — The new access token
676
787
  sig { returns(String) }
677
788
  def refresh_token_impl; end
789
+ end
790
+
791
+ # Persistent state for the token provider
792
+ class TokenState
793
+ # Create a new TokenState
794
+ #
795
+ # _@param_ `token` — The access token
796
+ #
797
+ # _@param_ `expires_at_unix_secs` — The unix timestamp when the token expires
798
+ sig { params(token: String, expires_at_unix_secs: Integer).void }
799
+ def initialize(token:, expires_at_unix_secs:); end
800
+
801
+ # Returns the value of attribute token.
802
+ sig { returns(T.untyped) }
803
+ attr_reader :token
678
804
 
805
+ # Returns the value of attribute expires_at_unix_secs.
679
806
  sig { returns(T.untyped) }
680
- def make_auth0_request; end
807
+ attr_reader :expires_at_unix_secs
681
808
  end
682
809
  end
683
810
  end
@@ -1069,6 +1196,22 @@ module Stately
1069
1196
  end
1070
1197
  end
1071
1198
 
1199
+ module Auth
1200
+ GetAuthTokenRequest = T.let(::Google::Protobuf::DescriptorPool.generated_pool.lookup("stately.auth.GetAuthTokenRequest").msgclass, T.untyped)
1201
+ GetAuthTokenResponse = T.let(::Google::Protobuf::DescriptorPool.generated_pool.lookup("stately.auth.GetAuthTokenResponse").msgclass, T.untyped)
1202
+
1203
+ module AuthService
1204
+ Stub = T.let(Service.rpc_stub_class, T.untyped)
1205
+
1206
+ # AuthService is the service for vending access tokens used to connect to
1207
+ # StatelyDB. This API is meant to be used from SDKs. Access Keys are created
1208
+ # and managed from the stately.dbmanagement.UserService.
1209
+ class Service
1210
+ include GRPC::GenericService
1211
+ end
1212
+ end
1213
+ end
1214
+
1072
1215
  module Errors
1073
1216
  StatelyErrorDetails = T.let(::Google::Protobuf::DescriptorPool.generated_pool.lookup("stately.errors.StatelyErrorDetails").msgclass, T.untyped)
1074
1217
  end
data/sig/statelydb.rbs CHANGED
@@ -496,6 +496,85 @@ module StatelyDB
496
496
  def add_jwt_to_grpc_request: (metadata: ::Hash[untyped, untyped]) -> void
497
497
  end
498
498
 
499
+ # Result from a token fetch operation
500
+ class TokenResult
501
+ # Create a new TokenResult
502
+ #
503
+ # _@param_ `token` — The access token
504
+ #
505
+ # _@param_ `expires_in_secs` — The number of seconds until the token expires
506
+ def initialize: (token: String, expires_in_secs: Integer) -> void
507
+
508
+ # Returns the value of attribute token.
509
+ attr_reader token: untyped
510
+
511
+ # Returns the value of attribute expires_in_secs.
512
+ attr_reader expires_in_secs: untyped
513
+ end
514
+
515
+ # TokenFetcher is an abstract base class that should be extended
516
+ # for individual token fetcher implementations
517
+ class TokenFetcher
518
+ # Get the current access token
519
+ #
520
+ # _@return_ — The fetched TokenResult
521
+ def fetch: () -> TokenResult
522
+
523
+ # Close the token provider and kill any background operations
524
+ def close: () -> untyped
525
+ end
526
+
527
+ # Auth0TokenFetcher is a TokenFetcher that fetches tokens from an Auth0 server
528
+ class Auth0TokenFetcher < StatelyDB::Common::Auth::TokenFetcher
529
+ # _@param_ `origin` — The origin of the OAuth server
530
+ #
531
+ # _@param_ `audience` — The OAuth Audience for the token
532
+ #
533
+ # _@param_ `client_secret` — The StatelyDB client secret credential
534
+ #
535
+ # _@param_ `client_id` — The StatelyDB client ID credential
536
+ def initialize: (
537
+ origin: String,
538
+ audience: String,
539
+ client_secret: String,
540
+ client_id: String
541
+ ) -> void
542
+
543
+ # Fetch a new token from auth0
544
+ #
545
+ # _@return_ — The fetched TokenResult
546
+ def fetch: () -> TokenResult
547
+
548
+ def close: () -> untyped
549
+ end
550
+
551
+ # StatelyAccessTokenFetcher is a TokenFetcher that fetches tokens from the StatelyDB API
552
+ class StatelyAccessTokenFetcher < StatelyDB::Common::Auth::TokenFetcher
553
+ NON_RETRYABLE_ERRORS: untyped
554
+ RETRY_ATTEMPTS: untyped
555
+
556
+ # _@param_ `origin` — The origin of the OAuth server
557
+ #
558
+ # _@param_ `access_key` — The StatelyDB access key credential
559
+ #
560
+ # _@param_ `base_retry_backoff_secs` — The base backoff time in seconds
561
+ def initialize: (origin: String, access_key: String, base_retry_backoff_secs: Float) -> void
562
+
563
+ # Fetch a new token from the StatelyDB API
564
+ #
565
+ # _@return_ — The fetched TokenResult
566
+ def fetch: () -> TokenResult
567
+
568
+ def close: () -> untyped
569
+
570
+ # Check if an error is retryable
571
+ #
572
+ # _@param_ `err` — The error to check
573
+ #
574
+ # _@return_ — True if the error is retryable
575
+ def self.retryable_error?: (StatelyDB::Error err) -> bool
576
+ end
577
+
499
578
  # TokenProvider is an abstract base class that should be extended
500
579
  # for individual token provider implementations
501
580
  class TokenProvider
@@ -514,7 +593,7 @@ module StatelyDB
514
593
  # which vends tokens from auth0 with the given client_id and client_secret.
515
594
  # It will default to using the values of `STATELY_CLIENT_ID` and `STATELY_CLIENT_SECRET` if
516
595
  # no credentials are explicitly passed and will throw an error if none are found.
517
- class Auth0TokenProvider < StatelyDB::Common::Auth::TokenProvider
596
+ class AuthTokenProvider < StatelyDB::Common::Auth::TokenProvider
518
597
  # _@param_ `origin` — The origin of the OAuth server
519
598
  #
520
599
  # _@param_ `audience` — The OAuth Audience for the token
@@ -522,11 +601,17 @@ module StatelyDB
522
601
  # _@param_ `client_secret` — The StatelyDB client secret credential
523
602
  #
524
603
  # _@param_ `client_id` — The StatelyDB client ID credential
604
+ #
605
+ # _@param_ `access_key` — The StatelyDB access key credential
606
+ #
607
+ # _@param_ `base_retry_backoff_secs` — The base retry backoff in seconds
525
608
  def initialize: (
526
609
  ?origin: String,
527
610
  ?audience: String,
528
611
  ?client_secret: String,
529
- ?client_id: String
612
+ ?client_id: String,
613
+ ?access_key: String,
614
+ ?base_retry_backoff_secs: Float
530
615
  ) -> void
531
616
 
532
617
  # Close the token provider and kill any background operations
@@ -548,11 +633,17 @@ module StatelyDB
548
633
  # _@param_ `client_secret` — The StatelyDB client secret credential
549
634
  #
550
635
  # _@param_ `client_id` — The StatelyDB client ID credential
636
+ #
637
+ # _@param_ `access_key` — The StatelyDB access key credential
638
+ #
639
+ # _@param_ `base_retry_backoff_secs` — The base retry backoff in seconds
551
640
  def initialize: (
552
641
  origin: String,
553
642
  audience: String,
554
643
  client_secret: String,
555
- client_id: String
644
+ client_id: String,
645
+ access_key: String,
646
+ base_retry_backoff_secs: Float
556
647
  ) -> void
557
648
 
558
649
  # Initialize the actor. This runs on the actor thread which means
@@ -583,8 +674,22 @@ module StatelyDB
583
674
  #
584
675
  # _@return_ — The new access token
585
676
  def refresh_token_impl: () -> String
677
+ end
678
+
679
+ # Persistent state for the token provider
680
+ class TokenState
681
+ # Create a new TokenState
682
+ #
683
+ # _@param_ `token` — The access token
684
+ #
685
+ # _@param_ `expires_at_unix_secs` — The unix timestamp when the token expires
686
+ def initialize: (token: String, expires_at_unix_secs: Integer) -> void
586
687
 
587
- def make_auth0_request: () -> untyped
688
+ # Returns the value of attribute token.
689
+ attr_reader token: untyped
690
+
691
+ # Returns the value of attribute expires_at_unix_secs.
692
+ attr_reader expires_at_unix_secs: untyped
588
693
  end
589
694
  end
590
695
  end
@@ -936,6 +1041,22 @@ module Stately
936
1041
  end
937
1042
  end
938
1043
 
1044
+ module Auth
1045
+ GetAuthTokenRequest: untyped
1046
+ GetAuthTokenResponse: untyped
1047
+
1048
+ module AuthService
1049
+ Stub: untyped
1050
+
1051
+ # AuthService is the service for vending access tokens used to connect to
1052
+ # StatelyDB. This API is meant to be used from SDKs. Access Keys are created
1053
+ # and managed from the stately.dbmanagement.UserService.
1054
+ class Service
1055
+ include GRPC::GenericService
1056
+ end
1057
+ end
1058
+ end
1059
+
939
1060
  module Errors
940
1061
  StatelyErrorDetails: untyped
941
1062
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: statelydb
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.13.0
4
+ version: 0.15.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stately Cloud, Inc.
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-12-12 00:00:00.000000000 Z
11
+ date: 2024-12-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: async
@@ -73,6 +73,9 @@ extensions: []
73
73
  extra_rdoc_files: []
74
74
  files:
75
75
  - README.md
76
+ - lib/api/auth/get_auth_token_pb.rb
77
+ - lib/api/auth/service_pb.rb
78
+ - lib/api/auth/service_services_pb.rb
76
79
  - lib/api/db/continue_list_pb.rb
77
80
  - lib/api/db/delete_pb.rb
78
81
  - lib/api/db/get_pb.rb
@@ -87,8 +90,9 @@ files:
87
90
  - lib/api/db/sync_list_pb.rb
88
91
  - lib/api/db/transaction_pb.rb
89
92
  - lib/api/errors/error_details_pb.rb
90
- - lib/common/auth/auth0_token_provider.rb
93
+ - lib/common/auth/auth_token_provider.rb
91
94
  - lib/common/auth/interceptor.rb
95
+ - lib/common/auth/token_fetcher.rb
92
96
  - lib/common/auth/token_provider.rb
93
97
  - lib/common/error_interceptor.rb
94
98
  - lib/common/net/conn.rb