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.
Files changed (32) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +8 -11
  3. data/README.md +7 -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 +6 -1
  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 +18 -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. data/workos.gemspec +1 -1
  32. metadata +33 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5a179e7a1a9dcd3e5bb85ab328900de17d3987e0763bb285ce4539e44fb11524
4
- data.tar.gz: 52586ff03484f8550db6e1f538e3ffdbc22b0ccc2dd275e3dc75056c8d18a407
3
+ metadata.gz: 4d0c25850478e9c956759f36703587fd5da039b17063b8bbe5471937aadc15cf
4
+ data.tar.gz: ab19b3a879da2a46560d2fa0dffeb4da85428ebec07ceeabe6f45f38059e6734
5
5
  SHA512:
6
- metadata.gz: cd6378af304ecb31e392dfe8c1a8807b48e8290e466865937aae9565f79fe3e598df0a9a165d0849db8ae548e45076f1a2f8e5fc54ad2962fda1cc1060ea34ee
7
- data.tar.gz: 9249a469a463e5bb9e9cfb8e300bde22b153e8c39b2e7327f2aefa628a932607635bc2df5309d109336177024f8b8157ba2622b0245faf5b3cf11a223650956f
6
+ metadata.gz: 1894c61c6804fa7089f7475461e402d1803bca62fcf728ee7c549bd318980602df6f7aa6641cbd9050ebcacfe4fd12c9e85c8c3e5e880eed80d7fa3ce8468327
7
+ data.tar.gz: ce0521b22be9055589b774fab6d726105c01cd86cab54ddaa0e22b99338175a62d27b11e7aa5783b76b4b2bb69514e619897fd2c048ebacb71a1a057920d554d
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- workos (0.4.2)
4
+ workos (0.8.1)
5
5
  sorbet-runtime (~> 0.5)
6
6
 
7
7
  GEM
@@ -10,17 +10,16 @@ GEM
10
10
  addressable (2.7.0)
11
11
  public_suffix (>= 2.0.2, < 5.0)
12
12
  ast (2.4.0)
13
- codecov (0.1.16)
13
+ codecov (0.2.8)
14
14
  json
15
15
  simplecov
16
- url
17
16
  crack (0.4.3)
18
17
  safe_yaml (~> 1.0.0)
19
18
  diff-lcs (1.3)
20
19
  docile (1.3.2)
21
20
  hashdiff (1.0.0)
22
21
  jaro_winkler (1.5.4)
23
- json (2.3.0)
22
+ json (2.3.1)
24
23
  parallel (1.19.1)
25
24
  parser (2.7.0.0)
26
25
  ast (~> 2.4.0)
@@ -49,17 +48,15 @@ GEM
49
48
  unicode-display_width (>= 1.4.0, < 1.7)
50
49
  ruby-progressbar (1.10.1)
51
50
  safe_yaml (1.0.5)
52
- simplecov (0.17.1)
51
+ simplecov (0.19.0)
53
52
  docile (~> 1.1)
54
- json (>= 1.8, < 3)
55
- simplecov-html (~> 0.10.0)
56
- simplecov-html (0.10.2)
53
+ simplecov-html (~> 0.11)
54
+ simplecov-html (0.12.2)
57
55
  sorbet (0.5.5560)
58
56
  sorbet-static (= 0.5.5560)
59
- sorbet-runtime (0.5.5870)
57
+ sorbet-runtime (0.5.5923)
60
58
  sorbet-static (0.5.5560-universal-darwin-14)
61
59
  unicode-display_width (1.6.0)
62
- url (0.3.2)
63
60
  vcr (5.0.0)
64
61
  webmock (3.7.6)
65
62
  addressable (>= 2.3.6)
@@ -72,7 +69,7 @@ PLATFORMS
72
69
 
73
70
  DEPENDENCIES
74
71
  bundler (>= 2.0.1)
75
- codecov (~> 0.1.16)
72
+ codecov (~> 0.2.8)
76
73
  rake
77
74
  rspec (~> 3.9.0)
78
75
  rubocop (~> 0.77)
data/README.md CHANGED
@@ -157,6 +157,13 @@ This method will return an instance of a `WorkOS::Profile` with the following at
157
157
  @connection_type="OktaSAML",
158
158
  @last_name="Demo",
159
159
  @idp_id="00u1klkowm8EGah2H357",
160
+ @raw_attributes={
161
+ :id=>"prof_01DRA1XNSJDZ19A31F183ECQW5",
162
+ :email=>"demo@workos-okta.com",
163
+ :first_name=>"WorkOS",
164
+ :last_name=>"Demo",
165
+ :idp_id=>"00u1klkowm8EGah2H357"
166
+ },
160
167
  >
161
168
  ```
162
169
 
@@ -31,6 +31,9 @@ module WorkOS
31
31
  autoload :AuditTrail, 'workos/audit_trail'
32
32
  autoload :Connection, 'workos/connection'
33
33
  autoload :DirectorySync, 'workos/directory_sync'
34
+ autoload :Organization, 'workos/organization'
35
+ autoload :Passwordless, 'workos/passwordless'
36
+ autoload :Portal, 'workos/portal'
34
37
  autoload :Profile, 'workos/profile'
35
38
  autoload :SSO, 'workos/sso'
36
39
 
@@ -83,8 +83,8 @@ module WorkOS
83
83
  # event occurred at or after
84
84
  # @option options [String] occurred_at_lt ISO-8601 datetime of when an
85
85
  # event occurred before
86
- # @option options [String] ISO-8601 datetime of when an event occured at
87
- # or before
86
+ # @option options [String] occurred_at_lte ISO-8601 datetime of when an
87
+ # event occured at or before
88
88
  # @option options [String] search Keyword search
89
89
  #
90
90
  # @return [Array<Hash>]
@@ -81,8 +81,7 @@ module WorkOS
81
81
  ].join('; ')
82
82
  end
83
83
 
84
-
85
- # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
84
+ # rubocop:disable Metrics/MethodLength, Metrics/AbcSize, Metrics/CyclomaticComplexity
86
85
  sig { params(response: ::T.untyped).void }
87
86
  def handle_error_response(response:)
88
87
  http_status = response.code.to_i
@@ -108,11 +107,10 @@ module WorkOS
108
107
  request_id: response['x-request-id'],
109
108
  )
110
109
  when 422
111
- errors = json['errors'].map do |error|
112
- "#{error['field']}: #{error['code']}"
113
- end.join('; ')
110
+ message = json['message']
111
+ errors = extract_error(json['errors']) if json['errors']
112
+ message += " (#{errors})" if errors
114
113
 
115
- message = "#{json['message']} (#{errors})"
116
114
  raise InvalidRequestError.new(
117
115
  message: message,
118
116
  http_status: http_status,
@@ -120,6 +118,14 @@ module WorkOS
120
118
  )
121
119
  end
122
120
  end
123
- # rubocop:enable Metrics/MethodLength, Metrics/AbcSize
121
+ # rubocop:enable Metrics/MethodLength, Metrics/AbcSize, Metrics/CyclomaticComplexity
122
+
123
+ private
124
+
125
+ def extract_error(errors)
126
+ errors.map do |error|
127
+ "#{error['field']}: #{error['code']}"
128
+ end.join('; ')
129
+ end
124
130
  end
125
131
  end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+ # typed: true
3
+
4
+ require 'json'
5
+
6
+ module WorkOS
7
+ # The Organization class provides a lightweight wrapper around
8
+ # a WorkOS Organization resource. This class is not meant to be instantiated
9
+ # in user space, and is instantiated internally but exposed.
10
+ class Organization
11
+ extend T::Sig
12
+
13
+ attr_accessor :id, :domains, :name
14
+
15
+ sig { params(json: String).void }
16
+ def initialize(json)
17
+ raw = parse_json(json)
18
+
19
+ @id = T.let(raw.id, String)
20
+ @name = T.let(raw.name, String)
21
+ @domains = T.let(raw.domains, Array)
22
+ end
23
+
24
+ def to_json(*)
25
+ {
26
+ id: id,
27
+ name: name,
28
+ domains: domains,
29
+ }
30
+ end
31
+
32
+ private
33
+
34
+ sig do
35
+ params(
36
+ json_string: String,
37
+ ).returns(WorkOS::Types::OrganizationStruct)
38
+ end
39
+ def parse_json(json_string)
40
+ hash = JSON.parse(json_string, symbolize_names: true)
41
+
42
+ WorkOS::Types::OrganizationStruct.new(
43
+ id: hash[:id],
44
+ name: hash[:name],
45
+ domains: hash[:domains],
46
+ )
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+ # typed: true
3
+
4
+ require 'net/http'
5
+
6
+ module WorkOS
7
+ # The Passwordless module provides convenience methods for working with
8
+ # passwordless sessions including the WorkOS Magic Link. You'll need a valid
9
+ # API key.
10
+ #
11
+ # @see https://workos.com/docs/sso/configuring-magic-link
12
+ module Passwordless
13
+ class << self
14
+ extend T::Sig
15
+ include Base
16
+ include Client
17
+
18
+ # Create a Passwordless Session.
19
+ #
20
+ # @param [Hash] options A hash with options for the session
21
+ # @option options [String] email The email of the user to authenticate.
22
+ # @option options [String] state Optional parameter that the redirect URI
23
+ # received from WorkOS will contain. The state parameter can be used to
24
+ # encode arbitrary information to help restore application state between
25
+ # redirects.
26
+ # @option options [String] type The type of Passwordless Session to
27
+ # create. Currently, the only supported value is 'MagicLink'.
28
+ #
29
+ # @return Hash
30
+ sig do
31
+ params(
32
+ options: Hash,
33
+ ).returns(WorkOS::Types::PasswordlessSessionStruct)
34
+ end
35
+
36
+ # rubocop:disable Metrics/MethodLength
37
+ def create_session(options)
38
+ response = execute_request(
39
+ request: post_request(
40
+ path: '/passwordless/sessions',
41
+ auth: true,
42
+ body: options,
43
+ ),
44
+ )
45
+
46
+ hash = JSON.parse(response.body)
47
+
48
+ WorkOS::Types::PasswordlessSessionStruct.new(
49
+ id: hash['id'],
50
+ email: hash['email'],
51
+ expires_at: Date.parse(hash['expires_at']),
52
+ link: hash['link'],
53
+ )
54
+ end
55
+ # rubocop:enable Metrics/MethodLength
56
+
57
+ # Send a Passwordless Session via email.
58
+ #
59
+ # @param [String] session_id The unique identifier of the Passwordless
60
+ # Session to send an email for.
61
+ #
62
+ # @return Hash
63
+ sig do
64
+ params(
65
+ session_id: String,
66
+ ).returns(T::Hash[String, T::Boolean])
67
+ end
68
+
69
+ def send_session(session_id)
70
+ response = execute_request(
71
+ request: post_request(
72
+ path: "/passwordless/sessions/#{session_id}/send",
73
+ auth: true,
74
+ ),
75
+ )
76
+
77
+ JSON.parse(response.body)
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,149 @@
1
+ # frozen_string_literal: true
2
+ # typed: true
3
+
4
+ require 'net/http'
5
+
6
+ module WorkOS
7
+ # The Portal module provides resource methods for working with the Admin
8
+ # Portal product
9
+ module Portal
10
+ class << self
11
+ extend T::Sig
12
+ include Base
13
+ include Client
14
+
15
+ GENERATE_LINK_INTENTS = WorkOS::Types::Intent.values.map(&:serialize).
16
+ freeze
17
+
18
+ # Create an organization
19
+ #
20
+ # @param [Array<String>] domains List of domains that belong to the
21
+ # organization
22
+ # @param [String] name A unique, descriptive name for the organization
23
+ sig do
24
+ params(
25
+ domains: T::Array[String],
26
+ name: String,
27
+ ).returns(WorkOS::Organization)
28
+ end
29
+ def create_organization(domains:, name:)
30
+ request = post_request(
31
+ auth: true,
32
+ body: { domains: domains, name: name },
33
+ path: '/organizations',
34
+ )
35
+
36
+ response = execute_request(request: request)
37
+ check_and_raise_organization_error(response: response)
38
+
39
+ WorkOS::Organization.new(response.body)
40
+ end
41
+
42
+ # Generate a link to grant access to an organization's Admin Portal
43
+ #
44
+ # @param [String] intent The access scope for the generated Admin Portal
45
+ # link. Valid values are: ["sso"]
46
+ # @param [String] organization The ID of the organization the Admin
47
+ # Portal link will be generated for.
48
+ # @param [String] The URL that the end user will be redirected to upon
49
+ # exiting the generated Admin Portal. If none is provided, the default
50
+ # redirect link set in your WorkOS Dashboard will be used.
51
+ sig do
52
+ params(
53
+ intent: String,
54
+ organization: String,
55
+ return_url: T.nilable(String),
56
+ ).returns(String)
57
+ end
58
+ # rubocop:disable Metrics/MethodLength
59
+ def generate_link(intent:, organization:, return_url: nil)
60
+ validate_intent(intent)
61
+
62
+ request = post_request(
63
+ auth: true,
64
+ body: {
65
+ intent: intent,
66
+ organization: organization,
67
+ return_url: return_url,
68
+ },
69
+ path: '/portal/generate_link',
70
+ )
71
+
72
+ response = execute_request(request: request)
73
+
74
+ JSON.parse(response.body)['link']
75
+ end
76
+ # rubocop:enable Metrics/MethodLength
77
+
78
+ # Retrieve a list of organizations that have connections configured
79
+ # within your WorkOS dashboard.
80
+ #
81
+ # @param [Array<String>] domains Filter organizations to only return those
82
+ # that are associated with the provided domains.
83
+ # @param [String] before A pagination argument used to request
84
+ # organizations before the provided Organization ID.
85
+ # @param [String] after A pagination argument used to request
86
+ # organizations after the provided Organization ID.
87
+ # @param [Integer] limit A pagination argument used to limit the number
88
+ # of listed Organizations that are returned.
89
+ sig do
90
+ params(
91
+ options: T::Hash[Symbol, String],
92
+ ).returns(WorkOS::Types::ListStruct)
93
+ end
94
+ # rubocop:disable Metrics/MethodLength
95
+ def list_organizations(options = {})
96
+ response = execute_request(
97
+ request: get_request(
98
+ path: '/organizations',
99
+ auth: true,
100
+ params: options,
101
+ ),
102
+ )
103
+
104
+ parsed_response = JSON.parse(response.body)
105
+
106
+ organizations = parsed_response['data'].map do |organization|
107
+ ::WorkOS::Organization.new(organization.to_json)
108
+ end
109
+
110
+ WorkOS::Types::ListStruct.new(
111
+ data: organizations,
112
+ list_metadata: parsed_response['listMetadata'],
113
+ )
114
+ end
115
+ # rubocop:enable Metrics/MethodLength
116
+
117
+ private
118
+
119
+ sig { params(response: Net::HTTPResponse).void }
120
+ # rubocop:disable Metrics/MethodLength
121
+ def check_and_raise_organization_error(response:)
122
+ begin
123
+ body = JSON.parse(response.body)
124
+ return unless body['message']
125
+
126
+ message = body['message']
127
+ request_id = response['x-request-id']
128
+ rescue StandardError
129
+ message = 'Something went wrong'
130
+ end
131
+
132
+ raise APIError.new(
133
+ message: message,
134
+ http_status: nil,
135
+ request_id: request_id,
136
+ )
137
+ end
138
+ # rubocop:enable Metrics/MethodLength
139
+
140
+ sig { params(intent: String).void }
141
+ def validate_intent(intent)
142
+ return if GENERATE_LINK_INTENTS.include?(intent)
143
+
144
+ raise ArgumentError, "#{intent} is not a valid value." \
145
+ " `intent` must be in #{GENERATE_LINK_INTENTS}"
146
+ end
147
+ end
148
+ end
149
+ end
@@ -14,7 +14,7 @@ module WorkOS
14
14
 
15
15
  sig { returns(String) }
16
16
  attr_accessor :id, :email, :first_name, :last_name,
17
- :connection_type, :idp_id
17
+ :connection_type, :idp_id, :raw_attributes
18
18
 
19
19
  sig { params(profile_json: String).void }
20
20
  def initialize(profile_json)
@@ -26,6 +26,7 @@ module WorkOS
26
26
  @last_name = raw.last_name
27
27
  @connection_type = T.let(raw.connection_type, String)
28
28
  @idp_id = raw.idp_id
29
+ @raw_attributes = raw.raw_attributes
29
30
  end
30
31
 
31
32
  sig { returns(String) }
@@ -41,11 +42,13 @@ module WorkOS
41
42
  last_name: last_name,
42
43
  connection_type: connection_type,
43
44
  idp_id: idp_id,
45
+ raw_attributes: raw_attributes,
44
46
  }
45
47
  end
46
48
 
47
49
  private
48
50
 
51
+ # rubocop:disable Metrics/AbcSize
49
52
  sig { params(json_string: String).returns(WorkOS::Types::ProfileStruct) }
50
53
  def parse_json(json_string)
51
54
  hash = JSON.parse(json_string, symbolize_names: true)
@@ -57,7 +60,9 @@ module WorkOS
57
60
  last_name: hash[:profile][:last_name],
58
61
  connection_type: hash[:profile][:connection_type],
59
62
  idp_id: hash[:profile][:idp_id],
63
+ raw_attributes: hash[:profile][:raw_attributes],
60
64
  )
61
65
  end
66
+ # rubocop:enable Metrics/AbcSize
62
67
  end
63
68
  end