workos 0.4.0 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (33) 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/directory_sync.rb +2 -0
  8. data/lib/workos/organization.rb +49 -0
  9. data/lib/workos/passwordless.rb +81 -0
  10. data/lib/workos/portal.rb +145 -0
  11. data/lib/workos/profile.rb +7 -2
  12. data/lib/workos/types.rb +4 -0
  13. data/lib/workos/types/intent_enum.rb +14 -0
  14. data/lib/workos/types/list_struct.rb +13 -0
  15. data/lib/workos/types/organization_struct.rb +14 -0
  16. data/lib/workos/types/passwordless_session_struct.rb +15 -0
  17. data/lib/workos/types/profile_struct.rb +2 -1
  18. data/lib/workos/version.rb +1 -1
  19. data/spec/lib/workos/passwordless_spec.rb +82 -0
  20. data/spec/lib/workos/portal_spec.rb +176 -0
  21. data/spec/lib/workos/sso_spec.rb +18 -0
  22. data/spec/support/fixtures/vcr_cassettes/organization/create.yml +72 -0
  23. data/spec/support/fixtures/vcr_cassettes/organization/create_invalid.yml +72 -0
  24. data/spec/support/fixtures/vcr_cassettes/organization/list.yml +72 -0
  25. data/spec/support/fixtures/vcr_cassettes/passwordless/create_session.yml +72 -0
  26. data/spec/support/fixtures/vcr_cassettes/passwordless/create_session_invalid.yml +73 -0
  27. data/spec/support/fixtures/vcr_cassettes/passwordless/send_session.yml +72 -0
  28. data/spec/support/fixtures/vcr_cassettes/passwordless/send_session_invalid.yml +73 -0
  29. data/spec/support/fixtures/vcr_cassettes/portal/generate_link.yml +72 -0
  30. data/spec/support/fixtures/vcr_cassettes/portal/generate_link_invalid.yml +72 -0
  31. data/spec/support/profile.txt +1 -1
  32. data/workos.gemspec +1 -1
  33. metadata +33 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ce58ab479d49e4f828ebe675fb3f3b8104a6d82d85f19da6087f9a907dc1e827
4
- data.tar.gz: 96d0c77cf047d6e56952414cda3d80d2395b692b4125af95c15a9ba85b00c0c9
3
+ metadata.gz: dbb2aa0d20fb9d4fa99930c63d67240f2bb911613dd48848aa2c89d5dc94fcf7
4
+ data.tar.gz: 838ca6771237c0df8acd0e7b1ef59c021f87f8b4c7e94b32a5f6fb83c3158ac9
5
5
  SHA512:
6
- metadata.gz: 8529507b9187453055b084ec30446123a2572f4045380f17916d640b1c0f052eb64089d620695469b3eb0839c3d88e4f6827a24de1802bd432f2c5f134cb3094
7
- data.tar.gz: 505133b5a111f5efdfd1b9c26b773f6359dc21841273df43ae0f7090afec8ee11350830004f2dd285d7b607b3d423e95513c50484cab36ade8be60686be214f1
6
+ metadata.gz: 6bf3d16a8e76427557d5b08dc2897e167730a7b8b81fc96c42b6c04b5db74f64a5c06b24f381784f6b9c845fcff945fc273f3d0ceaf872d97c80c0b1beb6639f
7
+ data.tar.gz: 0f33de9781d1df7fd2f02bac813cbb3d192fe17f5690a28804a848400d422575e2d7e91c955259d99ff7fbc5bd60d457a595fe7279128058d3ba0100c1858378
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- workos (0.4.0)
4
+ workos (0.7.0)
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.5858)
57
+ sorbet-runtime (0.5.5909)
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
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
  # typed: true
3
3
 
4
+ require 'net/http'
5
+
4
6
  module WorkOS
5
7
  # The Directory Sync module provides convenience methods for working with the
6
8
  # WorkOS Directory Sync platform. You'll need a valid API key and to have
@@ -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,145 @@
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
+ WorkOS::Types::ListStruct.new(
107
+ data: parsed_response['data'],
108
+ list_metadata: parsed_response['listMetadata'],
109
+ )
110
+ end
111
+ # rubocop:enable Metrics/MethodLength
112
+
113
+ private
114
+
115
+ sig { params(response: Net::HTTPResponse).void }
116
+ # rubocop:disable Metrics/MethodLength
117
+ def check_and_raise_organization_error(response:)
118
+ begin
119
+ body = JSON.parse(response.body)
120
+ return unless body['message']
121
+
122
+ message = body['message']
123
+ request_id = response['x-request-id']
124
+ rescue StandardError
125
+ message = 'Something went wrong'
126
+ end
127
+
128
+ raise APIError.new(
129
+ message: message,
130
+ http_status: nil,
131
+ request_id: request_id,
132
+ )
133
+ end
134
+ # rubocop:enable Metrics/MethodLength
135
+
136
+ sig { params(intent: String).void }
137
+ def validate_intent(intent)
138
+ return if GENERATE_LINK_INTENTS.include?(intent)
139
+
140
+ raise ArgumentError, "#{intent} is not a valid value." \
141
+ " `intent` must be in #{GENERATE_LINK_INTENTS}"
142
+ end
143
+ end
144
+ end
145
+ 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)
@@ -25,7 +25,8 @@ module WorkOS
25
25
  @first_name = raw.first_name
26
26
  @last_name = raw.last_name
27
27
  @connection_type = T.let(raw.connection_type, String)
28
- @idp_id = T.let(raw.idp_id, String)
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