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 +4 -4
- data/Gemfile.lock +1 -1
- data/lib/workos/organizations.rb +8 -1
- data/lib/workos/role.rb +3 -1
- data/lib/workos/session.rb +1 -0
- data/lib/workos/sso.rb +3 -24
- data/lib/workos/user.rb +3 -1
- data/lib/workos/user_management.rb +9 -6
- data/lib/workos/version.rb +1 -1
- data/spec/lib/workos/organizations_spec.rb +42 -0
- data/spec/lib/workos/role_spec.rb +142 -0
- data/spec/lib/workos/session_spec.rb +38 -0
- data/spec/lib/workos/sso_spec.rb +87 -4
- data/spec/lib/workos/user_management_spec.rb +89 -0
- data/spec/support/fixtures/vcr_cassettes/organization/list_organization_roles.yml +7 -7
- data/spec/support/fixtures/vcr_cassettes/user_management/update_user/valid.yml +2 -2
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 65c18f67663b8681a74442c6771d1527b66fecd20a9c114557255d4ce8d9ca38
|
4
|
+
data.tar.gz: 242b03d06e9caa456b3128d284fee00481fb4e7edec79934fd22c2e57598901b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f6f3ccbbc9b6026beee00f92fcb8d87ce3f321ba8be4c808cd524daeb1dc1aa7fa13ef3c9c885096f83e8a68347b70a78d2d9297a396a8071727506e31787c9f
|
7
|
+
data.tar.gz: d8f71832e8767dcfbcc2bcad5760ff5f59cbb3513a73708dc41c50dd5d4f3ac515788a8b81c596b9bb069244d912f3c3a2db9b1e39a0e34ff920bc87a7233b39
|
data/Gemfile.lock
CHANGED
data/lib/workos/organizations.rb
CHANGED
@@ -185,7 +185,14 @@ module WorkOS
|
|
185
185
|
|
186
186
|
# Retrieve a list of roles for the given organization.
|
187
187
|
#
|
188
|
-
# @param [String]
|
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,
|
data/lib/workos/session.rb
CHANGED
data/lib/workos/sso.rb
CHANGED
@@ -120,8 +120,9 @@ module WorkOS
|
|
120
120
|
code: code,
|
121
121
|
}
|
122
122
|
|
123
|
-
response =
|
124
|
-
|
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
|
)
|
data/lib/workos/version.rb
CHANGED
@@ -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,
|
data/spec/lib/workos/sso_spec.rb
CHANGED
@@ -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::
|
420
|
-
'
|
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::
|
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.
|
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-
|
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
|