workos 5.20.0 → 5.22.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: dc1f97c76fdf489cb3be51bd9026dc515195c34aaf9810379bfe4773029aafc1
4
- data.tar.gz: 2176f6680ae6a7c46ca77824c7dcf2fbe91ca244986a05f7039a1d40265db881
3
+ metadata.gz: 65c18f67663b8681a74442c6771d1527b66fecd20a9c114557255d4ce8d9ca38
4
+ data.tar.gz: 242b03d06e9caa456b3128d284fee00481fb4e7edec79934fd22c2e57598901b
5
5
  SHA512:
6
- metadata.gz: 6bda209314485831a8237e381566472c5815dea3b727db277e2a06c3db1c06191da54dc59292ec37bd4415291ca78c7593ae4a78cad82775ce6ffb8eae6d0931
7
- data.tar.gz: fd199d57bd3e29e0bea6b21840597ae255db869c9815b6a06348a90c3d70532606732c999c8bef3caa2ba2900914c20b222b526c7e1a9b61c836f30f5e55d694
6
+ metadata.gz: f6f3ccbbc9b6026beee00f92fcb8d87ce3f321ba8be4c808cd524daeb1dc1aa7fa13ef3c9c885096f83e8a68347b70a78d2d9297a396a8071727506e31787c9f
7
+ data.tar.gz: d8f71832e8767dcfbcc2bcad5760ff5f59cbb3513a73708dc41c50dd5d4f3ac515788a8b81c596b9bb069244d912f3c3a2db9b1e39a0e34ff920bc87a7233b39
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- workos (5.20.0)
4
+ workos (5.22.0)
5
5
  encryptor (~> 3.0)
6
6
  jwt (~> 2.8)
7
7
 
@@ -185,7 +185,14 @@ module WorkOS
185
185
 
186
186
  # Retrieve a list of roles for the given organization.
187
187
  #
188
- # @param [String] organizationId The ID of the organization to fetch roles for.
188
+ # @param [String] organization_id The ID of the organization to fetch roles for.
189
+ #
190
+ # @example
191
+ # WorkOS::Organizations.list_organization_roles(organization_id: 'org_01EHZNVPK3SFK441A1RGBFSHRT')
192
+ # => #<WorkOS::Types::ListStruct data=[#<WorkOS::Role id="role_123" name="Admin" slug="admin"
193
+ # permissions=["admin:all"] ...>] ...>
194
+ #
195
+ # @return [WorkOS::Types::ListStruct] - Collection of Role objects, each including permissions array
189
196
  def list_organization_roles(organization_id:)
190
197
  response = execute_request(
191
198
  request: get_request(
data/lib/workos/role.rb CHANGED
@@ -7,7 +7,7 @@ module WorkOS
7
7
  class Role
8
8
  include HashProvider
9
9
 
10
- attr_accessor :id, :name, :slug, :description, :type, :created_at, :updated_at
10
+ attr_accessor :id, :name, :slug, :description, :permissions, :type, :created_at, :updated_at
11
11
 
12
12
  def initialize(json)
13
13
  hash = JSON.parse(json, symbolize_names: true)
@@ -16,6 +16,7 @@ module WorkOS
16
16
  @name = hash[:name]
17
17
  @slug = hash[:slug]
18
18
  @description = hash[:description]
19
+ @permissions = hash[:permissions] || []
19
20
  @type = hash[:type]
20
21
  @created_at = hash[:created_at]
21
22
  @updated_at = hash[:updated_at]
@@ -27,6 +28,7 @@ module WorkOS
27
28
  name: name,
28
29
  slug: slug,
29
30
  description: description,
31
+ permissions: permissions,
30
32
  type: type,
31
33
  created_at: created_at,
32
34
  updated_at: updated_at,
@@ -51,6 +51,7 @@ module WorkOS
51
51
  role: decoded['role'],
52
52
  permissions: decoded['permissions'],
53
53
  entitlements: decoded['entitlements'],
54
+ feature_flags: decoded['feature_flags'],
54
55
  user: session[:user],
55
56
  impersonator: session[:impersonator],
56
57
  reason: nil,
data/lib/workos/sso.rb CHANGED
@@ -120,8 +120,9 @@ module WorkOS
120
120
  code: code,
121
121
  }
122
122
 
123
- response = client.request(post_request(path: '/sso/token', body: body))
124
- check_and_raise_profile_and_token_error(response: response)
123
+ response = execute_request(
124
+ request: post_request(path: '/sso/token', body: body),
125
+ )
125
126
 
126
127
  WorkOS::ProfileAndToken.new(response.body)
127
128
  end
@@ -229,28 +230,6 @@ module WorkOS
229
230
  raise ArgumentError, "#{provider} is not a valid value." \
230
231
  " `provider` must be in #{PROVIDERS}"
231
232
  end
232
-
233
- def check_and_raise_profile_and_token_error(response:)
234
- begin
235
- body = JSON.parse(response.body)
236
- return if body['access_token'] && body['profile']
237
-
238
- message = body['message']
239
- error = body['error']
240
- error_description = body['error_description']
241
- request_id = response['x-request-id']
242
- rescue StandardError
243
- message = 'Something went wrong'
244
- end
245
-
246
- raise APIError.new(
247
- message: message,
248
- error: error,
249
- error_description: error_description,
250
- http_status: nil,
251
- request_id: request_id,
252
- )
253
- end
254
233
  end
255
234
  end
256
235
  end
data/lib/workos/user.rb CHANGED
@@ -8,7 +8,7 @@ module WorkOS
8
8
  include HashProvider
9
9
 
10
10
  attr_accessor :id, :email, :first_name, :last_name, :email_verified,
11
- :profile_picture_url, :last_sign_in_at, :created_at, :updated_at
11
+ :profile_picture_url, :external_id, :last_sign_in_at, :created_at, :updated_at
12
12
 
13
13
  def initialize(json)
14
14
  hash = JSON.parse(json, symbolize_names: true)
@@ -19,6 +19,7 @@ module WorkOS
19
19
  @last_name = hash[:last_name]
20
20
  @email_verified = hash[:email_verified]
21
21
  @profile_picture_url = hash[:profile_picture_url]
22
+ @external_id = hash[:external_id]
22
23
  @last_sign_in_at = hash[:last_sign_in_at]
23
24
  @created_at = hash[:created_at]
24
25
  @updated_at = hash[:updated_at]
@@ -32,6 +33,7 @@ module WorkOS
32
33
  last_name: last_name,
33
34
  email_verified: email_verified,
34
35
  profile_picture_url: profile_picture_url,
36
+ external_id: external_id,
35
37
  last_sign_in_at: last_sign_in_at,
36
38
  created_at: created_at,
37
39
  updated_at: updated_at,
@@ -206,7 +206,7 @@ module WorkOS
206
206
  email_verified: email_verified,
207
207
  password_hash: password_hash,
208
208
  password_hash_type: password_hash_type,
209
- },
209
+ }.compact,
210
210
  auth: true,
211
211
  )
212
212
 
@@ -222,6 +222,7 @@ module WorkOS
222
222
  # @param [String] first_name The user's first name.
223
223
  # @param [String] last_name The user's last name.
224
224
  # @param [Boolean] email_verified Whether the user's email address was previously verified.
225
+ # @param [String] external_id The users's external ID
225
226
  # @param [String] password The user's password.
226
227
  # @param [String] password_hash The user's hashed password.
227
228
  # @option [String] password_hash_type The algorithm originally used to hash the password.
@@ -234,6 +235,7 @@ module WorkOS
234
235
  first_name: nil,
235
236
  last_name: nil,
236
237
  email_verified: nil,
238
+ external_id: nil,
237
239
  password: nil,
238
240
  password_hash: nil,
239
241
  password_hash_type: nil
@@ -245,10 +247,11 @@ module WorkOS
245
247
  first_name: first_name,
246
248
  last_name: last_name,
247
249
  email_verified: email_verified,
250
+ external_id: external_id,
248
251
  password: password,
249
252
  password_hash: password_hash,
250
253
  password_hash_type: password_hash_type,
251
- },
254
+ }.compact,
252
255
  auth: true,
253
256
  )
254
257
 
@@ -640,7 +643,7 @@ module WorkOS
640
643
  body: {
641
644
  email: email,
642
645
  invitation_token: invitation_token,
643
- },
646
+ }.compact,
644
647
  auth: true,
645
648
  ),
646
649
  )
@@ -694,7 +697,7 @@ module WorkOS
694
697
  totp_issuer: totp_issuer,
695
698
  totp_user: totp_user,
696
699
  totp_secret: totp_secret,
697
- },
700
+ }.compact,
698
701
  auth: true,
699
702
  ),
700
703
  )
@@ -925,7 +928,7 @@ module WorkOS
925
928
  user_id: user_id,
926
929
  organization_id: organization_id,
927
930
  role_slug: role_slug,
928
- },
931
+ }.compact,
929
932
  auth: true,
930
933
  )
931
934
 
@@ -1090,7 +1093,7 @@ module WorkOS
1090
1093
  expires_in_days: expires_in_days,
1091
1094
  inviter_user_id: inviter_user_id,
1092
1095
  role_slug: role_slug,
1093
- },
1096
+ }.compact,
1094
1097
  auth: true,
1095
1098
  ),
1096
1099
  )
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module WorkOS
4
- VERSION = '5.20.0'
4
+ VERSION = '5.22.0'
5
5
  end
@@ -354,6 +354,48 @@ describe WorkOS::Organizations do
354
354
  expect(roles.list_metadata).to eq(expected_metadata)
355
355
  end
356
356
  end
357
+
358
+ it 'returns properly initialized Role objects with all attributes' do
359
+ VCR.use_cassette 'organization/list_organization_roles' do
360
+ roles = described_class.list_organization_roles(organization_id: 'org_01JEXP6Z3X7HE4CB6WQSH9ZAFE')
361
+
362
+ first_role = roles.data.first
363
+ expect(first_role).to be_a(WorkOS::Role)
364
+ expect(first_role.id).to eq('role_01HS1C7GRJE08PBR3M6Y0ZYGDZ')
365
+ expect(first_role.name).to eq('Admin')
366
+ expect(first_role.slug).to eq('admin')
367
+ expect(first_role.description).to eq('Write access to every resource available')
368
+ expect(first_role.permissions).to eq(['admin:all', 'read:users', 'write:users', 'manage:roles'])
369
+ expect(first_role.type).to eq('EnvironmentRole')
370
+ expect(first_role.created_at).to eq('2024-03-15T15:38:29.521Z')
371
+ expect(first_role.updated_at).to eq('2024-11-14T17:08:00.556Z')
372
+ end
373
+ end
374
+
375
+ it 'handles roles with empty permissions arrays' do
376
+ VCR.use_cassette 'organization/list_organization_roles' do
377
+ roles = described_class.list_organization_roles(organization_id: 'org_01JEXP6Z3X7HE4CB6WQSH9ZAFE')
378
+
379
+ platform_manager_role = roles.data.find { |role| role.slug == 'org-platform-manager' }
380
+ expect(platform_manager_role).to be_a(WorkOS::Role)
381
+ expect(platform_manager_role.permissions).to eq([])
382
+ end
383
+ end
384
+
385
+ it 'properly serializes Role objects including permissions' do
386
+ VCR.use_cassette 'organization/list_organization_roles' do
387
+ roles = described_class.list_organization_roles(organization_id: 'org_01JEXP6Z3X7HE4CB6WQSH9ZAFE')
388
+
389
+ billing_role = roles.data.find { |role| role.slug == 'billing' }
390
+ serialized = billing_role.to_json
391
+
392
+ expect(serialized[:id]).to eq('role_01JA8GJZRDSZEB9289DQXJ3N9Z')
393
+ expect(serialized[:name]).to eq('Billing Manager')
394
+ expect(serialized[:slug]).to eq('billing')
395
+ expect(serialized[:permissions]).to eq(['read:billing', 'write:billing'])
396
+ expect(serialized[:type]).to eq('EnvironmentRole')
397
+ end
398
+ end
357
399
  end
358
400
  end
359
401
  end
@@ -0,0 +1,142 @@
1
+ # frozen_string_literal: true
2
+
3
+ describe WorkOS::Role do
4
+ describe '.initialize' do
5
+ context 'with full role data including permissions' do
6
+ it 'initializes all attributes correctly' do
7
+ role_json = {
8
+ id: 'role_01FAEAJCJ3P1Z6WP5Y9VQPN2XY',
9
+ name: 'Admin',
10
+ slug: 'admin',
11
+ description: 'Administrator role with full access',
12
+ permissions: ['read:users', 'write:users', 'admin:all'],
13
+ type: 'system',
14
+ created_at: '2022-05-13T17:45:31.732Z',
15
+ updated_at: '2022-07-13T17:45:42.618Z',
16
+ }.to_json
17
+
18
+ role = described_class.new(role_json)
19
+
20
+ expect(role.id).to eq('role_01FAEAJCJ3P1Z6WP5Y9VQPN2XY')
21
+ expect(role.name).to eq('Admin')
22
+ expect(role.slug).to eq('admin')
23
+ expect(role.description).to eq('Administrator role with full access')
24
+ expect(role.permissions).to eq(['read:users', 'write:users', 'admin:all'])
25
+ expect(role.type).to eq('system')
26
+ expect(role.created_at).to eq('2022-05-13T17:45:31.732Z')
27
+ expect(role.updated_at).to eq('2022-07-13T17:45:42.618Z')
28
+ end
29
+ end
30
+
31
+ context 'with role data without permissions' do
32
+ it 'initializes permissions as empty array' do
33
+ role_json = {
34
+ id: 'role_01FAEAJCJ3P1Z6WP5Y9VQPN2XY',
35
+ name: 'User',
36
+ slug: 'user',
37
+ description: 'Basic user role',
38
+ type: 'custom',
39
+ created_at: '2022-05-13T17:45:31.732Z',
40
+ updated_at: '2022-07-13T17:45:42.618Z',
41
+ }.to_json
42
+
43
+ role = described_class.new(role_json)
44
+
45
+ expect(role.id).to eq('role_01FAEAJCJ3P1Z6WP5Y9VQPN2XY')
46
+ expect(role.name).to eq('User')
47
+ expect(role.slug).to eq('user')
48
+ expect(role.description).to eq('Basic user role')
49
+ expect(role.permissions).to eq([])
50
+ expect(role.type).to eq('custom')
51
+ expect(role.created_at).to eq('2022-05-13T17:45:31.732Z')
52
+ expect(role.updated_at).to eq('2022-07-13T17:45:42.618Z')
53
+ end
54
+ end
55
+
56
+ context 'with role data with null permissions' do
57
+ it 'initializes permissions as empty array' do
58
+ role_json = {
59
+ id: 'role_01FAEAJCJ3P1Z6WP5Y9VQPN2XY',
60
+ name: 'User',
61
+ slug: 'user',
62
+ description: 'Basic user role',
63
+ permissions: nil,
64
+ type: 'custom',
65
+ created_at: '2022-05-13T17:45:31.732Z',
66
+ updated_at: '2022-07-13T17:45:42.618Z',
67
+ }.to_json
68
+
69
+ role = described_class.new(role_json)
70
+
71
+ expect(role.permissions).to eq([])
72
+ end
73
+ end
74
+
75
+ context 'with role data with empty permissions array' do
76
+ it 'preserves empty permissions array' do
77
+ role_json = {
78
+ id: 'role_01FAEAJCJ3P1Z6WP5Y9VQPN2XY',
79
+ name: 'User',
80
+ slug: 'user',
81
+ description: 'Basic user role',
82
+ permissions: [],
83
+ type: 'custom',
84
+ created_at: '2022-05-13T17:45:31.732Z',
85
+ updated_at: '2022-07-13T17:45:42.618Z',
86
+ }.to_json
87
+
88
+ role = described_class.new(role_json)
89
+
90
+ expect(role.permissions).to eq([])
91
+ end
92
+ end
93
+ end
94
+
95
+ describe '.to_json' do
96
+ context 'with role that has permissions' do
97
+ it 'includes permissions in serialized output' do
98
+ role_json = {
99
+ id: 'role_01FAEAJCJ3P1Z6WP5Y9VQPN2XY',
100
+ name: 'Admin',
101
+ slug: 'admin',
102
+ description: 'Administrator role',
103
+ permissions: ['read:all', 'write:all'],
104
+ type: 'system',
105
+ created_at: '2022-05-13T17:45:31.732Z',
106
+ updated_at: '2022-07-13T17:45:42.618Z',
107
+ }.to_json
108
+
109
+ role = described_class.new(role_json)
110
+ serialized = role.to_json
111
+
112
+ expect(serialized[:id]).to eq('role_01FAEAJCJ3P1Z6WP5Y9VQPN2XY')
113
+ expect(serialized[:name]).to eq('Admin')
114
+ expect(serialized[:slug]).to eq('admin')
115
+ expect(serialized[:description]).to eq('Administrator role')
116
+ expect(serialized[:permissions]).to eq(['read:all', 'write:all'])
117
+ expect(serialized[:type]).to eq('system')
118
+ expect(serialized[:created_at]).to eq('2022-05-13T17:45:31.732Z')
119
+ expect(serialized[:updated_at]).to eq('2022-07-13T17:45:42.618Z')
120
+ end
121
+ end
122
+
123
+ context 'with role that has no permissions' do
124
+ it 'includes empty permissions array in serialized output' do
125
+ role_json = {
126
+ id: 'role_01FAEAJCJ3P1Z6WP5Y9VQPN2XY',
127
+ name: 'User',
128
+ slug: 'user',
129
+ description: 'Basic user role',
130
+ type: 'custom',
131
+ created_at: '2022-05-13T17:45:31.732Z',
132
+ updated_at: '2022-07-13T17:45:42.618Z',
133
+ }.to_json
134
+
135
+ role = described_class.new(role_json)
136
+ serialized = role.to_json
137
+
138
+ expect(serialized[:permissions]).to eq([])
139
+ end
140
+ end
141
+ end
142
+ end
@@ -174,6 +174,7 @@ describe WorkOS::Session do
174
174
  organization_id: 'org_id',
175
175
  role: 'role',
176
176
  permissions: ['read'],
177
+ feature_flags: nil,
177
178
  entitlements: nil,
178
179
  user: 'user',
179
180
  impersonator: 'impersonator',
@@ -209,6 +210,43 @@ describe WorkOS::Session do
209
210
  role: 'role',
210
211
  permissions: ['read'],
211
212
  entitlements: ['billing'],
213
+ feature_flags: nil,
214
+ user: 'user',
215
+ impersonator: 'impersonator',
216
+ reason: nil,
217
+ })
218
+ end
219
+ end
220
+
221
+ describe 'with feature flags' do
222
+ let(:payload) do
223
+ {
224
+ sid: 'session_id',
225
+ org_id: 'org_id',
226
+ role: 'role',
227
+ permissions: ['read'],
228
+ feature_flags: ['new_feature_enabled'],
229
+ exp: Time.now.to_i + 3600,
230
+ }
231
+ end
232
+
233
+ it 'includes feature flags in the result' do
234
+ session = WorkOS::Session.new(
235
+ user_management: user_management,
236
+ client_id: client_id,
237
+ session_data: session_data,
238
+ cookie_password: cookie_password,
239
+ )
240
+ allow_any_instance_of(JWT::Decode).to receive(:verify_signature).and_return(true)
241
+ result = session.authenticate
242
+ expect(result).to eq({
243
+ authenticated: true,
244
+ session_id: 'session_id',
245
+ organization_id: 'org_id',
246
+ role: 'role',
247
+ permissions: ['read'],
248
+ entitlements: nil,
249
+ feature_flags: ['new_feature_enabled'],
212
250
  user: 'user',
213
251
  impersonator: 'impersonator',
214
252
  reason: nil,
@@ -416,10 +416,73 @@ describe WorkOS::SSO do
416
416
  expect do
417
417
  described_class.profile_and_token(**args)
418
418
  end.to raise_error(
419
- WorkOS::APIError,
420
- 'error: some error, error_description: some error description - request ID: request-id',
419
+ WorkOS::UnprocessableEntityError,
420
+ 'Status 422, some error - request ID: request-id',
421
421
  )
422
422
  end
423
+
424
+ it 'raises an exception with proper error object attributes' do
425
+ expect do
426
+ described_class.profile_and_token(**args)
427
+ end.to raise_error(WorkOS::UnprocessableEntityError)
428
+ end
429
+
430
+ it 'includes proper error attributes' do
431
+ error = begin
432
+ described_class.profile_and_token(**args)
433
+ rescue WorkOS::UnprocessableEntityError => e
434
+ e
435
+ end
436
+
437
+ expect(error.http_status).to eq(422)
438
+ expect(error.request_id).to eq('request-id')
439
+ expect(error.error).to eq('some error')
440
+ expect(error.message).to include('some error')
441
+ end
442
+ end
443
+
444
+ context 'with detailed field validation errors' do
445
+ before do
446
+ stub_request(:post, 'https://api.workos.com/sso/token').
447
+ with(headers: headers, body: request_body).
448
+ to_return(
449
+ headers: { 'X-Request-ID' => 'request-id' },
450
+ status: 422,
451
+ body: {
452
+ "message": 'Validation failed',
453
+ "code": 'invalid_request_parameters',
454
+ "errors": [
455
+ {
456
+ "field": 'code',
457
+ "code": 'missing_required_parameter',
458
+ "message": 'The code parameter is required',
459
+ }
460
+ ],
461
+ }.to_json,
462
+ )
463
+ end
464
+
465
+ it 'raises an exception with detailed field errors' do
466
+ expect do
467
+ described_class.profile_and_token(**args)
468
+ end.to raise_error(WorkOS::UnprocessableEntityError)
469
+ end
470
+
471
+ it 'includes detailed field error attributes' do
472
+ error = begin
473
+ described_class.profile_and_token(**args)
474
+ rescue WorkOS::UnprocessableEntityError => e
475
+ e
476
+ end
477
+
478
+ expect(error.http_status).to eq(422)
479
+ expect(error.request_id).to eq('request-id')
480
+ expect(error.code).to eq('invalid_request_parameters')
481
+ expect(error.errors).not_to be_nil
482
+ expect(error.errors).to include('code: missing_required_parameter')
483
+ expect(error.message).to include('Validation failed')
484
+ expect(error.message).to include('(code: missing_required_parameter)')
485
+ end
423
486
  end
424
487
 
425
488
  context 'with an expired code' do
@@ -440,11 +503,31 @@ describe WorkOS::SSO do
440
503
  expect do
441
504
  described_class.profile_and_token(**args)
442
505
  end.to raise_error(
443
- WorkOS::APIError,
444
- "error: invalid_grant, error_description: The code '01DVX3C5Z367SFHR8QNDMK7V24'" \
506
+ WorkOS::InvalidRequestError,
507
+ "Status 400, error: invalid_grant, error_description: The code '01DVX3C5Z367SFHR8QNDMK7V24'" \
445
508
  ' has expired or is invalid. - request ID: request-id',
446
509
  )
447
510
  end
511
+
512
+ it 'raises an exception with proper error object attributes' do
513
+ expect do
514
+ described_class.profile_and_token(**args)
515
+ end.to raise_error(WorkOS::InvalidRequestError)
516
+ end
517
+
518
+ it 'includes proper error attributes' do
519
+ error = begin
520
+ described_class.profile_and_token(**args)
521
+ rescue WorkOS::InvalidRequestError => e
522
+ e
523
+ end
524
+
525
+ expect(error.http_status).to eq(400)
526
+ expect(error.request_id).to eq('request-id')
527
+ expect(error.error).to eq('invalid_grant')
528
+ expect(error.error_description).to eq("The code '01DVX3C5Z367SFHR8QNDMK7V24' has expired or is invalid.")
529
+ expect(error.message).to include('invalid_grant')
530
+ end
448
531
  end
449
532
  end
450
533
 
@@ -338,6 +338,26 @@ describe WorkOS::UserManagement do
338
338
  end
339
339
  end
340
340
 
341
+ it 'only sends non-nil values in request body' do
342
+ expect(described_class).to receive(:post_request) do |options|
343
+ body = options[:body]
344
+ expect(body).to eq({ email: 'test@example.com', first_name: 'John' })
345
+ expect(body).not_to have_key(:last_name)
346
+ expect(body).not_to have_key(:email_verified)
347
+
348
+ double('request')
349
+ end.and_return(double('request'))
350
+
351
+ expect(described_class).to receive(:execute_request).and_return(
352
+ double('response', body: '{"id": "test_user", "email": "test@example.com"}'),
353
+ )
354
+
355
+ described_class.create_user(
356
+ email: 'test@example.com',
357
+ first_name: 'John',
358
+ )
359
+ end
360
+
341
361
  context 'with an invalid payload' do
342
362
  it 'returns an error' do
343
363
  VCR.use_cassette 'user_management/create_user_invalid' do
@@ -362,10 +382,12 @@ describe WorkOS::UserManagement do
362
382
  first_name: 'Jane',
363
383
  last_name: 'Doe',
364
384
  email_verified: false,
385
+ external_id: '123',
365
386
  )
366
387
  expect(user.first_name).to eq('Jane')
367
388
  expect(user.last_name).to eq('Doe')
368
389
  expect(user.email_verified).to eq(false)
390
+ expect(user.external_id).to eq('123')
369
391
  end
370
392
  end
371
393
 
@@ -380,6 +402,30 @@ describe WorkOS::UserManagement do
380
402
  end
381
403
  end
382
404
 
405
+ it 'only sends non-nil values in request body' do
406
+ # Mock the request to inspect what's being sent
407
+ expect(described_class).to receive(:put_request) do |options|
408
+ # Verify that the body only contains non-nil values
409
+ body = options[:body]
410
+ expect(body).to eq({ email_verified: true })
411
+ expect(body).not_to have_key(:first_name)
412
+ expect(body).not_to have_key(:last_name)
413
+ expect(body).not_to have_key(:email)
414
+
415
+ # Return a mock request object
416
+ double('request')
417
+ end.and_return(double('request'))
418
+
419
+ expect(described_class).to receive(:execute_request).and_return(
420
+ double('response', body: '{"id": "test_user", "email_verified": true}'),
421
+ )
422
+
423
+ described_class.update_user(
424
+ id: 'user_01H7TVSKS45SDHN5V9XPSM6H44',
425
+ email_verified: true,
426
+ )
427
+ end
428
+
383
429
  context 'with an invalid payload' do
384
430
  it 'returns an error' do
385
431
  VCR.use_cassette 'user_management/update_user/invalid' do
@@ -776,6 +822,28 @@ describe WorkOS::UserManagement do
776
822
  expect(authentication_response.authentication_challenge.id).to eq('auth_challenge_01H96FETXGTW1QMBSBT2T36PW0')
777
823
  end
778
824
  end
825
+
826
+ it 'only sends non-nil values in request body' do
827
+ expect(described_class).to receive(:post_request) do |options|
828
+ body = options[:body]
829
+ expect(body).to eq({ type: 'totp', totp_issuer: 'Test App' })
830
+ expect(body).not_to have_key(:totp_user)
831
+ expect(body).not_to have_key(:totp_secret)
832
+
833
+ double('request')
834
+ end.and_return(double('request'))
835
+
836
+ expect(described_class).to receive(:execute_request).and_return(
837
+ double('response',
838
+ body: '{"authentication_factor": {"id": "test"}, "authentication_challenge": {"id": "test"}}',),
839
+ )
840
+
841
+ described_class.enroll_auth_factor(
842
+ user_id: 'user_123',
843
+ type: 'totp',
844
+ totp_issuer: 'Test App',
845
+ )
846
+ end
779
847
  end
780
848
 
781
849
  context 'with an incorrect user id' do
@@ -1442,6 +1510,27 @@ describe WorkOS::UserManagement do
1442
1510
  expect(invitation.email).to eq('test@workos.com')
1443
1511
  end
1444
1512
  end
1513
+
1514
+ it 'only sends non-nil values in request body' do
1515
+ expect(described_class).to receive(:post_request) do |options|
1516
+ body = options[:body]
1517
+ expect(body).to eq({ email: 'test@workos.com', organization_id: 'org_123' })
1518
+ expect(body).not_to have_key(:expires_in_days)
1519
+ expect(body).not_to have_key(:inviter_user_id)
1520
+ expect(body).not_to have_key(:role_slug)
1521
+
1522
+ double('request')
1523
+ end.and_return(double('request'))
1524
+
1525
+ expect(described_class).to receive(:execute_request).and_return(
1526
+ double('response', body: '{"id": "test_invitation"}'),
1527
+ )
1528
+
1529
+ described_class.send_invitation(
1530
+ email: 'test@workos.com',
1531
+ organization_id: 'org_123',
1532
+ )
1533
+ end
1445
1534
  end
1446
1535
 
1447
1536
  context 'with an invalid payload' do
@@ -70,13 +70,13 @@ http_interactions:
70
70
  encoding: ASCII-8BIT
71
71
  string:
72
72
  '{"object":"list","data":[{"object":"role","id":"role_01HS1C7GRJE08PBR3M6Y0ZYGDZ","description":"Write
73
- access to every resource available","name":"Admin","slug":"admin","type":"EnvironmentRole","created_at":"2024-03-15T15:38:29.521Z","updated_at":"2024-11-14T17:08:00.556Z"},{"object":"role","id":"role_01JA8GJZRDSZEB9289DQXJ3N9Z","description":"","name":"Billing
74
- Manager","slug":"billing","type":"EnvironmentRole","created_at":"2024-10-15T16:36:11.653Z","updated_at":"2024-12-19T21:27:01.286Z"},{"object":"role","id":"role_01HSBH4R6RX0V86S3R590NNZW2","description":"Developer
75
- role","name":"Developer","slug":"developer","type":"EnvironmentRole","created_at":"2024-03-19T14:16:46.038Z","updated_at":"2024-03-19T14:16:46.038Z"},{"object":"role","id":"role_01HS4GDWJ8T6NQPTX2D0R5KBHN","description":"Edit
76
- and view access to non-critical resources","name":"Editor","slug":"editor","type":"EnvironmentRole","created_at":"2024-03-16T20:49:35.815Z","updated_at":"2024-03-16T20:52:19.410Z"},{"object":"role","id":"role_01HRFZE22WS2MGX6EWAG2JX6NW","description":"The
77
- default user role","name":"Member","slug":"member","type":"EnvironmentRole","created_at":"2024-03-08T21:27:47.034Z","updated_at":"2024-08-14T00:27:46.265Z"},{"object":"role","id":"role_01JEYJ2Z5MYG0TZYTDF02MW11N","description":"Manage
78
- billing for organization.","name":"Billing manager","slug":"org-billing-manager","type":"OrganizationRole","created_at":"2024-12-12T23:08:28.712Z","updated_at":"2024-12-12T23:08:28.712Z"},{"object":"role","id":"role_01JF0B7MQ9X414WQRAQMQYE1GS","description":"","name":"Platform
79
- Manager","slug":"org-platform-manager","type":"OrganizationRole","created_at":"2024-12-13T15:47:10.692Z","updated_at":"2024-12-13T15:47:10.692Z"}]}'
73
+ access to every resource available","name":"Admin","slug":"admin","permissions":["admin:all","read:users","write:users","manage:roles"],"type":"EnvironmentRole","created_at":"2024-03-15T15:38:29.521Z","updated_at":"2024-11-14T17:08:00.556Z"},{"object":"role","id":"role_01JA8GJZRDSZEB9289DQXJ3N9Z","description":"","name":"Billing
74
+ Manager","slug":"billing","permissions":["read:billing","write:billing"],"type":"EnvironmentRole","created_at":"2024-10-15T16:36:11.653Z","updated_at":"2024-12-19T21:27:01.286Z"},{"object":"role","id":"role_01HSBH4R6RX0V86S3R590NNZW2","description":"Developer
75
+ role","name":"Developer","slug":"developer","permissions":["read:code","write:code","deploy:apps"],"type":"EnvironmentRole","created_at":"2024-03-19T14:16:46.038Z","updated_at":"2024-03-19T14:16:46.038Z"},{"object":"role","id":"role_01HS4GDWJ8T6NQPTX2D0R5KBHN","description":"Edit
76
+ and view access to non-critical resources","name":"Editor","slug":"editor","permissions":["read:content","write:content","publish:content"],"type":"EnvironmentRole","created_at":"2024-03-16T20:49:35.815Z","updated_at":"2024-03-16T20:52:19.410Z"},{"object":"role","id":"role_01HRFZE22WS2MGX6EWAG2JX6NW","description":"The
77
+ default user role","name":"Member","slug":"member","permissions":["read:basic"],"type":"EnvironmentRole","created_at":"2024-03-08T21:27:47.034Z","updated_at":"2024-08-14T00:27:46.265Z"},{"object":"role","id":"role_01JEYJ2Z5MYG0TZYTDF02MW11N","description":"Manage
78
+ billing for organization.","name":"Billing manager","slug":"org-billing-manager","permissions":["read:org:billing","write:org:billing"],"type":"OrganizationRole","created_at":"2024-12-12T23:08:28.712Z","updated_at":"2024-12-12T23:08:28.712Z"},{"object":"role","id":"role_01JF0B7MQ9X414WQRAQMQYE1GS","description":"","name":"Platform
79
+ Manager","slug":"org-platform-manager","permissions":[],"type":"OrganizationRole","created_at":"2024-12-13T15:47:10.692Z","updated_at":"2024-12-13T15:47:10.692Z"}]}'
80
80
  http_version:
81
81
  recorded_at: Mon, 23 Dec 2024 20:23:07 GMT
82
82
  recorded_with: VCR 5.0.0
@@ -5,7 +5,7 @@ http_interactions:
5
5
  uri: https://api.workos.com/user_management/users/user_01H7TVSKS45SDHN5V9XPSM6H44
6
6
  body:
7
7
  encoding: UTF-8
8
- string: '{"first_name":"Jane","last_name":"Doe","email_verified":false}'
8
+ string: '{"first_name":"Jane","last_name":"Doe","email_verified":false,"external_id":"123"}'
9
9
  headers:
10
10
  Content-Type:
11
11
  - application/json
@@ -76,7 +76,7 @@ http_interactions:
76
76
  - cloudflare
77
77
  body:
78
78
  encoding: ASCII-8BIT
79
- string: '{"object":"user","id":"user_01H7TVSKS45SDHN5V9XPSM6H44","email":"willman@blips.app","email_verified":false,"first_name":"Jane","last_name":"Doe","created_at":"2023-08-14T20:28:58.929Z","updated_at":"2023-08-25T22:57:44.262Z","user_type":"unmanaged","email_verified_at":null,"google_oauth_profile_id":null,"microsoft_oauth_profile_id":null}'
79
+ string: '{"object":"user","id":"user_01H7TVSKS45SDHN5V9XPSM6H44","email":"willman@blips.app","email_verified":false,"first_name":"Jane","last_name":"Doe","external_id":"123","created_at":"2023-08-14T20:28:58.929Z","updated_at":"2023-08-25T22:57:44.262Z","user_type":"unmanaged","email_verified_at":null,"google_oauth_profile_id":null,"microsoft_oauth_profile_id":null}'
80
80
  http_version:
81
81
  recorded_at: Fri, 25 Aug 2023 23:37:04 GMT
82
82
  recorded_with: VCR 5.0.0
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: workos
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.20.0
4
+ version: 5.22.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - WorkOS
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-06-23 00:00:00.000000000 Z
11
+ date: 2025-07-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: encryptor
@@ -197,6 +197,7 @@ files:
197
197
  - spec/lib/workos/organizations_spec.rb
198
198
  - spec/lib/workos/passwordless_spec.rb
199
199
  - spec/lib/workos/portal_spec.rb
200
+ - spec/lib/workos/role_spec.rb
200
201
  - spec/lib/workos/session_spec.rb
201
202
  - spec/lib/workos/sso_spec.rb
202
203
  - spec/lib/workos/user_management_spec.rb
@@ -421,6 +422,7 @@ test_files:
421
422
  - spec/lib/workos/organizations_spec.rb
422
423
  - spec/lib/workos/passwordless_spec.rb
423
424
  - spec/lib/workos/portal_spec.rb
425
+ - spec/lib/workos/role_spec.rb
424
426
  - spec/lib/workos/session_spec.rb
425
427
  - spec/lib/workos/sso_spec.rb
426
428
  - spec/lib/workos/user_management_spec.rb