workos 9.4.0 → 9.5.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/.github/workflows/release-please.yml +3 -2
- data/.last-synced-sha +1 -1
- data/.oagen-manifest.json +8 -1
- data/.release-please-manifest.json +1 -1
- data/CHANGELOG.md +23 -0
- data/Gemfile.lock +2 -2
- data/lib/workos/base_client.rb +14 -1
- data/lib/workos/user_management/magic_auth_send_magic_auth_code_and_return_response.rb +26 -1
- data/lib/workos/user_management/user_create_response.rb +55 -1
- data/lib/workos/user_management.rb +96 -3
- data/lib/workos/version.rb +1 -1
- data/rbi/workos/magic_auth_send_magic_auth_code_and_return_response.rbi +48 -0
- data/rbi/workos/user_create_response.rbi +84 -0
- data/test/workos/test_base_client.rb +155 -0
- data/test/workos/test_user_management.rb +30 -0
- data/test/workos/test_user_management_model_round_trip.rb +39 -0
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1c34fe37d22956705fc72fc7bc0aabd35e5dde5a4a784263705ac684708d8482
|
|
4
|
+
data.tar.gz: 8d40b87964c3fb62b7f06bf58d9a93256a37aa45428741ba61f8f05fa0b93aba
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 43edc59178986404b4ce4efbf2bfc614775dd53a82d987adea6e9713a38b71ab7b2c8c11e5579bdf26b83a48f980c493b2c387a2eb433c8dbc9293e4aa3edaf2
|
|
7
|
+
data.tar.gz: 3a302db04edaaf67e5b27e66a114587a7e9cef1d1ae163fad0e1b240207ac84faed2a60e7ebc8fabd62b0ea7ceb6ad84e64d1a0632830f6d87e7787c64e9fd20
|
|
@@ -3,10 +3,10 @@ name: Release Please
|
|
|
3
3
|
on:
|
|
4
4
|
push:
|
|
5
5
|
branches:
|
|
6
|
-
-
|
|
6
|
+
- v9.5.x
|
|
7
7
|
|
|
8
8
|
concurrency:
|
|
9
|
-
group: release-please
|
|
9
|
+
group: release-please-v9.5.x
|
|
10
10
|
cancel-in-progress: false
|
|
11
11
|
|
|
12
12
|
permissions:
|
|
@@ -40,6 +40,7 @@ jobs:
|
|
|
40
40
|
id: release
|
|
41
41
|
with:
|
|
42
42
|
token: ${{ steps.generate-token.outputs.token }}
|
|
43
|
+
target-branch: v9.5.x
|
|
43
44
|
|
|
44
45
|
# While the release PR is open, enrich it before it merges. Runs as its
|
|
45
46
|
# own job (gated on the PR existing) so a failure here can never block the
|
data/.last-synced-sha
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
23faa38318d596e581656934ed72c4a18476d742
|
data/.oagen-manifest.json
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 2,
|
|
3
3
|
"language": "ruby",
|
|
4
|
-
"generatedAt": "2026-07-02T17:29:49.181Z",
|
|
5
4
|
"files": [
|
|
6
5
|
"lib/workos.rb",
|
|
7
6
|
"lib/workos/admin_portal.rb",
|
|
@@ -2057,6 +2056,14 @@
|
|
|
2057
2056
|
"PUT /user_management/users/{user_id}/connected_accounts/{slug}": {
|
|
2058
2057
|
"sdkMethod": "update_user_connected_account",
|
|
2059
2058
|
"service": "pipes"
|
|
2059
|
+
},
|
|
2060
|
+
"POST /user_management/authenticate#RadarEmailChallengeCodeSessionAuthenticateRequest": {
|
|
2061
|
+
"sdkMethod": "authenticate_with_radar_email_challenge",
|
|
2062
|
+
"service": "user_management"
|
|
2063
|
+
},
|
|
2064
|
+
"POST /user_management/authenticate#RadarSmsChallengeCodeSessionAuthenticateRequest": {
|
|
2065
|
+
"sdkMethod": "authenticate_with_radar_sms_challenge",
|
|
2066
|
+
"service": "user_management"
|
|
2060
2067
|
}
|
|
2061
2068
|
}
|
|
2062
2069
|
}
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,28 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [9.5.1](https://github.com/workos/workos-ruby/compare/v9.5.0...v9.5.1) (2026-08-20)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* explicitly evict connections if request does not complete as success ([bfee53e](https://github.com/workos/workos-ruby/commit/bfee53efbd80a2156cbd15b70b483be66913e714))
|
|
9
|
+
|
|
10
|
+
## [9.5.0](https://github.com/workos/workos-ruby/compare/v9.4.0...v9.5.0) (2026-07-06)
|
|
11
|
+
|
|
12
|
+
* [#514](https://github.com/workos/workos-ruby/pull/514) fix(generated): regenerate from spec
|
|
13
|
+
|
|
14
|
+
**Features**
|
|
15
|
+
* **[user_management](https://workos.com/docs/reference/authkit/user)**:
|
|
16
|
+
* Added model `UserRoleAssignmentSource`
|
|
17
|
+
* Added `source` to `UserRoleAssignment`
|
|
18
|
+
* Added enum `UserRoleAssignmentSourceType`
|
|
19
|
+
* Added parameter `UserManagementAuthentication.authorize.max_age`
|
|
20
|
+
* Added endpoint `GET /user_management/cors_origins`
|
|
21
|
+
* Added endpoint `GET /user_management/redirect_uris`
|
|
22
|
+
|
|
23
|
+
**Fixes**
|
|
24
|
+
* Restore mistakenly removed CreateMagicAuth logic from previous release
|
|
25
|
+
|
|
3
26
|
## [9.4.0](https://github.com/workos/workos-ruby/compare/v9.3.0...v9.4.0) (2026-07-02)
|
|
4
27
|
|
|
5
28
|
* [#508](https://github.com/workos/workos-ruby/pull/508) fix(generated): regenerate from spec
|
data/Gemfile.lock
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
workos (9.
|
|
4
|
+
workos (9.5.1)
|
|
5
5
|
jwt (~> 3.1)
|
|
6
6
|
logger (~> 1.7)
|
|
7
7
|
zeitwerk (~> 2.6)
|
|
@@ -153,7 +153,7 @@ CHECKSUMS
|
|
|
153
153
|
unicode-emoji (4.2.0) sha256=519e69150f75652e40bf736106cfbc8f0f73aa3fb6a65afe62fefa7f80b0f80f
|
|
154
154
|
webmock (3.26.2) sha256=774556f2ea6371846cca68c01769b2eac0d134492d21f6d0ab5dd643965a4c90
|
|
155
155
|
webrick (1.9.2) sha256=beb4a15fc474defed24a3bda4ffd88a490d517c9e4e6118c3edce59e45864131
|
|
156
|
-
workos (9.
|
|
156
|
+
workos (9.5.1)
|
|
157
157
|
yard (0.9.44) sha256=eb087e9b631ccd887b049f303d489963945452d5e2a7eb49a5a74a7cf6887f28
|
|
158
158
|
yard-markdown (0.7.1) sha256=06c378632dfe7ba053be9ba469eb4701aa0470e36bcf7e5546f353eb90c1bfd1
|
|
159
159
|
zeitwerk (2.7.5) sha256=d8da92128c09ea6ec62c949011b00ed4a20242b255293dd66bf41545398f73dd
|
data/lib/workos/base_client.rb
CHANGED
|
@@ -137,7 +137,20 @@ module WorkOS
|
|
|
137
137
|
loggable_path = redact_path(request.path)
|
|
138
138
|
log(:debug, "request start", method: request.method, path: loggable_path, attempt: attempt + 1)
|
|
139
139
|
http = connection_for(base, timeout)
|
|
140
|
-
|
|
140
|
+
request_completed = false
|
|
141
|
+
begin
|
|
142
|
+
response = http.request(request)
|
|
143
|
+
request_completed = true
|
|
144
|
+
ensure
|
|
145
|
+
# Any exit from #request other than a returned response can leave
|
|
146
|
+
# the socket mid-stream: a connection error the rescue below knows
|
|
147
|
+
# about, one it doesn't (OpenSSL::SSL::SSLError,
|
|
148
|
+
# Net::HTTPBadResponse), or a non-local exit such as an
|
|
149
|
+
# application-level Timeout.timeout or Thread#kill. A half-read
|
|
150
|
+
# socket handed back to the pool desyncs the *next* request on this
|
|
151
|
+
# thread, so drop it here rather than in the rescue.
|
|
152
|
+
evict_connection(base) unless request_completed
|
|
153
|
+
end
|
|
141
154
|
return response if response.is_a?(Net::HTTPSuccess)
|
|
142
155
|
|
|
143
156
|
if attempt < retries && retryable?(response)
|
|
@@ -5,13 +5,38 @@
|
|
|
5
5
|
module WorkOS
|
|
6
6
|
class MagicAuthSendMagicAuthCodeAndReturnResponse < WorkOS::Types::BaseModel
|
|
7
7
|
HASH_ATTRS = {
|
|
8
|
+
object: :object,
|
|
9
|
+
id: :id,
|
|
10
|
+
user_id: :user_id,
|
|
11
|
+
email: :email,
|
|
12
|
+
expires_at: :expires_at,
|
|
13
|
+
created_at: :created_at,
|
|
14
|
+
updated_at: :updated_at,
|
|
15
|
+
code: :code,
|
|
8
16
|
radar_auth_attempt_id: :radar_auth_attempt_id
|
|
9
17
|
}.freeze
|
|
10
18
|
|
|
11
|
-
attr_accessor
|
|
19
|
+
attr_accessor \
|
|
20
|
+
:object,
|
|
21
|
+
:id,
|
|
22
|
+
:user_id,
|
|
23
|
+
:email,
|
|
24
|
+
:expires_at,
|
|
25
|
+
:created_at,
|
|
26
|
+
:updated_at,
|
|
27
|
+
:code,
|
|
28
|
+
:radar_auth_attempt_id
|
|
12
29
|
|
|
13
30
|
def initialize(json)
|
|
14
31
|
hash = self.class.normalize(json)
|
|
32
|
+
@object = hash[:object]
|
|
33
|
+
@id = hash[:id]
|
|
34
|
+
@user_id = hash[:user_id]
|
|
35
|
+
@email = hash[:email]
|
|
36
|
+
@expires_at = hash[:expires_at]
|
|
37
|
+
@created_at = hash[:created_at]
|
|
38
|
+
@updated_at = hash[:updated_at]
|
|
39
|
+
@code = hash[:code]
|
|
15
40
|
@radar_auth_attempt_id = hash[:radar_auth_attempt_id]
|
|
16
41
|
end
|
|
17
42
|
end
|
|
@@ -3,5 +3,59 @@
|
|
|
3
3
|
# This file is auto-generated by oagen. Do not edit.
|
|
4
4
|
|
|
5
5
|
module WorkOS
|
|
6
|
-
UserCreateResponse
|
|
6
|
+
class UserCreateResponse < WorkOS::Types::BaseModel
|
|
7
|
+
HASH_ATTRS = {
|
|
8
|
+
object: :object,
|
|
9
|
+
id: :id,
|
|
10
|
+
first_name: :first_name,
|
|
11
|
+
last_name: :last_name,
|
|
12
|
+
name: :name,
|
|
13
|
+
profile_picture_url: :profile_picture_url,
|
|
14
|
+
email: :email,
|
|
15
|
+
email_verified: :email_verified,
|
|
16
|
+
external_id: :external_id,
|
|
17
|
+
metadata: :metadata,
|
|
18
|
+
last_sign_in_at: :last_sign_in_at,
|
|
19
|
+
locale: :locale,
|
|
20
|
+
created_at: :created_at,
|
|
21
|
+
updated_at: :updated_at,
|
|
22
|
+
radar_auth_attempt_id: :radar_auth_attempt_id
|
|
23
|
+
}.freeze
|
|
24
|
+
|
|
25
|
+
attr_accessor \
|
|
26
|
+
:object,
|
|
27
|
+
:id,
|
|
28
|
+
:first_name,
|
|
29
|
+
:last_name,
|
|
30
|
+
:name,
|
|
31
|
+
:profile_picture_url,
|
|
32
|
+
:email,
|
|
33
|
+
:email_verified,
|
|
34
|
+
:external_id,
|
|
35
|
+
:metadata,
|
|
36
|
+
:last_sign_in_at,
|
|
37
|
+
:locale,
|
|
38
|
+
:created_at,
|
|
39
|
+
:updated_at,
|
|
40
|
+
:radar_auth_attempt_id
|
|
41
|
+
|
|
42
|
+
def initialize(json)
|
|
43
|
+
hash = self.class.normalize(json)
|
|
44
|
+
@object = hash[:object]
|
|
45
|
+
@id = hash[:id]
|
|
46
|
+
@first_name = hash[:first_name]
|
|
47
|
+
@last_name = hash[:last_name]
|
|
48
|
+
@name = hash[:name]
|
|
49
|
+
@profile_picture_url = hash[:profile_picture_url]
|
|
50
|
+
@email = hash[:email]
|
|
51
|
+
@email_verified = hash[:email_verified]
|
|
52
|
+
@external_id = hash[:external_id]
|
|
53
|
+
@metadata = hash[:metadata] || {}
|
|
54
|
+
@last_sign_in_at = hash[:last_sign_in_at]
|
|
55
|
+
@locale = hash[:locale]
|
|
56
|
+
@created_at = hash[:created_at]
|
|
57
|
+
@updated_at = hash[:updated_at]
|
|
58
|
+
@radar_auth_attempt_id = hash[:radar_auth_attempt_id]
|
|
59
|
+
end
|
|
60
|
+
end
|
|
7
61
|
end
|
|
@@ -133,6 +133,8 @@ module WorkOS
|
|
|
133
133
|
# @param ip_address [String, nil]
|
|
134
134
|
# @param device_id [String, nil]
|
|
135
135
|
# @param user_agent [String, nil]
|
|
136
|
+
# @param signals_id [String, nil]
|
|
137
|
+
# @param radar_auth_attempt_id [String, nil]
|
|
136
138
|
# @param request_options [Hash] Per-request overrides.
|
|
137
139
|
# @return [WorkOS::AuthenticateResponse]
|
|
138
140
|
def authenticate_with_password(
|
|
@@ -142,6 +144,8 @@ module WorkOS
|
|
|
142
144
|
ip_address: nil,
|
|
143
145
|
device_id: nil,
|
|
144
146
|
user_agent: nil,
|
|
147
|
+
signals_id: nil,
|
|
148
|
+
radar_auth_attempt_id: nil,
|
|
145
149
|
request_options: {}
|
|
146
150
|
)
|
|
147
151
|
body = {
|
|
@@ -153,7 +157,9 @@ module WorkOS
|
|
|
153
157
|
"invitation_token" => invitation_token,
|
|
154
158
|
"ip_address" => ip_address,
|
|
155
159
|
"device_id" => device_id,
|
|
156
|
-
"user_agent" => user_agent
|
|
160
|
+
"user_agent" => user_agent,
|
|
161
|
+
"signals_id" => signals_id,
|
|
162
|
+
"radar_auth_attempt_id" => radar_auth_attempt_id
|
|
157
163
|
}.compact
|
|
158
164
|
response = @client.request(
|
|
159
165
|
method: :post,
|
|
@@ -172,6 +178,7 @@ module WorkOS
|
|
|
172
178
|
# @param ip_address [String, nil]
|
|
173
179
|
# @param device_id [String, nil]
|
|
174
180
|
# @param user_agent [String, nil]
|
|
181
|
+
# @param signals_id [String, nil]
|
|
175
182
|
# @param request_options [Hash] Per-request overrides.
|
|
176
183
|
# @return [WorkOS::AuthenticateResponse]
|
|
177
184
|
def authenticate_with_code(
|
|
@@ -181,6 +188,7 @@ module WorkOS
|
|
|
181
188
|
ip_address: nil,
|
|
182
189
|
device_id: nil,
|
|
183
190
|
user_agent: nil,
|
|
191
|
+
signals_id: nil,
|
|
184
192
|
request_options: {}
|
|
185
193
|
)
|
|
186
194
|
body = {
|
|
@@ -192,7 +200,8 @@ module WorkOS
|
|
|
192
200
|
"invitation_token" => invitation_token,
|
|
193
201
|
"ip_address" => ip_address,
|
|
194
202
|
"device_id" => device_id,
|
|
195
|
-
"user_agent" => user_agent
|
|
203
|
+
"user_agent" => user_agent,
|
|
204
|
+
"signals_id" => signals_id
|
|
196
205
|
}.compact
|
|
197
206
|
response = @client.request(
|
|
198
207
|
method: :post,
|
|
@@ -247,6 +256,7 @@ module WorkOS
|
|
|
247
256
|
# @param ip_address [String, nil]
|
|
248
257
|
# @param device_id [String, nil]
|
|
249
258
|
# @param user_agent [String, nil]
|
|
259
|
+
# @param radar_auth_attempt_id [String, nil]
|
|
250
260
|
# @param request_options [Hash] Per-request overrides.
|
|
251
261
|
# @return [WorkOS::AuthenticateResponse]
|
|
252
262
|
def authenticate_with_magic_auth(
|
|
@@ -256,6 +266,7 @@ module WorkOS
|
|
|
256
266
|
ip_address: nil,
|
|
257
267
|
device_id: nil,
|
|
258
268
|
user_agent: nil,
|
|
269
|
+
radar_auth_attempt_id: nil,
|
|
259
270
|
request_options: {}
|
|
260
271
|
)
|
|
261
272
|
body = {
|
|
@@ -267,7 +278,8 @@ module WorkOS
|
|
|
267
278
|
"invitation_token" => invitation_token,
|
|
268
279
|
"ip_address" => ip_address,
|
|
269
280
|
"device_id" => device_id,
|
|
270
|
-
"user_agent" => user_agent
|
|
281
|
+
"user_agent" => user_agent,
|
|
282
|
+
"radar_auth_attempt_id" => radar_auth_attempt_id
|
|
271
283
|
}.compact
|
|
272
284
|
response = @client.request(
|
|
273
285
|
method: :post,
|
|
@@ -422,6 +434,87 @@ module WorkOS
|
|
|
422
434
|
WorkOS::AuthenticateResponse.new(response.body)
|
|
423
435
|
end
|
|
424
436
|
|
|
437
|
+
# Authenticate with radar email challenge.
|
|
438
|
+
# @param code [String]
|
|
439
|
+
# @param radar_challenge_id [String]
|
|
440
|
+
# @param pending_authentication_token [String]
|
|
441
|
+
# @param ip_address [String, nil]
|
|
442
|
+
# @param device_id [String, nil]
|
|
443
|
+
# @param user_agent [String, nil]
|
|
444
|
+
# @param request_options [Hash] Per-request overrides.
|
|
445
|
+
# @return [WorkOS::AuthenticateResponse]
|
|
446
|
+
def authenticate_with_radar_email_challenge(
|
|
447
|
+
code:,
|
|
448
|
+
radar_challenge_id:,
|
|
449
|
+
pending_authentication_token:,
|
|
450
|
+
ip_address: nil,
|
|
451
|
+
device_id: nil,
|
|
452
|
+
user_agent: nil,
|
|
453
|
+
request_options: {}
|
|
454
|
+
)
|
|
455
|
+
body = {
|
|
456
|
+
"grant_type" => "urn:workos:oauth:grant-type:radar-email-challenge:code",
|
|
457
|
+
"client_id" => @client.client_id,
|
|
458
|
+
"client_secret" => @client.api_key,
|
|
459
|
+
"code" => code,
|
|
460
|
+
"radar_challenge_id" => radar_challenge_id,
|
|
461
|
+
"pending_authentication_token" => pending_authentication_token,
|
|
462
|
+
"ip_address" => ip_address,
|
|
463
|
+
"device_id" => device_id,
|
|
464
|
+
"user_agent" => user_agent
|
|
465
|
+
}.compact
|
|
466
|
+
response = @client.request(
|
|
467
|
+
method: :post,
|
|
468
|
+
path: "/user_management/authenticate",
|
|
469
|
+
auth: true,
|
|
470
|
+
body: body,
|
|
471
|
+
request_options: request_options
|
|
472
|
+
)
|
|
473
|
+
WorkOS::AuthenticateResponse.new(response.body)
|
|
474
|
+
end
|
|
475
|
+
|
|
476
|
+
# Authenticate with radar sms challenge.
|
|
477
|
+
# @param code [String]
|
|
478
|
+
# @param verification_id [String]
|
|
479
|
+
# @param phone_number [String]
|
|
480
|
+
# @param pending_authentication_token [String]
|
|
481
|
+
# @param ip_address [String, nil]
|
|
482
|
+
# @param device_id [String, nil]
|
|
483
|
+
# @param user_agent [String, nil]
|
|
484
|
+
# @param request_options [Hash] Per-request overrides.
|
|
485
|
+
# @return [WorkOS::AuthenticateResponse]
|
|
486
|
+
def authenticate_with_radar_sms_challenge(
|
|
487
|
+
code:,
|
|
488
|
+
verification_id:,
|
|
489
|
+
phone_number:,
|
|
490
|
+
pending_authentication_token:,
|
|
491
|
+
ip_address: nil,
|
|
492
|
+
device_id: nil,
|
|
493
|
+
user_agent: nil,
|
|
494
|
+
request_options: {}
|
|
495
|
+
)
|
|
496
|
+
body = {
|
|
497
|
+
"grant_type" => "urn:workos:oauth:grant-type:radar-sms-challenge:code",
|
|
498
|
+
"client_id" => @client.client_id,
|
|
499
|
+
"client_secret" => @client.api_key,
|
|
500
|
+
"code" => code,
|
|
501
|
+
"verification_id" => verification_id,
|
|
502
|
+
"phone_number" => phone_number,
|
|
503
|
+
"pending_authentication_token" => pending_authentication_token,
|
|
504
|
+
"ip_address" => ip_address,
|
|
505
|
+
"device_id" => device_id,
|
|
506
|
+
"user_agent" => user_agent
|
|
507
|
+
}.compact
|
|
508
|
+
response = @client.request(
|
|
509
|
+
method: :post,
|
|
510
|
+
path: "/user_management/authenticate",
|
|
511
|
+
auth: true,
|
|
512
|
+
body: body,
|
|
513
|
+
request_options: request_options
|
|
514
|
+
)
|
|
515
|
+
WorkOS::AuthenticateResponse.new(response.body)
|
|
516
|
+
end
|
|
517
|
+
|
|
425
518
|
# Get device authorization URL
|
|
426
519
|
# @param client_id [String] The WorkOS client ID for your application.
|
|
427
520
|
# @param request_options [Hash] (see WorkOS::Types::RequestOptions)
|
data/lib/workos/version.rb
CHANGED
|
@@ -9,6 +9,54 @@ module WorkOS
|
|
|
9
9
|
sig { params(json: T.any(String, T::Hash[Symbol, T.untyped])).void }
|
|
10
10
|
def initialize(json); end
|
|
11
11
|
|
|
12
|
+
sig { returns(String) }
|
|
13
|
+
def object; end
|
|
14
|
+
|
|
15
|
+
sig { params(value: String).returns(String) }
|
|
16
|
+
def object=(value); end
|
|
17
|
+
|
|
18
|
+
sig { returns(String) }
|
|
19
|
+
def id; end
|
|
20
|
+
|
|
21
|
+
sig { params(value: String).returns(String) }
|
|
22
|
+
def id=(value); end
|
|
23
|
+
|
|
24
|
+
sig { returns(String) }
|
|
25
|
+
def user_id; end
|
|
26
|
+
|
|
27
|
+
sig { params(value: String).returns(String) }
|
|
28
|
+
def user_id=(value); end
|
|
29
|
+
|
|
30
|
+
sig { returns(String) }
|
|
31
|
+
def email; end
|
|
32
|
+
|
|
33
|
+
sig { params(value: String).returns(String) }
|
|
34
|
+
def email=(value); end
|
|
35
|
+
|
|
36
|
+
sig { returns(String) }
|
|
37
|
+
def expires_at; end
|
|
38
|
+
|
|
39
|
+
sig { params(value: String).returns(String) }
|
|
40
|
+
def expires_at=(value); end
|
|
41
|
+
|
|
42
|
+
sig { returns(String) }
|
|
43
|
+
def created_at; end
|
|
44
|
+
|
|
45
|
+
sig { params(value: String).returns(String) }
|
|
46
|
+
def created_at=(value); end
|
|
47
|
+
|
|
48
|
+
sig { returns(String) }
|
|
49
|
+
def updated_at; end
|
|
50
|
+
|
|
51
|
+
sig { params(value: String).returns(String) }
|
|
52
|
+
def updated_at=(value); end
|
|
53
|
+
|
|
54
|
+
sig { returns(String) }
|
|
55
|
+
def code; end
|
|
56
|
+
|
|
57
|
+
sig { params(value: String).returns(String) }
|
|
58
|
+
def code=(value); end
|
|
59
|
+
|
|
12
60
|
sig { returns(T.nilable(String)) }
|
|
13
61
|
def radar_auth_attempt_id; end
|
|
14
62
|
|
|
@@ -9,6 +9,90 @@ module WorkOS
|
|
|
9
9
|
sig { params(json: T.any(String, T::Hash[Symbol, T.untyped])).void }
|
|
10
10
|
def initialize(json); end
|
|
11
11
|
|
|
12
|
+
sig { returns(String) }
|
|
13
|
+
def object; end
|
|
14
|
+
|
|
15
|
+
sig { params(value: String).returns(String) }
|
|
16
|
+
def object=(value); end
|
|
17
|
+
|
|
18
|
+
sig { returns(String) }
|
|
19
|
+
def id; end
|
|
20
|
+
|
|
21
|
+
sig { params(value: String).returns(String) }
|
|
22
|
+
def id=(value); end
|
|
23
|
+
|
|
24
|
+
sig { returns(T.nilable(String)) }
|
|
25
|
+
def first_name; end
|
|
26
|
+
|
|
27
|
+
sig { params(value: T.nilable(String)).returns(T.nilable(String)) }
|
|
28
|
+
def first_name=(value); end
|
|
29
|
+
|
|
30
|
+
sig { returns(T.nilable(String)) }
|
|
31
|
+
def last_name; end
|
|
32
|
+
|
|
33
|
+
sig { params(value: T.nilable(String)).returns(T.nilable(String)) }
|
|
34
|
+
def last_name=(value); end
|
|
35
|
+
|
|
36
|
+
sig { returns(T.nilable(String)) }
|
|
37
|
+
def name; end
|
|
38
|
+
|
|
39
|
+
sig { params(value: T.nilable(String)).returns(T.nilable(String)) }
|
|
40
|
+
def name=(value); end
|
|
41
|
+
|
|
42
|
+
sig { returns(T.nilable(String)) }
|
|
43
|
+
def profile_picture_url; end
|
|
44
|
+
|
|
45
|
+
sig { params(value: T.nilable(String)).returns(T.nilable(String)) }
|
|
46
|
+
def profile_picture_url=(value); end
|
|
47
|
+
|
|
48
|
+
sig { returns(String) }
|
|
49
|
+
def email; end
|
|
50
|
+
|
|
51
|
+
sig { params(value: String).returns(String) }
|
|
52
|
+
def email=(value); end
|
|
53
|
+
|
|
54
|
+
sig { returns(T::Boolean) }
|
|
55
|
+
def email_verified; end
|
|
56
|
+
|
|
57
|
+
sig { params(value: T::Boolean).returns(T::Boolean) }
|
|
58
|
+
def email_verified=(value); end
|
|
59
|
+
|
|
60
|
+
sig { returns(T.nilable(String)) }
|
|
61
|
+
def external_id; end
|
|
62
|
+
|
|
63
|
+
sig { params(value: T.nilable(String)).returns(T.nilable(String)) }
|
|
64
|
+
def external_id=(value); end
|
|
65
|
+
|
|
66
|
+
sig { returns(T.nilable(T::Hash[String, String])) }
|
|
67
|
+
def metadata; end
|
|
68
|
+
|
|
69
|
+
sig { params(value: T.nilable(T::Hash[String, String])).returns(T.nilable(T::Hash[String, String])) }
|
|
70
|
+
def metadata=(value); end
|
|
71
|
+
|
|
72
|
+
sig { returns(T.nilable(String)) }
|
|
73
|
+
def last_sign_in_at; end
|
|
74
|
+
|
|
75
|
+
sig { params(value: T.nilable(String)).returns(T.nilable(String)) }
|
|
76
|
+
def last_sign_in_at=(value); end
|
|
77
|
+
|
|
78
|
+
sig { returns(T.nilable(String)) }
|
|
79
|
+
def locale; end
|
|
80
|
+
|
|
81
|
+
sig { params(value: T.nilable(String)).returns(T.nilable(String)) }
|
|
82
|
+
def locale=(value); end
|
|
83
|
+
|
|
84
|
+
sig { returns(String) }
|
|
85
|
+
def created_at; end
|
|
86
|
+
|
|
87
|
+
sig { params(value: String).returns(String) }
|
|
88
|
+
def created_at=(value); end
|
|
89
|
+
|
|
90
|
+
sig { returns(String) }
|
|
91
|
+
def updated_at; end
|
|
92
|
+
|
|
93
|
+
sig { params(value: String).returns(String) }
|
|
94
|
+
def updated_at=(value); end
|
|
95
|
+
|
|
12
96
|
sig { returns(T.nilable(String)) }
|
|
13
97
|
def radar_auth_attempt_id; end
|
|
14
98
|
|
|
@@ -82,6 +82,24 @@ class BaseClientTest < Minitest::Test
|
|
|
82
82
|
end
|
|
83
83
|
end
|
|
84
84
|
|
|
85
|
+
# A pooled connection that either returns a canned response or raises when
|
|
86
|
+
# driven, so execute_request can be exercised without real TCP+TLS.
|
|
87
|
+
class StubConnection < FakeConnection
|
|
88
|
+
attr_accessor :read_timeout, :open_timeout
|
|
89
|
+
|
|
90
|
+
def initialize(response: nil, error: nil, **kwargs)
|
|
91
|
+
super(**kwargs)
|
|
92
|
+
@response = response
|
|
93
|
+
@error = error
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def request(_request)
|
|
97
|
+
raise @error if @error
|
|
98
|
+
|
|
99
|
+
@response
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
85
103
|
def setup
|
|
86
104
|
@client = WorkOS::BaseClient.new(api_key: "sk_test_123", max_retries: 1)
|
|
87
105
|
end
|
|
@@ -179,6 +197,143 @@ class BaseClientTest < Minitest::Test
|
|
|
179
197
|
refute keep.finished
|
|
180
198
|
end
|
|
181
199
|
|
|
200
|
+
# An exception execute_request's rescue clause doesn't list still leaves the
|
|
201
|
+
# socket mid-stream, so the connection must not survive in the pool for the
|
|
202
|
+
# next request on this thread to pick up.
|
|
203
|
+
def test_unlisted_request_error_evicts_the_pooled_connection
|
|
204
|
+
conn = StubConnection.new(error: Net::HTTPBadResponse.new("wrong version"))
|
|
205
|
+
cache = @client.send(:thread_connections)
|
|
206
|
+
cache["https:api.workos.com:443:30"] = conn
|
|
207
|
+
|
|
208
|
+
assert_raises(Net::HTTPBadResponse) do
|
|
209
|
+
@client.execute_request(request: Net::HTTP::Get.new("/things"))
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
refute cache.key?("https:api.workos.com:443:30"),
|
|
213
|
+
"a connection whose request did not complete must not stay pooled"
|
|
214
|
+
assert conn.finished
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
def test_completed_request_keeps_the_connection_pooled
|
|
218
|
+
conn = StubConnection.new(response: Net::HTTPOK.new("1.1", "200", "OK"))
|
|
219
|
+
cache = @client.send(:thread_connections)
|
|
220
|
+
cache["https:api.workos.com:443:30"] = conn
|
|
221
|
+
|
|
222
|
+
@client.execute_request(request: Net::HTTP::Get.new("/things"))
|
|
223
|
+
|
|
224
|
+
assert cache.key?("https:api.workos.com:443:30")
|
|
225
|
+
refute conn.finished
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
# Raised asynchronously into the worker thread to stand in for a caller-side
|
|
229
|
+
# abort: Timeout.timeout, Thread#kill, a signal handler unwinding the stack.
|
|
230
|
+
# It descends from Exception rather than StandardError so that nothing in
|
|
231
|
+
# execute_request can catch it — the `ensure` is the only cleanup that runs.
|
|
232
|
+
class AbortSignal < Exception; end # standard:disable Lint/InheritException
|
|
233
|
+
|
|
234
|
+
# End-to-end regression test over a real keep-alive socket, for the failure
|
|
235
|
+
# the eviction actually prevents: request one is abandoned mid-flight, the
|
|
236
|
+
# server then writes response one onto that socket, and request two on the
|
|
237
|
+
# same thread reads those stale bytes as if they were its own response.
|
|
238
|
+
#
|
|
239
|
+
# Before the fix, request two sees marker "one" (or a mangled response) off
|
|
240
|
+
# the pooled socket. After it, the abandoned socket is closed and the server
|
|
241
|
+
# accepts a fresh connection for request two.
|
|
242
|
+
def test_aborted_request_does_not_leak_its_response_to_the_next_request
|
|
243
|
+
WebMock.disable!
|
|
244
|
+
server = TCPServer.new("127.0.0.1", 0)
|
|
245
|
+
port = server.addr[1]
|
|
246
|
+
client = WorkOS::BaseClient.new(
|
|
247
|
+
api_key: "sk_test_123",
|
|
248
|
+
base_url: "http://127.0.0.1:#{port}",
|
|
249
|
+
timeout: 5,
|
|
250
|
+
max_retries: 0
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
request_one_read = Queue.new
|
|
254
|
+
release_response_one = Queue.new
|
|
255
|
+
response_one_written = Queue.new
|
|
256
|
+
aborted = Queue.new
|
|
257
|
+
connections = Queue.new
|
|
258
|
+
|
|
259
|
+
server_thread = Thread.new do
|
|
260
|
+
# Connection one: read the request, then hold the response back until
|
|
261
|
+
# the client has been aborted and has unwound.
|
|
262
|
+
first = server.accept
|
|
263
|
+
connections << first
|
|
264
|
+
read_http_request(first)
|
|
265
|
+
request_one_read << true
|
|
266
|
+
release_response_one.pop
|
|
267
|
+
begin
|
|
268
|
+
write_http_response(first, {marker: "one"})
|
|
269
|
+
rescue Errno::EPIPE, Errno::ECONNRESET, IOError
|
|
270
|
+
# Expected once the fix closes the abandoned socket.
|
|
271
|
+
end
|
|
272
|
+
response_one_written << true
|
|
273
|
+
|
|
274
|
+
# Connection two: a fresh accept, which only completes because the
|
|
275
|
+
# client did not reuse the socket above.
|
|
276
|
+
second = server.accept
|
|
277
|
+
connections << second
|
|
278
|
+
read_http_request(second)
|
|
279
|
+
write_http_response(second, {marker: "two"})
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
worker = Thread.new do
|
|
283
|
+
begin
|
|
284
|
+
client.execute_request(request: Net::HTTP::Get.new("/one"))
|
|
285
|
+
rescue AbortSignal
|
|
286
|
+
# The caller unwinds but the thread survives and goes on to serve more
|
|
287
|
+
# work, the way a Puma or Solid Queue worker does. execute_request's
|
|
288
|
+
# `ensure` has already run by the time this body executes.
|
|
289
|
+
aborted << true
|
|
290
|
+
response_one_written.pop
|
|
291
|
+
end
|
|
292
|
+
response = client.execute_request(request: Net::HTTP::Get.new("/two"))
|
|
293
|
+
JSON.parse(response.body)["marker"]
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
marker = Timeout.timeout(15) do
|
|
297
|
+
request_one_read.pop
|
|
298
|
+
worker.raise(AbortSignal)
|
|
299
|
+
aborted.pop
|
|
300
|
+
release_response_one << true
|
|
301
|
+
worker.value
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
assert_equal "two", marker,
|
|
305
|
+
"request two read the abandoned socket's response instead of its own"
|
|
306
|
+
ensure
|
|
307
|
+
server_thread&.kill
|
|
308
|
+
worker&.kill
|
|
309
|
+
until connections.nil? || connections.empty?
|
|
310
|
+
socket = connections.pop
|
|
311
|
+
socket.close unless socket.closed?
|
|
312
|
+
end
|
|
313
|
+
server&.close
|
|
314
|
+
WebMock.enable!
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
def read_http_request(socket)
|
|
318
|
+
socket.gets # request line
|
|
319
|
+
loop do
|
|
320
|
+
line = socket.gets
|
|
321
|
+
break if line.nil? || line == "\r\n"
|
|
322
|
+
end
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
def write_http_response(socket, body)
|
|
326
|
+
payload = JSON.generate(body)
|
|
327
|
+
socket.write(
|
|
328
|
+
"HTTP/1.1 200 OK\r\n" \
|
|
329
|
+
"Content-Type: application/json\r\n" \
|
|
330
|
+
"Content-Length: #{payload.bytesize}\r\n" \
|
|
331
|
+
"Connection: keep-alive\r\n" \
|
|
332
|
+
"\r\n#{payload}"
|
|
333
|
+
)
|
|
334
|
+
socket.flush
|
|
335
|
+
end
|
|
336
|
+
|
|
182
337
|
# Regression test for https://github.com/workos/workos-ruby/issues/496
|
|
183
338
|
#
|
|
184
339
|
# The connection cache must be isolated per thread. A previous version
|
|
@@ -145,6 +145,36 @@ class UserManagementTest < Minitest::Test
|
|
|
145
145
|
end
|
|
146
146
|
end
|
|
147
147
|
|
|
148
|
+
def test_authenticate_with_radar_email_challenge_returns_expected_result
|
|
149
|
+
stub_request(:post, %r{\Ahttps://api\.workos\.com/user_management/authenticate(\?|\z)})
|
|
150
|
+
.to_return(body: "{}", status: 200)
|
|
151
|
+
result = @client.user_management.authenticate_with_radar_email_challenge(code: "stub", radar_challenge_id: "stub", pending_authentication_token: "stub")
|
|
152
|
+
refute_nil result
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def test_authenticate_with_radar_email_challenge_raises_authentication_error_on_401
|
|
156
|
+
stub_request(:post, %r{\Ahttps://api\.workos\.com/user_management/authenticate(\?|\z)})
|
|
157
|
+
.to_return(body: '{"message": "Unauthorized"}', status: 401)
|
|
158
|
+
assert_raises(WorkOS::AuthenticationError) do
|
|
159
|
+
@client.user_management.authenticate_with_radar_email_challenge(code: "stub", radar_challenge_id: "stub", pending_authentication_token: "stub")
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def test_authenticate_with_radar_sms_challenge_returns_expected_result
|
|
164
|
+
stub_request(:post, %r{\Ahttps://api\.workos\.com/user_management/authenticate(\?|\z)})
|
|
165
|
+
.to_return(body: "{}", status: 200)
|
|
166
|
+
result = @client.user_management.authenticate_with_radar_sms_challenge(code: "stub", verification_id: "stub", phone_number: "stub", pending_authentication_token: "stub")
|
|
167
|
+
refute_nil result
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def test_authenticate_with_radar_sms_challenge_raises_authentication_error_on_401
|
|
171
|
+
stub_request(:post, %r{\Ahttps://api\.workos\.com/user_management/authenticate(\?|\z)})
|
|
172
|
+
.to_return(body: '{"message": "Unauthorized"}', status: 401)
|
|
173
|
+
assert_raises(WorkOS::AuthenticationError) do
|
|
174
|
+
@client.user_management.authenticate_with_radar_sms_challenge(code: "stub", verification_id: "stub", phone_number: "stub", pending_authentication_token: "stub")
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
|
|
148
178
|
def test_create_device_returns_expected_result
|
|
149
179
|
stub_request(:post, %r{\Ahttps://api\.workos\.com/user_management/authorize/device(\?|\z)})
|
|
150
180
|
.to_return(body: "{}", status: 200)
|
|
@@ -928,21 +928,60 @@ class UserManagementModelRoundTripTest < Minitest::Test
|
|
|
928
928
|
|
|
929
929
|
def test_magic_auth_send_magic_auth_code_and_return_response_round_trip
|
|
930
930
|
fixture = {
|
|
931
|
+
"object" => "magic_auth",
|
|
932
|
+
"id" => "stub",
|
|
933
|
+
"user_id" => "stub",
|
|
934
|
+
"email" => "stub",
|
|
935
|
+
"expires_at" => "stub",
|
|
936
|
+
"created_at" => "stub",
|
|
937
|
+
"updated_at" => "stub",
|
|
938
|
+
"code" => "stub",
|
|
931
939
|
"radar_auth_attempt_id" => "stub"
|
|
932
940
|
}
|
|
933
941
|
model = WorkOS::MagicAuthSendMagicAuthCodeAndReturnResponse.new(fixture.to_json)
|
|
934
942
|
json = model.to_h
|
|
935
943
|
assert_kind_of Hash, json
|
|
944
|
+
assert_equal fixture["id"], json[:id]
|
|
945
|
+
assert_equal fixture["user_id"], json[:user_id]
|
|
946
|
+
assert_equal fixture["email"], json[:email]
|
|
947
|
+
assert_equal fixture["expires_at"], json[:expires_at]
|
|
948
|
+
assert_equal fixture["created_at"], json[:created_at]
|
|
949
|
+
assert_equal fixture["updated_at"], json[:updated_at]
|
|
950
|
+
assert_equal fixture["code"], json[:code]
|
|
936
951
|
fixture.each_key { |k| assert json.key?(k.to_sym) || json.key?(k), "Expected to_h to include key #{k}" }
|
|
937
952
|
end
|
|
938
953
|
|
|
939
954
|
def test_user_create_response_round_trip
|
|
940
955
|
fixture = {
|
|
956
|
+
"object" => "user",
|
|
957
|
+
"id" => "stub",
|
|
958
|
+
"first_name" => nil,
|
|
959
|
+
"last_name" => nil,
|
|
960
|
+
"name" => nil,
|
|
961
|
+
"profile_picture_url" => nil,
|
|
962
|
+
"email" => "stub",
|
|
963
|
+
"email_verified" => true,
|
|
964
|
+
"external_id" => nil,
|
|
965
|
+
"metadata" => {},
|
|
966
|
+
"last_sign_in_at" => nil,
|
|
967
|
+
"locale" => nil,
|
|
968
|
+
"created_at" => "stub",
|
|
969
|
+
"updated_at" => "stub",
|
|
941
970
|
"radar_auth_attempt_id" => "stub"
|
|
942
971
|
}
|
|
943
972
|
model = WorkOS::UserCreateResponse.new(fixture.to_json)
|
|
944
973
|
json = model.to_h
|
|
945
974
|
assert_kind_of Hash, json
|
|
975
|
+
assert_equal fixture["id"], json[:id]
|
|
976
|
+
assert_nil json[:first_name]
|
|
977
|
+
assert_nil json[:last_name]
|
|
978
|
+
assert_nil json[:profile_picture_url]
|
|
979
|
+
assert_equal fixture["email"], json[:email]
|
|
980
|
+
assert_equal fixture["email_verified"], json[:email_verified]
|
|
981
|
+
assert_nil json[:external_id]
|
|
982
|
+
assert_nil json[:last_sign_in_at]
|
|
983
|
+
assert_equal fixture["created_at"], json[:created_at]
|
|
984
|
+
assert_equal fixture["updated_at"], json[:updated_at]
|
|
946
985
|
fixture.each_key { |k| assert json.key?(k.to_sym) || json.key?(k), "Expected to_h to include key #{k}" }
|
|
947
986
|
end
|
|
948
987
|
|