statelydb 0.13.0 → 0.15.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: 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