workos 5.21.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: 44c2f0a70f0a653685d6e7a8ca2601b64c4a1ff43276680ff52d0fcd2c80f5b3
4
- data.tar.gz: 1d3aa7e958196efbe79018014e2ffd5be229dc93812a60725d05a958afcd1c20
3
+ metadata.gz: 65c18f67663b8681a74442c6771d1527b66fecd20a9c114557255d4ce8d9ca38
4
+ data.tar.gz: 242b03d06e9caa456b3128d284fee00481fb4e7edec79934fd22c2e57598901b
5
5
  SHA512:
6
- metadata.gz: f51ad812297b13109f943a23d15592991b14f2d1c18ba91505c4227fffc64c2ac7912c32df163e974eb95a1f8069b3289f33b1bf783c18e757ba56e7869717d7
7
- data.tar.gz: 5a5428749ac62c22f4fa33db0ad5a491e262c01e445a8318210d09e654e6123a17dec582e9d8b54f09913f123679f1ebe8bc57a2fec7d032f690acf28d77b8a4
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.21.0)
4
+ workos (5.22.0)
5
5
  encryptor (~> 3.0)
6
6
  jwt (~> 2.8)
7
7
 
@@ -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
@@ -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
 
@@ -251,7 +251,7 @@ module WorkOS
251
251
  password: password,
252
252
  password_hash: password_hash,
253
253
  password_hash_type: password_hash_type,
254
- },
254
+ }.compact,
255
255
  auth: true,
256
256
  )
257
257
 
@@ -643,7 +643,7 @@ module WorkOS
643
643
  body: {
644
644
  email: email,
645
645
  invitation_token: invitation_token,
646
- },
646
+ }.compact,
647
647
  auth: true,
648
648
  ),
649
649
  )
@@ -697,7 +697,7 @@ module WorkOS
697
697
  totp_issuer: totp_issuer,
698
698
  totp_user: totp_user,
699
699
  totp_secret: totp_secret,
700
- },
700
+ }.compact,
701
701
  auth: true,
702
702
  ),
703
703
  )
@@ -928,7 +928,7 @@ module WorkOS
928
928
  user_id: user_id,
929
929
  organization_id: organization_id,
930
930
  role_slug: role_slug,
931
- },
931
+ }.compact,
932
932
  auth: true,
933
933
  )
934
934
 
@@ -1093,7 +1093,7 @@ module WorkOS
1093
1093
  expires_in_days: expires_in_days,
1094
1094
  inviter_user_id: inviter_user_id,
1095
1095
  role_slug: role_slug,
1096
- },
1096
+ }.compact,
1097
1097
  auth: true,
1098
1098
  ),
1099
1099
  )
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module WorkOS
4
- VERSION = '5.21.0'
4
+ VERSION = '5.22.0'
5
5
  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
@@ -382,6 +402,30 @@ describe WorkOS::UserManagement do
382
402
  end
383
403
  end
384
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
+
385
429
  context 'with an invalid payload' do
386
430
  it 'returns an error' do
387
431
  VCR.use_cassette 'user_management/update_user/invalid' do
@@ -778,6 +822,28 @@ describe WorkOS::UserManagement do
778
822
  expect(authentication_response.authentication_challenge.id).to eq('auth_challenge_01H96FETXGTW1QMBSBT2T36PW0')
779
823
  end
780
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
781
847
  end
782
848
 
783
849
  context 'with an incorrect user id' do
@@ -1444,6 +1510,27 @@ describe WorkOS::UserManagement do
1444
1510
  expect(invitation.email).to eq('test@workos.com')
1445
1511
  end
1446
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
1447
1534
  end
1448
1535
 
1449
1536
  context 'with an invalid payload' do
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.21.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-07-07 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