statelydb 0.12.2 → 0.14.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: 16704510255d3248c18f2f26a63e2e94debe5207e6bdb820b8be999a44aeb486
4
- data.tar.gz: 981f9a3030231adfcf253dbde5c84bb54232d64c4cec169a630d8c085194cc0e
3
+ metadata.gz: a723737fdce6e8ad2bec8937cfd79f6d968df0fc3e9e747c64e18af9ce769601
4
+ data.tar.gz: 1f0279a8b9507f4cffacc360ab82e2289471a40de39bd3891f0c7db5c63877c6
5
5
  SHA512:
6
- metadata.gz: 647935dfc070e7b871933d9f75d24c7b6e651cd95005b374a65c5af7e1d0f3271259c5bf585350b3c9065fef8e33c10e86212bb3500d87ef0647ed77cf9bb79d
7
- data.tar.gz: a5857dbb91d7f4f91e2d3518d1e0fcdaf8104e5e26b792440207c18f4c18f3d7e0a639c2d090c9ef95cf25e623b00ee8d708956d8989993a54e4a44b8f5f0947
6
+ metadata.gz: 815835070a91366b18948afe145eb548ae4f6ecbc6559ed3b8193288cd1084b7ac1a416264b0769afea93f4a8c01e17b0474facb46fe90460b0b3db803466428
7
+ data.tar.gz: e0c75f31911432f2544c4097fbc0c7a1615624257e80495aef9e5b3a0dddc92dd9341683d24416c4a32ab19b8bd34a2d79abb577781d75ee41b55a02f2e2e11c
@@ -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
data/lib/api/db/get_pb.rb CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  require 'google/protobuf'
6
6
 
7
- require 'db/item_pb'
7
+ require 'api/db/item_pb'
8
8
 
9
9
 
10
10
  descriptor_data = "\n\x0c\x64\x62/get.proto\x12\nstately.db\x1a\rdb/item.proto\"\xa3\x01\n\nGetRequest\x12\x19\n\x08store_id\x18\x01 \x01(\x04R\x07storeId\x12\'\n\x04gets\x18\x02 \x03(\x0b\x32\x13.stately.db.GetItemR\x04gets\x12\x1f\n\x0b\x61llow_stale\x18\x03 \x01(\x08R\nallowStale\x12*\n\x11schema_version_id\x18\x05 \x01(\rR\x0fschemaVersionIdJ\x04\x08\x04\x10\x05\"$\n\x07GetItem\x12\x19\n\x08key_path\x18\x01 \x01(\tR\x07keyPath\"5\n\x0bGetResponse\x12&\n\x05items\x18\x01 \x03(\x0b\x32\x10.stately.db.ItemR\x05itemsBc\n\x0e\x63om.stately.dbB\x08GetProtoP\x01\xa2\x02\x03SDX\xaa\x02\nStately.Db\xca\x02\nStately\\Db\xe2\x02\x16Stately\\Db\\GPBMetadata\xea\x02\x0bStately::Dbb\x06proto3"
@@ -4,9 +4,9 @@
4
4
 
5
5
  require 'google/protobuf'
6
6
 
7
- require 'db/item_pb'
8
- require 'db/item_property_pb'
9
- require 'db/list_token_pb'
7
+ require 'api/db/item_pb'
8
+ require 'api/db/item_property_pb'
9
+ require 'api/db/list_token_pb'
10
10
 
11
11
 
12
12
  descriptor_data = "\n\rdb/list.proto\x12\nstately.db\x1a\rdb/item.proto\x1a\x16\x64\x62/item_property.proto\x1a\x13\x64\x62/list_token.proto\"\xbd\x02\n\x10\x42\x65ginListRequest\x12\x19\n\x08store_id\x18\x01 \x01(\x04R\x07storeId\x12&\n\x0fkey_path_prefix\x18\x02 \x01(\tR\rkeyPathPrefix\x12\x14\n\x05limit\x18\x03 \x01(\rR\x05limit\x12\x1f\n\x0b\x61llow_stale\x18\x04 \x01(\x08R\nallowStale\x12\x41\n\rsort_property\x18\x05 \x01(\x0e\x32\x1c.stately.db.SortablePropertyR\x0csortProperty\x12@\n\x0esort_direction\x18\x06 \x01(\x0e\x32\x19.stately.db.SortDirectionR\rsortDirection\x12*\n\x11schema_version_id\x18\x07 \x01(\rR\x0fschemaVersionId\"\x8b\x01\n\x0cListResponse\x12\x37\n\x06result\x18\x01 \x01(\x0b\x32\x1d.stately.db.ListPartialResultH\x00R\x06result\x12\x36\n\x08\x66inished\x18\x02 \x01(\x0b\x32\x18.stately.db.ListFinishedH\x00R\x08\x66inishedB\n\n\x08response\";\n\x11ListPartialResult\x12&\n\x05items\x18\x01 \x03(\x0b\x32\x10.stately.db.ItemR\x05items\";\n\x0cListFinished\x12+\n\x05token\x18\x01 \x01(\x0b\x32\x15.stately.db.ListTokenR\x05token*8\n\rSortDirection\x12\x12\n\x0eSORT_ASCENDING\x10\x00\x12\x13\n\x0fSORT_DESCENDING\x10\x01\x42\x64\n\x0e\x63om.stately.dbB\tListProtoP\x01\xa2\x02\x03SDX\xaa\x02\nStately.Db\xca\x02\nStately\\Db\xe2\x02\x16Stately\\Db\\GPBMetadata\xea\x02\x0bStately::Dbb\x06proto3"
data/lib/api/db/put_pb.rb CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  require 'google/protobuf'
6
6
 
7
- require 'db/item_pb'
7
+ require 'api/db/item_pb'
8
8
 
9
9
 
10
10
  descriptor_data = "\n\x0c\x64\x62/put.proto\x12\nstately.db\x1a\rdb/item.proto\"|\n\nPutRequest\x12\x19\n\x08store_id\x18\x01 \x01(\x04R\x07storeId\x12\'\n\x04puts\x18\x02 \x03(\x0b\x32\x13.stately.db.PutItemR\x04puts\x12*\n\x11schema_version_id\x18\x03 \x01(\rR\x0fschemaVersionId\"U\n\x07PutItem\x12$\n\x04item\x18\x01 \x01(\x0b\x32\x10.stately.db.ItemR\x04item\x12$\n\x0emust_not_exist\x18\x03 \x01(\x08R\x0cmustNotExist\"5\n\x0bPutResponse\x12&\n\x05items\x18\x01 \x03(\x0b\x32\x10.stately.db.ItemR\x05itemsBc\n\x0e\x63om.stately.dbB\x08PutProtoP\x01\xa2\x02\x03SDX\xaa\x02\nStately.Db\xca\x02\nStately\\Db\xe2\x02\x16Stately\\Db\\GPBMetadata\xea\x02\x0bStately::Dbb\x06proto3"
@@ -4,14 +4,14 @@
4
4
 
5
5
  require 'google/protobuf'
6
6
 
7
- require 'db/continue_list_pb'
8
- require 'db/delete_pb'
9
- require 'db/get_pb'
10
- require 'db/list_pb'
11
- require 'db/put_pb'
12
- require 'db/scan_root_paths_pb'
13
- require 'db/sync_list_pb'
14
- require 'db/transaction_pb'
7
+ require 'api/db/continue_list_pb'
8
+ require 'api/db/delete_pb'
9
+ require 'api/db/get_pb'
10
+ require 'api/db/list_pb'
11
+ require 'api/db/put_pb'
12
+ require 'api/db/scan_root_paths_pb'
13
+ require 'api/db/sync_list_pb'
14
+ require 'api/db/transaction_pb'
15
15
 
16
16
 
17
17
  descriptor_data = "\n\x10\x64\x62/service.proto\x12\nstately.db\x1a\x16\x64\x62/continue_list.proto\x1a\x0f\x64\x62/delete.proto\x1a\x0c\x64\x62/get.proto\x1a\rdb/list.proto\x1a\x0c\x64\x62/put.proto\x1a\x18\x64\x62/scan_root_paths.proto\x1a\x12\x64\x62/sync_list.proto\x1a\x14\x64\x62/transaction.proto2\xee\x04\n\x0f\x44\x61tabaseService\x12;\n\x03Put\x12\x16.stately.db.PutRequest\x1a\x17.stately.db.PutResponse\"\x03\x90\x02\x02\x12;\n\x03Get\x12\x16.stately.db.GetRequest\x1a\x17.stately.db.GetResponse\"\x03\x90\x02\x01\x12\x44\n\x06\x44\x65lete\x12\x19.stately.db.DeleteRequest\x1a\x1a.stately.db.DeleteResponse\"\x03\x90\x02\x02\x12J\n\tBeginList\x12\x1c.stately.db.BeginListRequest\x1a\x18.stately.db.ListResponse\"\x03\x90\x02\x01\x30\x01\x12P\n\x0c\x43ontinueList\x12\x1f.stately.db.ContinueListRequest\x1a\x18.stately.db.ListResponse\"\x03\x90\x02\x01\x30\x01\x12L\n\x08SyncList\x12\x1b.stately.db.SyncListRequest\x1a\x1c.stately.db.SyncListResponse\"\x03\x90\x02\x01\x30\x01\x12T\n\x0bTransaction\x12\x1e.stately.db.TransactionRequest\x1a\x1f.stately.db.TransactionResponse\"\x00(\x01\x30\x01\x12Y\n\rScanRootPaths\x12 .stately.db.ScanRootPathsRequest\x1a!.stately.db.ScanRootPathsResponse\"\x03\x90\x02\x01\x42g\n\x0e\x63om.stately.dbB\x0cServiceProtoP\x01\xa2\x02\x03SDX\xaa\x02\nStately.Db\xca\x02\nStately\\Db\xe2\x02\x16Stately\\Db\\GPBMetadata\xea\x02\x0bStately::Dbb\x06proto3"
@@ -2,7 +2,7 @@
2
2
  # Source: db/service.proto for package 'Stately.Db'
3
3
 
4
4
  require 'grpc'
5
- require 'db/service_pb'
5
+ require 'api/db/service_pb'
6
6
 
7
7
  module Stately
8
8
  module Db
@@ -4,8 +4,8 @@
4
4
 
5
5
  require 'google/protobuf'
6
6
 
7
- require 'db/item_pb'
8
- require 'db/list_pb'
7
+ require 'api/db/item_pb'
8
+ require 'api/db/list_pb'
9
9
 
10
10
 
11
11
  descriptor_data = "\n\x12\x64\x62/sync_list.proto\x12\nstately.db\x1a\rdb/item.proto\x1a\rdb/list.proto\"\\\n\x0fSyncListRequest\x12\x1d\n\ntoken_data\x18\x01 \x01(\x0cR\ttokenData\x12*\n\x11schema_version_id\x18\x05 \x01(\rR\x0fschemaVersionId\"\xc8\x01\n\x10SyncListResponse\x12\x31\n\x05reset\x18\x01 \x01(\x0b\x32\x19.stately.db.SyncListResetH\x00R\x05reset\x12=\n\x06result\x18\x02 \x01(\x0b\x32#.stately.db.SyncListPartialResponseH\x00R\x06result\x12\x36\n\x08\x66inished\x18\x03 \x01(\x0b\x32\x18.stately.db.ListFinishedH\x00R\x08\x66inishedB\n\n\x08response\"\x0f\n\rSyncListReset\"\xdf\x01\n\x17SyncListPartialResponse\x12\x35\n\rchanged_items\x18\x01 \x03(\x0b\x32\x10.stately.db.ItemR\x0c\x63hangedItems\x12<\n\rdeleted_items\x18\x02 \x03(\x0b\x32\x17.stately.db.DeletedItemR\x0c\x64\x65letedItems\x12O\n%updated_item_keys_outside_list_window\x18\x03 \x03(\tR updatedItemKeysOutsideListWindow\"(\n\x0b\x44\x65letedItem\x12\x19\n\x08key_path\x18\x01 \x01(\tR\x07keyPathBh\n\x0e\x63om.stately.dbB\rSyncListProtoP\x01\xa2\x02\x03SDX\xaa\x02\nStately.Db\xca\x02\nStately\\Db\xe2\x02\x16Stately\\Db\\GPBMetadata\xea\x02\x0bStately::Dbb\x06proto3"
@@ -4,13 +4,13 @@
4
4
 
5
5
  require 'google/protobuf'
6
6
 
7
- require 'db/continue_list_pb'
8
- require 'db/delete_pb'
9
- require 'db/get_pb'
10
- require 'db/item_pb'
11
- require 'db/item_property_pb'
12
- require 'db/list_pb'
13
- require 'db/put_pb'
7
+ require 'api/db/continue_list_pb'
8
+ require 'api/db/delete_pb'
9
+ require 'api/db/get_pb'
10
+ require 'api/db/item_pb'
11
+ require 'api/db/item_property_pb'
12
+ require 'api/db/list_pb'
13
+ require 'api/db/put_pb'
14
14
  require 'google/protobuf/empty_pb'
15
15
 
16
16
 
@@ -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,22 @@ 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
30
33
  def initialize(
31
34
  origin: "https://oauth.stately.cloud",
32
35
  audience: "api.stately.cloud",
33
- client_secret: ENV.fetch("STATELY_CLIENT_SECRET"),
34
- client_id: ENV.fetch("STATELY_CLIENT_ID")
36
+ client_secret: ENV.fetch("STATELY_CLIENT_SECRET", nil),
37
+ client_id: ENV.fetch("STATELY_CLIENT_ID", nil),
38
+ access_key: ENV.fetch("STATELY_ACCESS_KEY", nil)
35
39
  )
36
40
  super()
37
41
  @actor = Async::Actor.new(Actor.new(origin: origin, audience: audience,
38
- client_secret: client_secret, client_id: client_id))
42
+ client_secret: client_secret, client_id: client_id, access_key: access_key))
39
43
  # this initialization cannot happen in the constructor because it is async and must run on the event loop
40
44
  # which is not available in the constructor
41
45
  @actor.init
@@ -64,29 +68,41 @@ module StatelyDB
64
68
  origin:,
65
69
  audience:,
66
70
  client_secret:,
67
- client_id:
71
+ client_id:,
72
+ access_key:
68
73
  )
69
74
  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
75
 
75
- @access_token = nil
76
- @expires_at_unix_secs = nil
76
+ @token_fetcher = nil
77
+ if !access_key.nil?
78
+ @token_fetcher = StatelyDB::Common::Auth::StatelyAccessTokenFetcher.new(origin: origin, access_key: access_key)
79
+ elsif !client_secret.nil? && !client_id.nil?
80
+ @token_fetcher = StatelyDB::Common::Auth::Auth0TokenFetcher.new(origin: origin, audience: audience,
81
+ client_secret: client_secret, client_id: client_id)
82
+ else
83
+ raise StatelyDB::Error.new("unable to find client credentials in STATELY_ACCESS_KEY or STATELY_CLIENT_ID and " \
84
+ "STATELY_CLIENT_SECRET environment variables. Either pass your credentials in " \
85
+ "explicitly or set these environment variables",
86
+ code: GRPC::Core::StatusCodes::UNAUTHENTICATED,
87
+ stately_code: "Unauthenticated")
88
+ end
89
+
90
+ @token_state = nil
77
91
  @pending_refresh = nil
78
92
  end
79
93
 
80
94
  # Initialize the actor. This runs on the actor thread which means
81
95
  # we can dispatch async operations here.
82
96
  def init
97
+ # disable the async lib logger. We do our own error handling and propagation
98
+ Console.logger.disable(Async::Task)
83
99
  refresh_token
84
100
  end
85
101
 
86
102
  # Close the token provider and kill any background operations
87
103
  def close
88
104
  @scheduled&.stop
89
- @client&.close
105
+ @token_fetcher&.close
90
106
  end
91
107
 
92
108
  # Get the current access token
@@ -94,8 +110,7 @@ module StatelyDB
94
110
  # @return [String] The current access token
95
111
  def get_token(force: false)
96
112
  if force
97
- @access_token = nil
98
- @expires_at_unix_secs = nil
113
+ @token_state = nil
99
114
  else
100
115
  token, ok = valid_access_token
101
116
  return token if ok
@@ -107,11 +122,10 @@ module StatelyDB
107
122
  # Get the current access token and whether it is valid
108
123
  # @return [Array] The current access token and whether it is valid
109
124
  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
125
+ return "", false if @token_state.nil?
126
+ return "", false if @token_state.expires_at_unix_secs < Time.now.to_i
113
127
 
114
- [@access_token, true]
128
+ [@token_state.token, true]
115
129
  end
116
130
 
117
131
  # Refresh the access token
@@ -151,19 +165,16 @@ module StatelyDB
151
165
  # @return [String] The new access token
152
166
  def refresh_token_impl
153
167
  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"]
168
+ token_result = @token_fetcher.fetch
169
+ new_expires_in_secs = token_result.expires_in_secs
158
170
  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
171
 
161
- @access_token = new_access_token
162
- @expires_at_unix_secs = new_expires_at_unix_secs
172
+ # only update the token state if the new expiry is later than the current one
173
+ if @token_state.nil? || new_expires_at_unix_secs > @token_state.expires_at_unix_secs
174
+ @token_state = TokenState.new(token: token_result.token, expires_at_unix_secs: new_expires_at_unix_secs)
163
175
  else
164
-
165
- new_access_token = @access_token
166
- new_expires_in_secs = @expires_at_unix_secs - Time.now.to_i
176
+ # otherwise use the existing expiry time for scheduling the refresh
177
+ new_expires_in_secs = @token_state.expires_at_unix_secs - Time.now.to_i
167
178
  end
168
179
 
169
180
  # Schedule a refresh of the token ahead of the expiry time
@@ -182,24 +193,21 @@ module StatelyDB
182
193
  @scheduled = nil
183
194
  end
184
195
 
185
- new_access_token
196
+ @token_state.token
186
197
  end
187
198
  end
199
+ end
188
200
 
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
201
+ # Persistent state for the token provider
202
+ class TokenState
203
+ attr_reader :token, :expires_at_unix_secs
204
+
205
+ # Create a new TokenState
206
+ # @param [String] token The access token
207
+ # @param [Integer] expires_at_unix_secs The unix timestamp when the token expires
208
+ def initialize(token:, expires_at_unix_secs:)
209
+ @token = token
210
+ @expires_at_unix_secs = expires_at_unix_secs
203
211
  end
204
212
  end
205
213
  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,104 @@
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
+ # TODO: Wrap this in a retry loop and parse errors like we
62
+ # do in the Go SDK.
63
+ response = @client.post("/oauth/token", headers, body)
64
+ raise "Auth request failed" if response.status != 200
65
+
66
+ resp_data = JSON.parse(response.read)
67
+ TokenResult.new(token: resp_data["access_token"], expires_in_secs: resp_data["expires_in"])
68
+ ensure
69
+ response&.close
70
+ end
71
+ end
72
+
73
+ def close
74
+ @client&.close
75
+ end
76
+ end
77
+
78
+ # StatelyAccessTokenFetcher is a TokenFetcher that fetches tokens from the StatelyDB API
79
+ class StatelyAccessTokenFetcher < TokenFetcher
80
+ # @param [String] origin The origin of the OAuth server
81
+ # @param [String] access_key The StatelyDB access key credential
82
+ def initialize(origin:, access_key:)
83
+ super()
84
+ @access_key = access_key
85
+ @channel = Common::Net.new_channel(endpoint: origin)
86
+ error_interceptor = Common::ErrorInterceptor.new
87
+ @stub = Stately::Auth::AuthService::Stub.new(nil, nil, channel_override: @channel,
88
+ interceptors: [error_interceptor])
89
+ end
90
+
91
+ # Fetch a new token from the StatelyDB API
92
+ # @return [TokenResult] The fetched TokenResult
93
+ def fetch
94
+ resp = @stub.get_auth_token(Stately::Auth::GetAuthTokenRequest.new(access_key: @access_key))
95
+ TokenResult.new(token: resp.auth_token, expires_in_secs: resp.expires_in_s)
96
+ end
97
+
98
+ def close
99
+ @channel&.close
100
+ end
101
+ end
102
+ end
103
+ end
104
+ end
data/lib/error.rb CHANGED
@@ -1,10 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Add the pb dir to the LOAD_PATH because generated proto imports are not relative and
4
- # we don't want the protos polluting the main namespace.
5
- # Tracking here: https://github.com/grpc/grpc/issues/6164
6
- $LOAD_PATH.unshift "#{File.dirname(__FILE__)}/api"
7
-
8
3
  require "api/errors/error_details_pb"
9
4
 
10
5
  module StatelyDB
data/lib/statelydb.rb CHANGED
@@ -1,12 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Add the pb dir to the LOAD_PATH because generated proto imports are not relative and
4
- # we don't want the protos polluting the main namespace.
5
- # Tracking here: https://github.com/grpc/grpc/issues/6164
6
- $LOAD_PATH.unshift "#{File.dirname(__FILE__)}/api"
7
-
8
3
  require "api/db/service_services_pb"
9
- require "common/auth/auth0_token_provider"
4
+ require "common/auth/auth_token_provider"
10
5
  require "common/auth/interceptor"
11
6
  require "common/net/conn"
12
7
  require "common/error_interceptor"
@@ -35,7 +30,7 @@ module StatelyDB
35
30
  # @param region [String] the region to connect to.
36
31
  def initialize(store_id:,
37
32
  schema:,
38
- token_provider: Common::Auth::Auth0TokenProvider.new,
33
+ token_provider: Common::Auth::AuthTokenProvider.new,
39
34
  endpoint: nil,
40
35
  region: nil)
41
36
  if store_id.nil?
@@ -63,6 +58,7 @@ module StatelyDB
63
58
  @allow_stale = false
64
59
  end
65
60
 
61
+ # @return [void] nil
66
62
  def close
67
63
  @channel&.close
68
64
  @token_provider&.close
data/lib/token.rb CHANGED
@@ -34,8 +34,7 @@ module StatelyDB
34
34
  end
35
35
 
36
36
  # Returns the schema version ID associated with the token.
37
- def schema_version_id
38
- !!@schema_version_id
39
- end
37
+ # @return [Integer]
38
+ attr_reader :schema_version_id
40
39
  end
41
40
  end
@@ -254,7 +254,7 @@ module StatelyDB
254
254
  #
255
255
  # @param items [StatelyDB::Item, Array<StatelyDB::Item>] the items to store. Max
256
256
  # 50 items.
257
- # @return [Array<StatelyDB::UUID, String, Integer, nil>] the ids of the items
257
+ # @return [Array<StatelyDB::UUID, String, Integer, NilClass>] the ids of the items
258
258
  #
259
259
  # @example
260
260
  # results = client.data.transaction do |txn|
data/sig/grpc.rbs ADDED
@@ -0,0 +1,80 @@
1
+ module GRPC
2
+ class ActiveCall
3
+ end
4
+
5
+ class Interceptor
6
+ end
7
+ class ClientInterceptor < Interceptor
8
+ # gRPC client unary interceptor
9
+ #
10
+ # _@param_ `request` — The request object
11
+ #
12
+ # _@param_ `call` — The active call object
13
+ #
14
+ # _@param_ `method` — The method being called
15
+ #
16
+ # _@param_ `metadata` — The metadata hash
17
+ #
18
+ # _@return_ — The response object
19
+ def request_response: (
20
+ request: Object,
21
+ call: GRPC::ActiveCall,
22
+ method: Symbol,
23
+ metadata: ::Hash[untyped, untyped]
24
+ ) -> Object
25
+
26
+ # gRPC client streaming interceptor
27
+ #
28
+ # _@param_ `requests` — The list of requests
29
+ #
30
+ # _@param_ `call` — The active call object
31
+ #
32
+ # _@param_ `method` — The method being called
33
+ #
34
+ # _@param_ `metadata` — The metadata hash
35
+ #
36
+ # _@return_ — The response enumerator
37
+ def client_streamer: (
38
+ requests: ::Enumerable[untyped],
39
+ call: GRPC::ActiveCall,
40
+ method: Symbol,
41
+ metadata: ::Hash[untyped, untyped]
42
+ ) -> ::Enumerator[untyped]
43
+
44
+ # gRPC server streaming interceptor
45
+ #
46
+ # _@param_ `request` — The request object
47
+ #
48
+ # _@param_ `call` — The active call object
49
+ #
50
+ # _@param_ `method` — The method being called
51
+ #
52
+ # _@param_ `metadata` — The metadata hash
53
+ #
54
+ # _@return_ — The response enumerator
55
+ def server_streamer: (
56
+ request: Object,
57
+ call: GRPC::ActiveCall,
58
+ method: Symbol,
59
+ metadata: ::Hash[untyped, untyped]
60
+ ) -> ::Enumerator[untyped]
61
+
62
+ # gRPC bidirectional streaming interceptor
63
+ #
64
+ # _@param_ `requests` — The list of requests
65
+ #
66
+ # _@param_ `call` — The active call object
67
+ #
68
+ # _@param_ `method` — The method being called
69
+ #
70
+ # _@param_ `metadata` — The metadata hash
71
+ #
72
+ # _@return_ — The response enumerator
73
+ def bidi_streamer: (
74
+ requests: ::Enumerable[untyped],
75
+ call: GRPC::ActiveCall,
76
+ method: Symbol,
77
+ metadata: ::Hash[untyped, untyped]
78
+ ) -> ::Enumerator[untyped]
79
+ end
80
+ end