workos 1.4.0 → 1.6.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (62) 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 +9 -1
  8. data/lib/workos/directory.rb +10 -2
  9. data/lib/workos/directory_user.rb +4 -1
  10. data/lib/workos/errors.rb +4 -0
  11. data/lib/workos/organization.rb +10 -1
  12. data/lib/workos/organizations.rb +18 -4
  13. data/lib/workos/profile.rb +7 -2
  14. data/lib/workos/types/connection_struct.rb +2 -0
  15. data/lib/workos/types/directory_struct.rb +3 -1
  16. data/lib/workos/types/directory_user_struct.rb +1 -0
  17. data/lib/workos/types/organization_struct.rb +3 -0
  18. data/lib/workos/types/profile_struct.rb +1 -0
  19. data/lib/workos/types/webhook_struct.rb +14 -0
  20. data/lib/workos/types.rb +1 -0
  21. data/lib/workos/version.rb +1 -1
  22. data/lib/workos/webhook.rb +47 -0
  23. data/lib/workos/webhooks.rb +168 -0
  24. data/lib/workos.rb +3 -0
  25. data/spec/lib/workos/audit_trail_spec.rb +2 -0
  26. data/spec/lib/workos/directory_sync_spec.rb +32 -29
  27. data/spec/lib/workos/organizations_spec.rb +13 -11
  28. data/spec/lib/workos/passwordless_spec.rb +2 -0
  29. data/spec/lib/workos/portal_spec.rb +2 -0
  30. data/spec/lib/workos/sso_spec.rb +16 -12
  31. data/spec/lib/workos/webhooks_spec.rb +190 -0
  32. data/spec/spec_helper.rb +3 -0
  33. data/spec/support/fixtures/vcr_cassettes/directory_sync/get_user.yml +40 -16
  34. data/spec/support/fixtures/vcr_cassettes/directory_sync/list_directories/with_after.yml +34 -22
  35. data/spec/support/fixtures/vcr_cassettes/directory_sync/list_directories/with_before.yml +36 -22
  36. data/spec/support/fixtures/vcr_cassettes/directory_sync/list_directories/with_domain.yml +30 -19
  37. data/spec/support/fixtures/vcr_cassettes/directory_sync/list_directories/with_limit.yml +31 -20
  38. data/spec/support/fixtures/vcr_cassettes/directory_sync/list_directories/with_no_options.yml +39 -21
  39. data/spec/support/fixtures/vcr_cassettes/directory_sync/list_directories/with_search.yml +32 -20
  40. data/spec/support/fixtures/vcr_cassettes/directory_sync/list_users/with_after.yml +128 -28
  41. data/spec/support/fixtures/vcr_cassettes/directory_sync/list_users/with_before.yml +31 -18
  42. data/spec/support/fixtures/vcr_cassettes/directory_sync/list_users/with_directory.yml +136 -35
  43. data/spec/support/fixtures/vcr_cassettes/directory_sync/list_users/with_group.yml +128 -18
  44. data/spec/support/fixtures/vcr_cassettes/directory_sync/list_users/with_limit.yml +131 -17
  45. data/spec/support/fixtures/vcr_cassettes/organization/create.yml +28 -16
  46. data/spec/support/fixtures/vcr_cassettes/organization/get.yml +27 -16
  47. data/spec/support/fixtures/vcr_cassettes/organization/list.yml +29 -14
  48. data/spec/support/fixtures/vcr_cassettes/organization/update.yml +27 -16
  49. data/spec/support/fixtures/vcr_cassettes/sso/get_connection_with_valid_id.yml +28 -16
  50. data/spec/support/fixtures/vcr_cassettes/sso/list_connections/with_after.yml +25 -15
  51. data/spec/support/fixtures/vcr_cassettes/sso/list_connections/with_before.yml +28 -15
  52. data/spec/support/fixtures/vcr_cassettes/sso/list_connections/with_connection_type.yml +31 -14
  53. data/spec/support/fixtures/vcr_cassettes/sso/list_connections/with_domain.yml +27 -13
  54. data/spec/support/fixtures/vcr_cassettes/sso/list_connections/with_limit.yml +24 -15
  55. data/spec/support/fixtures/vcr_cassettes/sso/list_connections/with_no_options.yml +30 -14
  56. data/spec/support/fixtures/vcr_cassettes/sso/list_connections/with_organization_id.yml +28 -14
  57. data/spec/support/fixtures/vcr_cassettes/sso/profile.yml +1 -1
  58. data/spec/support/profile.txt +1 -1
  59. data/spec/support/shared_examples/client_spec.rb +16 -0
  60. data/spec/support/webhook_payload.txt +1 -0
  61. metadata +12 -5
  62. data/spec/support/fixtures/vcr_cassettes/organization/update_invalid.yml +0 -73
@@ -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
@@ -282,7 +285,7 @@ describe WorkOS::DirectorySync do
282
285
  context 'with directory option' do
283
286
  it 'forms the proper request to the API' do
284
287
  request_args = [
285
- '/directory_users?directory=directory_01EK2YEMVTWGX27STRDR0N3MP9',
288
+ '/directory_users?directory=directory_01FAZYMST676QMTFN1DDJZZX87',
286
289
  'Content-Type' => 'application/json'
287
290
  ]
288
291
 
@@ -293,10 +296,10 @@ describe WorkOS::DirectorySync do
293
296
 
294
297
  VCR.use_cassette 'directory_sync/list_users/with_directory' do
295
298
  users = described_class.list_users(
296
- directory: 'directory_01EK2YEMVTWGX27STRDR0N3MP9',
299
+ directory: 'directory_01FAZYMST676QMTFN1DDJZZX87',
297
300
  )
298
301
 
299
- expect(users.data.size).to eq(10)
302
+ expect(users.data.size).to eq(4)
300
303
  end
301
304
  end
302
305
  end
@@ -304,7 +307,7 @@ describe WorkOS::DirectorySync do
304
307
  context 'with group option' do
305
308
  it 'forms the proper request to the API' do
306
309
  request_args = [
307
- '/directory_users?group=directory_group_01EQ7V7C6Y4RPMCH3KNB9853FF',
310
+ '/directory_users?group=directory_group_01FBXGP79EJAYKW0WS9JCK1V6E',
308
311
  'Content-Type' => 'application/json'
309
312
  ]
310
313
 
@@ -315,10 +318,10 @@ describe WorkOS::DirectorySync do
315
318
 
316
319
  VCR.use_cassette 'directory_sync/list_users/with_group' do
317
320
  users = described_class.list_users(
318
- group: 'directory_group_01EQ7V7C6Y4RPMCH3KNB9853FF',
321
+ group: 'directory_group_01FBXGP79EJAYKW0WS9JCK1V6E',
319
322
  )
320
323
 
321
- expect(users.data.size).to eq(2)
324
+ expect(users.data.size).to eq(1)
322
325
  end
323
326
  end
324
327
  end
@@ -326,8 +329,8 @@ describe WorkOS::DirectorySync do
326
329
  context 'with the before option' do
327
330
  it 'forms the proper request to the API' do
328
331
  request_args = [
329
- '/directory_users?before=before-id&'\
330
- 'directory=directory_01EK2YEMVTWGX27STRDR0N3MP9',
332
+ '/directory_users?before=directory_user_01FAZYNPC8TJBP7Y2ERT51MGDF&'\
333
+ 'directory=directory_01FAZYMST676QMTFN1DDJZZX87',
331
334
  'Content-Type' => 'application/json'
332
335
  ]
333
336
 
@@ -338,8 +341,8 @@ describe WorkOS::DirectorySync do
338
341
 
339
342
  VCR.use_cassette 'directory_sync/list_users/with_before' do
340
343
  users = described_class.list_users(
341
- before: 'before-id',
342
- directory: 'directory_01EK2YEMVTWGX27STRDR0N3MP9',
344
+ before: 'directory_user_01FAZYNPC8TJBP7Y2ERT51MGDF',
345
+ directory: 'directory_01FAZYMST676QMTFN1DDJZZX87',
343
346
  )
344
347
 
345
348
  expect(users.data.size).to eq(2)
@@ -350,8 +353,8 @@ describe WorkOS::DirectorySync do
350
353
  context 'with the after option' do
351
354
  it 'forms the proper request to the API' do
352
355
  request_args = [
353
- '/directory_users?after=after-id&' \
354
- 'directory=directory_01EK2YEMVTWGX27STRDR0N3MP9',
356
+ '/directory_users?after=directory_user_01FAZYNPC8TJBP7Y2ERT51MGDF&' \
357
+ 'directory=directory_01FAZYMST676QMTFN1DDJZZX87',
355
358
  'Content-Type' => 'application/json'
356
359
  ]
357
360
 
@@ -362,11 +365,11 @@ describe WorkOS::DirectorySync do
362
365
 
363
366
  VCR.use_cassette 'directory_sync/list_users/with_after' do
364
367
  users = described_class.list_users(
365
- after: 'after-id',
366
- directory: 'directory_01EK2YEMVTWGX27STRDR0N3MP9',
368
+ after: 'directory_user_01FAZYNPC8TJBP7Y2ERT51MGDF',
369
+ directory: 'directory_01FAZYMST676QMTFN1DDJZZX87',
367
370
  )
368
371
 
369
- expect(users.data.size).to eq(10)
372
+ expect(users.data.size).to eq(1)
370
373
  end
371
374
  end
372
375
  end
@@ -375,7 +378,7 @@ describe WorkOS::DirectorySync do
375
378
  it 'forms the proper request to the API' do
376
379
  request_args = [
377
380
  '/directory_users?limit=2&' \
378
- 'directory=directory_01EK2YEMVTWGX27STRDR0N3MP9',
381
+ 'directory=directory_01FAZYMST676QMTFN1DDJZZX87',
379
382
  'Content-Type' => 'application/json'
380
383
  ]
381
384
 
@@ -387,7 +390,7 @@ describe WorkOS::DirectorySync do
387
390
  VCR.use_cassette 'directory_sync/list_users/with_limit' do
388
391
  users = described_class.list_users(
389
392
  limit: 2,
390
- directory: 'directory_01EK2YEMVTWGX27STRDR0N3MP9',
393
+ directory: 'directory_01FAZYMST676QMTFN1DDJZZX87',
391
394
  )
392
395
 
393
396
  expect(users.data.size).to eq(2)
@@ -425,10 +428,10 @@ describe WorkOS::DirectorySync do
425
428
  it 'returns a user' do
426
429
  VCR.use_cassette('directory_sync/get_user') do
427
430
  user = WorkOS::DirectorySync.get_user(
428
- 'directory_usr_01E64QS50EAY48S0XJ1AA4WX4D',
431
+ 'directory_user_01FAZYNPC8M0HRYTKFP2GNX852',
429
432
  )
430
433
 
431
- expect(user['first_name']).to eq('Mark')
434
+ expect(user['first_name']).to eq('Logan')
432
435
  end
433
436
  end
434
437
  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')
@@ -0,0 +1,190 @@
1
+ # frozen_string_literal: true
2
+ # typed: false
3
+
4
+ require 'json'
5
+ require 'openssl'
6
+
7
+ describe WorkOS::Webhooks do
8
+ describe '.construct_event' do
9
+ before(:each) do
10
+ @payload = File.read("#{SPEC_ROOT}/support/webhook_payload.txt")
11
+ @secret = 'secret'
12
+ @timestamp = Time.at(Time.now.to_i * 1000)
13
+ unhashed_string = "#{@timestamp.to_i}.#{@payload}"
14
+ digest = OpenSSL::Digest.new('sha256')
15
+ @signature_hash = OpenSSL::HMAC.hexdigest(digest, @secret, unhashed_string)
16
+ @expectation = {
17
+ id: 'directory_user_01FAEAJCR3ZBZ30D8BD1924TVG',
18
+ state: 'active',
19
+ emails: [{
20
+ type: 'work',
21
+ value: 'blair@foo-corp.com',
22
+ primary: true,
23
+ }],
24
+ idp_id: '00u1e8mutl6wlH3lL4x7',
25
+ object: 'directory_user',
26
+ username: 'blair@foo-corp.com',
27
+ last_name: 'Lunceford',
28
+ first_name: 'Blair',
29
+ directory_id: 'directory_01F9M7F68PZP8QXP8G7X5QRHS7',
30
+ raw_attributes: {
31
+ name: {
32
+ givenName: 'Blair',
33
+ familyName: 'Lunceford',
34
+ middleName: 'Elizabeth',
35
+ honorificPrefix: 'Ms.',
36
+ },
37
+ title: 'Developer Success Engineer',
38
+ active: true,
39
+ emails: [{
40
+ type: 'work',
41
+ value: 'blair@foo-corp.com',
42
+ primary: true,
43
+ }],
44
+ groups: [],
45
+ locale: 'en-US',
46
+ schemas: [
47
+ 'urn:ietf:params:scim:schemas:core:2.0:User',
48
+ 'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User'
49
+ ],
50
+ userName: 'blair@foo-corp.com',
51
+ addresses: [{
52
+ region: 'CO',
53
+ primary: true,
54
+ locality: 'Steamboat Springs',
55
+ postalCode: '80487',
56
+ }],
57
+ externalId: '00u1e8mutl6wlH3lL4x7',
58
+ displayName: 'Blair Lunceford',
59
+ "urn:ietf:params:scim:schemas:extension:enterprise:2.0:User": {
60
+ manager: {
61
+ value: '2',
62
+ displayName: 'Kathleen Chung',
63
+ },
64
+ division: 'Engineering',
65
+ department: 'Customer Success',
66
+ },
67
+ },
68
+ }
69
+ end
70
+
71
+ context 'with the correct payload, sig_header, and secret' do
72
+ it 'returns a webhook event' do
73
+ webhook = described_class.construct_event(
74
+ payload: @payload,
75
+ sig_header: "t=#{@timestamp.to_i}, v1=#{@signature_hash}",
76
+ secret: @secret,
77
+ )
78
+
79
+ expect(webhook.data).to eq(@expectation)
80
+ expect(webhook.event).to eq('dsync.user.created')
81
+ expect(webhook.id).to eq('wh_123')
82
+ end
83
+ end
84
+
85
+ context 'with the correct payload, sig_header, secret, and tolerance' do
86
+ it 'returns a webhook event' do
87
+ webhook = described_class.construct_event(
88
+ payload: @payload,
89
+ sig_header: "t=#{@timestamp.to_i}, v1=#{@signature_hash}",
90
+ secret: @secret,
91
+ tolerance: 300,
92
+ )
93
+
94
+ expect(webhook.data).to eq(@expectation)
95
+ expect(webhook.event).to eq('dsync.user.created')
96
+ expect(webhook.id).to eq('wh_123')
97
+ end
98
+ end
99
+
100
+ context 'with an empty header' do
101
+ it 'raises an error' do
102
+ expect do
103
+ described_class.construct_event(
104
+ payload: @payload,
105
+ sig_header: '',
106
+ secret: @secret,
107
+ )
108
+ end.to raise_error(
109
+ WorkOS::SignatureVerificationError,
110
+ 'Unable to extract timestamp and signature hash from header',
111
+ )
112
+ end
113
+ end
114
+
115
+ context 'with an empty signature hash' do
116
+ it 'raises an error' do
117
+ expect do
118
+ described_class.construct_event(
119
+ payload: @payload,
120
+ sig_header: "t=#{@timestamp.to_i}, v1=",
121
+ secret: @secret,
122
+ )
123
+ end.to raise_error(
124
+ WorkOS::SignatureVerificationError,
125
+ 'No signature hash found with expected scheme v1',
126
+ )
127
+ end
128
+ end
129
+
130
+ context 'with an incorrect signature hash' do
131
+ it 'raises an error' do
132
+ expect do
133
+ described_class.construct_event(
134
+ payload: @payload,
135
+ sig_header: "t=#{@timestamp.to_i}, v1=99999",
136
+ secret: @secret,
137
+ )
138
+ end.to raise_error(
139
+ WorkOS::SignatureVerificationError,
140
+ 'Signature hash does not match the expected signature hash for payload',
141
+ )
142
+ end
143
+ end
144
+
145
+ context 'with an incorrect payload' do
146
+ it 'raises an error' do
147
+ expect do
148
+ described_class.construct_event(
149
+ payload: 'invalid',
150
+ sig_header: "t=#{@timestamp.to_i}, v1=#{@signature_hash}",
151
+ secret: @secret,
152
+ )
153
+ end.to raise_error(
154
+ WorkOS::SignatureVerificationError,
155
+ 'Signature hash does not match the expected signature hash for payload',
156
+ )
157
+ end
158
+ end
159
+
160
+ context 'with an incorrect webhook secret' do
161
+ it 'raises an error' do
162
+ expect do
163
+ described_class.construct_event(
164
+ payload: @payload,
165
+ sig_header: "t=#{@timestamp.to_i}, v1=#{@signature_hash}",
166
+ secret: 'invalid',
167
+ )
168
+ end.to raise_error(
169
+ WorkOS::SignatureVerificationError,
170
+ 'Signature hash does not match the expected signature hash for payload',
171
+ )
172
+ end
173
+ end
174
+
175
+ context 'with a timestamp outside tolerance' do
176
+ it 'raises an error' do
177
+ expect do
178
+ described_class.construct_event(
179
+ payload: @payload,
180
+ sig_header: "t=9999, v1=#{@signature_hash}",
181
+ secret: @secret,
182
+ )
183
+ end.to raise_error(
184
+ WorkOS::SignatureVerificationError,
185
+ 'Timestamp outside the tolerance zone',
186
+ )
187
+ end
188
+ end
189
+ end
190
+ end
data/spec/spec_helper.rb CHANGED
@@ -18,6 +18,9 @@ require 'webmock/rspec'
18
18
  require 'workos'
19
19
  require 'vcr'
20
20
 
21
+ # Support
22
+ Dir['./spec/support/**/*.rb'].sort.each { |f| require f }
23
+
21
24
  SPEC_ROOT = File.dirname __FILE__
22
25
 
23
26
  VCR.configure do |config|