volcano-sdk 0.9.2 → 0.9.3
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 +4 -4
- data/README.md +55 -30
- data/lib/volcano/function_auth.rb +51 -0
- data/lib/volcano/functions.rb +26 -18
- data/lib/volcano/version.rb +1 -1
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: bb4b4a206147014ad99f42983e545238fd1c46600a127cbe932db4877afd6db9
|
|
4
|
+
data.tar.gz: 5064a928176a3dd8350d066000c405c1d03b87499b3cbc9bd0a9534b65785247
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9ea3fff976b1358651b6f0117a23ccb353edb01139b9876f07b2bca473acd607550779702d0a8821c5cc612194e0fb51f279df52fd41a192b44916d60dd50844
|
|
7
|
+
data.tar.gz: 7b4761b2c1c25f131968de7662668f04c83ad90fe13532b4b7b277685fe23966ca585f578d6337bbe28dbf9c1a584dc38c0d243420f56851af559f0f6fbce085
|
data/README.md
CHANGED
|
@@ -4,6 +4,8 @@ Official Ruby SDK for Volcano. Requires Ruby 3.2 or later.
|
|
|
4
4
|
|
|
5
5
|
Start with the [Ruby quickstart](https://github.com/Kong/volcano-sdk-ruby/blob/main/docs/README.md).
|
|
6
6
|
|
|
7
|
+
See [Authentication](https://github.com/Kong/volcano-sdk-ruby/blob/main/docs/authentication.md) for account, session, email, and OAuth workflows.
|
|
8
|
+
|
|
7
9
|
## Install
|
|
8
10
|
|
|
9
11
|
Add the gem to your Gemfile and run `bundle install`:
|
|
@@ -41,7 +43,7 @@ failures raise typed errors under `Volcano::Error`.
|
|
|
41
43
|
```ruby
|
|
42
44
|
result = client.auth.sign_up(
|
|
43
45
|
email: "new-user@example.com",
|
|
44
|
-
password: "
|
|
46
|
+
password: "correct-horse-battery-staple",
|
|
45
47
|
metadata: { display_name: "New User" }
|
|
46
48
|
)
|
|
47
49
|
puts result.message if result.confirmation_required
|
|
@@ -53,7 +55,7 @@ addresses. Pass `sign_in_when_allowed: true` to follow it with `sign_in` only wh
|
|
|
53
55
|
confirmation is not required:
|
|
54
56
|
|
|
55
57
|
```ruby
|
|
56
|
-
result = client.auth.sign_up(email: "new-user@example.com", password: "
|
|
58
|
+
result = client.auth.sign_up(email: "new-user@example.com", password: "correct-horse-battery-staple", sign_in_when_allowed: true)
|
|
57
59
|
session = result.session # nil when no follow-up sign-in ran.
|
|
58
60
|
```
|
|
59
61
|
|
|
@@ -63,7 +65,7 @@ A failed follow-up raises its usual typed error; it does not undo the successful
|
|
|
63
65
|
### Sign in
|
|
64
66
|
|
|
65
67
|
```ruby
|
|
66
|
-
session = client.auth.sign_in(email: "user@example.com", password: "
|
|
68
|
+
session = client.auth.sign_in(email: "user@example.com", password: "correct-horse-battery-staple")
|
|
67
69
|
current_session = client.auth.current_session
|
|
68
70
|
raise "session changed" unless current_session == session
|
|
69
71
|
```
|
|
@@ -82,7 +84,8 @@ container/string/time subclasses, non-finite numbers, duplicate JSON keys, and
|
|
|
82
84
|
structures deeper than 100 levels raise `TypeError`. Use the typed `auth.user`
|
|
83
85
|
profile when you need Ruby `Time` values. Successful `user`, `update_user`,
|
|
84
86
|
`convert_anonymous`, and `confirm_email_change` calls update this snapshot
|
|
85
|
-
without changing credentials or emitting an authentication-state event
|
|
87
|
+
without changing credentials or emitting an authentication-state event unless an HTTP 401
|
|
88
|
+
requires automatic refresh. Successful recovery rotates credentials and emits `:token_refreshed`.
|
|
86
89
|
Previously returned sessions remain unchanged.
|
|
87
90
|
|
|
88
91
|
### Get the current user
|
|
@@ -103,7 +106,7 @@ is in flight raises
|
|
|
103
106
|
|
|
104
107
|
```ruby
|
|
105
108
|
user = client.auth.update_user(
|
|
106
|
-
password: "new-
|
|
109
|
+
password: "new-correct-horse-battery-staple",
|
|
107
110
|
metadata: { display_name: "Grace", avatar: nil }
|
|
108
111
|
)
|
|
109
112
|
raise "wrong user" unless user.id == session.user_id
|
|
@@ -207,8 +210,9 @@ hosted_url = client.auth.get_hosted_auth_url(
|
|
|
207
210
|
```
|
|
208
211
|
|
|
209
212
|
Store `hosted_state` in the user's signed server-side session before redirecting
|
|
210
|
-
to `hosted_url`.
|
|
211
|
-
|
|
213
|
+
to `hosted_url`. In the callback, atomically fetch and delete the stored state before validation,
|
|
214
|
+
even if validation or adoption fails. Reject a missing or already-consumed state.
|
|
215
|
+
After parsing the returned fragment into a `Volcano::Session`, validate and adopt it:
|
|
212
216
|
|
|
213
217
|
```ruby
|
|
214
218
|
session = client.auth.adopt_hosted_auth_session(
|
|
@@ -239,8 +243,9 @@ authorization_url = client.auth.sign_in_with_oauth(
|
|
|
239
243
|
```
|
|
240
244
|
|
|
241
245
|
Store `oauth_state` in the user's signed server-side session, then redirect the
|
|
242
|
-
user to the returned URL. In the callback,
|
|
243
|
-
|
|
246
|
+
user to the returned URL. In the callback, atomically fetch and delete the stored nonce as `stored_oauth_state`;
|
|
247
|
+
reject a missing or already-consumed nonce. Pass the returned and consumed states to
|
|
248
|
+
the SDK so it rejects login CSRF before exchanging the one-time code:
|
|
244
249
|
|
|
245
250
|
```ruby
|
|
246
251
|
session = client.auth.exchange_oauth_code(
|
|
@@ -338,9 +343,11 @@ replacement as an "other" session. If replacement occurs, the method raises
|
|
|
338
343
|
client.auth.delete_session('00000000-0000-4000-8000-000000000099')
|
|
339
344
|
```
|
|
340
345
|
|
|
341
|
-
The request uses the current access token.
|
|
342
|
-
clears local credentials
|
|
343
|
-
|
|
346
|
+
The request uses the current access token. When its JWT contains a readable UUID `session_id`,
|
|
347
|
+
deleting that session clears local credentials even if the request outcome is uncertain.
|
|
348
|
+
Without that identifier, the SDK cannot recognize self-deletion. Deleting another session does not
|
|
349
|
+
itself clear local state. HTTP 401 recovery can rotate credentials and emit `:token_refreshed`;
|
|
350
|
+
a server-rejected refresh clears the captured session before the operation raises. If another authentication operation
|
|
344
351
|
replaces the session before deletion finishes, the method raises
|
|
345
352
|
`Volcano::Error::SessionChangedError` instead of clearing the replacement or
|
|
346
353
|
acknowledging a stale result.
|
|
@@ -354,13 +361,12 @@ session = client.auth.sign_in_anonymously(metadata: { device: "mobile" })
|
|
|
354
361
|
Anonymous sign-ins must be enabled for the project. Convert the account before
|
|
355
362
|
signing out if the user needs to recover it later.
|
|
356
363
|
|
|
357
|
-
Attach email credentials
|
|
358
|
-
session:
|
|
364
|
+
Attach email credentials while preserving the anonymous user's ID:
|
|
359
365
|
|
|
360
366
|
```ruby
|
|
361
367
|
user = client.auth.convert_anonymous(
|
|
362
368
|
email: "user@example.com",
|
|
363
|
-
password: "
|
|
369
|
+
password: "a-long-example-password-2026",
|
|
364
370
|
metadata: { display_name: "Ada" }
|
|
365
371
|
)
|
|
366
372
|
```
|
|
@@ -371,7 +377,7 @@ as verified.
|
|
|
371
377
|
### Reset the password
|
|
372
378
|
|
|
373
379
|
```ruby
|
|
374
|
-
client.auth.reset_password(token: "recovery-token", new_password: "new-
|
|
380
|
+
client.auth.reset_password(token: "recovery-token", new_password: "new-correct-horse-battery-staple")
|
|
375
381
|
```
|
|
376
382
|
|
|
377
383
|
Success returns `nil`. The reset revokes the recovered account's existing
|
|
@@ -384,9 +390,9 @@ Pass `access_token` to `Volcano::Client.new` to start without a refresh token or
|
|
|
384
390
|
known user identity. Construction makes no request and leaves `refresh_token`,
|
|
385
391
|
`user_id`, and `user` as `nil`. `auth.user` validates and caches the profile
|
|
386
392
|
without changing credentials. Without a refresh token, `refresh_session` raises
|
|
387
|
-
`Volcano::Error::AuthenticationError`. `sign_out` revokes the server
|
|
388
|
-
the access token and
|
|
389
|
-
`
|
|
393
|
+
`Volcano::Error::AuthenticationError`. `sign_out` clears local state and revokes the server
|
|
394
|
+
session when the access JWT contains a readable UUID `session_id`. Supplied credentials require both a refresh token and an access JWT with a readable UUID
|
|
395
|
+
`session_id` to enable refresh.
|
|
390
396
|
See the [token bootstrap example](https://github.com/Kong/volcano-sdk-ruby/blob/main/docs/README.md#use-a-supplied-access-token).
|
|
391
397
|
|
|
392
398
|
### Adopt an existing session
|
|
@@ -412,8 +418,9 @@ raise "refresh failed" unless client.auth.current_session.equal?(refreshed)
|
|
|
412
418
|
```
|
|
413
419
|
|
|
414
420
|
On success, `refresh_session` replaces the in-memory session and returns the
|
|
415
|
-
immutable new snapshot. An authentication
|
|
416
|
-
|
|
421
|
+
immutable new snapshot. An authentication rejection from the refresh endpoint clears the captured session.
|
|
422
|
+
Missing refresh credentials, failed session-continuity checks, server errors, and
|
|
423
|
+
transport failures preserve it, and a late
|
|
417
424
|
response never replaces a newer session. The SDK does not persist sessions.
|
|
418
425
|
|
|
419
426
|
### Observe auth-state changes
|
|
@@ -430,7 +437,8 @@ subscription.unsubscribe
|
|
|
430
437
|
Registration immediately yields `:initial_session`. Successful session
|
|
431
438
|
creation, refresh, and local clearing yield `:signed_in`, `:token_refreshed`,
|
|
432
439
|
and `:signed_out`. Callbacks are delivered locally in transition order after the
|
|
433
|
-
state lock is released
|
|
440
|
+
state lock is released. Callback `StandardError` failures are isolated; exceptions such as
|
|
441
|
+
`Interrupt` propagate after the session transition has committed.
|
|
434
442
|
The SDK does not broadcast between processes or persist sessions.
|
|
435
443
|
|
|
436
444
|
### Sign out the current session
|
|
@@ -441,8 +449,10 @@ raise "still signed in" if client.auth.current_session
|
|
|
441
449
|
```
|
|
442
450
|
|
|
443
451
|
Sign-out uses the refresh token directly when the SDK received both credentials together from
|
|
444
|
-
sign-in or a validated refresh. Supplied credentials use the access-token session
|
|
445
|
-
the SDK can refresh once and revoke that
|
|
452
|
+
sign-in or a validated refresh. Supplied credentials use the access-token session when its JWT
|
|
453
|
+
contains a readable UUID `session_id`; on HTTP 401, the SDK can refresh once and revoke that
|
|
454
|
+
same session without adopting the renewed credentials. Without that identifier, sign-out uses
|
|
455
|
+
the supplied refresh token, or only clears local state if no refresh token is available.
|
|
446
456
|
It revokes the captured session and clears the captured in-memory
|
|
447
457
|
session. It succeeds without a request when no session exists. If revocation
|
|
448
458
|
fails, the SDK still clears that session and raises the typed error. Sign-out waits for an already-running refresh and uses its validated credentials.
|
|
@@ -464,7 +474,7 @@ goes to the function's own domain rather than to `api_url`, so an egress rule
|
|
|
464
474
|
that allows only the API host will block it; the resolved endpoint is cached for
|
|
465
475
|
the lifetime the platform gives it. Deployments with no public function domain
|
|
466
476
|
invoke through the API host instead. It uses the
|
|
467
|
-
|
|
477
|
+
current session token, including a supplied `access_token`, then a configured service key, then the
|
|
468
478
|
anonymous key. An anonymous key can invoke a public function without a user
|
|
469
479
|
session; the function receives no user identity. The immutable result includes
|
|
470
480
|
the response body, status, headers, and `X-Volcano-Version`. The body can be a
|
|
@@ -473,20 +483,33 @@ deeply frozen. Invalid JSON or JSON that cannot decode to valid UTF-8 is
|
|
|
473
483
|
returned as the original decoded response text; malformed Unicode is not repaired.
|
|
474
484
|
Ruby's standard JSON nesting limit (100) also falls back to text.
|
|
475
485
|
A function's own
|
|
476
|
-
non-2xx response is returned when
|
|
477
|
-
|
|
486
|
+
non-2xx response is returned when Volcano confirms it ran; non-success platform
|
|
487
|
+
HTTP responses raise typed SDK errors.
|
|
488
|
+
|
|
489
|
+
Function resolution and invocation recover from a platform HTTP 401 before dispatch:
|
|
490
|
+
the SDK refreshes the captured session and retries the rejected request once.
|
|
491
|
+
Concurrent calls share successful recovery. Replacing or signing out that session
|
|
492
|
+
prevents replay under another identity. The call preserves its original payload values.
|
|
493
|
+
A function's own response, HTTP 403, or a network failure never triggers this retry.
|
|
494
|
+
Anonymous and service keys do not refresh.
|
|
495
|
+
|
|
496
|
+
See the [functions guide](https://github.com/Kong/volcano-sdk-ruby/blob/main/docs/functions.md).
|
|
478
497
|
|
|
479
498
|
### Read project logs
|
|
480
499
|
|
|
481
500
|
```ruby
|
|
482
501
|
project_id = "00000000-0000-4000-8000-000000000001"
|
|
483
|
-
|
|
502
|
+
logs_client = Volcano::Client.new(
|
|
503
|
+
anon_key: ENV.fetch("VOLCANO_ANON_KEY"),
|
|
504
|
+
access_token: ENV.fetch("VOLCANO_PROJECT_ACCESS_TOKEN")
|
|
505
|
+
)
|
|
506
|
+
page = logs_client.logs.search(
|
|
484
507
|
project_id,
|
|
485
508
|
{ resource: { type: "function" }, limit: 100 }
|
|
486
509
|
)
|
|
487
510
|
page.data.each { |event| puts [event["timestamp"], event["body"]] }
|
|
488
511
|
|
|
489
|
-
activity =
|
|
512
|
+
activity = logs_client.logs.activity(
|
|
490
513
|
project_id,
|
|
491
514
|
{ resource: { type: "function" }, bucket_count: 24 }
|
|
492
515
|
)
|
|
@@ -496,7 +519,9 @@ puts activity.total
|
|
|
496
519
|
`search` returns an immutable page of retained runtime or deployment log
|
|
497
520
|
events. Pass `next_cursor` back as `cursor` to continue a search. `activity`
|
|
498
521
|
returns immutable time buckets using the same resource selector and query
|
|
499
|
-
syntax. Both methods require
|
|
522
|
+
syntax. Both methods require a platform user token or a project access token.
|
|
523
|
+
A `read_only` project token is sufficient; end-user sessions cannot read project logs.
|
|
524
|
+
See the [logs guide](https://github.com/Kong/volcano-sdk-ruby/blob/main/docs/logs.md).
|
|
500
525
|
|
|
501
526
|
### Query a database
|
|
502
527
|
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Volcano
|
|
4
|
+
# Keeps function resolution and invocation on the captured session lineage.
|
|
5
|
+
class FunctionAuth
|
|
6
|
+
def initialize(client)
|
|
7
|
+
@client = client
|
|
8
|
+
@binding = client.capture_session_binding
|
|
9
|
+
@fallback_token = client.function_token.dup.freeze
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def run
|
|
13
|
+
@binding = @client.auth.__send__(:owned_session_binding, @binding) if @binding.last
|
|
14
|
+
yield(token)
|
|
15
|
+
rescue Error::AuthenticationError => e
|
|
16
|
+
raise unless @binding.last && e.status == 401
|
|
17
|
+
|
|
18
|
+
refresh(e)
|
|
19
|
+
yield(token)
|
|
20
|
+
ensure
|
|
21
|
+
validate
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
def token
|
|
27
|
+
return @client.auth.__send__(:owned_session_binding, @binding).last.access_token if @binding.last
|
|
28
|
+
|
|
29
|
+
validate
|
|
30
|
+
@fallback_token
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def refresh(original)
|
|
34
|
+
# Resolution has released its cache lock before refresh callbacks run.
|
|
35
|
+
@client.auth.__send__(:refresh_captured_session, @binding)
|
|
36
|
+
rescue Error::SessionChangedError
|
|
37
|
+
raise
|
|
38
|
+
rescue Error::VolcanoError
|
|
39
|
+
raise original
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def validate
|
|
43
|
+
if @binding.last
|
|
44
|
+
@client.auth.__send__(:validate_read_failure, @binding)
|
|
45
|
+
elsif @client.capture_session_binding[1] != @binding[1]
|
|
46
|
+
raise Error::SessionChangedError
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
private_constant :FunctionAuth
|
|
51
|
+
end
|
data/lib/volcano/functions.rb
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative 'function_auth'
|
|
4
|
+
|
|
3
5
|
module Volcano
|
|
4
6
|
# Invokes deployed Volcano functions by name.
|
|
5
7
|
class Functions
|
|
@@ -17,21 +19,33 @@ module Volcano
|
|
|
17
19
|
end
|
|
18
20
|
|
|
19
21
|
def invoke(name, payload = {})
|
|
22
|
+
auth = FunctionAuth.new(@client)
|
|
20
23
|
validate_invocation(name, payload)
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
+
invoke_owned(auth, name.dup.freeze, ImmutableQueryValue.capture(payload))
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
|
|
29
|
+
def invoke_owned(auth, name, payload)
|
|
30
|
+
authorization, resolution = auth.run { |token| [token, resolve(token, name)] }
|
|
31
|
+
response = authenticated_invoke(auth, resolution, payload)
|
|
24
32
|
if stale_mapping?(response)
|
|
25
|
-
# The function was deleted and recreated, so the cached identity no
|
|
26
|
-
# longer exists. Resolve again before giving up.
|
|
27
33
|
FunctionResolution.forget(@api_url, authorization, name)
|
|
28
|
-
resolution = resolve(
|
|
29
|
-
response =
|
|
34
|
+
resolution = auth.run { |token| resolve(token, name) }
|
|
35
|
+
response = authenticated_invoke(auth, resolution, payload)
|
|
30
36
|
end
|
|
31
37
|
function_response(response)
|
|
32
38
|
end
|
|
33
39
|
|
|
34
|
-
|
|
40
|
+
def authenticated_invoke(auth, resolution, payload)
|
|
41
|
+
auth.run do |token|
|
|
42
|
+
response = Transport.invoke { invoke_resolved(token, resolution, payload) }
|
|
43
|
+
if response.status == 401 && header(response.headers, FUNCTION_INVOKED_HEADER).nil?
|
|
44
|
+
Transport.body(response, 200)
|
|
45
|
+
end
|
|
46
|
+
response
|
|
47
|
+
end
|
|
48
|
+
end
|
|
35
49
|
|
|
36
50
|
# A platform 404 means the cached function identity is gone. A function that
|
|
37
51
|
# answers 404 itself must be returned rather than retried: invoking twice
|
|
@@ -82,9 +96,7 @@ module Volcano
|
|
|
82
96
|
end
|
|
83
97
|
|
|
84
98
|
def cached_resolution(cached)
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
raise Error::NotFoundError.new('Function was not found', status: 404)
|
|
99
|
+
cached.resolution || raise(Error::NotFoundError.new('Function was not found', status: 404))
|
|
88
100
|
end
|
|
89
101
|
|
|
90
102
|
def validate_invocation(name, payload)
|
|
@@ -101,10 +113,8 @@ module Volcano
|
|
|
101
113
|
|
|
102
114
|
# Absent when the deployment serves no public invocation domain, as in
|
|
103
115
|
# local development; the function is reached through the API instead.
|
|
104
|
-
FunctionResolution
|
|
105
|
-
|
|
106
|
-
invoke_url: FunctionResolution.valid_invoke_url(payload['invoke_url'], @api_url)
|
|
107
|
-
)
|
|
116
|
+
invoke_url = FunctionResolution.valid_invoke_url(payload['invoke_url'], @api_url)
|
|
117
|
+
FunctionResolution::Resolution.new(function_id: function_id, invoke_url: invoke_url)
|
|
108
118
|
end
|
|
109
119
|
|
|
110
120
|
def cache_ttl(payload)
|
|
@@ -129,8 +139,6 @@ module Volcano
|
|
|
129
139
|
)
|
|
130
140
|
end
|
|
131
141
|
|
|
132
|
-
def header(headers, name)
|
|
133
|
-
headers&.find { |key, _| key.casecmp?(name) }&.last
|
|
134
|
-
end
|
|
142
|
+
def header(headers, name) = headers&.find { |key, _| key.casecmp?(name) }&.last
|
|
135
143
|
end
|
|
136
144
|
end
|
data/lib/volcano/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: volcano-sdk
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.9.
|
|
4
|
+
version: 0.9.3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Kong
|
|
@@ -153,6 +153,7 @@ files:
|
|
|
153
153
|
- lib/volcano/connection_string.rb
|
|
154
154
|
- lib/volcano/database.rb
|
|
155
155
|
- lib/volcano/errors.rb
|
|
156
|
+
- lib/volcano/function_auth.rb
|
|
156
157
|
- lib/volcano/function_resolution.rb
|
|
157
158
|
- lib/volcano/function_response.rb
|
|
158
159
|
- lib/volcano/functions.rb
|