volcano-sdk 0.9.0 → 0.9.2

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: bf20d0cdb7959be9f989d8b1f1e5f88b53a065a7b899b42f7c3a09af5d1327c6
4
- data.tar.gz: 3350d3a721b2d25b67aaf0a76a8cab45cb1ca26c69d386c600789bb60589cfca
3
+ metadata.gz: 92b94c10570347e736103b57ee437cf54d72ca334a8e87f3528087655fd80e78
4
+ data.tar.gz: 68c2e41eac585bc12ee2086c5574431136de600236737e820224b88579ca91ca
5
5
  SHA512:
6
- metadata.gz: 94a0671011cb9256c3bf768fe05fbea0e1d49fb8c3d29adb694cc5a01eb83e80fb0d978b4aa1c83919a3b38ca7cdd6069a7af5b41eaefa743622bdc745f68083
7
- data.tar.gz: 756140f1665c4f360839a37da9ad15de033cf45373bee40a57b7b109d8d0830094af36974923841d538722869bd9f7fe987c7e3f50a55d5b5f9e657d6106d84f
6
+ metadata.gz: 76a68bc5ba09715be91401c60e8b7183f75450907e51d9b9fc1a0437d262538de69c8b22ab78f184ff0b3cb57f267a8bf45efd8f29c8669b2c82a0a655a95806
7
+ data.tar.gz: 1b055512555848049b55205e62fb9b60b126f333469d3c9eef8d09712201136c03dc70e93fe2cb2f67b388f6902f5dd4df8b76da7618c54ccfb9d9bb2ac3625e
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
- `sign_out` uses the access-token session when available, even if a refresh token was supplied.
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. A concurrent
447
- refresh of the same session is cleared; a separate sign-in or adoption remains current.
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
@@ -36,6 +33,15 @@ module Volcano
36
33
 
37
34
  private
38
35
 
36
+ def session_payload(status, decode: nil)
37
+ binding = @client.capture_session_binding
38
+ request = yield
39
+ payload = Transport.body(session_request(binding: binding, &request), status)
40
+ result = decode ? send(decode, payload) : payload
41
+ owned_session_binding(binding)
42
+ result
43
+ end
44
+
39
45
  def sign_in_for_generation(email:, password:, generation:)
40
46
  raise Error::SessionChangedError unless @client.capture_session.first == generation
41
47
 
@@ -56,6 +62,15 @@ module Volcano
56
62
  end
57
63
  end
58
64
 
65
+ def refresh_response(refresh_token)
66
+ Transport.invoke do
67
+ @transport.auth_refresh(
68
+ authorization: @client.anon_token,
69
+ refresh_token: refresh_token
70
+ )
71
+ end
72
+ end
73
+
59
74
  def build_session(payload)
60
75
  owned_session(
61
76
  access_token: payload.fetch('access_token'),
@@ -65,6 +80,12 @@ module Volcano
65
80
  )
66
81
  end
67
82
 
83
+ def parse_refresh_session(payload)
84
+ owned_complete_session(build_session(payload))
85
+ rescue KeyError, TypeError, NoMethodError, ArgumentError => e
86
+ raise Error::TransportError, INCOMPLETE_SESSION, cause: e
87
+ end
88
+
68
89
  def complete_session?(session)
69
90
  return false unless session.is_a?(Session)
70
91
 
@@ -7,22 +7,15 @@ module Volcano
7
7
  private_constant :INVALID_EMAIL_CHANGE_RESULT
8
8
 
9
9
  def request_email_change(new_email:)
10
- generation, current = @client.capture_session
11
- raise Error::AuthenticationError, 'No active session' unless current
12
-
13
- payload = Transport.body(email_change_response(current.access_token, new_email), 200)
14
- result = email_change_result(payload)
15
- raise Error::SessionChangedError unless @client.capture_session.first == generation
16
-
17
- result
10
+ session_payload(200, decode: :email_change_result) do
11
+ request_email = new_email.dup.freeze
12
+ ->(token) { email_change_response(token, request_email) }
13
+ end
18
14
  end
19
15
 
20
16
  def cancel_email_change
21
- generation, current = @client.capture_session
22
- raise Error::AuthenticationError, 'No active session' unless current
23
-
24
- Transport.body(cancel_email_change_response(current.access_token), 200)
25
- raise Error::SessionChangedError unless @client.capture_session.first == generation
17
+ session_payload(200) { ->(token) { cancel_email_change_response(token) } }
18
+ nil
26
19
  end
27
20
 
28
21
  def confirm_email_change(token:)
@@ -7,35 +7,22 @@ module Volcano
7
7
  private_constant :OAUTH_PROVIDERS
8
8
 
9
9
  def list_linked_oauth_providers
10
- generation, current = @client.capture_session
11
- raise Error::AuthenticationError, 'No active session' unless current
12
-
13
- body = Transport.body(list_oauth_providers_response(current.access_token), 200)
14
- result = linked_oauth_providers(body)
15
- raise Error::SessionChangedError unless @client.capture_session.first == generation
16
-
17
- result
10
+ session_payload(200, decode: :linked_oauth_providers) { ->(token) { list_oauth_providers_response(token) } }
18
11
  end
19
12
 
20
13
  def link_oauth_provider(provider)
21
- provider_name = oauth_provider_name(provider)
22
- generation, current = @client.capture_session
23
- raise Error::AuthenticationError, 'No active session' unless current
24
-
25
- body = Transport.body(link_oauth_provider_response(current.access_token, provider_name), 200)
26
- result = oauth_authorization_url(body)
27
- raise Error::SessionChangedError unless @client.capture_session.first == generation
28
-
29
- result
14
+ session_payload(200, decode: :oauth_authorization_url) do
15
+ provider_name = oauth_provider_name(provider)
16
+ ->(token) { link_oauth_provider_response(token, provider_name) }
17
+ end
30
18
  end
31
19
 
32
20
  def unlink_oauth_provider(provider)
33
- provider_name = oauth_provider_name(provider)
34
- generation, current = @client.capture_session
35
- raise Error::AuthenticationError, 'No active session' unless current
36
-
37
- Transport.body(unlink_oauth_provider_response(current.access_token, provider_name), 204)
38
- raise Error::SessionChangedError unless @client.capture_session.first == generation
21
+ session_payload(204) do
22
+ provider_name = oauth_provider_name(provider)
23
+ ->(token) { unlink_oauth_provider_response(token, provider_name) }
24
+ end
25
+ nil
39
26
  end
40
27
 
41
28
  private
@@ -59,7 +46,7 @@ module Volcano
59
46
  end
60
47
 
61
48
  def oauth_provider_name(provider)
62
- return provider if provider.is_a?(String) && OAUTH_PROVIDERS.include?(provider)
49
+ return provider.dup.freeze if provider.is_a?(String) && OAUTH_PROVIDERS.include?(provider)
63
50
 
64
51
  raise ArgumentError, 'Unsupported OAuth provider'
65
52
  end
@@ -4,17 +4,12 @@ module Volcano
4
4
  # Fixed-host OAuth-provider API proxy behavior for the auth facade.
5
5
  class Auth
6
6
  def call_oauth_api(provider, endpoint:, method: 'GET', body: nil)
7
- provider_name = oauth_provider_name(provider)
8
- generation, current = @client.capture_session
9
- raise Error::AuthenticationError, 'No active session' unless current
10
-
11
- response = oauth_api_response(
12
- current.access_token, provider_name, endpoint: endpoint, method: method, body: body
13
- )
14
- result = oauth_api_data(Transport.body(response, 200))
15
- raise Error::SessionChangedError unless @client.capture_session.first == generation
16
-
17
- result
7
+ session_payload(200, decode: :oauth_api_data) do
8
+ provider_name = oauth_provider_name(provider)
9
+ request = { endpoint: endpoint.dup.freeze, method: method.dup.freeze,
10
+ body: body.nil? ? nil : JSON.parse(JSON.generate(body), freeze: true) }
11
+ ->(token) { oauth_api_response(token, provider_name, **request) }
12
+ end
18
13
  end
19
14
 
20
15
  private
@@ -4,27 +4,17 @@ module Volcano
4
4
  # Server-held OAuth-provider token metadata behavior for the auth facade.
5
5
  class Auth
6
6
  def get_oauth_provider_token(provider)
7
- provider_name = oauth_provider_name(provider)
8
- generation, current = @client.capture_session
9
- raise Error::AuthenticationError, 'No active session' unless current
10
-
11
- body = Transport.body(oauth_provider_token_response(current.access_token, provider_name), 200)
12
- result = oauth_provider_token_status(body)
13
- raise Error::SessionChangedError unless @client.capture_session.first == generation
14
-
15
- result
7
+ session_payload(200, decode: :oauth_provider_token_status) do
8
+ provider_name = oauth_provider_name(provider)
9
+ ->(token) { oauth_provider_token_response(token, provider_name) }
10
+ end
16
11
  end
17
12
 
18
13
  def refresh_oauth_provider_token(provider)
19
- provider_name = oauth_provider_name(provider)
20
- generation, current = @client.capture_session
21
- raise Error::AuthenticationError, 'No active session' unless current
22
-
23
- body = Transport.body(refresh_oauth_provider_token_response(current.access_token, provider_name), 200)
24
- result = oauth_provider_token_status(body)
25
- raise Error::SessionChangedError unless @client.capture_session.first == generation
26
-
27
- result
14
+ session_payload(200, decode: :oauth_provider_token_status) do
15
+ provider_name = oauth_provider_name(provider)
16
+ ->(token) { refresh_oauth_provider_token_response(token, provider_name) }
17
+ end
28
18
  end
29
19
 
30
20
  private
@@ -52,10 +52,12 @@ module Volcano
52
52
 
53
53
  def refresh_with_notifications(binding)
54
54
  notifications = []
55
- @refresh_lock.synchronize do
56
- active_generation, = owned_session_binding(binding)
57
- perform_refresh(binding, notifications) if active_generation == binding.first
58
- end
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 == [generation + 1, lineage + 1, nil]
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
- stored = @client.store_session_if_current?(
86
- session, binding.first, event: :token_refreshed, notifications: notifications
87
- )
88
- raise Error::SessionChangedError unless stored
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
- parse_refresh_session(refresh_payload(binding, notifications))
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 parse_refresh_session(payload)
96
- owned_complete_session(build_session(payload))
97
- rescue KeyError, TypeError, NoMethodError, ArgumentError => e
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, lineage, = binding
114
- cleared = @client.clear_session_if_current?(generation, notifications: notifications)
115
- raise Error::SessionChangedError unless cleared
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
@@ -47,31 +47,22 @@ module Volcano
47
47
  include SessionPageMapping
48
48
 
49
49
  def list_sessions(page: 1, limit: 20)
50
- generation, current = @client.capture_session
51
- raise Error::AuthenticationError, 'No active session' unless current
52
-
53
- body = Transport.body(list_sessions_response(current.access_token, page, limit), 200)
54
- result = session_page(body)
55
- raise Error::SessionChangedError unless @client.capture_session.first == generation
56
-
57
- result
50
+ session_payload(200, decode: :session_page) { ->(token) { list_sessions_response(token, page, limit) } }
58
51
  end
59
52
 
60
53
  def delete_all_other_sessions
61
- generation, current = @client.capture_session
62
- raise Error::AuthenticationError, 'No active session' unless current
63
-
64
- Transport.body(delete_all_other_sessions_response(current.access_token), 204)
65
- raise Error::SessionChangedError unless @client.capture_session.first == generation
54
+ session_payload(204) { ->(token) { delete_all_other_sessions_response(token) } }
55
+ nil
66
56
  end
67
57
 
68
58
  def delete_session(session_id)
69
- generation, current = @client.capture_session
70
- raise Error::AuthenticationError, 'No active session' unless current
59
+ binding = @client.capture_session_binding
60
+ raise Error::AuthenticationError, 'No active session' unless binding.last
71
61
 
72
- deletes_current = same_session_id?(current.access_token, session_id)
73
- error = delete_session_error(current.access_token, session_id)
74
- current_unchanged = session_unchanged_after_deletion?(deletes_current, error, generation)
62
+ request_id = session_id.dup.freeze
63
+ deletes_current = same_session_id?(binding.last.access_token, request_id)
64
+ error = delete_bound_session_error(binding, request_id)
65
+ current_unchanged = session_unchanged_after_deletion?(deletes_current, error, binding)
75
66
  raise Error::SessionChangedError, cause: error unless current_unchanged
76
67
 
77
68
  raise error if error
@@ -111,11 +102,20 @@ module Volcano
111
102
  e
112
103
  end
113
104
 
114
- def session_unchanged_after_deletion?(deletes_current, error, generation)
105
+ def delete_bound_session_error(binding, session_id)
106
+ response = session_request(binding: binding) { |token| delete_session_response(token, session_id) }
107
+ Transport.body(response, 204)
108
+ nil
109
+ rescue Error::VolcanoError => e
110
+ e
111
+ end
112
+
113
+ def session_unchanged_after_deletion?(deletes_current, error, binding)
115
114
  uncertain = error.nil? || error.is_a?(Error::TransportError)
116
- return @client.clear_session_if_current?(generation) if deletes_current && uncertain
115
+ return @client.clear_session_if_current?(binding.first, lineage: binding[1]) if deletes_current && uncertain
117
116
 
118
- @client.capture_session.first == generation
117
+ active = @client.capture_session_binding
118
+ (active.last && active[1] == binding[1]) || (error && rejected_refresh?(binding, active))
119
119
  end
120
120
 
121
121
  def same_session_id?(access_token, session_id)
@@ -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
- @refresh_lock.synchronize { sign_out_captured(binding, notifications) }
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, lineage, current = revocation_binding(binding)
20
- error = revocation_error(current)
21
- lineage = nil unless access_token_session_id(current.access_token)
22
- cleared = @client.clear_session_if_current?(generation, lineage: lineage, notifications: notifications)
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 revocation_binding(binding)
29
- active = @client.capture_session_binding
30
- active.last && active[1] == binding[1] ? active : binding
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
- return revoke_access_session(session, session_id) if session_id
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)
@@ -27,7 +27,7 @@ module Volcano
27
27
  def initialize(session: nil)
28
28
  @mutex = Mutex.new
29
29
  @generation = 0
30
- @lineage = 0
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 += 1 unless event == :token_refreshed
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[callback_id] = callback
95
- [callback_id, @session]
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
@@ -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 current.user_id || session_id(current.access_token)
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 unknown identity without a session identifier'
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Volcano
4
- VERSION = '0.9.0'
4
+ VERSION = '0.9.2'
5
5
  end
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.0
4
+ version: 0.9.2
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