workos 0.4.2 → 0.8.1
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 +8 -11
- data/README.md +7 -0
- data/lib/workos.rb +3 -0
- data/lib/workos/audit_trail.rb +2 -2
- data/lib/workos/client.rb +13 -7
- data/lib/workos/organization.rb +49 -0
- data/lib/workos/passwordless.rb +81 -0
- data/lib/workos/portal.rb +149 -0
- data/lib/workos/profile.rb +6 -1
- data/lib/workos/types.rb +4 -0
- data/lib/workos/types/intent_enum.rb +14 -0
- data/lib/workos/types/list_struct.rb +13 -0
- data/lib/workos/types/organization_struct.rb +14 -0
- data/lib/workos/types/passwordless_session_struct.rb +15 -0
- data/lib/workos/types/profile_struct.rb +1 -0
- data/lib/workos/version.rb +1 -1
- data/spec/lib/workos/passwordless_spec.rb +82 -0
- data/spec/lib/workos/portal_spec.rb +176 -0
- data/spec/lib/workos/sso_spec.rb +18 -0
- data/spec/support/fixtures/vcr_cassettes/organization/create.yml +72 -0
- data/spec/support/fixtures/vcr_cassettes/organization/create_invalid.yml +72 -0
- data/spec/support/fixtures/vcr_cassettes/organization/list.yml +72 -0
- data/spec/support/fixtures/vcr_cassettes/passwordless/create_session.yml +72 -0
- data/spec/support/fixtures/vcr_cassettes/passwordless/create_session_invalid.yml +73 -0
- data/spec/support/fixtures/vcr_cassettes/passwordless/send_session.yml +72 -0
- data/spec/support/fixtures/vcr_cassettes/passwordless/send_session_invalid.yml +73 -0
- data/spec/support/fixtures/vcr_cassettes/portal/generate_link.yml +72 -0
- data/spec/support/fixtures/vcr_cassettes/portal/generate_link_invalid.yml +72 -0
- data/spec/support/profile.txt +1 -1
- data/workos.gemspec +1 -1
- metadata +33 -4
data/lib/workos/types.rb
CHANGED
@@ -6,6 +6,10 @@ module WorkOS
|
|
6
6
|
# so we're using Sorbet throughout this Ruby gem.
|
7
7
|
module Types
|
8
8
|
require_relative 'types/connection_struct'
|
9
|
+
require_relative 'types/intent_enum'
|
10
|
+
require_relative 'types/list_struct'
|
11
|
+
require_relative 'types/organization_struct'
|
12
|
+
require_relative 'types/passwordless_session_struct'
|
9
13
|
require_relative 'types/profile_struct'
|
10
14
|
require_relative 'types/provider_enum'
|
11
15
|
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
# typed: strict
|
3
|
+
|
4
|
+
module WorkOS
|
5
|
+
module Types
|
6
|
+
# The IntentEnum is type-safe declarations of a fixed set of values for
|
7
|
+
# intents while generating an Admin Portal link.
|
8
|
+
class Intent < T::Enum
|
9
|
+
enums do
|
10
|
+
SSO = new('sso')
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
# typed: strict
|
3
|
+
|
4
|
+
module WorkOS
|
5
|
+
module Types
|
6
|
+
# ListStruct acts as a typed interface to expose lists of data and related
|
7
|
+
# metadata
|
8
|
+
class ListStruct < T::Struct
|
9
|
+
const :data, T::Array[T.untyped]
|
10
|
+
const :list_metadata, T::Hash[String, T.nilable(String)]
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
# typed: strict
|
3
|
+
|
4
|
+
module WorkOS
|
5
|
+
module Types
|
6
|
+
# This OrganizationStruct acts as a typed interface
|
7
|
+
# for the Organization class
|
8
|
+
class OrganizationStruct < T::Struct
|
9
|
+
const :id, String
|
10
|
+
const :name, String
|
11
|
+
const :domains, T::Array[T.untyped]
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
# typed: strict
|
3
|
+
|
4
|
+
module WorkOS
|
5
|
+
module Types
|
6
|
+
# This PasswordlessSessionStruct acts as a typed interface
|
7
|
+
# for the Passwordless class
|
8
|
+
class PasswordlessSessionStruct < T::Struct
|
9
|
+
const :id, String
|
10
|
+
const :email, String
|
11
|
+
const :expires_at, Date
|
12
|
+
const :link, String
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
data/lib/workos/version.rb
CHANGED
@@ -0,0 +1,82 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
# typed: false
|
3
|
+
|
4
|
+
describe WorkOS::Passwordless 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_session' do
|
14
|
+
context 'with valid options payload' do
|
15
|
+
let(:valid_options) do
|
16
|
+
{
|
17
|
+
email: 'demo@workos-okta.com',
|
18
|
+
type: 'MagicLink',
|
19
|
+
}
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'creates a session' do
|
23
|
+
VCR.use_cassette('passwordless/create_session') do
|
24
|
+
response = described_class.create_session(valid_options)
|
25
|
+
|
26
|
+
expect(response.email).to eq 'demo@workos-okta.com'
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
context 'with invalid event payload' do
|
32
|
+
let(:invalid_options) do
|
33
|
+
{}
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'raises an error' do
|
37
|
+
VCR.use_cassette('passwordless/create_session_invalid') do
|
38
|
+
expect do
|
39
|
+
described_class.create_session(invalid_options)
|
40
|
+
end.to raise_error(
|
41
|
+
WorkOS::InvalidRequestError,
|
42
|
+
/Status 422, Validation failed \(email: email must be a string; type: type must be a valid enum value\)/,
|
43
|
+
)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
describe '.send_session' do
|
50
|
+
context 'with valid session id' do
|
51
|
+
let(:valid_options) do
|
52
|
+
{
|
53
|
+
email: 'demo@workos-okta.com',
|
54
|
+
type: 'MagicLink',
|
55
|
+
}
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'send a session' do
|
59
|
+
VCR.use_cassette('passwordless/send_session') do
|
60
|
+
response = described_class.send_session(
|
61
|
+
'passwordless_session_01EJC0F4KH42T11Y2DHPEB09BM',
|
62
|
+
)
|
63
|
+
|
64
|
+
expect(response['success']).to eq true
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
context 'with invalid session id' do
|
70
|
+
it 'raises an error' do
|
71
|
+
VCR.use_cassette('passwordless/send_session_invalid') do
|
72
|
+
expect do
|
73
|
+
described_class.send_session('session_123')
|
74
|
+
end.to raise_error(
|
75
|
+
WorkOS::InvalidRequestError,
|
76
|
+
/Status 422, The passwordless session 'session_123' has expired or is invalid./,
|
77
|
+
)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,176 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
# typed: false
|
3
|
+
|
4
|
+
describe WorkOS::Portal do
|
5
|
+
before :all do
|
6
|
+
WorkOS.key = 'test'
|
7
|
+
end
|
8
|
+
|
9
|
+
after :all do
|
10
|
+
WorkOS.key = nil
|
11
|
+
end
|
12
|
+
|
13
|
+
describe '.create_organization' do
|
14
|
+
context 'with valid payload' do
|
15
|
+
it 'creates an organization' do
|
16
|
+
VCR.use_cassette 'organization/create' do
|
17
|
+
organization = described_class.create_organization(
|
18
|
+
domains: ['example.com'],
|
19
|
+
name: 'Test Organization',
|
20
|
+
)
|
21
|
+
|
22
|
+
expect(organization.id).to eq('org_01EHT88Z8J8795GZNQ4ZP1J81T')
|
23
|
+
expect(organization.name).to eq('Test Organization')
|
24
|
+
expect(organization.domains.first[:domain]).to eq('example.com')
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
context 'with an invalid payload' do
|
30
|
+
it 'returns an error' do
|
31
|
+
VCR.use_cassette 'organization/create_invalid' do
|
32
|
+
expect do
|
33
|
+
described_class.create_organization(
|
34
|
+
domains: ['example.com'],
|
35
|
+
name: 'Test Organization 2',
|
36
|
+
)
|
37
|
+
end.to raise_error(
|
38
|
+
WorkOS::APIError,
|
39
|
+
/An Organization with the domain example.com already exists/,
|
40
|
+
)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
describe '.generate_link' do
|
47
|
+
let(:organization) { 'org_01EHQMYV6MBK39QC5PZXHY59C3' }
|
48
|
+
|
49
|
+
describe 'with a valid organization' do
|
50
|
+
describe 'with the minimal params' do
|
51
|
+
it 'returns an Admin Portal link' do
|
52
|
+
VCR.use_cassette 'portal/generate_link' do
|
53
|
+
portal_link = described_class.generate_link(
|
54
|
+
intent: 'sso',
|
55
|
+
organization: organization,
|
56
|
+
)
|
57
|
+
|
58
|
+
expect(portal_link).to eq(
|
59
|
+
'https://id.workos.com/portal/launch?secret=secret',
|
60
|
+
)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
describe 'with an invalid organization' do
|
67
|
+
it 'raises an error' do
|
68
|
+
VCR.use_cassette 'portal/generate_link_invalid' do
|
69
|
+
expect do
|
70
|
+
described_class.generate_link(
|
71
|
+
intent: 'sso',
|
72
|
+
organization: 'bogus-id',
|
73
|
+
)
|
74
|
+
end.to raise_error(
|
75
|
+
WorkOS::InvalidRequestError,
|
76
|
+
/Could not find an organization with the id, bogus-id/,
|
77
|
+
)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
describe 'with an invalid intent' do
|
83
|
+
it 'raises an error' do
|
84
|
+
expect do
|
85
|
+
described_class.generate_link(
|
86
|
+
intent: 'bogus-intent',
|
87
|
+
organization: organization,
|
88
|
+
)
|
89
|
+
end.to raise_error(
|
90
|
+
ArgumentError,
|
91
|
+
'bogus-intent is not a valid value. `intent` must be in ["sso"]',
|
92
|
+
)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
describe '.list_organizations' do
|
98
|
+
context 'with no options' do
|
99
|
+
it 'returns organizations and metadata' do
|
100
|
+
expected_metadata = {
|
101
|
+
'after' => nil,
|
102
|
+
'before' => 'before-id',
|
103
|
+
}
|
104
|
+
|
105
|
+
VCR.use_cassette 'organization/list' do
|
106
|
+
organizations = described_class.list_organizations
|
107
|
+
|
108
|
+
expect(organizations.data.size).to eq(7)
|
109
|
+
expect(organizations.list_metadata).to eq(expected_metadata)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
context 'with the before option' do
|
115
|
+
it 'forms the proper request to the API' do
|
116
|
+
request_args = [
|
117
|
+
'/organizations?before=before-id',
|
118
|
+
'Content-Type' => 'application/json'
|
119
|
+
]
|
120
|
+
|
121
|
+
expected_request = Net::HTTP::Get.new(*request_args)
|
122
|
+
|
123
|
+
expect(Net::HTTP::Get).to receive(:new).with(*request_args).
|
124
|
+
and_return(expected_request)
|
125
|
+
|
126
|
+
VCR.use_cassette 'organization/list', match_requests_on: [:path] do
|
127
|
+
organizations = described_class.list_organizations(
|
128
|
+
before: 'before-id',
|
129
|
+
)
|
130
|
+
|
131
|
+
expect(organizations.data.size).to eq(7)
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
context 'with the after option' do
|
137
|
+
it 'forms the proper request to the API' do
|
138
|
+
request_args = [
|
139
|
+
'/organizations?after=after-id',
|
140
|
+
'Content-Type' => 'application/json'
|
141
|
+
]
|
142
|
+
|
143
|
+
expected_request = Net::HTTP::Get.new(*request_args)
|
144
|
+
|
145
|
+
expect(Net::HTTP::Get).to receive(:new).with(*request_args).
|
146
|
+
and_return(expected_request)
|
147
|
+
|
148
|
+
VCR.use_cassette 'organization/list', match_requests_on: [:path] do
|
149
|
+
organizations = described_class.list_organizations(after: 'after-id')
|
150
|
+
|
151
|
+
expect(organizations.data.size).to eq(7)
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
context 'with the limit option' do
|
157
|
+
it 'forms the proper request to the API' do
|
158
|
+
request_args = [
|
159
|
+
'/organizations?limit=10',
|
160
|
+
'Content-Type' => 'application/json'
|
161
|
+
]
|
162
|
+
|
163
|
+
expected_request = Net::HTTP::Get.new(*request_args)
|
164
|
+
|
165
|
+
expect(Net::HTTP::Get).to receive(:new).with(*request_args).
|
166
|
+
and_return(expected_request)
|
167
|
+
|
168
|
+
VCR.use_cassette 'organization/list', match_requests_on: [:path] do
|
169
|
+
organizations = described_class.list_organizations(limit: 10)
|
170
|
+
|
171
|
+
expect(organizations.data.size).to eq(7)
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
data/spec/lib/workos/sso_spec.rb
CHANGED
@@ -161,6 +161,24 @@ describe WorkOS::SSO do
|
|
161
161
|
it 'returns a WorkOS::Profile' do
|
162
162
|
profile = described_class.profile(**args)
|
163
163
|
expect(profile).to be_a(WorkOS::Profile)
|
164
|
+
|
165
|
+
expectation = {
|
166
|
+
connection_type: 'OktaSAML',
|
167
|
+
email: 'demo@workos-okta.com',
|
168
|
+
first_name: 'WorkOS',
|
169
|
+
id: 'prof_01DRA1XNSJDZ19A31F183ECQW5',
|
170
|
+
idp_id: '00u1klkowm8EGah2H357',
|
171
|
+
last_name: 'Demo',
|
172
|
+
raw_attributes: {
|
173
|
+
email: 'demo@workos-okta.com',
|
174
|
+
first_name: 'WorkOS',
|
175
|
+
id: 'prof_01DRA1XNSJDZ19A31F183ECQW5',
|
176
|
+
idp_id: '00u1klkowm8EGah2H357',
|
177
|
+
last_name: 'Demo',
|
178
|
+
},
|
179
|
+
}
|
180
|
+
|
181
|
+
expect(profile.to_json).to eq(expectation)
|
164
182
|
end
|
165
183
|
end
|
166
184
|
|
@@ -0,0 +1,72 @@
|
|
1
|
+
---
|
2
|
+
http_interactions:
|
3
|
+
- request:
|
4
|
+
method: post
|
5
|
+
uri: https://api.workos.com/organizations
|
6
|
+
body:
|
7
|
+
encoding: UTF-8
|
8
|
+
string: '{"domains":["example.com"],"name":"Test Organization"}'
|
9
|
+
headers:
|
10
|
+
Content-Type:
|
11
|
+
- application/json
|
12
|
+
Accept-Encoding:
|
13
|
+
- gzip;q=1.0,deflate;q=0.6,identity;q=0.3
|
14
|
+
Accept:
|
15
|
+
- "*/*"
|
16
|
+
User-Agent:
|
17
|
+
- WorkOS; ruby/2.7.1; x86_64-darwin19; v0.5.0
|
18
|
+
Authorization:
|
19
|
+
- Bearer <API_KEY>
|
20
|
+
response:
|
21
|
+
status:
|
22
|
+
code: 201
|
23
|
+
message: Created
|
24
|
+
headers:
|
25
|
+
Server:
|
26
|
+
- Cowboy
|
27
|
+
Connection:
|
28
|
+
- keep-alive
|
29
|
+
Vary:
|
30
|
+
- Origin, Accept-Encoding
|
31
|
+
Access-Control-Allow-Credentials:
|
32
|
+
- 'true'
|
33
|
+
Content-Security-Policy:
|
34
|
+
- 'default-src ''self'';base-uri ''self'';block-all-mixed-content;font-src ''self''
|
35
|
+
https: data:;frame-ancestors ''self'';img-src ''self'' data:;object-src ''none'';script-src
|
36
|
+
''self'';script-src-attr ''none'';style-src ''self'' https: ''unsafe-inline'';upgrade-insecure-requests'
|
37
|
+
X-Dns-Prefetch-Control:
|
38
|
+
- 'off'
|
39
|
+
Expect-Ct:
|
40
|
+
- max-age=0
|
41
|
+
X-Frame-Options:
|
42
|
+
- SAMEORIGIN
|
43
|
+
Strict-Transport-Security:
|
44
|
+
- max-age=15552000; includeSubDomains
|
45
|
+
X-Download-Options:
|
46
|
+
- noopen
|
47
|
+
X-Content-Type-Options:
|
48
|
+
- nosniff
|
49
|
+
X-Permitted-Cross-Domain-Policies:
|
50
|
+
- none
|
51
|
+
Referrer-Policy:
|
52
|
+
- no-referrer
|
53
|
+
X-Xss-Protection:
|
54
|
+
- '0'
|
55
|
+
X-Request-Id:
|
56
|
+
- bcddfa8b-3a12-4d48-b265-6094a26225a4
|
57
|
+
Content-Type:
|
58
|
+
- application/json; charset=utf-8
|
59
|
+
Content-Length:
|
60
|
+
- '203'
|
61
|
+
Etag:
|
62
|
+
- W/"cb-T4+GTGrJeuAmC0PAjcaSX5ZXL18"
|
63
|
+
Date:
|
64
|
+
- Wed, 09 Sep 2020 20:17:53 GMT
|
65
|
+
Via:
|
66
|
+
- 1.1 vegur
|
67
|
+
body:
|
68
|
+
encoding: UTF-8
|
69
|
+
string: '{"name":"Test Organization","object":"organization","id":"org_01EHT88Z8J8795GZNQ4ZP1J81T","domains":[{"domain":"example.com","object":"organization_domain","id":"org_domain_01EHT88Z8WZEFWYPM6EC9BX2R8"}]}'
|
70
|
+
http_version:
|
71
|
+
recorded_at: Wed, 09 Sep 2020 20:17:54 GMT
|
72
|
+
recorded_with: VCR 5.0.0
|