workos 0.0.2 → 0.1.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/.rubocop.yml +5 -2
- data/Gemfile.lock +6 -2
- data/README.md +179 -8
- data/bin/docs +2 -2
- data/codecov.yml +1 -0
- data/docs/WorkOS.html +22 -26
- data/docs/WorkOS/APIError.html +160 -0
- data/docs/WorkOS/AuditLog.html +235 -0
- data/docs/WorkOS/AuthenticationError.html +160 -0
- data/docs/WorkOS/Base.html +27 -32
- data/docs/WorkOS/Client.html +493 -0
- data/docs/WorkOS/InvalidRequestError.html +160 -0
- data/docs/WorkOS/Profile.html +80 -17
- data/docs/WorkOS/RequestError.html +6 -6
- data/docs/WorkOS/SSO.html +118 -74
- data/docs/WorkOS/Types.html +9 -10
- data/docs/WorkOS/Types/ProfileStruct.html +6 -6
- data/docs/WorkOS/Types/Provider.html +135 -0
- data/docs/WorkOS/WorkOSError.html +447 -0
- data/docs/class_list.html +3 -3
- data/docs/css/style.css +2 -2
- data/docs/file.README.html +173 -13
- data/docs/file_list.html +2 -2
- data/docs/frames.html +2 -2
- data/docs/index.html +77 -16
- data/docs/js/app.js +14 -3
- data/docs/method_list.html +96 -8
- data/docs/top-level-namespace.html +6 -6
- data/lib/workos.rb +7 -2
- data/lib/workos/audit_log.rb +78 -0
- data/lib/workos/base.rb +5 -6
- data/lib/workos/client.rb +86 -0
- data/lib/workos/errors.rb +48 -0
- data/lib/workos/sso.rb +49 -27
- data/lib/workos/types.rb +2 -1
- data/lib/workos/types/provider_enum.rb +14 -0
- data/lib/workos/version.rb +2 -2
- data/sorbet/rbi/hidden-definitions/errors.txt +22108 -4368
- data/sorbet/rbi/hidden-definitions/hidden.rbi +32490 -6059
- data/sorbet/rbi/sorbet-typed/lib/rainbow/all/rainbow.rbi +1 -1
- data/sorbet/rbi/todo.rbi +5 -0
- data/spec/lib/workos/audit_log_spec.rb +140 -0
- data/spec/lib/workos/base_spec.rb +30 -0
- data/spec/lib/workos/sso_spec.rb +131 -36
- data/spec/spec_helper.rb +21 -1
- data/spec/support/fixtures/vcr_cassettes/audit_log/create_event.yml +65 -0
- data/spec/support/fixtures/vcr_cassettes/audit_log/create_event_custom_idempotency_key.yml +67 -0
- data/spec/support/fixtures/vcr_cassettes/audit_log/create_event_invalid.yml +68 -0
- data/spec/support/fixtures/vcr_cassettes/audit_log/create_events_duplicate_idempotency_key_and_payload.yml +131 -0
- data/spec/support/fixtures/vcr_cassettes/audit_log/create_events_duplicate_idempotency_key_different_payload.yml +134 -0
- data/spec/support/fixtures/vcr_cassettes/base/execute_request_unauthenticated.yml +66 -0
- data/workos.gemspec +2 -0
- metadata +57 -27
- data/lib/workos/request_error.rb +0 -5
- data/sorbet/rbi/gems/addressable.rbi +0 -198
- data/sorbet/rbi/gems/ast.rbi +0 -47
- data/sorbet/rbi/gems/codecov.rbi +0 -19
- data/sorbet/rbi/gems/crack.rbi +0 -47
- data/sorbet/rbi/gems/docile.rbi +0 -31
- data/sorbet/rbi/gems/hashdiff.rbi +0 -65
- data/sorbet/rbi/gems/jaro_winkler.rbi +0 -14
- data/sorbet/rbi/gems/parallel.rbi +0 -81
- data/sorbet/rbi/gems/parser.rbi +0 -856
- data/sorbet/rbi/gems/public_suffix.rbi +0 -102
- data/sorbet/rbi/gems/rack.rbi +0 -103
- data/sorbet/rbi/gems/rainbow.rbi +0 -117
- data/sorbet/rbi/gems/rake.rbi +0 -632
- data/sorbet/rbi/gems/rspec-core.rbi +0 -1661
- data/sorbet/rbi/gems/rspec-expectations.rbi +0 -388
- data/sorbet/rbi/gems/rspec-mocks.rbi +0 -823
- data/sorbet/rbi/gems/rspec-support.rbi +0 -266
- data/sorbet/rbi/gems/rspec.rbi +0 -14
- data/sorbet/rbi/gems/rubocop.rbi +0 -7083
- data/sorbet/rbi/gems/ruby-progressbar.rbi +0 -304
- data/sorbet/rbi/gems/simplecov-html.rbi +0 -30
- data/sorbet/rbi/gems/simplecov.rbi +0 -225
- data/sorbet/rbi/gems/unicode-display_width.rbi +0 -16
- data/sorbet/rbi/gems/webmock.rbi +0 -526
data/sorbet/rbi/todo.rbi
CHANGED
@@ -3,5 +3,10 @@
|
|
3
3
|
|
4
4
|
# typed: strong
|
5
5
|
module T::Private::Methods::MethodHooks; end
|
6
|
+
module T::Private::Methods::MethodHooks; end
|
7
|
+
module T::Private::Methods::MethodHooks; end
|
8
|
+
module T::Private::Methods::SingletonMethodHooks; end
|
9
|
+
module T::Private::Methods::SingletonMethodHooks; end
|
10
|
+
module T::Private::Methods::SingletonMethodHooks; end
|
6
11
|
module T::Private::Methods::SingletonMethodHooks; end
|
7
12
|
module T::Private::Methods::SingletonMethodHooks; end
|
@@ -0,0 +1,140 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
# typed: false
|
3
|
+
|
4
|
+
describe WorkOS::AuditLog do
|
5
|
+
before(:all) do
|
6
|
+
WorkOS.key = 'key'
|
7
|
+
end
|
8
|
+
|
9
|
+
after(:all) do
|
10
|
+
WorkOS.key = nil
|
11
|
+
end
|
12
|
+
|
13
|
+
describe '.create_event' do
|
14
|
+
context 'with valid event payload' do
|
15
|
+
let(:valid_event) do
|
16
|
+
{
|
17
|
+
group: 'Terrace House',
|
18
|
+
location: '1.1.1.1',
|
19
|
+
action: 'house.created',
|
20
|
+
action_type: 'C',
|
21
|
+
actor_name: 'Daiki Miyagi',
|
22
|
+
actor_id: 'user_12345',
|
23
|
+
target_name: 'Ryota Yamasato',
|
24
|
+
target_id: 'user_67890',
|
25
|
+
occurred_at: '2020-01-10T15:30:00-05:00',
|
26
|
+
metadata: {
|
27
|
+
a: 'b',
|
28
|
+
},
|
29
|
+
}
|
30
|
+
end
|
31
|
+
|
32
|
+
context 'with idempotency key' do
|
33
|
+
context 'when idempotency key is used once' do
|
34
|
+
it 'creates an event' do
|
35
|
+
VCR.use_cassette('audit_log/create_event_custom_idempotency_key') do
|
36
|
+
response = described_class.create_event(
|
37
|
+
event: valid_event,
|
38
|
+
idempotency_key: 'key',
|
39
|
+
)
|
40
|
+
|
41
|
+
expect(response.code).to eq '201'
|
42
|
+
json = JSON.parse(response.body)
|
43
|
+
expect(json['success']).to be true
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
context 'when idempotency key is used more than once' do
|
49
|
+
context 'with duplicate event payloads' do
|
50
|
+
it 'creates an event' do
|
51
|
+
VCR.use_cassette('audit_log/create_events_duplicate_idempotency_key_and_payload') do
|
52
|
+
response1 = described_class.create_event(
|
53
|
+
event: valid_event,
|
54
|
+
idempotency_key: 'foo',
|
55
|
+
)
|
56
|
+
response2 = described_class.create_event(
|
57
|
+
event: valid_event,
|
58
|
+
idempotency_key: 'foo',
|
59
|
+
)
|
60
|
+
|
61
|
+
expect(response1.code).to eq '201'
|
62
|
+
json1 = JSON.parse(response1.body)
|
63
|
+
expect(json1['success']).to be true
|
64
|
+
|
65
|
+
expect(response2.code).to eq '201'
|
66
|
+
json2 = JSON.parse(response1.body)
|
67
|
+
expect(json2['success']).to be true
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
context 'with different event payloads' do
|
73
|
+
it 'raises an error' do
|
74
|
+
VCR.use_cassette('audit_log/create_events_duplicate_idempotency_key_different_payload') do
|
75
|
+
described_class.create_event(
|
76
|
+
event: valid_event,
|
77
|
+
idempotency_key: 'bar',
|
78
|
+
)
|
79
|
+
|
80
|
+
payload = valid_event.clone
|
81
|
+
payload[:actor_name] = 'Tetsuya Sugaya'
|
82
|
+
|
83
|
+
expect do
|
84
|
+
described_class.create_event(
|
85
|
+
event: payload,
|
86
|
+
idempotency_key: 'bar',
|
87
|
+
)
|
88
|
+
end.to raise_error(
|
89
|
+
WorkOS::InvalidRequestError,
|
90
|
+
/Status 400, Another idempotency key \(bar\) with different request parameters was found. Please use a different idempotency key./,
|
91
|
+
)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
context 'with no idempotency key' do
|
99
|
+
it 'creates an event' do
|
100
|
+
VCR.use_cassette('audit_log/create_event') do
|
101
|
+
response = described_class.create_event(event: valid_event)
|
102
|
+
|
103
|
+
expect(response.code).to eq '201'
|
104
|
+
json = JSON.parse(response.body)
|
105
|
+
expect(json['success']).to be true
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
context 'with invalid event payload' do
|
112
|
+
let(:invalid_event) do
|
113
|
+
{
|
114
|
+
group: 'Terrace House',
|
115
|
+
location: '1.1.1.1',
|
116
|
+
action: 'house.created',
|
117
|
+
actor_name: 'Daiki Miyagi',
|
118
|
+
actor_id: 'user_12345',
|
119
|
+
target_name: 'Ryota Yamasato',
|
120
|
+
target_id: 'user_67890',
|
121
|
+
occurred_at: '2020-01-10T15:30:00-05:00',
|
122
|
+
metadata: {
|
123
|
+
a: 'b',
|
124
|
+
},
|
125
|
+
}
|
126
|
+
end
|
127
|
+
|
128
|
+
it 'raises an error' do
|
129
|
+
VCR.use_cassette('audit_log/create_event_invalid') do
|
130
|
+
expect do
|
131
|
+
described_class.create_event(event: invalid_event)
|
132
|
+
end.to raise_error(
|
133
|
+
WorkOS::InvalidRequestError,
|
134
|
+
/Status 422, Validation failed \(action_type: action_type must be a string\)/,
|
135
|
+
)
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
# typed: false
|
3
|
+
|
4
|
+
module WorkOS
|
5
|
+
module Test
|
6
|
+
class << self
|
7
|
+
include Base
|
8
|
+
include Client
|
9
|
+
|
10
|
+
def request
|
11
|
+
execute_request(request: post_request(path: '/events', body: {}))
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
describe WorkOS::Base do
|
18
|
+
describe '.execute_request' do
|
19
|
+
context 'when unauthenticated' do
|
20
|
+
it 'raises an error' do
|
21
|
+
VCR.use_cassette('base/execute_request_unauthenticated') do
|
22
|
+
expect { WorkOS::Test.request }.to raise_error(
|
23
|
+
WorkOS::AuthenticationError,
|
24
|
+
/Status 401, Unauthorized/,
|
25
|
+
)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
data/spec/lib/workos/sso_spec.rb
CHANGED
@@ -1,41 +1,117 @@
|
|
1
|
-
# typed: false
|
2
1
|
# frozen_string_literal: true
|
2
|
+
# typed: false
|
3
3
|
|
4
4
|
require 'securerandom'
|
5
5
|
|
6
6
|
describe WorkOS::SSO do
|
7
7
|
describe '.authorization_url' do
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
8
|
+
context 'with a domain' do
|
9
|
+
let(:args) do
|
10
|
+
{
|
11
|
+
domain: 'foo.com',
|
12
|
+
project_id: 'workos-proj-123',
|
13
|
+
redirect_uri: 'foo.com/auth/callback',
|
14
|
+
state: {
|
15
|
+
next_page: '/dashboard/edit',
|
16
|
+
},
|
17
|
+
}
|
18
|
+
end
|
19
|
+
it 'returns a valid URL' do
|
20
|
+
authorization_url = described_class.authorization_url(**args)
|
21
|
+
|
22
|
+
expect(URI.parse(authorization_url)).to be_a URI
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'returns the expected hostname' do
|
26
|
+
authorization_url = described_class.authorization_url(**args)
|
27
|
+
|
28
|
+
expect(URI.parse(authorization_url).host).to eq(WorkOS::API_HOSTNAME)
|
29
|
+
end
|
18
30
|
|
19
|
-
|
20
|
-
|
31
|
+
it 'returns the expected query string' do
|
32
|
+
authorization_url = described_class.authorization_url(**args)
|
21
33
|
|
22
|
-
|
34
|
+
expect(URI.parse(authorization_url).query).to eq(
|
35
|
+
'client_id=workos-proj-123&redirect_uri=foo.com%2Fauth%2Fcallback' \
|
36
|
+
'&response_type=code&state=%7B%3Anext_page%3D%3E%22%2Fdashboard%2F' \
|
37
|
+
'edit%22%7D&domain=foo.com',
|
38
|
+
)
|
39
|
+
end
|
23
40
|
end
|
24
41
|
|
25
|
-
|
26
|
-
|
42
|
+
context 'with a provider' do
|
43
|
+
let(:args) do
|
44
|
+
{
|
45
|
+
provider: 'GoogleOAuth',
|
46
|
+
project_id: 'workos-proj-123',
|
47
|
+
redirect_uri: 'foo.com/auth/callback',
|
48
|
+
state: {
|
49
|
+
next_page: '/dashboard/edit',
|
50
|
+
},
|
51
|
+
}
|
52
|
+
end
|
53
|
+
it 'returns a valid URL' do
|
54
|
+
authorization_url = described_class.authorization_url(**args)
|
55
|
+
|
56
|
+
expect(URI.parse(authorization_url)).to be_a URI
|
57
|
+
end
|
58
|
+
|
59
|
+
it 'returns the expected hostname' do
|
60
|
+
authorization_url = described_class.authorization_url(**args)
|
61
|
+
|
62
|
+
expect(URI.parse(authorization_url).host).to eq(WorkOS::API_HOSTNAME)
|
63
|
+
end
|
64
|
+
|
65
|
+
it 'returns the expected query string' do
|
66
|
+
authorization_url = described_class.authorization_url(**args)
|
27
67
|
|
28
|
-
|
68
|
+
expect(URI.parse(authorization_url).query).to eq(
|
69
|
+
'client_id=workos-proj-123&redirect_uri=foo.com%2Fauth%2Fcallback' \
|
70
|
+
'&response_type=code&state=%7B%3Anext_page%3D%3E%22%2Fdashboard%2F' \
|
71
|
+
'edit%22%7D&provider=GoogleOAuth',
|
72
|
+
)
|
73
|
+
end
|
29
74
|
end
|
30
75
|
|
31
|
-
|
32
|
-
|
76
|
+
context 'with neither domain or provider' do
|
77
|
+
let(:args) do
|
78
|
+
{
|
79
|
+
project_id: 'workos-proj-123',
|
80
|
+
redirect_uri: 'foo.com/auth/callback',
|
81
|
+
state: {
|
82
|
+
next_page: '/dashboard/edit',
|
83
|
+
},
|
84
|
+
}
|
85
|
+
end
|
86
|
+
it 'raises an error' do
|
87
|
+
expect do
|
88
|
+
described_class.authorization_url(**args)
|
89
|
+
end.to raise_error(
|
90
|
+
ArgumentError,
|
91
|
+
'Either domain or provider is required.',
|
92
|
+
)
|
93
|
+
end
|
94
|
+
end
|
33
95
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
96
|
+
context 'with an invalid provider' do
|
97
|
+
let(:args) do
|
98
|
+
{
|
99
|
+
provider: 'Okta',
|
100
|
+
project_id: 'workos-proj-123',
|
101
|
+
redirect_uri: 'foo.com/auth/callback',
|
102
|
+
state: {
|
103
|
+
next_page: '/dashboard/edit',
|
104
|
+
},
|
105
|
+
}
|
106
|
+
end
|
107
|
+
it 'raises an error' do
|
108
|
+
expect do
|
109
|
+
described_class.authorization_url(**args)
|
110
|
+
end.to raise_error(
|
111
|
+
ArgumentError,
|
112
|
+
'Okta is not a valid value. `provider` must be in ["GoogleOAuth"]',
|
113
|
+
)
|
114
|
+
end
|
39
115
|
end
|
40
116
|
end
|
41
117
|
|
@@ -48,7 +124,6 @@ describe WorkOS::SSO do
|
|
48
124
|
{
|
49
125
|
code: SecureRandom.hex(10),
|
50
126
|
project_id: 'workos-proj-123',
|
51
|
-
redirect_uri: 'foo.com/auth/callback',
|
52
127
|
}
|
53
128
|
end
|
54
129
|
|
@@ -58,19 +133,30 @@ describe WorkOS::SSO do
|
|
58
133
|
client_secret: WorkOS.key,
|
59
134
|
code: args[:code],
|
60
135
|
grant_type: 'authorization_code',
|
61
|
-
redirect_uri: args[:redirect_uri],
|
62
136
|
}
|
63
137
|
end
|
138
|
+
let(:user_agent) { 'user-agent-string' }
|
139
|
+
let(:headers) { { 'User-Agent' => user_agent } }
|
140
|
+
before do
|
141
|
+
allow(described_class).to receive(:user_agent).and_return(user_agent)
|
142
|
+
end
|
64
143
|
|
65
144
|
context 'with a successful response' do
|
66
145
|
let(:body) { File.read("#{SPEC_ROOT}/support/profile.txt") }
|
67
146
|
|
68
147
|
before do
|
69
148
|
stub_request(:post, 'https://api.workos.com/sso/token').
|
70
|
-
with(query: query).
|
149
|
+
with(query: query, headers: headers).
|
71
150
|
to_return(status: 200, body: body)
|
72
151
|
end
|
73
152
|
|
153
|
+
it 'includes the SDK Version header' do
|
154
|
+
described_class.profile(**args)
|
155
|
+
|
156
|
+
expect(a_request(:post, 'https://api.workos.com/sso/token').
|
157
|
+
with(query: query, headers: headers)).to have_been_made
|
158
|
+
end
|
159
|
+
|
74
160
|
it 'returns a WorkOS::Profile' do
|
75
161
|
profile = described_class.profile(**args)
|
76
162
|
|
@@ -81,17 +167,21 @@ describe WorkOS::SSO do
|
|
81
167
|
context 'with an unprocessable request' do
|
82
168
|
before do
|
83
169
|
stub_request(:post, 'https://api.workos.com/sso/token').
|
84
|
-
with(query: query).
|
170
|
+
with(query: query, headers: headers).
|
85
171
|
to_return(
|
172
|
+
headers: { 'X-Request-ID' => 'request-id' },
|
86
173
|
status: 422,
|
87
174
|
body: { "message": 'some error message' }.to_json,
|
88
175
|
)
|
89
176
|
end
|
90
177
|
|
91
|
-
it 'raises an exception' do
|
178
|
+
it 'raises an exception with request ID' do
|
92
179
|
expect do
|
93
180
|
described_class.profile(**args)
|
94
|
-
end.to raise_error(
|
181
|
+
end.to raise_error(
|
182
|
+
WorkOS::APIError,
|
183
|
+
'some error message - request ID: request-id',
|
184
|
+
)
|
95
185
|
end
|
96
186
|
end
|
97
187
|
|
@@ -99,18 +189,23 @@ describe WorkOS::SSO do
|
|
99
189
|
before do
|
100
190
|
stub_request(:post, 'https://api.workos.com/sso/token').
|
101
191
|
with(query: query).
|
102
|
-
to_return(
|
103
|
-
|
104
|
-
|
105
|
-
|
192
|
+
to_return(
|
193
|
+
status: 201,
|
194
|
+
headers: { 'X-Request-ID' => 'request-id' },
|
195
|
+
body: {
|
196
|
+
message: "The code '01DVX3C5Z367SFHR8QNDMK7V24'" \
|
197
|
+
' has expired or is invalid.',
|
198
|
+
}.to_json,
|
199
|
+
)
|
106
200
|
end
|
107
201
|
|
108
202
|
it 'raises an exception' do
|
109
203
|
expect do
|
110
204
|
described_class.profile(**args)
|
111
205
|
end.to raise_error(
|
112
|
-
WorkOS::
|
113
|
-
"The code '01DVX3C5Z367SFHR8QNDMK7V24'
|
206
|
+
WorkOS::APIError,
|
207
|
+
"The code '01DVX3C5Z367SFHR8QNDMK7V24'" \
|
208
|
+
' has expired or is invalid. - request ID: request-id',
|
114
209
|
)
|
115
210
|
end
|
116
211
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
-
# typed:
|
2
|
+
# typed: true
|
3
3
|
|
4
4
|
require 'simplecov'
|
5
5
|
SimpleCov.start
|
@@ -16,9 +16,16 @@ require 'rubygems'
|
|
16
16
|
require 'rspec'
|
17
17
|
require 'webmock/rspec'
|
18
18
|
require 'workos'
|
19
|
+
require 'vcr'
|
19
20
|
|
20
21
|
SPEC_ROOT = File.dirname __FILE__
|
21
22
|
|
23
|
+
VCR.configure do |config|
|
24
|
+
config.cassette_library_dir = 'spec/support/fixtures/vcr_cassettes'
|
25
|
+
config.filter_sensitive_data('<API_KEY>') { WorkOS.key }
|
26
|
+
config.hook_into :webmock
|
27
|
+
end
|
28
|
+
|
22
29
|
RSpec.configure do |config|
|
23
30
|
config.expect_with :rspec do |expectations|
|
24
31
|
expectations.include_chain_clauses_in_custom_matcher_descriptions = true
|
@@ -29,4 +36,17 @@ RSpec.configure do |config|
|
|
29
36
|
end
|
30
37
|
|
31
38
|
config.shared_context_metadata_behavior = :apply_to_host_groups
|
39
|
+
|
40
|
+
WebMock::API.prepend(Module.new do
|
41
|
+
extend self
|
42
|
+
|
43
|
+
# Disable VCR when a WebMock stub is created
|
44
|
+
# for clearer spec failure messaging
|
45
|
+
def stub_request(*args)
|
46
|
+
VCR.turn_off!
|
47
|
+
super
|
48
|
+
end
|
49
|
+
end)
|
50
|
+
|
51
|
+
config.before(:each) { VCR.turn_on! }
|
32
52
|
end
|