workos 1.4.0 → 2.0.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.
Files changed (64) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +1 -1
  3. data/.ruby-version +1 -1
  4. data/.semaphore/semaphore.yml +2 -2
  5. data/Gemfile.lock +3 -3
  6. data/lib/workos/client.rb +3 -6
  7. data/lib/workos/connection.rb +10 -2
  8. data/lib/workos/directory.rb +11 -3
  9. data/lib/workos/directory_sync.rb +29 -0
  10. data/lib/workos/directory_user.rb +4 -1
  11. data/lib/workos/errors.rb +4 -0
  12. data/lib/workos/organization.rb +10 -1
  13. data/lib/workos/organizations.rb +18 -4
  14. data/lib/workos/profile.rb +7 -2
  15. data/lib/workos/types/connection_struct.rb +3 -1
  16. data/lib/workos/types/directory_struct.rb +4 -2
  17. data/lib/workos/types/directory_user_struct.rb +1 -0
  18. data/lib/workos/types/organization_struct.rb +3 -0
  19. data/lib/workos/types/profile_struct.rb +1 -0
  20. data/lib/workos/types/webhook_struct.rb +14 -0
  21. data/lib/workos/types.rb +1 -0
  22. data/lib/workos/version.rb +1 -1
  23. data/lib/workos/webhook.rb +47 -0
  24. data/lib/workos/webhooks.rb +168 -0
  25. data/lib/workos.rb +3 -0
  26. data/spec/lib/workos/audit_trail_spec.rb +2 -0
  27. data/spec/lib/workos/directory_sync_spec.rb +64 -29
  28. data/spec/lib/workos/organizations_spec.rb +13 -11
  29. data/spec/lib/workos/passwordless_spec.rb +2 -0
  30. data/spec/lib/workos/portal_spec.rb +2 -0
  31. data/spec/lib/workos/sso_spec.rb +16 -12
  32. data/spec/lib/workos/webhooks_spec.rb +190 -0
  33. data/spec/spec_helper.rb +3 -0
  34. data/spec/support/fixtures/vcr_cassettes/{organization/update_invalid.yml → directory_sync/get_directory_with_invalid_id.yml} +35 -25
  35. data/spec/support/fixtures/vcr_cassettes/directory_sync/get_directory_with_valid_id.yml +84 -0
  36. data/spec/support/fixtures/vcr_cassettes/directory_sync/get_user.yml +40 -16
  37. data/spec/support/fixtures/vcr_cassettes/directory_sync/list_directories/with_after.yml +34 -22
  38. data/spec/support/fixtures/vcr_cassettes/directory_sync/list_directories/with_before.yml +36 -22
  39. data/spec/support/fixtures/vcr_cassettes/directory_sync/list_directories/with_domain.yml +30 -19
  40. data/spec/support/fixtures/vcr_cassettes/directory_sync/list_directories/with_limit.yml +31 -20
  41. data/spec/support/fixtures/vcr_cassettes/directory_sync/list_directories/with_no_options.yml +39 -21
  42. data/spec/support/fixtures/vcr_cassettes/directory_sync/list_directories/with_search.yml +32 -20
  43. data/spec/support/fixtures/vcr_cassettes/directory_sync/list_users/with_after.yml +128 -28
  44. data/spec/support/fixtures/vcr_cassettes/directory_sync/list_users/with_before.yml +31 -18
  45. data/spec/support/fixtures/vcr_cassettes/directory_sync/list_users/with_directory.yml +136 -35
  46. data/spec/support/fixtures/vcr_cassettes/directory_sync/list_users/with_group.yml +128 -18
  47. data/spec/support/fixtures/vcr_cassettes/directory_sync/list_users/with_limit.yml +131 -17
  48. data/spec/support/fixtures/vcr_cassettes/organization/create.yml +28 -16
  49. data/spec/support/fixtures/vcr_cassettes/organization/get.yml +27 -16
  50. data/spec/support/fixtures/vcr_cassettes/organization/list.yml +29 -14
  51. data/spec/support/fixtures/vcr_cassettes/organization/update.yml +27 -16
  52. data/spec/support/fixtures/vcr_cassettes/sso/get_connection_with_valid_id.yml +28 -16
  53. data/spec/support/fixtures/vcr_cassettes/sso/list_connections/with_after.yml +25 -15
  54. data/spec/support/fixtures/vcr_cassettes/sso/list_connections/with_before.yml +28 -15
  55. data/spec/support/fixtures/vcr_cassettes/sso/list_connections/with_connection_type.yml +31 -14
  56. data/spec/support/fixtures/vcr_cassettes/sso/list_connections/with_domain.yml +27 -13
  57. data/spec/support/fixtures/vcr_cassettes/sso/list_connections/with_limit.yml +24 -15
  58. data/spec/support/fixtures/vcr_cassettes/sso/list_connections/with_no_options.yml +30 -14
  59. data/spec/support/fixtures/vcr_cassettes/sso/list_connections/with_organization_id.yml +28 -14
  60. data/spec/support/fixtures/vcr_cassettes/sso/profile.yml +1 -1
  61. data/spec/support/profile.txt +1 -1
  62. data/spec/support/shared_examples/client_spec.rb +16 -0
  63. data/spec/support/webhook_payload.txt +1 -0
  64. metadata +16 -5
@@ -0,0 +1,168 @@
1
+ # frozen_string_literal: true
2
+ # typed: true
3
+
4
+ require 'openssl'
5
+
6
+ module WorkOS
7
+ # The Webhooks module provides convenience methods for working with the WorkOS webhooks.
8
+ # You'll need to extract the signature header and payload from the webhook request
9
+ # sig_header = request.headers['WorkOS-Signature']
10
+ # payload = request.body.read
11
+ #
12
+ # The secret is the Webhook Secret from your WorkOS Dashboard
13
+ # The tolerance is for the timestamp validation
14
+ #
15
+ module Webhooks
16
+ class << self
17
+ extend T::Sig
18
+
19
+ DEFAULT_TOLERANCE = 180
20
+
21
+ # Initializes an Event object from a JSON payload
22
+ # rubocop:disable Layout/LineLength
23
+ #
24
+ # @param [String] payload The payload from the webhook sent by WorkOS. This is the RAW_POST_DATA of the request.
25
+ # @param [String] sig_header The signature from the webhook sent by WorkOS.
26
+ # @param [String] secret The webhook secret from the WorkOS dashboard.
27
+ # @param [Integer] tolerance The time tolerance in seconds for the webhook.
28
+ #
29
+ # @example
30
+ # WorkOS::Webhooks.construct_event(
31
+ # payload: "{"id": "wh_123","data":{"id":"directory_user_01FAEAJCR3ZBZ30D8BD1924TVG","state":"active","emails":[{"type":"work","value":"blair@foo-corp.com","primary":true}],"idp_id":"00u1e8mutl6wlH3lL4x7","object":"directory_user","username":"blair@foo-corp.com","last_name":"Lunceford","first_name":"Blair","directory_id":"directory_01F9M7F68PZP8QXP8G7X5QRHS7","raw_attributes":{"name":{"givenName":"Blair","familyName":"Lunceford","middleName":"Elizabeth","honorificPrefix":"Ms."},"title":"Developer Success Engineer","active":true,"emails":[{"type":"work","value":"blair@foo-corp.com","primary":true}],"groups":[],"locale":"en-US","schemas":["urn:ietf:params:scim:schemas:core:2.0:User","urn:ietf:params:scim:schemas:extension:enterprise:2.0:User"],"userName":"blair@foo-corp.com","addresses":[{"region":"CO","primary":true,"locality":"Steamboat Springs","postalCode":"80487"}],"externalId":"00u1e8mutl6wlH3lL4x7","displayName":"Blair Lunceford","urn:ietf:params:scim:schemas:extension:enterprise:2.0:User":{"manager":{"value":"2","displayName":"Kathleen Chung"},"division":"Engineering","department":"Customer Success"}}},"event":"dsync.user.created"}",
32
+ # sig_header: 't=1626125972272, v1=80f7ab7efadc306eb5797c588cee9410da9be4416782b497bf1e1bf4175fb928',
33
+ # secret: 'LJlTiC19GmCKWs8AE0IaOQcos',
34
+ # )
35
+ #
36
+ # => #<WorkOS::Webhook:0x00007fa64b980910 @event="dsync.user.created", @data={:id=>"directory_user_01FAEAJCR3ZBZ30D8BD1924TVG", :state=>"active", :emails=>[{:type=>"work", :value=>"blair@foo-corp.com", :primary=>true}], :idp_id=>"00u1e8mutl6wlH3lL4x7", :object=>"directory_user", :username=>"blair@foo-corp.com", :last_name=>"Lunceford", :first_name=>"Blair", :directory_id=>"directory_01F9M7F68PZP8QXP8G7X5QRHS7", :raw_attributes=>{:name=>{:givenName=>"Blair", :familyName=>"Lunceford", :middleName=>"Elizabeth", :honorificPrefix=>"Ms."}, :title=>"Developer Success Engineer", :active=>true, :emails=>[{:type=>"work", :value=>"blair@foo-corp.com", :primary=>true}], :groups=>[], :locale=>"en-US", :schemas=>["urn:ietf:params:scim:schemas:core:2.0:User", "urn:ietf:params:scim:schemas:extension:enterprise:2.0:User"], :userName=>"blair@foo-corp.com", :addresses=>[{:region=>"CO", :primary=>true, :locality=>"Steamboat Springs", :postalCode=>"80487"}], :externalId=>"00u1e8mutl6wlH3lL4x7", :displayName=>"Blair Lunceford", :"urn:ietf:params:scim:schemas:extension:enterprise:2.0:User"=>{:manager=>{:value=>"2", :displayName=>"Kathleen Chung"}, :division=>"Engineering", :department=>"Customer Success"}}}>
37
+ #
38
+ # @return [WorkOS::Webhook]
39
+ # rubocop:enable Layout/LineLength
40
+ sig do
41
+ params(
42
+ payload: String,
43
+ sig_header: String,
44
+ secret: String,
45
+ tolerance: Integer,
46
+ ).returns(WorkOS::Webhook)
47
+ end
48
+ def construct_event(
49
+ payload:,
50
+ sig_header:,
51
+ secret:,
52
+ tolerance: DEFAULT_TOLERANCE
53
+ )
54
+ verify_header(payload: payload, sig_header: sig_header, secret: secret, tolerance: tolerance)
55
+ WorkOS::Webhook.new(payload)
56
+ end
57
+
58
+ private
59
+
60
+ sig do
61
+ params(
62
+ payload: String,
63
+ sig_header: String,
64
+ secret: String,
65
+ tolerance: Integer,
66
+ ).returns(T::Boolean)
67
+ end
68
+ # rubocop:disable Metrics/MethodLength
69
+ def verify_header(
70
+ payload:,
71
+ sig_header:,
72
+ secret:,
73
+ tolerance: DEFAULT_TOLERANCE
74
+ )
75
+ begin
76
+ timestamp, signature_hash = get_timestamp_and_signature_hash(sig_header: sig_header)
77
+ rescue StandardError
78
+ raise WorkOS::SignatureVerificationError.new(
79
+ message: 'Unable to extract timestamp and signature hash from header',
80
+ )
81
+ end
82
+
83
+ if signature_hash.empty?
84
+ raise WorkOS::SignatureVerificationError.new(
85
+ message: 'No signature hash found with expected scheme v1',
86
+ )
87
+ end
88
+
89
+ if timestamp < Time.now - tolerance
90
+ raise WorkOS::SignatureVerificationError.new(
91
+ message: 'Timestamp outside the tolerance zone',
92
+ )
93
+ end
94
+
95
+ expected_sig = compute_signature(timestamp: timestamp, payload: payload, secret: secret)
96
+ unless secure_compare(str_a: expected_sig, str_b: signature_hash)
97
+ raise WorkOS::SignatureVerificationError.new(
98
+ message: 'Signature hash does not match the expected signature hash for payload',
99
+ )
100
+ end
101
+
102
+ true
103
+ end
104
+ # rubocop:enable Metrics/MethodLength
105
+
106
+ sig do
107
+ params(
108
+ sig_header: String,
109
+ ).returns(T::Array[T.untyped])
110
+ end
111
+ def get_timestamp_and_signature_hash(
112
+ sig_header:
113
+ )
114
+ timestamp, signature_hash = sig_header.split(', ')
115
+
116
+ if timestamp.nil? || signature_hash.nil?
117
+ raise WorkOS::SignatureVerificationError.new(
118
+ message: 'Unable to extract timestamp and signature hash from header',
119
+ )
120
+ end
121
+
122
+ timestamp = timestamp.sub('t=', '')
123
+ signature_hash = signature_hash.sub('v1=', '')
124
+
125
+ [Time.at(timestamp.to_i), signature_hash]
126
+ end
127
+
128
+ sig do
129
+ params(
130
+ timestamp: Time,
131
+ payload: String,
132
+ secret: String,
133
+ ).returns(String)
134
+ end
135
+ def compute_signature(
136
+ timestamp:,
137
+ payload:,
138
+ secret:
139
+ )
140
+ unhashed_string = "#{timestamp.to_i}.#{payload}"
141
+ digest = OpenSSL::Digest.new('sha256')
142
+ OpenSSL::HMAC.hexdigest(digest, secret, unhashed_string)
143
+ end
144
+
145
+ # Constant time string comparison to prevent timing attacks
146
+ # Code borrowed from ActiveSupport
147
+ sig do
148
+ params(
149
+ str_a: String,
150
+ str_b: String,
151
+ ).returns(T::Boolean)
152
+ end
153
+ def secure_compare(
154
+ str_a:,
155
+ str_b:
156
+ )
157
+ return false unless str_a.bytesize == str_b.bytesize
158
+
159
+ l = T.unsafe(str_a.unpack("C#{str_a.bytesize}"))
160
+
161
+ res = 0
162
+ str_b.each_byte { |byte| res |= byte ^ l.shift }
163
+
164
+ res.zero?
165
+ end
166
+ end
167
+ end
168
+ end
data/lib/workos.rb CHANGED
@@ -42,11 +42,14 @@ module WorkOS
42
42
  autoload :ProfileAndToken, 'workos/profile_and_token'
43
43
  autoload :SSO, 'workos/sso'
44
44
  autoload :DirectoryUser, 'workos/directory_user'
45
+ autoload :Webhook, 'workos/webhook'
46
+ autoload :Webhooks, 'workos/webhooks'
45
47
 
46
48
  # Errors
47
49
  autoload :APIError, 'workos/errors'
48
50
  autoload :AuthenticationError, 'workos/errors'
49
51
  autoload :InvalidRequestError, 'workos/errors'
52
+ autoload :SignatureVerificationError, 'workos/errors'
50
53
 
51
54
  # Remove WORKOS_KEY at some point in the future. Keeping it here now for
52
55
  # backwards compatibility.
@@ -2,6 +2,8 @@
2
2
  # typed: false
3
3
 
4
4
  describe WorkOS::AuditTrail do
5
+ it_behaves_like 'client'
6
+
5
7
  describe '.create_event' do
6
8
  context 'with valid event payload' do
7
9
  let(:valid_event) do
@@ -2,6 +2,8 @@
2
2
  # typed: false
3
3
 
4
4
  describe WorkOS::DirectorySync do
5
+ it_behaves_like 'client'
6
+
5
7
  describe '.list_directories' do
6
8
  context 'with no options' do
7
9
  it 'returns directories and metadata' do
@@ -13,7 +15,7 @@ describe WorkOS::DirectorySync do
13
15
  VCR.use_cassette 'directory_sync/list_directories/with_no_options' do
14
16
  directories = described_class.list_directories
15
17
 
16
- expect(directories.data.size).to eq(3)
18
+ expect(directories.data.size).to eq(10)
17
19
  expect(directories.list_metadata).to eq(expected_metadata)
18
20
  end
19
21
  end
@@ -44,7 +46,7 @@ describe WorkOS::DirectorySync do
44
46
  context 'with search option' do
45
47
  it 'forms the proper request to the API' do
46
48
  request_args = [
47
- '/directories?search=Foo',
49
+ '/directories?search=Testing',
48
50
  'Content-Type' => 'application/json'
49
51
  ]
50
52
 
@@ -55,10 +57,11 @@ describe WorkOS::DirectorySync do
55
57
 
56
58
  VCR.use_cassette 'directory_sync/list_directories/with_search' do
57
59
  directories = described_class.list_directories(
58
- search: 'Foo',
60
+ search: 'Testing',
59
61
  )
60
62
 
61
- expect(directories.data.size).to eq(1)
63
+ expect(directories.data.size).to eq(2)
64
+ expect(directories.data[0].name).to include('Testing')
62
65
  end
63
66
  end
64
67
  end
@@ -66,7 +69,7 @@ describe WorkOS::DirectorySync do
66
69
  context 'with the before option' do
67
70
  it 'forms the proper request to the API' do
68
71
  request_args = [
69
- '/directories?before=before-id',
72
+ '/directories?before=directory_01FGCPNV312FHFRCX0BYWHVSE1',
70
73
  'Content-Type' => 'application/json'
71
74
  ]
72
75
 
@@ -77,10 +80,10 @@ describe WorkOS::DirectorySync do
77
80
 
78
81
  VCR.use_cassette 'directory_sync/list_directories/with_before' do
79
82
  directories = described_class.list_directories(
80
- before: 'before-id',
83
+ before: 'directory_01FGCPNV312FHFRCX0BYWHVSE1',
81
84
  )
82
85
 
83
- expect(directories.data.size).to eq(3)
86
+ expect(directories.data.size).to eq(6)
84
87
  end
85
88
  end
86
89
  end
@@ -88,7 +91,7 @@ describe WorkOS::DirectorySync do
88
91
  context 'with the after option' do
89
92
  it 'forms the proper request to the API' do
90
93
  request_args = [
91
- '/directories?after=after-id',
94
+ '/directories?after=directory_01FGCPNV312FHFRCX0BYWHVSE1',
92
95
  'Content-Type' => 'application/json'
93
96
  ]
94
97
 
@@ -98,9 +101,9 @@ describe WorkOS::DirectorySync do
98
101
  and_return(expected_request)
99
102
 
100
103
  VCR.use_cassette 'directory_sync/list_directories/with_after' do
101
- directories = described_class.list_directories(after: 'after-id')
104
+ directories = described_class.list_directories(after: 'directory_01FGCPNV312FHFRCX0BYWHVSE1')
102
105
 
103
- expect(directories.data.size).to eq(3)
106
+ expect(directories.data.size).to eq(4)
104
107
  end
105
108
  end
106
109
  end
@@ -140,6 +143,38 @@ describe WorkOS::DirectorySync do
140
143
  end
141
144
  end
142
145
 
146
+ describe '.get_directory' do
147
+ context 'with a valid id' do
148
+ it 'gets the directory details' do
149
+ VCR.use_cassette('directory_sync/get_directory_with_valid_id') do
150
+ directory = WorkOS::DirectorySync.get_directory(
151
+ id: 'directory_01FK17DWRHH7APAFXT5B52PV0W',
152
+ )
153
+
154
+ expect(directory.id).to eq('directory_01FK17DWRHH7APAFXT5B52PV0W')
155
+ expect(directory.name).to eq('Testing Active Attribute')
156
+ expect(directory.domain).to eq('example.me')
157
+ expect(directory.type).to eq('azure scim v2.0')
158
+ expect(directory.state).to eq('linked')
159
+ expect(directory.organization_id).to eq('org_01F6Q6TFP7RD2PF6J03ANNWDKV')
160
+ end
161
+ end
162
+ end
163
+
164
+ context 'with an invalid id' do
165
+ it 'raises an error' do
166
+ VCR.use_cassette('directory_sync/get_directory_with_invalid_id') do
167
+ expect do
168
+ WorkOS::DirectorySync.get_directory(id: 'invalid')
169
+ end.to raise_error(
170
+ WorkOS::APIError,
171
+ "Status 404, Directory not found: 'invalid'. - request ID: ",
172
+ )
173
+ end
174
+ end
175
+ end
176
+ end
177
+
143
178
  describe '.list_groups' do
144
179
  context 'with no options' do
145
180
  it 'raises an error' do
@@ -282,7 +317,7 @@ describe WorkOS::DirectorySync do
282
317
  context 'with directory option' do
283
318
  it 'forms the proper request to the API' do
284
319
  request_args = [
285
- '/directory_users?directory=directory_01EK2YEMVTWGX27STRDR0N3MP9',
320
+ '/directory_users?directory=directory_01FAZYMST676QMTFN1DDJZZX87',
286
321
  'Content-Type' => 'application/json'
287
322
  ]
288
323
 
@@ -293,10 +328,10 @@ describe WorkOS::DirectorySync do
293
328
 
294
329
  VCR.use_cassette 'directory_sync/list_users/with_directory' do
295
330
  users = described_class.list_users(
296
- directory: 'directory_01EK2YEMVTWGX27STRDR0N3MP9',
331
+ directory: 'directory_01FAZYMST676QMTFN1DDJZZX87',
297
332
  )
298
333
 
299
- expect(users.data.size).to eq(10)
334
+ expect(users.data.size).to eq(4)
300
335
  end
301
336
  end
302
337
  end
@@ -304,7 +339,7 @@ describe WorkOS::DirectorySync do
304
339
  context 'with group option' do
305
340
  it 'forms the proper request to the API' do
306
341
  request_args = [
307
- '/directory_users?group=directory_group_01EQ7V7C6Y4RPMCH3KNB9853FF',
342
+ '/directory_users?group=directory_group_01FBXGP79EJAYKW0WS9JCK1V6E',
308
343
  'Content-Type' => 'application/json'
309
344
  ]
310
345
 
@@ -315,10 +350,10 @@ describe WorkOS::DirectorySync do
315
350
 
316
351
  VCR.use_cassette 'directory_sync/list_users/with_group' do
317
352
  users = described_class.list_users(
318
- group: 'directory_group_01EQ7V7C6Y4RPMCH3KNB9853FF',
353
+ group: 'directory_group_01FBXGP79EJAYKW0WS9JCK1V6E',
319
354
  )
320
355
 
321
- expect(users.data.size).to eq(2)
356
+ expect(users.data.size).to eq(1)
322
357
  end
323
358
  end
324
359
  end
@@ -326,8 +361,8 @@ describe WorkOS::DirectorySync do
326
361
  context 'with the before option' do
327
362
  it 'forms the proper request to the API' do
328
363
  request_args = [
329
- '/directory_users?before=before-id&'\
330
- 'directory=directory_01EK2YEMVTWGX27STRDR0N3MP9',
364
+ '/directory_users?before=directory_user_01FAZYNPC8TJBP7Y2ERT51MGDF&'\
365
+ 'directory=directory_01FAZYMST676QMTFN1DDJZZX87',
331
366
  'Content-Type' => 'application/json'
332
367
  ]
333
368
 
@@ -338,8 +373,8 @@ describe WorkOS::DirectorySync do
338
373
 
339
374
  VCR.use_cassette 'directory_sync/list_users/with_before' do
340
375
  users = described_class.list_users(
341
- before: 'before-id',
342
- directory: 'directory_01EK2YEMVTWGX27STRDR0N3MP9',
376
+ before: 'directory_user_01FAZYNPC8TJBP7Y2ERT51MGDF',
377
+ directory: 'directory_01FAZYMST676QMTFN1DDJZZX87',
343
378
  )
344
379
 
345
380
  expect(users.data.size).to eq(2)
@@ -350,8 +385,8 @@ describe WorkOS::DirectorySync do
350
385
  context 'with the after option' do
351
386
  it 'forms the proper request to the API' do
352
387
  request_args = [
353
- '/directory_users?after=after-id&' \
354
- 'directory=directory_01EK2YEMVTWGX27STRDR0N3MP9',
388
+ '/directory_users?after=directory_user_01FAZYNPC8TJBP7Y2ERT51MGDF&' \
389
+ 'directory=directory_01FAZYMST676QMTFN1DDJZZX87',
355
390
  'Content-Type' => 'application/json'
356
391
  ]
357
392
 
@@ -362,11 +397,11 @@ describe WorkOS::DirectorySync do
362
397
 
363
398
  VCR.use_cassette 'directory_sync/list_users/with_after' do
364
399
  users = described_class.list_users(
365
- after: 'after-id',
366
- directory: 'directory_01EK2YEMVTWGX27STRDR0N3MP9',
400
+ after: 'directory_user_01FAZYNPC8TJBP7Y2ERT51MGDF',
401
+ directory: 'directory_01FAZYMST676QMTFN1DDJZZX87',
367
402
  )
368
403
 
369
- expect(users.data.size).to eq(10)
404
+ expect(users.data.size).to eq(1)
370
405
  end
371
406
  end
372
407
  end
@@ -375,7 +410,7 @@ describe WorkOS::DirectorySync do
375
410
  it 'forms the proper request to the API' do
376
411
  request_args = [
377
412
  '/directory_users?limit=2&' \
378
- 'directory=directory_01EK2YEMVTWGX27STRDR0N3MP9',
413
+ 'directory=directory_01FAZYMST676QMTFN1DDJZZX87',
379
414
  'Content-Type' => 'application/json'
380
415
  ]
381
416
 
@@ -387,7 +422,7 @@ describe WorkOS::DirectorySync do
387
422
  VCR.use_cassette 'directory_sync/list_users/with_limit' do
388
423
  users = described_class.list_users(
389
424
  limit: 2,
390
- directory: 'directory_01EK2YEMVTWGX27STRDR0N3MP9',
425
+ directory: 'directory_01FAZYMST676QMTFN1DDJZZX87',
391
426
  )
392
427
 
393
428
  expect(users.data.size).to eq(2)
@@ -425,10 +460,10 @@ describe WorkOS::DirectorySync do
425
460
  it 'returns a user' do
426
461
  VCR.use_cassette('directory_sync/get_user') do
427
462
  user = WorkOS::DirectorySync.get_user(
428
- 'directory_usr_01E64QS50EAY48S0XJ1AA4WX4D',
463
+ 'directory_user_01FAZYNPC8M0HRYTKFP2GNX852',
429
464
  )
430
465
 
431
- expect(user['first_name']).to eq('Mark')
466
+ expect(user['first_name']).to eq('Logan')
432
467
  end
433
468
  end
434
469
  end
@@ -2,18 +2,20 @@
2
2
  # typed: false
3
3
 
4
4
  describe WorkOS::Organizations do
5
+ it_behaves_like 'client'
6
+
5
7
  describe '.create_organization' do
6
8
  context 'with valid payload' do
7
9
  it 'creates an organization' do
8
10
  VCR.use_cassette 'organization/create' do
9
11
  organization = described_class.create_organization(
10
- domains: ['example.com'],
12
+ domains: ['example.io'],
11
13
  name: 'Test Organization',
12
14
  )
13
15
 
14
- expect(organization.id).to eq('org_01EHT88Z8J8795GZNQ4ZP1J81T')
16
+ expect(organization.id).to eq('org_01FCPEJXEZR4DSBA625YMGQT9N')
15
17
  expect(organization.name).to eq('Test Organization')
16
- expect(organization.domains.first[:domain]).to eq('example.com')
18
+ expect(organization.domains.first[:domain]).to eq('example.io')
17
19
  end
18
20
  end
19
21
  end
@@ -46,7 +48,7 @@ describe WorkOS::Organizations do
46
48
  VCR.use_cassette 'organization/list' do
47
49
  organizations = described_class.list_organizations
48
50
 
49
- expect(organizations.data.size).to eq(7)
51
+ expect(organizations.data.size).to eq(6)
50
52
  expect(organizations.list_metadata).to eq(expected_metadata)
51
53
  end
52
54
  end
@@ -69,7 +71,7 @@ describe WorkOS::Organizations do
69
71
  before: 'before-id',
70
72
  )
71
73
 
72
- expect(organizations.data.size).to eq(7)
74
+ expect(organizations.data.size).to eq(6)
73
75
  end
74
76
  end
75
77
  end
@@ -89,7 +91,7 @@ describe WorkOS::Organizations do
89
91
  VCR.use_cassette 'organization/list', match_requests_on: [:path] do
90
92
  organizations = described_class.list_organizations(after: 'after-id')
91
93
 
92
- expect(organizations.data.size).to eq(7)
94
+ expect(organizations.data.size).to eq(6)
93
95
  end
94
96
  end
95
97
  end
@@ -109,7 +111,7 @@ describe WorkOS::Organizations do
109
111
  VCR.use_cassette 'organization/list', match_requests_on: [:path] do
110
112
  organizations = described_class.list_organizations(limit: 10)
111
113
 
112
- expect(organizations.data.size).to eq(7)
114
+ expect(organizations.data.size).to eq(6)
113
115
  end
114
116
  end
115
117
  end
@@ -120,10 +122,10 @@ describe WorkOS::Organizations do
120
122
  it 'gets the organization details' do
121
123
  VCR.use_cassette('organization/get') do
122
124
  organization = described_class.get_organization(
123
- id: 'org_01EZDF20TZEJXKPSX2BJRN6TV6',
125
+ id: 'org_01F9293WD2PDEEV4Y625XPZVG7',
124
126
  )
125
127
 
126
- expect(organization.id).to eq('org_01EZDF20TZEJXKPSX2BJRN6TV6')
128
+ expect(organization.id).to eq('org_01F9293WD2PDEEV4Y625XPZVG7')
127
129
  expect(organization.name).to eq('Foo Corp')
128
130
  expect(organization.domains.first[:domain]).to eq('foo-corp.com')
129
131
  end
@@ -149,12 +151,12 @@ describe WorkOS::Organizations do
149
151
  it 'creates an organization' do
150
152
  VCR.use_cassette 'organization/update' do
151
153
  organization = described_class.update_organization(
152
- organization: 'org_01F29YJ068E52HGEB8ZQGC9MJG',
154
+ organization: 'org_01F6Q6TFP7RD2PF6J03ANNWDKV',
153
155
  domains: ['example.me'],
154
156
  name: 'Test Organization',
155
157
  )
156
158
 
157
- expect(organization.id).to eq('org_01F29YJ068E52HGEB8ZQGC9MJG')
159
+ expect(organization.id).to eq('org_01F6Q6TFP7RD2PF6J03ANNWDKV')
158
160
  expect(organization.name).to eq('Test Organization')
159
161
  expect(organization.domains.first[:domain]).to eq('example.me')
160
162
  end
@@ -2,6 +2,8 @@
2
2
  # typed: false
3
3
 
4
4
  describe WorkOS::Passwordless do
5
+ it_behaves_like 'client'
6
+
5
7
  describe '.create_session' do
6
8
  context 'with valid options payload' do
7
9
  let(:valid_options) do
@@ -2,6 +2,8 @@
2
2
  # typed: false
3
3
 
4
4
  describe WorkOS::Portal do
5
+ it_behaves_like 'client'
6
+
5
7
  describe '.generate_link' do
6
8
  let(:organization) { 'org_01EHQMYV6MBK39QC5PZXHY59C3' }
7
9
 
@@ -4,6 +4,8 @@
4
4
  require 'securerandom'
5
5
 
6
6
  describe WorkOS::SSO do
7
+ it_behaves_like 'client'
8
+
7
9
  describe '.authorization_url' do
8
10
  context 'with a domain' do
9
11
  let(:args) do
@@ -162,6 +164,7 @@ describe WorkOS::SSO do
162
164
  id: 'prof_01EEJTY9SZ1R350RB7B73SNBKF',
163
165
  idp_id: '116485463307139932699',
164
166
  last_name: 'Loblaw',
167
+ organization_id: 'org_01FG53X8636WSNW2WEKB2C31ZB',
165
168
  raw_attributes: {
166
169
  email: 'bob.loblaw@workos.com',
167
170
  family_name: 'Loblaw',
@@ -231,6 +234,7 @@ describe WorkOS::SSO do
231
234
  id: 'prof_01DRA1XNSJDZ19A31F183ECQW5',
232
235
  idp_id: '00u1klkowm8EGah2H357',
233
236
  last_name: 'Demo',
237
+ organization_id: 'org_01FG53X8636WSNW2WEKB2C31ZB',
234
238
  raw_attributes: {
235
239
  email: 'demo@workos-okta.com',
236
240
  first_name: 'WorkOS',
@@ -303,7 +307,7 @@ describe WorkOS::SSO do
303
307
  VCR.use_cassette 'sso/list_connections/with_no_options' do
304
308
  connections = described_class.list_connections
305
309
 
306
- expect(connections.data.size).to eq(3)
310
+ expect(connections.data.size).to eq(6)
307
311
  expect(connections.list_metadata).to eq(expected_metadata)
308
312
  end
309
313
  end
@@ -326,7 +330,7 @@ describe WorkOS::SSO do
326
330
  connection_type: 'OktaSAML',
327
331
  )
328
332
 
329
- expect(connections.data.size).to eq(3)
333
+ expect(connections.data.size).to eq(10)
330
334
  expect(connections.data.first.connection_type).to eq('OktaSAML')
331
335
  end
332
336
  end
@@ -357,7 +361,7 @@ describe WorkOS::SSO do
357
361
  context 'with organization_id option' do
358
362
  it 'forms the proper request to the API' do
359
363
  request_args = [
360
- '/connections?organization_id=org_01EGS4P7QR31EZ4YWD1Z1XA176',
364
+ '/connections?organization_id=org_01F9293WD2PDEEV4Y625XPZVG7',
361
365
  'Content-Type' => 'application/json'
362
366
  ]
363
367
 
@@ -368,12 +372,12 @@ describe WorkOS::SSO do
368
372
 
369
373
  VCR.use_cassette 'sso/list_connections/with_organization_id' do
370
374
  connections = described_class.list_connections(
371
- organization_id: 'org_01EGS4P7QR31EZ4YWD1Z1XA176',
375
+ organization_id: 'org_01F9293WD2PDEEV4Y625XPZVG7',
372
376
  )
373
377
 
374
378
  expect(connections.data.size).to eq(1)
375
379
  expect(connections.data.first.organization_id).to eq(
376
- 'org_01EGS4P7QR31EZ4YWD1Z1XA176',
380
+ 'org_01F9293WD2PDEEV4Y625XPZVG7',
377
381
  )
378
382
  end
379
383
  end
@@ -404,7 +408,7 @@ describe WorkOS::SSO do
404
408
  context 'with before option' do
405
409
  it 'forms the proper request to the API' do
406
410
  request_args = [
407
- '/connections?before=conn_01EQKPMQAPV02H270HKVNS4CTA',
411
+ '/connections?before=conn_01FA3WGCWPCCY1V2FGES2FDNP7',
408
412
  'Content-Type' => 'application/json'
409
413
  ]
410
414
 
@@ -415,7 +419,7 @@ describe WorkOS::SSO do
415
419
 
416
420
  VCR.use_cassette 'sso/list_connections/with_before' do
417
421
  connections = described_class.list_connections(
418
- before: 'conn_01EQKPMQAPV02H270HKVNS4CTA',
422
+ before: 'conn_01FA3WGCWPCCY1V2FGES2FDNP7',
419
423
  )
420
424
 
421
425
  expect(connections.data.size).to eq(3)
@@ -426,7 +430,7 @@ describe WorkOS::SSO do
426
430
  context 'with after option' do
427
431
  it 'forms the proper request to the API' do
428
432
  request_args = [
429
- '/connections?after=conn_01EQKPMQAPV02H270HKVNS4CTA',
433
+ '/connections?after=conn_01FA3WGCWPCCY1V2FGES2FDNP7',
430
434
  'Content-Type' => 'application/json'
431
435
  ]
432
436
 
@@ -437,10 +441,10 @@ describe WorkOS::SSO do
437
441
 
438
442
  VCR.use_cassette 'sso/list_connections/with_after' do
439
443
  connections = described_class.list_connections(
440
- after: 'conn_01EQKPMQAPV02H270HKVNS4CTA',
444
+ after: 'conn_01FA3WGCWPCCY1V2FGES2FDNP7',
441
445
  )
442
446
 
443
- expect(connections.data.size).to eq(3)
447
+ expect(connections.data.size).to eq(2)
444
448
  end
445
449
  end
446
450
  end
@@ -451,10 +455,10 @@ describe WorkOS::SSO do
451
455
  it 'gets the connection details' do
452
456
  VCR.use_cassette('sso/get_connection_with_valid_id') do
453
457
  connection = WorkOS::SSO.get_connection(
454
- id: 'conn_01EX00NB050H354WKGC7990AR2',
458
+ id: 'conn_01FA3WGCWPCCY1V2FGES2FDNP7',
455
459
  )
456
460
 
457
- expect(connection.id).to eq('conn_01EX00NB050H354WKGC7990AR2')
461
+ expect(connection.id).to eq('conn_01FA3WGCWPCCY1V2FGES2FDNP7')
458
462
  expect(connection.connection_type).to eq('OktaSAML')
459
463
  expect(connection.name).to eq('Foo Corp')
460
464
  expect(connection.domains.first[:domain]).to eq('foo-corp.com')