workos 0.5.0 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (31) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +2 -2
  3. data/README.md +1 -0
  4. data/lib/workos.rb +3 -0
  5. data/lib/workos/audit_trail.rb +2 -2
  6. data/lib/workos/client.rb +13 -7
  7. data/lib/workos/organization.rb +49 -0
  8. data/lib/workos/passwordless.rb +81 -0
  9. data/lib/workos/portal.rb +149 -0
  10. data/lib/workos/profile.rb +8 -4
  11. data/lib/workos/types.rb +4 -0
  12. data/lib/workos/types/intent_enum.rb +14 -0
  13. data/lib/workos/types/list_struct.rb +13 -0
  14. data/lib/workos/types/organization_struct.rb +14 -0
  15. data/lib/workos/types/passwordless_session_struct.rb +15 -0
  16. data/lib/workos/types/profile_struct.rb +1 -0
  17. data/lib/workos/version.rb +1 -1
  18. data/spec/lib/workos/passwordless_spec.rb +82 -0
  19. data/spec/lib/workos/portal_spec.rb +176 -0
  20. data/spec/lib/workos/sso_spec.rb +1 -0
  21. data/spec/support/fixtures/vcr_cassettes/organization/create.yml +72 -0
  22. data/spec/support/fixtures/vcr_cassettes/organization/create_invalid.yml +72 -0
  23. data/spec/support/fixtures/vcr_cassettes/organization/list.yml +72 -0
  24. data/spec/support/fixtures/vcr_cassettes/passwordless/create_session.yml +72 -0
  25. data/spec/support/fixtures/vcr_cassettes/passwordless/create_session_invalid.yml +73 -0
  26. data/spec/support/fixtures/vcr_cassettes/passwordless/send_session.yml +72 -0
  27. data/spec/support/fixtures/vcr_cassettes/passwordless/send_session_invalid.yml +73 -0
  28. data/spec/support/fixtures/vcr_cassettes/portal/generate_link.yml +72 -0
  29. data/spec/support/fixtures/vcr_cassettes/portal/generate_link_invalid.yml +72 -0
  30. data/spec/support/profile.txt +1 -1
  31. metadata +31 -2
@@ -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
@@ -10,6 +10,7 @@ module WorkOS
10
10
  const :email, String
11
11
  const :first_name, T.nilable(String)
12
12
  const :last_name, T.nilable(String)
13
+ const :connection_id, String
13
14
  const :connection_type, String
14
15
  const :idp_id, T.nilable(String)
15
16
  const :raw_attributes, T::Hash[Symbol, Object]
@@ -2,5 +2,5 @@
2
2
  # typed: strong
3
3
 
4
4
  module WorkOS
5
- VERSION = '0.5.0'
5
+ VERSION = '0.9.0'
6
6
  end
@@ -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
@@ -163,6 +163,7 @@ describe WorkOS::SSO do
163
163
  expect(profile).to be_a(WorkOS::Profile)
164
164
 
165
165
  expectation = {
166
+ connection_id: 'conn_01EMH8WAK20T42N2NBMNBCYHAG',
166
167
  connection_type: 'OktaSAML',
167
168
  email: 'demo@workos-okta.com',
168
169
  first_name: 'WorkOS',
@@ -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
@@ -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 2"}'
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: 409
23
+ message: Conflict
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
+ - 929940d6-33dd-404c-9856-eca6cc606d28
57
+ Content-Type:
58
+ - application/json; charset=utf-8
59
+ Content-Length:
60
+ - '73'
61
+ Etag:
62
+ - W/"49-8i1S2EtfSciiA8rvGWbYFNlSlhw"
63
+ Date:
64
+ - Wed, 09 Sep 2020 21:26:03 GMT
65
+ Via:
66
+ - 1.1 vegur
67
+ body:
68
+ encoding: UTF-8
69
+ string: '{"message":"An Organization with the domain example.com already exists."}'
70
+ http_version:
71
+ recorded_at: Wed, 09 Sep 2020 21:26:03 GMT
72
+ recorded_with: VCR 5.0.0