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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 56cf52cb4a104516de13499b34f7c6f36a102c0c2af1bf6934e963579f27a596
4
- data.tar.gz: 45dee16f5ed91f99133e7f974d607e76effa73e0d0d3b271fd7e9756e72055c5
3
+ metadata.gz: 9fd3e38d33d428497a46f40df28c4d142bc9adaf90c166b61c05914030c50b25
4
+ data.tar.gz: c445aa218658fe1fb57add7cc9081b1e63223641d29cac1e276429d3fb1244fd
5
5
  SHA512:
6
- metadata.gz: 00d537dcaf496aa3b8244f72b04137496fef5613100ec61d32f837f20f42f6f5ed20180d41eb560dd6bfe6491810f07d4f4401d0e0cacc121c991b5c74d80676
7
- data.tar.gz: 843f4d8558e3412ed6fa3becaf6c7717a301121baa7fd6edb292184518d335b2f616f024fdf13a9adf8659110aae3411744525be76f25f1bf238dd05acf14d99
6
+ metadata.gz: b94cf0c103286a457c3a73ab4ead5196ad2a8111f486ff22adbf4ecbdd5a21685dcb9483325b15813dcd5a25ea2383aec7d64fbc7545d4c80267731e97ed4a48
7
+ data.tar.gz: 30d4256a4b9218890dd880a5513dd90a09052f746085dffc282532bbf9ed5683b04d690181cfe1b209f02edc1fd1e37e04149819c8731afb81f5aba5561a2c18
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- workos (0.5.0)
4
+ workos (0.9.0)
5
5
  sorbet-runtime (~> 0.5)
6
6
 
7
7
  GEM
@@ -54,7 +54,7 @@ GEM
54
54
  simplecov-html (0.12.2)
55
55
  sorbet (0.5.5560)
56
56
  sorbet-static (= 0.5.5560)
57
- sorbet-runtime (0.5.5877)
57
+ sorbet-runtime (0.5.5943)
58
58
  sorbet-static (0.5.5560-universal-darwin-14)
59
59
  unicode-display_width (1.6.0)
60
60
  vcr (5.0.0)
data/README.md CHANGED
@@ -154,6 +154,7 @@ This method will return an instance of a `WorkOS::Profile` with the following at
154
154
  @id="prof_01DRA1XNSJDZ19A31F183ECQW5",
155
155
  @email="demo@workos-okta.com",
156
156
  @first_name="WorkOS",
157
+ @connection_id="conn_01EMH8WAK20T42N2NBMNBCYHAG",
157
158
  @connection_type="OktaSAML",
158
159
  @last_name="Demo",
159
160
  @idp_id="00u1klkowm8EGah2H357",
@@ -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
@@ -13,9 +13,9 @@ module WorkOS
13
13
  extend T::Sig
14
14
 
15
15
  sig { returns(String) }
16
- attr_accessor :id, :email, :first_name, :last_name,
16
+ attr_accessor :id, :email, :first_name, :last_name, :connection_id,
17
17
  :connection_type, :idp_id, :raw_attributes
18
-
18
+ # rubocop:disable Metrics/AbcSize
19
19
  sig { params(profile_json: String).void }
20
20
  def initialize(profile_json)
21
21
  raw = parse_json(profile_json)
@@ -24,10 +24,12 @@ module WorkOS
24
24
  @email = T.let(raw.email, String)
25
25
  @first_name = raw.first_name
26
26
  @last_name = raw.last_name
27
+ @connection_id = T.let(raw.connection_id, String)
27
28
  @connection_type = T.let(raw.connection_type, String)
28
29
  @idp_id = raw.idp_id
29
30
  @raw_attributes = raw.raw_attributes
30
31
  end
32
+ # rubocop:enable Metrics/AbcSize
31
33
 
32
34
  sig { returns(String) }
33
35
  def full_name
@@ -40,6 +42,7 @@ module WorkOS
40
42
  email: email,
41
43
  first_name: first_name,
42
44
  last_name: last_name,
45
+ connection_id: connection_id,
43
46
  connection_type: connection_type,
44
47
  idp_id: idp_id,
45
48
  raw_attributes: raw_attributes,
@@ -48,7 +51,7 @@ module WorkOS
48
51
 
49
52
  private
50
53
 
51
- # rubocop:disable Metrics/AbcSize
54
+ # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
52
55
  sig { params(json_string: String).returns(WorkOS::Types::ProfileStruct) }
53
56
  def parse_json(json_string)
54
57
  hash = JSON.parse(json_string, symbolize_names: true)
@@ -58,11 +61,12 @@ module WorkOS
58
61
  email: hash[:profile][:email],
59
62
  first_name: hash[:profile][:first_name],
60
63
  last_name: hash[:profile][:last_name],
64
+ connection_id: hash[:profile][:connection_id],
61
65
  connection_type: hash[:profile][:connection_type],
62
66
  idp_id: hash[:profile][:idp_id],
63
67
  raw_attributes: hash[:profile][:raw_attributes],
64
68
  )
65
69
  end
66
- # rubocop:enable Metrics/AbcSize
70
+ # rubocop:enable Metrics/AbcSize, Metrics/MethodLength
67
71
  end
68
72
  end
@@ -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