volcano-sdk 0.9.0 → 0.9.1
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 +6 -3
- data/lib/volcano/auth.rb +15 -3
- data/lib/volcano/auth_refresh.rb +32 -32
- data/lib/volcano/auth_sign_out.rb +19 -13
- data/lib/volcano/auth_state.rb +8 -7
- data/lib/volcano/realtime/postgres_delivery.rb +2 -2
- data/lib/volcano/session.rb +3 -4
- data/lib/volcano/session_operations.rb +105 -0
- data/lib/volcano/version.rb +1 -1
- data/lib/volcano.rb +2 -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: 9c502d5485b551f8308988375a3b89d581b46ef42af03143898afc583395cc0b
|
|
4
|
+
data.tar.gz: 336f60885935dd30f4ff6886505095f90bfb4d9c7c7b1f572e5511b53e824897
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7849ada9eaa8cf569961c5a4b750c33610ff46ff0914321a753e09ff2b3b5f969b9879c11db7014199f2e31ba61052b45fa67ef61603b4fa7e427f9857628f84
|
|
7
|
+
data.tar.gz: 782fbaf15537f564c4349cea3f206c63857b95f8e1b919203784f0e4963a0e31a0ce9739ac10011336847e84e9137b69232379acd96d9ee9e1d8218b14e5c1a1
|
data/README.md
CHANGED
|
@@ -440,11 +440,14 @@ client.auth.sign_out
|
|
|
440
440
|
raise "still signed in" if client.auth.current_session
|
|
441
441
|
```
|
|
442
442
|
|
|
443
|
-
|
|
443
|
+
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; on HTTP 401,
|
|
445
|
+
the SDK can refresh once and revoke that same session without adopting the renewed credentials.
|
|
444
446
|
It revokes the captured session and clears the captured in-memory
|
|
445
447
|
session. It succeeds without a request when no session exists. If revocation
|
|
446
|
-
fails, the SDK still clears that session and raises the typed error.
|
|
447
|
-
refresh
|
|
448
|
+
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.
|
|
449
|
+
Later refresh attempts raise `Volcano::Error::SessionChangedError` without a request.
|
|
450
|
+
Concurrent sign-out calls share one result. A separate sign-in or adoption remains current.
|
|
448
451
|
|
|
449
452
|
### Invoke a function
|
|
450
453
|
|
data/lib/volcano/auth.rb
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require 'monitor'
|
|
4
|
-
|
|
5
3
|
module Volcano
|
|
6
4
|
# Authenticates users and updates the client session.
|
|
7
5
|
class Auth
|
|
@@ -12,7 +10,6 @@ module Volcano
|
|
|
12
10
|
@client = client
|
|
13
11
|
@transport = transport
|
|
14
12
|
@api_url = api_url
|
|
15
|
-
@refresh_lock = Monitor.new
|
|
16
13
|
end
|
|
17
14
|
|
|
18
15
|
def current_session
|
|
@@ -56,6 +53,15 @@ module Volcano
|
|
|
56
53
|
end
|
|
57
54
|
end
|
|
58
55
|
|
|
56
|
+
def refresh_response(refresh_token)
|
|
57
|
+
Transport.invoke do
|
|
58
|
+
@transport.auth_refresh(
|
|
59
|
+
authorization: @client.anon_token,
|
|
60
|
+
refresh_token: refresh_token
|
|
61
|
+
)
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
59
65
|
def build_session(payload)
|
|
60
66
|
owned_session(
|
|
61
67
|
access_token: payload.fetch('access_token'),
|
|
@@ -65,6 +71,12 @@ module Volcano
|
|
|
65
71
|
)
|
|
66
72
|
end
|
|
67
73
|
|
|
74
|
+
def parse_refresh_session(payload)
|
|
75
|
+
owned_complete_session(build_session(payload))
|
|
76
|
+
rescue KeyError, TypeError, NoMethodError, ArgumentError => e
|
|
77
|
+
raise Error::TransportError, INCOMPLETE_SESSION, cause: e
|
|
78
|
+
end
|
|
79
|
+
|
|
68
80
|
def complete_session?(session)
|
|
69
81
|
return false unless session.is_a?(Session)
|
|
70
82
|
|
data/lib/volcano/auth_refresh.rb
CHANGED
|
@@ -52,10 +52,12 @@ module Volcano
|
|
|
52
52
|
|
|
53
53
|
def refresh_with_notifications(binding)
|
|
54
54
|
notifications = []
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
55
|
+
active_generation, = owned_session_binding(binding)
|
|
56
|
+
binding[1].refresh { perform_refresh(binding, notifications) } if active_generation == binding.first
|
|
57
|
+
raise Error::SessionChangedError if binding[1].closing?
|
|
58
|
+
rescue Error::VolcanoError
|
|
59
|
+
validate_read_failure(binding)
|
|
60
|
+
raise
|
|
59
61
|
ensure
|
|
60
62
|
# Only the dispatcher owner drains, after refresh coordination is released.
|
|
61
63
|
notifications.each(&:call)
|
|
@@ -74,50 +76,48 @@ module Volcano
|
|
|
74
76
|
def rejected_refresh?(binding, active)
|
|
75
77
|
generation, lineage, = binding
|
|
76
78
|
@rejected_refresh == [generation, lineage] &&
|
|
77
|
-
active ==
|
|
79
|
+
active.first == generation + 1 && active.last.nil?
|
|
78
80
|
end
|
|
79
81
|
|
|
80
82
|
def perform_refresh(binding, notifications)
|
|
83
|
+
active = owned_session_binding(binding)
|
|
84
|
+
return active.last if active.first != binding.first
|
|
81
85
|
raise Error::AuthenticationError, 'No refresh token' unless binding.last.refresh_token
|
|
82
86
|
|
|
83
|
-
SessionCredentials.validate_refresh_source(binding.last)
|
|
84
87
|
session = refreshed_session(binding, notifications)
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
88
|
+
unless binding[1].closing?
|
|
89
|
+
@client.store_session_if_current?(
|
|
90
|
+
session, binding.first, event: :token_refreshed, notifications: notifications
|
|
91
|
+
)
|
|
92
|
+
end
|
|
93
|
+
session
|
|
89
94
|
end
|
|
90
95
|
|
|
91
96
|
def refreshed_session(binding, notifications)
|
|
92
|
-
|
|
97
|
+
_, owner, current = binding
|
|
98
|
+
verified = owner.verified_pair?(current)
|
|
99
|
+
SessionCredentials.validate_refresh_source(current, verified: verified)
|
|
100
|
+
owner.verify_pair(nil)
|
|
101
|
+
session = parse_refresh_session(refresh_payload(binding, notifications))
|
|
102
|
+
verify_refreshed_session(owner, current, session)
|
|
103
|
+
session
|
|
104
|
+
rescue Error::RateLimitedError
|
|
105
|
+
owner.verify_pair(current) if verified
|
|
106
|
+
raise
|
|
93
107
|
end
|
|
94
108
|
|
|
95
|
-
def
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
raise Error::TransportError, INCOMPLETE_SESSION, cause: e
|
|
99
|
-
end
|
|
100
|
-
|
|
101
|
-
def refresh_response(refresh_token)
|
|
102
|
-
Transport.invoke do
|
|
103
|
-
@transport.auth_refresh(
|
|
104
|
-
authorization: @client.anon_token,
|
|
105
|
-
refresh_token: refresh_token
|
|
106
|
-
)
|
|
107
|
-
end
|
|
109
|
+
def verify_refreshed_session(owner, current, session)
|
|
110
|
+
SessionCredentials.validate_refresh(current, session)
|
|
111
|
+
owner.verify_pair(session)
|
|
108
112
|
end
|
|
109
113
|
|
|
110
114
|
def refresh_payload(binding, notifications)
|
|
111
115
|
Transport.body(refresh_response(binding.last.refresh_token), 200)
|
|
112
116
|
rescue Error::AuthenticationError
|
|
113
|
-
generation,
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
@rejected_refresh = [generation, lineage]
|
|
118
|
-
raise
|
|
119
|
-
rescue Error::VolcanoError
|
|
120
|
-
owned_session_binding(binding)
|
|
117
|
+
generation, owner, = binding
|
|
118
|
+
if !owner.closing? && @client.clear_session_if_current?(generation, notifications: notifications)
|
|
119
|
+
@rejected_refresh = [generation, owner]
|
|
120
|
+
end
|
|
121
121
|
raise
|
|
122
122
|
end
|
|
123
123
|
end
|
|
@@ -5,29 +5,32 @@ module Volcano
|
|
|
5
5
|
class Auth
|
|
6
6
|
def sign_out
|
|
7
7
|
binding = @client.capture_session_binding
|
|
8
|
-
return unless binding.last
|
|
8
|
+
return binding[1].wait_for_sign_out unless binding.last
|
|
9
9
|
|
|
10
10
|
notifications = []
|
|
11
|
-
|
|
11
|
+
binding[1].sign_out do |preceding, pending|
|
|
12
|
+
sign_out_captured(binding, preceding, pending, notifications)
|
|
13
|
+
end
|
|
12
14
|
ensure
|
|
13
15
|
notifications&.each(&:call)
|
|
14
16
|
end
|
|
15
17
|
|
|
16
18
|
private
|
|
17
19
|
|
|
18
|
-
def sign_out_captured(binding, notifications)
|
|
19
|
-
generation,
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
cleared = @client.clear_session_if_current?(generation, lineage:
|
|
20
|
+
def sign_out_captured(binding, preceding, pending, notifications)
|
|
21
|
+
generation, owner, = binding
|
|
22
|
+
current, refresh_error = revocation_credentials(binding, preceding)
|
|
23
|
+
error = revocation_error(current, owner, pending ? refresh_error : nil, joined: pending)
|
|
24
|
+
cleared = @client.clear_session_if_current?(generation, lineage: owner, notifications: notifications)
|
|
23
25
|
raise Error::SessionChangedError, cause: error unless cleared
|
|
24
26
|
|
|
25
27
|
raise error if error
|
|
26
28
|
end
|
|
27
29
|
|
|
28
|
-
def
|
|
29
|
-
|
|
30
|
-
|
|
30
|
+
def revocation_credentials(binding, preceding)
|
|
31
|
+
[binding[1].result(preceding) || binding.last, nil]
|
|
32
|
+
rescue Error::VolcanoError => e
|
|
33
|
+
[binding.last, e]
|
|
31
34
|
end
|
|
32
35
|
|
|
33
36
|
def logout_response(refresh_token)
|
|
@@ -39,9 +42,11 @@ module Volcano
|
|
|
39
42
|
end
|
|
40
43
|
end
|
|
41
44
|
|
|
42
|
-
def revocation_error(session)
|
|
45
|
+
def revocation_error(session, owner, refresh_error, joined:)
|
|
43
46
|
session_id = access_token_session_id(session.access_token)
|
|
44
|
-
|
|
47
|
+
verified = owner.verified_pair?(session)
|
|
48
|
+
return revoke_access_session(session, session_id, refresh_error, joined: joined) if session_id && !verified
|
|
49
|
+
return refresh_error if refresh_error && !verified
|
|
45
50
|
return unless session.refresh_token
|
|
46
51
|
|
|
47
52
|
Transport.body(logout_response(session.refresh_token), 204)
|
|
@@ -50,9 +55,10 @@ module Volcano
|
|
|
50
55
|
e
|
|
51
56
|
end
|
|
52
57
|
|
|
53
|
-
def revoke_access_session(session, session_id)
|
|
58
|
+
def revoke_access_session(session, session_id, refresh_error, joined:)
|
|
54
59
|
error = delete_session_error(session.access_token, session_id)
|
|
55
60
|
return error unless error&.status == 401 && session.refresh_token
|
|
61
|
+
return refresh_error || error if joined
|
|
56
62
|
|
|
57
63
|
refreshed = parse_refresh_session(Transport.body(refresh_response(session.refresh_token), 200))
|
|
58
64
|
SessionCredentials.validate_refresh(session, refreshed)
|
data/lib/volcano/auth_state.rb
CHANGED
|
@@ -27,7 +27,7 @@ module Volcano
|
|
|
27
27
|
def initialize(session: nil)
|
|
28
28
|
@mutex = Mutex.new
|
|
29
29
|
@generation = 0
|
|
30
|
-
@lineage =
|
|
30
|
+
@lineage = SessionOperations.new
|
|
31
31
|
@session = session
|
|
32
32
|
@callbacks = {}
|
|
33
33
|
@next_callback_id = 0
|
|
@@ -51,8 +51,9 @@ module Volcano
|
|
|
51
51
|
def store_if_current?(session, generation, event: :signed_in, notifications: nil, lineage: nil)
|
|
52
52
|
dispatch = @mutex.synchronize do
|
|
53
53
|
return false unless lineage.nil? ? generation == @generation : lineage == @lineage
|
|
54
|
+
return true if session.nil? && @session.nil?
|
|
54
55
|
|
|
55
|
-
enqueue_notification(replace(session, event), event, session)
|
|
56
|
+
enqueue_notification(replace(session, event, verified_pair: session), event, session)
|
|
56
57
|
end
|
|
57
58
|
return true unless dispatch
|
|
58
59
|
|
|
@@ -80,19 +81,19 @@ module Volcano
|
|
|
80
81
|
|
|
81
82
|
private
|
|
82
83
|
|
|
83
|
-
def replace(session, event)
|
|
84
|
+
def replace(session, event, verified_pair: nil)
|
|
84
85
|
SessionCredentials.validate_refresh(@session, session) if event == :token_refreshed
|
|
85
86
|
@session = session
|
|
86
87
|
@generation += 1
|
|
87
|
-
@lineage
|
|
88
|
+
@lineage.clear_local_credentials unless session
|
|
89
|
+
@lineage = SessionOperations.new(verified_pair) if session && event != :token_refreshed
|
|
88
90
|
@callbacks.keys
|
|
89
91
|
end
|
|
90
92
|
|
|
91
93
|
def register(callback)
|
|
92
|
-
callback_id = @next_callback_id
|
|
93
94
|
@next_callback_id += 1
|
|
94
|
-
@callbacks[
|
|
95
|
-
[
|
|
95
|
+
@callbacks[@next_callback_id] = callback
|
|
96
|
+
[@next_callback_id, @session]
|
|
96
97
|
end
|
|
97
98
|
|
|
98
99
|
def unsubscribe(callback_id)
|
|
@@ -103,8 +103,8 @@ module Volcano
|
|
|
103
103
|
def postgres_stop?(request) = request.equal?(STOP)
|
|
104
104
|
|
|
105
105
|
def current_postgres_request?(request)
|
|
106
|
-
_, lineage, = @realtime.__send__(:capture_session_binding)
|
|
107
|
-
@subscribed && request.subscription_epoch == @postgres_epoch &&
|
|
106
|
+
_, lineage, session = @realtime.__send__(:capture_session_binding)
|
|
107
|
+
@subscribed && session && request.subscription_epoch == @postgres_epoch &&
|
|
108
108
|
request.session_lineage == lineage
|
|
109
109
|
end
|
|
110
110
|
end
|
data/lib/volcano/session.rb
CHANGED
|
@@ -28,16 +28,15 @@ module Volcano
|
|
|
28
28
|
end
|
|
29
29
|
private_class_method :credential
|
|
30
30
|
|
|
31
|
-
def self.validate_refresh_source(current)
|
|
32
|
-
return if
|
|
31
|
+
def self.validate_refresh_source(current, verified: false)
|
|
32
|
+
return if verified || session_id(current.access_token)
|
|
33
33
|
|
|
34
|
-
raise Error::AuthenticationError, 'Cannot refresh
|
|
34
|
+
raise Error::AuthenticationError, 'Cannot refresh supplied credentials without a session identifier'
|
|
35
35
|
end
|
|
36
36
|
|
|
37
37
|
def self.validate_refresh(current, refreshed)
|
|
38
38
|
return unless current
|
|
39
39
|
|
|
40
|
-
validate_refresh_source(current)
|
|
41
40
|
expected = session_id(current.access_token)
|
|
42
41
|
if expected && expected != session_id(refreshed&.access_token)
|
|
43
42
|
raise Error::AuthenticationError, 'Refreshed credentials belong to a different server session'
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'English'
|
|
4
|
+
|
|
5
|
+
module Volcano
|
|
6
|
+
# Keeps refresh and revocation attached to one explicit sign-in/adoption.
|
|
7
|
+
class SessionOperations
|
|
8
|
+
Outcome = Struct.new(:done, :value, :error)
|
|
9
|
+
private_constant :Outcome
|
|
10
|
+
|
|
11
|
+
def initialize(verified = nil)
|
|
12
|
+
@mutex = Mutex.new
|
|
13
|
+
@condition = ConditionVariable.new
|
|
14
|
+
@refresh = nil
|
|
15
|
+
@sign_out = nil
|
|
16
|
+
@locally_cleared = false
|
|
17
|
+
verify_pair(verified)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def verify_pair(session)
|
|
21
|
+
@mutex.synchronize do
|
|
22
|
+
@verified_pair = session && [session.access_token, session.refresh_token].freeze unless @locally_cleared
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def clear_local_credentials
|
|
27
|
+
@mutex.synchronize do
|
|
28
|
+
return if @sign_out
|
|
29
|
+
|
|
30
|
+
@locally_cleared = true
|
|
31
|
+
@verified_pair = nil
|
|
32
|
+
@refresh = nil if @refresh&.done
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def verified_pair?(session)
|
|
37
|
+
@mutex.synchronize { session.refresh_token && @verified_pair == [session.access_token, session.refresh_token] }
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def closing? = @mutex.synchronize { !@sign_out.nil? }
|
|
41
|
+
|
|
42
|
+
def refresh(&)
|
|
43
|
+
operation, owner = @mutex.synchronize do
|
|
44
|
+
raise Error::SessionChangedError if @sign_out || @locally_cleared
|
|
45
|
+
next [@refresh, false] if @refresh && !@refresh.done
|
|
46
|
+
|
|
47
|
+
@refresh = Outcome.new(false)
|
|
48
|
+
[@refresh, true]
|
|
49
|
+
end
|
|
50
|
+
execute(operation, owner, &)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def sign_out
|
|
54
|
+
operation, owner, preceding, pending = @mutex.synchronize do
|
|
55
|
+
first = @sign_out.nil?
|
|
56
|
+
@sign_out ||= Outcome.new(false)
|
|
57
|
+
[@sign_out, first, @refresh, @refresh && !@refresh.done]
|
|
58
|
+
end
|
|
59
|
+
execute(operation, owner) do
|
|
60
|
+
yield(preceding, pending)
|
|
61
|
+
ensure
|
|
62
|
+
@mutex.synchronize { @verified_pair = @refresh = nil }
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def wait_for_sign_out
|
|
67
|
+
operation = @mutex.synchronize { @sign_out unless @sign_out&.done }
|
|
68
|
+
result(operation)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def result(operation)
|
|
72
|
+
return unless operation
|
|
73
|
+
|
|
74
|
+
@mutex.synchronize { @condition.wait(@mutex) until operation.done }
|
|
75
|
+
raise operation.error if operation.error
|
|
76
|
+
|
|
77
|
+
operation.value
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
private
|
|
81
|
+
|
|
82
|
+
def execute(operation, owner)
|
|
83
|
+
return result(operation) unless owner
|
|
84
|
+
|
|
85
|
+
completed = false
|
|
86
|
+
begin
|
|
87
|
+
value = yield
|
|
88
|
+
completed = true
|
|
89
|
+
ensure
|
|
90
|
+
complete(operation, value, completed ? nil : $ERROR_INFO)
|
|
91
|
+
end
|
|
92
|
+
value
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def complete(operation, value, error)
|
|
96
|
+
@mutex.synchronize do
|
|
97
|
+
operation.value = value
|
|
98
|
+
operation.error = error
|
|
99
|
+
operation.done = true
|
|
100
|
+
@refresh = nil if @locally_cleared && @refresh == operation
|
|
101
|
+
@condition.broadcast
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
data/lib/volcano/version.rb
CHANGED
data/lib/volcano.rb
CHANGED
|
@@ -7,6 +7,7 @@ require_relative 'volcano/session'
|
|
|
7
7
|
require_relative 'volcano/function_response'
|
|
8
8
|
require_relative 'volcano/log_responses'
|
|
9
9
|
require_relative 'volcano/storage_models'
|
|
10
|
+
require_relative 'volcano/session_operations'
|
|
10
11
|
require_relative 'volcano/auth_state'
|
|
11
12
|
require_relative 'volcano/errors'
|
|
12
13
|
require_relative 'volcano/redaction'
|
|
@@ -53,7 +54,7 @@ require_relative 'volcano/client'
|
|
|
53
54
|
# Public namespace for the Volcano Ruby SDK.
|
|
54
55
|
module Volcano
|
|
55
56
|
private_constant :GeneratedTransport, :LockAutoRenewal, :LockLeaseClock, :LockRenewer, :LockSession,
|
|
56
|
-
:Redaction
|
|
57
|
+
:Redaction, :SessionOperations
|
|
57
58
|
|
|
58
59
|
Realtime.private_constant :Protocol, :ProtocolDispatch, :ProtocolPublicationDispatch, :ProtocolRecovery,
|
|
59
60
|
:ProtocolRecoveryDispatch, :ProtocolRecoveryPosition
|
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.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Kong
|
|
@@ -576,6 +576,7 @@ files:
|
|
|
576
576
|
- lib/volcano/realtime/reconnect.rb
|
|
577
577
|
- lib/volcano/redaction.rb
|
|
578
578
|
- lib/volcano/session.rb
|
|
579
|
+
- lib/volcano/session_operations.rb
|
|
579
580
|
- lib/volcano/sign_up_result.rb
|
|
580
581
|
- lib/volcano/storage.rb
|
|
581
582
|
- lib/volcano/storage_models.rb
|