volcano-sdk 0.28.0 → 0.30.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 +4 -4
- data/lib/volcano/auth.rb +7 -0
- data/lib/volcano/auth_email_change.rb +1 -1
- data/lib/volcano/auth_get_user.rb +36 -77
- data/lib/volcano/auth_notification_dispatch.rb +16 -0
- data/lib/volcano/auth_oauth.rb +4 -0
- data/lib/volcano/auth_oauth_api.rb +8 -3
- data/lib/volcano/auth_oauth_sign_in.rb +1 -1
- data/lib/volcano/auth_oauth_token.rb +2 -0
- data/lib/volcano/auth_profile_fields.rb +98 -0
- data/lib/volcano/auth_refresh.rb +29 -32
- data/lib/volcano/auth_session_binding.rb +25 -0
- data/lib/volcano/auth_sessions.rb +9 -45
- data/lib/volcano/auth_sign_out.rb +22 -14
- data/lib/volcano/auth_state.rb +3 -2
- data/lib/volcano/auth_state_notifications.rb +7 -4
- data/lib/volcano/auth_update_user.rb +1 -1
- data/lib/volcano/generated_transport.rb +6 -3
- data/lib/volcano/lock_response.rb +48 -0
- data/lib/volcano/locks.rb +57 -33
- data/lib/volcano/session.rb +0 -73
- data/lib/volcano/session_credentials.rb +82 -0
- data/lib/volcano/session_operations.rb +13 -6
- data/lib/volcano/session_page_mapping.rb +89 -0
- data/lib/volcano/version.rb +1 -1
- data/lib/volcano.rb +14 -7
- data/sig/auth_oauth.rbs +16 -0
- data/sig/auth_profile.rbs +10 -0
- data/sig/auth_sessions.rbs +7 -0
- data/sig/auth_subscription.rbs +6 -0
- data/sig/session.rbs +1 -1
- metadata +11 -1
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
module Volcano
|
|
4
4
|
# Queues auth events under the state mutex and invokes subscribers outside it.
|
|
5
5
|
module AuthStateNotifications
|
|
6
|
-
|
|
7
|
-
private_constant :
|
|
6
|
+
CALLBACK_FAILURE = Exception
|
|
7
|
+
private_constant :CALLBACK_FAILURE
|
|
8
8
|
|
|
9
9
|
private
|
|
10
10
|
|
|
@@ -26,12 +26,14 @@ module Volcano
|
|
|
26
26
|
end
|
|
27
27
|
|
|
28
28
|
def drain_notifications
|
|
29
|
+
# @type var failure: Exception?
|
|
29
30
|
failure = nil
|
|
30
31
|
loop do
|
|
31
32
|
notification = next_notification
|
|
32
33
|
break unless notification
|
|
33
34
|
|
|
34
|
-
|
|
35
|
+
callback_ids, event, session = notification
|
|
36
|
+
current_failure = notify(callback_ids, event, session)
|
|
35
37
|
failure ||= current_failure
|
|
36
38
|
end
|
|
37
39
|
raise failure if failure
|
|
@@ -48,12 +50,13 @@ module Volcano
|
|
|
48
50
|
end
|
|
49
51
|
|
|
50
52
|
def notify(callback_ids, event, session)
|
|
53
|
+
# @type var failure: Exception?
|
|
51
54
|
failure = nil
|
|
52
55
|
callback_ids.each do |callback_id|
|
|
53
56
|
@mutex.synchronize { @callbacks[callback_id] }&.call(event, session)
|
|
54
57
|
rescue StandardError => e
|
|
55
58
|
Warning.warn("Volcano auth-state callback failed (#{e.class})\n")
|
|
56
|
-
rescue
|
|
59
|
+
rescue CALLBACK_FAILURE => e
|
|
57
60
|
unsubscribe(callback_id)
|
|
58
61
|
failure ||= e
|
|
59
62
|
end
|
|
@@ -52,7 +52,9 @@ module Volcano
|
|
|
52
52
|
invoke do
|
|
53
53
|
apis = @api_factory.call(authorization)
|
|
54
54
|
body = Generated::ProjectLockLeaseRequest.new(ttl_seconds: ttl)
|
|
55
|
-
result = apis.locks.acquire_project_lock_with_http_info(
|
|
55
|
+
result = apis.locks.acquire_project_lock_with_http_info(
|
|
56
|
+
key, token, request_id, body, debug_return_type: 'Object'
|
|
57
|
+
)
|
|
56
58
|
data, status, headers = result
|
|
57
59
|
response(data, status, headers)
|
|
58
60
|
end
|
|
@@ -63,7 +65,8 @@ module Volcano
|
|
|
63
65
|
apis = @api_factory.call(authorization)
|
|
64
66
|
data, status, headers = apis.locks.get_project_lock_with_http_info(
|
|
65
67
|
key,
|
|
66
|
-
request_id
|
|
68
|
+
request_id,
|
|
69
|
+
debug_return_type: 'Object'
|
|
67
70
|
)
|
|
68
71
|
response(data, status, headers)
|
|
69
72
|
end
|
|
@@ -85,7 +88,7 @@ module Volcano
|
|
|
85
88
|
apis = @api_factory.call(authorization)
|
|
86
89
|
body = Generated::ProjectLockLeaseRequest.new(ttl_seconds: ttl)
|
|
87
90
|
result = apis.locks.renew_project_lock_with_http_info(
|
|
88
|
-
key, token, request_id, body
|
|
91
|
+
key, token, request_id, body, debug_return_type: 'Object'
|
|
89
92
|
)
|
|
90
93
|
data, status, headers = result
|
|
91
94
|
response(data, status, headers)
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'time'
|
|
4
|
+
|
|
5
|
+
module Volcano
|
|
6
|
+
# Validates lock response fields before creating public records.
|
|
7
|
+
module LockResponse
|
|
8
|
+
private
|
|
9
|
+
|
|
10
|
+
def parse_time(value)
|
|
11
|
+
return Time.iso8601(value) if value.is_a?(String)
|
|
12
|
+
return value if value.nil? || value.is_a?(Time)
|
|
13
|
+
|
|
14
|
+
raise Error::TransportError, 'invalid lock expiry'
|
|
15
|
+
rescue ArgumentError => e
|
|
16
|
+
raise Error::TransportError, 'invalid lock expiry', cause: e
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def required_time(value)
|
|
20
|
+
parse_time(value) || raise(Error::TransportError, 'invalid lock expiry')
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def lock_payload(body)
|
|
24
|
+
raise Error::TransportError, 'invalid lock response' unless body.is_a?(Hash)
|
|
25
|
+
|
|
26
|
+
body
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def lock_held(value)
|
|
30
|
+
return true if value == true
|
|
31
|
+
return false if value == false
|
|
32
|
+
|
|
33
|
+
raise Error::TransportError, 'invalid lock held flag'
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def required_fencing_token(value)
|
|
37
|
+
raise Error::TransportError, 'invalid lock fencing token' unless value.is_a?(Integer)
|
|
38
|
+
|
|
39
|
+
value
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def optional_fencing_token(value)
|
|
43
|
+
return if value.nil?
|
|
44
|
+
|
|
45
|
+
required_fencing_token(value)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
data/lib/volcano/locks.rb
CHANGED
|
@@ -7,6 +7,7 @@ module Volcano
|
|
|
7
7
|
# Acquires and releases project-scoped distributed locks.
|
|
8
8
|
class Locks
|
|
9
9
|
include LockAutoRenewal
|
|
10
|
+
include LockResponse
|
|
10
11
|
|
|
11
12
|
def initialize(client, transport)
|
|
12
13
|
@client = client
|
|
@@ -14,80 +15,103 @@ module Volcano
|
|
|
14
15
|
end
|
|
15
16
|
|
|
16
17
|
def get(key, request_id: nil)
|
|
17
|
-
payload =
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
)
|
|
18
|
+
payload = lock_payload(perform_request(200) do
|
|
19
|
+
@transport.get_project_lock(
|
|
20
|
+
authorization: @client.service_token, key: key, request_id: request_uuid(request_id, 'request_id')
|
|
21
|
+
)
|
|
22
|
+
end)
|
|
23
|
+
build_state(payload)
|
|
23
24
|
end
|
|
24
25
|
|
|
25
26
|
def acquire(key, ttl:, token: nil, request_id: nil)
|
|
26
27
|
validate_ttl(ttl)
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
payload = acquire_payload(
|
|
32
|
-
|
|
28
|
+
authorization = @client.service_token.dup.freeze
|
|
29
|
+
owned_key = key.dup.freeze
|
|
30
|
+
owned_token = request_uuid(token, 'token')
|
|
31
|
+
owned_request_id = request_uuid(request_id, 'request_id')
|
|
32
|
+
payload = acquire_payload(
|
|
33
|
+
authorization: authorization, key: owned_key, ttl: ttl, token: owned_token, request_id: owned_request_id
|
|
34
|
+
)
|
|
35
|
+
build_lease(owned_key, owned_token, payload)
|
|
33
36
|
end
|
|
34
37
|
|
|
35
38
|
def renew(key, lease, ttl:, request_id: nil)
|
|
36
39
|
validate_ttl(ttl)
|
|
37
|
-
payload =
|
|
38
|
-
|
|
40
|
+
payload = lock_payload(perform_request(200) do
|
|
41
|
+
@transport.renew_project_lock(
|
|
42
|
+
authorization: @client.service_token, key: key, ttl: ttl,
|
|
43
|
+
token: lease.token, request_id: request_uuid(request_id, 'request_id')
|
|
44
|
+
)
|
|
45
|
+
end)
|
|
39
46
|
build_lease(key, lease.token, payload)
|
|
40
47
|
end
|
|
41
48
|
|
|
42
49
|
def release(key, lease, request_id: nil)
|
|
43
|
-
|
|
50
|
+
perform_request(204) do
|
|
51
|
+
@transport.release_project_lock(
|
|
52
|
+
authorization: @client.service_token, key: key, token: lease.token,
|
|
53
|
+
request_id: request_uuid(request_id, 'request_id')
|
|
54
|
+
)
|
|
55
|
+
end
|
|
44
56
|
nil
|
|
45
57
|
end
|
|
46
58
|
|
|
47
59
|
def force_release(key, request_id: nil)
|
|
48
|
-
|
|
60
|
+
perform_request(204) do
|
|
61
|
+
@transport.force_release_project_lock(
|
|
62
|
+
authorization: @client.service_token, key: key, request_id: request_uuid(request_id, 'request_id')
|
|
63
|
+
)
|
|
64
|
+
end
|
|
49
65
|
nil
|
|
50
66
|
end
|
|
51
67
|
|
|
52
68
|
private
|
|
53
69
|
|
|
54
|
-
def acquire_payload(
|
|
55
|
-
perform_request(
|
|
70
|
+
def acquire_payload(authorization:, key:, ttl:, token:, request_id:)
|
|
71
|
+
lock_payload(perform_request(201) do
|
|
72
|
+
@transport.acquire_project_lock(authorization: authorization, key: key, ttl: ttl,
|
|
73
|
+
token: token, request_id: request_id)
|
|
74
|
+
end)
|
|
56
75
|
rescue Error::TransportError, Error::ServerError => e
|
|
57
76
|
raise unless e.status.nil? || e.status == 503
|
|
58
77
|
|
|
59
|
-
perform_request(
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
parameters[:authorization] = @client.service_token
|
|
64
|
-
parameters[:request_id] = request_uuid(parameters[:request_id], 'request_id')
|
|
65
|
-
perform_request(operation, status, parameters)
|
|
78
|
+
lock_payload(perform_request(201) do
|
|
79
|
+
@transport.acquire_project_lock(authorization: authorization, key: key, ttl: ttl,
|
|
80
|
+
token: token, request_id: request_id)
|
|
81
|
+
end)
|
|
66
82
|
end
|
|
67
83
|
|
|
68
|
-
def perform_request(
|
|
69
|
-
response = Transport.invoke
|
|
84
|
+
def perform_request(status, &)
|
|
85
|
+
response = Transport.invoke(&)
|
|
70
86
|
Transport.body(response, status)
|
|
71
87
|
end
|
|
72
88
|
|
|
73
89
|
def request_uuid(value, name)
|
|
74
90
|
return SecureRandom.uuid.freeze if value.nil?
|
|
75
91
|
|
|
76
|
-
|
|
77
|
-
|
|
92
|
+
unless value.is_a?(String) && value.match?(/\A[0-9a-f]{8}(?:-[0-9a-f]{4}){3}-[0-9a-f]{12}\z/i)
|
|
93
|
+
raise ArgumentError, "#{name} must be a UUID string"
|
|
94
|
+
end
|
|
78
95
|
|
|
79
96
|
value.dup.freeze
|
|
80
97
|
end
|
|
81
98
|
|
|
82
99
|
def build_lease(key, token, payload)
|
|
83
100
|
LockLease.new(
|
|
84
|
-
key: key, token: token, expires_at:
|
|
85
|
-
fencing_token: payload['fencing_token']
|
|
101
|
+
key: key, token: token, expires_at: required_time(payload['expires_at']),
|
|
102
|
+
fencing_token: required_fencing_token(payload['fencing_token'])
|
|
86
103
|
)
|
|
87
104
|
end
|
|
88
105
|
|
|
89
|
-
def
|
|
90
|
-
|
|
106
|
+
def build_state(payload)
|
|
107
|
+
held = lock_held(payload['held'])
|
|
108
|
+
expiry = held ? required_time(payload['expires_at']) : parse_time(payload['expires_at'])
|
|
109
|
+
fencing = if held
|
|
110
|
+
required_fencing_token(payload['fencing_token'])
|
|
111
|
+
else
|
|
112
|
+
optional_fencing_token(payload['fencing_token'])
|
|
113
|
+
end
|
|
114
|
+
LockState.new(held: held, expires_at: expiry, fencing_token: fencing)
|
|
91
115
|
end
|
|
92
116
|
end
|
|
93
117
|
end
|
data/lib/volcano/session.rb
CHANGED
|
@@ -9,79 +9,6 @@ module Volcano
|
|
|
9
9
|
end
|
|
10
10
|
private_constant :SESSION_USER_CODER
|
|
11
11
|
|
|
12
|
-
# Owns credential snapshots and preserves validated identity during refresh.
|
|
13
|
-
module SessionCredentials
|
|
14
|
-
def self.build(access_token, refresh_token)
|
|
15
|
-
raise ArgumentError, 'refresh_token requires access_token' if access_token.nil? && !refresh_token.nil?
|
|
16
|
-
return if access_token.nil?
|
|
17
|
-
|
|
18
|
-
Session.new(access_token: credential(access_token, :access_token),
|
|
19
|
-
refresh_token: credential(refresh_token, :refresh_token))
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
def self.credential(value, name)
|
|
23
|
-
return if value.nil?
|
|
24
|
-
|
|
25
|
-
raise ArgumentError, "#{name} must be a non-empty string" unless value.is_a?(String) && !value.strip.empty?
|
|
26
|
-
|
|
27
|
-
value.dup.freeze
|
|
28
|
-
end
|
|
29
|
-
private_class_method :credential
|
|
30
|
-
|
|
31
|
-
def self.validate_refresh_source(current, verified: false)
|
|
32
|
-
return if verified || session_id(current.access_token)
|
|
33
|
-
|
|
34
|
-
raise Error::AuthenticationError, 'Cannot refresh supplied credentials without a session identifier'
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
def self.validate_refresh(current, refreshed)
|
|
38
|
-
return unless current
|
|
39
|
-
|
|
40
|
-
validate_refresh_session_id(current, refreshed)
|
|
41
|
-
return unless current.user_id && current.user_id != refreshed&.user_id
|
|
42
|
-
|
|
43
|
-
raise Error::AuthenticationError, 'Refreshed session belongs to a different user'
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
def self.validate_refresh_session_id(current, refreshed)
|
|
47
|
-
expected = session_id(current.access_token)
|
|
48
|
-
return unless expected && expected != session_id(refreshed&.access_token)
|
|
49
|
-
|
|
50
|
-
raise Error::AuthenticationError, 'Refreshed credentials belong to a different server session'
|
|
51
|
-
end
|
|
52
|
-
private_class_method :validate_refresh_session_id
|
|
53
|
-
|
|
54
|
-
# Untrusted continuity constraint, never authenticated user identity.
|
|
55
|
-
def self.session_id(access_token)
|
|
56
|
-
payload = token_payload(access_token)
|
|
57
|
-
return unless payload.is_a?(Hash)
|
|
58
|
-
|
|
59
|
-
value = payload['session_id']
|
|
60
|
-
value.downcase if value.is_a?(String) && value.match?(/\A[0-9a-f]{8}(?:-[0-9a-f]{4}){3}-[0-9a-f]{12}\z/i)
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
def self.token_payload(access_token)
|
|
64
|
-
return unless access_token.is_a?(String)
|
|
65
|
-
|
|
66
|
-
parts = access_token.split('.')
|
|
67
|
-
return unless parts.length == 3
|
|
68
|
-
|
|
69
|
-
encoded = parts.fetch(1).tr('-_', '+/')
|
|
70
|
-
JSON.parse((encoded + ('=' * (-encoded.length % 4))).unpack1('m0'))
|
|
71
|
-
rescue ArgumentError, JSON::ParserError
|
|
72
|
-
nil
|
|
73
|
-
end
|
|
74
|
-
private_class_method :token_payload
|
|
75
|
-
|
|
76
|
-
def self.with_user(session, user)
|
|
77
|
-
user_id = (session.user_id || user.fetch('id')).dup.freeze
|
|
78
|
-
raise Error::AuthenticationError, 'Profile belongs to a different user' unless user['id'] == user_id
|
|
79
|
-
|
|
80
|
-
Session.new(**session.to_h, user_id: user_id, user: user)
|
|
81
|
-
end
|
|
82
|
-
end
|
|
83
|
-
private_constant :SessionCredentials
|
|
84
|
-
|
|
85
12
|
# Local credentials; refresh credentials and user identity may be unknown.
|
|
86
13
|
Session = Data.define(:access_token, :refresh_token, :user_id, :user) do
|
|
87
14
|
def initialize(access_token:, refresh_token: nil, user_id: nil, user: nil)
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'base64'
|
|
4
|
+
require 'json'
|
|
5
|
+
require_relative 'session'
|
|
6
|
+
|
|
7
|
+
module Volcano
|
|
8
|
+
# Owns credential snapshots and preserves validated identity during refresh.
|
|
9
|
+
module SessionCredentials
|
|
10
|
+
def self.build(access_token, refresh_token)
|
|
11
|
+
raise ArgumentError, 'refresh_token requires access_token' if access_token.nil? && !refresh_token.nil?
|
|
12
|
+
return if access_token.nil?
|
|
13
|
+
|
|
14
|
+
Session.new(access_token: credential(access_token, :access_token),
|
|
15
|
+
refresh_token: refresh_token.nil? ? nil : credential(refresh_token, :refresh_token))
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def self.credential(value, name)
|
|
19
|
+
raise ArgumentError, "#{name} must be a non-empty string" unless value.is_a?(String) && !value.strip.empty?
|
|
20
|
+
|
|
21
|
+
value.dup.freeze
|
|
22
|
+
end
|
|
23
|
+
private_class_method :credential
|
|
24
|
+
|
|
25
|
+
def self.validate_refresh_source(current, verified: false)
|
|
26
|
+
return if verified || session_id(current.access_token)
|
|
27
|
+
|
|
28
|
+
raise Error::AuthenticationError, 'Cannot refresh supplied credentials without a session identifier'
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def self.validate_refresh(current, refreshed)
|
|
32
|
+
return unless current
|
|
33
|
+
|
|
34
|
+
validate_refresh_session_id(current, refreshed)
|
|
35
|
+
return unless current.user_id && current.user_id != refreshed&.user_id
|
|
36
|
+
|
|
37
|
+
raise Error::AuthenticationError, 'Refreshed session belongs to a different user'
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def self.validate_refresh_session_id(current, refreshed)
|
|
41
|
+
expected = session_id(current.access_token)
|
|
42
|
+
return unless expected && expected != session_id(refreshed&.access_token)
|
|
43
|
+
|
|
44
|
+
raise Error::AuthenticationError, 'Refreshed credentials belong to a different server session'
|
|
45
|
+
end
|
|
46
|
+
private_class_method :validate_refresh_session_id
|
|
47
|
+
|
|
48
|
+
# Untrusted continuity constraint, never authenticated user identity.
|
|
49
|
+
def self.session_id(access_token)
|
|
50
|
+
payload = token_payload(access_token)
|
|
51
|
+
return unless payload.is_a?(Hash)
|
|
52
|
+
|
|
53
|
+
value = payload['session_id']
|
|
54
|
+
value.downcase if value.is_a?(String) && value.match?(/\A[0-9a-f]{8}(?:-[0-9a-f]{4}){3}-[0-9a-f]{12}\z/i)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def self.token_payload(access_token)
|
|
58
|
+
return unless access_token.is_a?(String)
|
|
59
|
+
|
|
60
|
+
parts = access_token.split('.')
|
|
61
|
+
return unless parts.length == 3
|
|
62
|
+
|
|
63
|
+
encoded = parts.fetch(1).tr('-_', '+/')
|
|
64
|
+
JSON.parse(Base64.strict_decode64(encoded + ('=' * (-encoded.length % 4))))
|
|
65
|
+
rescue ArgumentError, JSON::ParserError
|
|
66
|
+
nil
|
|
67
|
+
end
|
|
68
|
+
private_class_method :token_payload
|
|
69
|
+
|
|
70
|
+
def self.with_user(session, user)
|
|
71
|
+
user_id = session.user_id || user.fetch('id')
|
|
72
|
+
raise Error::AuthenticationError, 'Profile has no valid user identifier' unless user_id.is_a?(String)
|
|
73
|
+
|
|
74
|
+
user_id = user_id.dup.freeze
|
|
75
|
+
raise Error::AuthenticationError, 'Profile belongs to a different user' unless user['id'] == user_id
|
|
76
|
+
|
|
77
|
+
Session.new(access_token: session.access_token, refresh_token: session.refresh_token,
|
|
78
|
+
user_id: user_id, user: user)
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
private_constant :SessionCredentials
|
|
82
|
+
end
|
|
@@ -42,7 +42,9 @@ module Volcano
|
|
|
42
42
|
def refresh(&)
|
|
43
43
|
operation, owner = @mutex.synchronize do
|
|
44
44
|
raise Error::SessionChangedError if @sign_out || @locally_cleared
|
|
45
|
-
|
|
45
|
+
|
|
46
|
+
current = @refresh
|
|
47
|
+
next [current, false] if current && !current.done
|
|
46
48
|
|
|
47
49
|
@refresh = Outcome.new(false)
|
|
48
50
|
[@refresh, true]
|
|
@@ -51,11 +53,7 @@ module Volcano
|
|
|
51
53
|
end
|
|
52
54
|
|
|
53
55
|
def sign_out
|
|
54
|
-
operation, owner, preceding, pending = @mutex.synchronize
|
|
55
|
-
first = @sign_out.nil?
|
|
56
|
-
@sign_out ||= Outcome.new(false)
|
|
57
|
-
[@sign_out, first, @refresh, @refresh && !@refresh.done]
|
|
58
|
-
end
|
|
56
|
+
operation, owner, preceding, pending = @mutex.synchronize { claim_sign_out }
|
|
59
57
|
execute(operation, owner) do
|
|
60
58
|
yield(preceding, pending)
|
|
61
59
|
ensure
|
|
@@ -79,10 +77,19 @@ module Volcano
|
|
|
79
77
|
|
|
80
78
|
private
|
|
81
79
|
|
|
80
|
+
def claim_sign_out
|
|
81
|
+
first = @sign_out.nil? == true
|
|
82
|
+
operation = @sign_out ||= Outcome.new(false)
|
|
83
|
+
current = @refresh
|
|
84
|
+
[operation, first, current, current && !current.done]
|
|
85
|
+
end
|
|
86
|
+
|
|
82
87
|
def execute(operation, owner)
|
|
83
88
|
return result(operation) unless owner
|
|
84
89
|
|
|
85
90
|
completed = false
|
|
91
|
+
# @type var value: Object?
|
|
92
|
+
value = nil
|
|
86
93
|
begin
|
|
87
94
|
value = yield
|
|
88
95
|
completed = true
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Volcano
|
|
4
|
+
# Maps the internal wire response to immutable public session values.
|
|
5
|
+
module SessionPageMapping
|
|
6
|
+
private
|
|
7
|
+
|
|
8
|
+
def session_page(body)
|
|
9
|
+
raise TypeError, 'Expected a complete session page' unless body.is_a?(Hash)
|
|
10
|
+
|
|
11
|
+
sessions = body.fetch('sessions')
|
|
12
|
+
raise TypeError, 'Expected a complete session page' unless sessions.is_a?(Array)
|
|
13
|
+
|
|
14
|
+
SessionPage.new(
|
|
15
|
+
sessions: sessions.map { |attributes| auth_session(attributes) },
|
|
16
|
+
total: required_integer(body, 'total'), page: required_integer(body, 'page'),
|
|
17
|
+
limit: required_integer(body, 'limit'), total_pages: required_integer(body, 'total_pages')
|
|
18
|
+
)
|
|
19
|
+
rescue KeyError
|
|
20
|
+
raise TypeError, 'Expected a complete session page'
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def auth_session(attributes)
|
|
24
|
+
raise TypeError, 'Expected a complete authentication session' unless attributes.is_a?(Hash)
|
|
25
|
+
|
|
26
|
+
build_auth_session(attributes)
|
|
27
|
+
rescue KeyError
|
|
28
|
+
raise TypeError, 'Expected a complete authentication session'
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def build_auth_session(attributes)
|
|
32
|
+
AuthSession.new(
|
|
33
|
+
id: required_string(attributes, 'id'), user_id: required_string(attributes, 'user_id'),
|
|
34
|
+
provider: required_string(attributes, 'provider'), expires_at: required_time(attributes, 'expires_at'),
|
|
35
|
+
is_active: required_boolean(attributes, 'is_active'), is_current: required_boolean(attributes, 'is_current'),
|
|
36
|
+
user_agent: optional_string(attributes, 'user_agent'), ip_address: optional_string(attributes, 'ip_address'),
|
|
37
|
+
last_ip_address: optional_string(attributes, 'last_ip_address'),
|
|
38
|
+
last_activity_at: optional_time(attributes, 'last_activity_at'),
|
|
39
|
+
session_started_at: optional_time(attributes, 'session_started_at'),
|
|
40
|
+
created_at: optional_time(attributes, 'created_at'), updated_at: optional_time(attributes, 'updated_at')
|
|
41
|
+
)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def required_integer(body, name)
|
|
45
|
+
value = body.fetch(name)
|
|
46
|
+
raise TypeError, 'Expected a complete session page' unless value.is_a?(Integer)
|
|
47
|
+
|
|
48
|
+
value
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def required_string(body, name)
|
|
52
|
+
value = body.fetch(name)
|
|
53
|
+
raise TypeError, 'Expected a complete authentication session' unless value.is_a?(String)
|
|
54
|
+
|
|
55
|
+
value
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def required_time(body, name)
|
|
59
|
+
value = body.fetch(name)
|
|
60
|
+
raise TypeError, 'Expected a complete authentication session' unless value.is_a?(Time)
|
|
61
|
+
|
|
62
|
+
value
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def required_boolean(body, name)
|
|
66
|
+
value = body.fetch(name)
|
|
67
|
+
unless value.is_a?(TrueClass) || value.is_a?(FalseClass)
|
|
68
|
+
raise TypeError, 'Expected a complete authentication session'
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
value
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def optional_string(body, name)
|
|
75
|
+
value = body[name]
|
|
76
|
+
raise TypeError, 'Expected a complete authentication session' unless value.nil? || value.is_a?(String)
|
|
77
|
+
|
|
78
|
+
value
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def optional_time(body, name)
|
|
82
|
+
value = body[name]
|
|
83
|
+
raise TypeError, 'Expected a complete authentication session' unless value.nil? || value.is_a?(Time)
|
|
84
|
+
|
|
85
|
+
value
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
private_constant :SessionPageMapping
|
|
89
|
+
end
|
data/lib/volcano/version.rb
CHANGED
data/lib/volcano.rb
CHANGED
|
@@ -5,6 +5,7 @@ require_relative 'volcano/connection_string'
|
|
|
5
5
|
require_relative 'volcano/models'
|
|
6
6
|
require_relative 'volcano/user'
|
|
7
7
|
require_relative 'volcano/session'
|
|
8
|
+
require_relative 'volcano/session_credentials'
|
|
8
9
|
require_relative 'volcano/function_response'
|
|
9
10
|
require_relative 'volcano/durable_models'
|
|
10
11
|
require_relative 'volcano/log_responses'
|
|
@@ -16,16 +17,21 @@ require_relative 'volcano/errors'
|
|
|
16
17
|
require_relative 'volcano/redaction'
|
|
17
18
|
require_relative 'volcano/transport'
|
|
18
19
|
require_relative 'volcano/generated_transport'
|
|
19
|
-
require_relative 'volcano/
|
|
20
|
+
require_relative 'volcano/auth_notification_dispatch'
|
|
21
|
+
require_relative 'volcano/auth_session_binding'
|
|
20
22
|
require_relative 'volcano/auth_sign_out'
|
|
21
23
|
require_relative 'volcano/auth_refresh'
|
|
24
|
+
require_relative 'volcano/auth_profile_fields'
|
|
25
|
+
require_relative 'volcano/auth_get_user'
|
|
26
|
+
require_relative 'volcano/auth_update_user'
|
|
27
|
+
require_relative 'volcano/auth_email_change'
|
|
28
|
+
require_relative 'volcano/auth_sessions'
|
|
29
|
+
require_relative 'volcano/auth'
|
|
22
30
|
require_relative 'volcano/auth_sign_up'
|
|
23
31
|
require_relative 'volcano/sign_up_result'
|
|
24
32
|
require_relative 'volcano/auth_anonymous'
|
|
25
33
|
require_relative 'volcano/auth_confirm_email'
|
|
26
34
|
require_relative 'volcano/auth_resend_confirmation'
|
|
27
|
-
require_relative 'volcano/auth_email_change'
|
|
28
|
-
require_relative 'volcano/auth_sessions'
|
|
29
35
|
require_relative 'volcano/auth_oauth'
|
|
30
36
|
require_relative 'volcano/auth_oauth_sign_in'
|
|
31
37
|
require_relative 'volcano/auth_hosted'
|
|
@@ -33,8 +39,6 @@ require_relative 'volcano/auth_oauth_token'
|
|
|
33
39
|
require_relative 'volcano/auth_oauth_api'
|
|
34
40
|
require_relative 'volcano/auth_reset_password_for_email'
|
|
35
41
|
require_relative 'volcano/auth_reset_password'
|
|
36
|
-
require_relative 'volcano/auth_get_user'
|
|
37
|
-
require_relative 'volcano/auth_update_user'
|
|
38
42
|
require_relative 'volcano/database'
|
|
39
43
|
require_relative 'volcano/function_resolution'
|
|
40
44
|
require_relative 'volcano/functions'
|
|
@@ -50,6 +54,7 @@ require_relative 'volcano/lock_guard'
|
|
|
50
54
|
require_relative 'volcano/lock_renewer'
|
|
51
55
|
require_relative 'volcano/lock_session'
|
|
52
56
|
require_relative 'volcano/lock_auto_renewal'
|
|
57
|
+
require_relative 'volcano/lock_response'
|
|
53
58
|
require_relative 'volcano/locks'
|
|
54
59
|
require_relative 'volcano/realtime/protocol'
|
|
55
60
|
require_relative 'volcano/realtime'
|
|
@@ -57,8 +62,10 @@ require_relative 'volcano/client'
|
|
|
57
62
|
|
|
58
63
|
# Public namespace for the Volcano Ruby SDK.
|
|
59
64
|
module Volcano
|
|
60
|
-
private_constant :
|
|
61
|
-
:
|
|
65
|
+
private_constant :AuthRefresh, :AuthSignOut, :AuthProfile, :AuthProfileFields, :AuthUpdateUser, :AuthEmailChange,
|
|
66
|
+
:AuthSessions,
|
|
67
|
+
:GeneratedTransport, :LockAutoRenewal, :LockLeaseClock, :LockRenewer,
|
|
68
|
+
:LockSession, :LockResponse, :Redaction, :SessionOperations
|
|
62
69
|
|
|
63
70
|
Realtime.private_constant :Protocol, :ProtocolDispatch, :ProtocolPublicationDispatch, :ProtocolRecovery,
|
|
64
71
|
:ProtocolRecoveryDispatch, :ProtocolRecoveryPosition
|
data/sig/auth_oauth.rbs
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
module Volcano
|
|
2
|
+
type oauth_api_input = session_input
|
|
3
|
+
|
|
4
|
+
class Auth
|
|
5
|
+
def list_linked_oauth_providers: () -> Array[LinkedOAuthProvider]
|
|
6
|
+
def link_oauth_provider: (String provider) -> String
|
|
7
|
+
def unlink_oauth_provider: (String provider) -> nil
|
|
8
|
+
def get_oauth_provider_token: (String provider) -> OAuthProviderTokenStatus
|
|
9
|
+
def refresh_oauth_provider_token: (String provider) -> OAuthProviderTokenStatus
|
|
10
|
+
def call_oauth_api: (String provider, endpoint: String, ?method: String, ?body: oauth_api_input) -> Object?
|
|
11
|
+
def sign_in_with_oauth: (String provider, redirect_to: String, state: String) -> String
|
|
12
|
+
def exchange_oauth_code: (code: String, redirect_to: String, state: String, expected_state: String) -> Session
|
|
13
|
+
def get_hosted_auth_url: (project_id: String, state: String, ?action: String) -> String
|
|
14
|
+
def adopt_hosted_auth_session: (Session session, state: String, expected_state: String) -> Session
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
module Volcano
|
|
2
|
+
class Auth
|
|
3
|
+
def user: () -> User
|
|
4
|
+
def get_user: () -> User
|
|
5
|
+
def update_user: (?password: String?, ?metadata: session_user_input?) -> User
|
|
6
|
+
def request_email_change: (new_email: String) -> EmailChangeResult
|
|
7
|
+
def cancel_email_change: () -> nil
|
|
8
|
+
def confirm_email_change: (token: String) -> User
|
|
9
|
+
end
|
|
10
|
+
end
|