workos 2.1.1 → 2.2.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/.semaphore/semaphore.yml +13 -39
  3. data/Gemfile.lock +2 -2
  4. data/README.md +4 -0
  5. data/lib/workos/challenge.rb +54 -0
  6. data/lib/workos/client.rb +2 -0
  7. data/lib/workos/directory_sync.rb +1 -0
  8. data/lib/workos/errors.rb +3 -1
  9. data/lib/workos/factor.rb +58 -0
  10. data/lib/workos/mfa.rb +165 -0
  11. data/lib/workos/types/challenge_struct.rb +18 -0
  12. data/lib/workos/types/factor_struct.rb +19 -0
  13. data/lib/workos/types/verify_factor_struct.rb +15 -0
  14. data/lib/workos/types.rb +3 -0
  15. data/lib/workos/verify_factor.rb +39 -0
  16. data/lib/workos/version.rb +1 -1
  17. data/lib/workos/webhooks.rb +1 -1
  18. data/lib/workos.rb +5 -0
  19. data/spec/lib/workos/mfa_spec.rb +232 -0
  20. data/spec/lib/workos/sso_spec.rb +0 -2
  21. data/spec/support/fixtures/vcr_cassettes/mfa/challenge_factor_generic_valid.yml +82 -0
  22. data/spec/support/fixtures/vcr_cassettes/mfa/challenge_factor_sms_valid.yml +82 -0
  23. data/spec/support/fixtures/vcr_cassettes/mfa/challenge_factor_totp_valid.yml +82 -0
  24. data/spec/support/fixtures/vcr_cassettes/mfa/delete_factor.yml +80 -0
  25. data/spec/support/fixtures/vcr_cassettes/mfa/enroll_factor_generic_valid.yml +82 -0
  26. data/spec/support/fixtures/vcr_cassettes/mfa/enroll_factor_sms_valid.yml +82 -0
  27. data/spec/support/fixtures/vcr_cassettes/mfa/enroll_factor_totp_valid.yml +82 -0
  28. data/spec/support/fixtures/vcr_cassettes/mfa/get_factor_invalid.yml +82 -0
  29. data/spec/support/fixtures/vcr_cassettes/mfa/get_factor_valid.yml +82 -0
  30. data/spec/support/fixtures/vcr_cassettes/mfa/verify_factor_generic_expired.yml +84 -0
  31. data/spec/support/fixtures/vcr_cassettes/mfa/verify_factor_generic_invalid.yml +84 -0
  32. data/spec/support/fixtures/vcr_cassettes/mfa/verify_factor_generic_valid.yml +82 -0
  33. metadata +36 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e98cbe7f34c4d550972ac7b4dc734e17ed9b2390b08898d2b20fdf5e69da71ea
4
- data.tar.gz: c998bf772be7cc75e1730debd7efefa764d87676c62b325daf45784846ce3a12
3
+ metadata.gz: 983d578de3e27144af8a7947eebfb770ad7ee640954511586617505c520ee657
4
+ data.tar.gz: 92cb0b75234f56a1e18c47d9cb339b2f389e17463798141bc7a3a2977f5f9372
5
5
  SHA512:
6
- metadata.gz: 01d4bcee2864d6a8b58e8cd3ffef78d77e11028bfbcd9005c4a2cecafaabe0cec97856f94b70aa9391dee50d08502ed8d7afa9456763d16a44f0fcc78a912694
7
- data.tar.gz: ae83d9e5e204771f3d261a2f3135e077cc8b19d700ccf7984447e5536297ba65fba3779c3988daf612ce2c9624c9a825b8dd9b0e633e47c8839f3466a6837440
6
+ metadata.gz: 1143a28ee95755c80fe36aa5fba02e069143ebdb96e67f909d566bbf091928176ac29f23ccfa7764da163a248268f7a882616e0db7bd73612eda060c78cf8515
7
+ data.tar.gz: a2e279f2f2427dd8089eddcc692e677d21978fdd16f3918ffe162822d116b7904f1dbc6826d151f583ba1c60cd5c0e8fbe8b825fb78212f772d7a1aa11faa6da
@@ -31,45 +31,19 @@ blocks:
31
31
  - name: codecov-workos-ruby
32
32
  jobs:
33
33
  - name: Ruby 1.9.3
34
- commands:
35
- - checkout
36
- - sem-version ruby 1.9.3-p551
37
- - bundle install
38
- - bundle exec rspec
39
- - name: Ruby 2.0.0
40
- commands:
41
- - checkout
42
- - sem-version ruby 2.0.0-p648
43
- - bundle install
44
- - bundle exec rspec
45
- - name: Ruby 2.3.4
46
- commands:
47
- - checkout
48
- - sem-version ruby 2.3.4
49
- - bundle install
50
- - bundle exec rspec
51
- - name: Ruby 2.5.7
52
- commands:
53
- - checkout
54
- - sem-version ruby 2.5.7
55
- - bundle install
56
- - bundle exec rspec
57
- - name: Ruby 2.6.5
58
- commands:
59
- - checkout
60
- - sem-version ruby 2.6.5
61
- - bundle install
62
- - bundle exec rspec
63
- - name: Ruby 2.7.3
64
- commands:
65
- - checkout
66
- - sem-version ruby 2.7.3
67
- - bundle install
68
- - bundle exec rspec
69
- - name: Ruby 3.0.2
70
- commands:
71
- - checkout
72
- - sem-version ruby 3.0.2
34
+ matrix:
35
+ - env_var: RUBY_VERSION
36
+ values:
37
+ - 1.9.3-p551
38
+ - 2.0.0-p648
39
+ - 2.3.4
40
+ - 2.5.7
41
+ - 2.6.5
42
+ - 2.7.3
43
+ - 3.0.2
44
+ commands:
45
+ - checkout
46
+ - sem-version ruby $RUBY_VERSION
73
47
  - bundle install
74
48
  - bundle exec rspec
75
49
  promotions:
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- workos (2.1.1)
4
+ workos (2.2.0)
5
5
  sorbet-runtime (~> 0.5)
6
6
 
7
7
  GEM
@@ -60,7 +60,7 @@ GEM
60
60
  simplecov_json_formatter (0.1.2)
61
61
  sorbet (0.5.6388)
62
62
  sorbet-static (= 0.5.6388)
63
- sorbet-runtime (0.5.9528)
63
+ sorbet-runtime (0.5.9300)
64
64
  sorbet-static (0.5.6388-universal-darwin-14)
65
65
  sorbet-static (0.5.6388-universal-darwin-15)
66
66
  sorbet-static (0.5.6388-universal-darwin-16)
data/README.md CHANGED
@@ -38,6 +38,10 @@ Or, you may set the key yourself, such as in an initializer in your application
38
38
  WorkOS.key = '[your api key]'
39
39
  ```
40
40
 
41
+ ## SDK Versioning
42
+
43
+ For our SDKs WorkOS follows a Semantic Versioning ([SemVer](https://semver.org/)) process where all releases will have a version X.Y.Z (like 1.0.0) pattern wherein Z would be a bug fix (e.g., 1.0.1), Y would be a minor release (1.1.0) and X would be a major release (2.0.0). We permit any breaking changes to only be released in major versions and strongly recommend reading changelogs before making any major version upgrades.
44
+
41
45
  ## More Information
42
46
 
43
47
  * [Single Sign-On Guide](https://workos.com/docs/sso/guide)
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+ # typed: false
3
+
4
+ module WorkOS
5
+ # The Challnge class provides a lightweight wrapper around
6
+ # a WorkOS DirectoryUser resource. This class is not meant to be instantiated
7
+ # in DirectoryUser space, and is instantiated internally but exposed.
8
+ class Challenge
9
+ extend T::Sig
10
+
11
+ attr_accessor :id, :object, :expires_at, :code, :authentication_factor_id, :updated_at, :created_at
12
+
13
+ sig { params(json: String).void }
14
+ def initialize(json)
15
+ raw = parse_json(json)
16
+ @id = T.let(raw.id, String)
17
+ @object = T.let(raw.object, String)
18
+ @expires_at = raw.expires_at
19
+ @code = raw.code
20
+ @authentication_factor_id = T.let(raw.authentication_factor_id, String)
21
+ @created_at = T.let(raw.created_at, String)
22
+ @updated_at = T.let(raw.updated_at, String)
23
+ end
24
+
25
+ def to_json(*)
26
+ {
27
+ id: id,
28
+ object: object,
29
+ expires_at: expires_at,
30
+ code: code,
31
+ authentication_factor_id: authentication_factor_id,
32
+ created_at: created_at,
33
+ updated_at: updated_at,
34
+ }
35
+ end
36
+
37
+ private
38
+
39
+ sig { params(json_string: String).returns(WorkOS::Types::ChallengeStruct) }
40
+ def parse_json(json_string)
41
+ hash = JSON.parse(json_string, symbolize_names: true)
42
+
43
+ WorkOS::Types::ChallengeStruct.new(
44
+ id: hash[:id],
45
+ object: hash[:object],
46
+ expires_at: hash[:expires_at],
47
+ code: hash[:code],
48
+ authentication_factor_id: hash[:authentication_factor_id],
49
+ created_at: hash[:created_at],
50
+ updated_at: hash[:updated_at],
51
+ )
52
+ end
53
+ end
54
+ end
data/lib/workos/client.rb CHANGED
@@ -144,6 +144,7 @@ module WorkOS
144
144
  )
145
145
  when 422
146
146
  message = json['message']
147
+ code = json['code']
147
148
  errors = extract_error(json['errors']) if json['errors']
148
149
  message += " (#{errors})" if errors
149
150
 
@@ -151,6 +152,7 @@ module WorkOS
151
152
  message: message,
152
153
  http_status: http_status,
153
154
  request_id: response['x-request-id'],
155
+ code: code,
154
156
  )
155
157
  end
156
158
  end
@@ -26,6 +26,7 @@ module WorkOS
26
26
  # before a provided Directory ID.
27
27
  # @option options [String] after Pagination cursor to receive records
28
28
  # before a provided Directory ID.
29
+ # @option options [String] organization_id The ID for an Organization configured on WorkOS.
29
30
  #
30
31
  # @return [Hash]
31
32
  sig do
data/lib/workos/errors.rb CHANGED
@@ -17,14 +17,16 @@ module WorkOS
17
17
  error_description: T.nilable(String),
18
18
  http_status: T.nilable(Integer),
19
19
  request_id: T.nilable(String),
20
+ code: T.nilable(String),
20
21
  ).void
21
22
  end
22
- def initialize(message: nil, error: nil, error_description: nil, http_status: nil, request_id: nil)
23
+ def initialize(message: nil, error: nil, error_description: nil, http_status: nil, request_id: nil, code: nil)
23
24
  @message = message
24
25
  @error = error
25
26
  @error_description = error_description
26
27
  @http_status = http_status
27
28
  @request_id = request_id
29
+ @code = code
28
30
  end
29
31
 
30
32
  sig { returns(String) }
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+ # typed: false
3
+
4
+ module WorkOS
5
+ # The Factor class provides a lightweight wrapper around
6
+ # a WorkOS DirectoryUser resource. This class is not meant to be instantiated
7
+ # in DirectoryUser space, and is instantiated internally but exposed.
8
+ class Factor
9
+ # rubocop:disable Metrics/AbcSize
10
+ extend T::Sig
11
+ attr_accessor :id, :environment_id, :object, :type, :sms, :totp, :updated_at, :created_at
12
+
13
+ sig { params(json: String).void }
14
+ def initialize(json)
15
+ raw = parse_json(json)
16
+ @id = T.let(raw.id, String)
17
+ @environment_id = T.let(raw.environment_id, String)
18
+ @object = T.let(raw.object, String)
19
+ @type = T.let(raw.type, String)
20
+ @created_at = T.let(raw.created_at, String)
21
+ @updated_at = T.let(raw.updated_at, String)
22
+ @totp = raw.totp
23
+ @sms = raw.sms
24
+ end
25
+
26
+ def to_json(*)
27
+ {
28
+ id: id,
29
+ environment_id: environment_id,
30
+ object: object,
31
+ type: type,
32
+ totp: totp,
33
+ sms: sms,
34
+ created_at: created_at,
35
+ updated_at: updated_at,
36
+ }
37
+ end
38
+
39
+ private
40
+
41
+ sig { params(json_string: String).returns(WorkOS::Types::FactorStruct) }
42
+ def parse_json(json_string)
43
+ hash = JSON.parse(json_string, symbolize_names: true)
44
+
45
+ WorkOS::Types::FactorStruct.new(
46
+ id: hash[:id],
47
+ environment_id: hash[:environment_id],
48
+ object: hash[:object],
49
+ type: hash[:type],
50
+ totp: hash[:totp],
51
+ sms: hash[:sms],
52
+ created_at: hash[:created_at],
53
+ updated_at: hash[:updated_at],
54
+ )
55
+ end
56
+ # rubocop:enable Metrics/AbcSize
57
+ end
58
+ end
data/lib/workos/mfa.rb ADDED
@@ -0,0 +1,165 @@
1
+ # frozen_string_literal: true
2
+ # typed: true
3
+
4
+
5
+ require 'net/http'
6
+ require 'uri'
7
+
8
+ module WorkOS
9
+ # The MFA module provides convenience methods for working with the WorkOS
10
+ # MFA platform. You'll need a valid API key
11
+ module MFA
12
+ class << self
13
+ extend T::Sig
14
+ include Base
15
+ include Client
16
+ sig { params(id: String).returns(T::Boolean) }
17
+ def delete_factor(id:)
18
+ response = execute_request(
19
+ request: delete_request(
20
+ path: "/auth/factors/#{id}",
21
+ auth: true,
22
+ ),
23
+ )
24
+ response.is_a? Net::HTTPSuccess
25
+ end
26
+
27
+ sig do
28
+ params(id: String).returns(WorkOS::Factor)
29
+ end
30
+ def get_factor(
31
+ id:
32
+ )
33
+ response = execute_request(
34
+ request: get_request(
35
+ path: "/auth/factors/#{id}",
36
+ auth: true,
37
+ ),
38
+ )
39
+ WorkOS::Factor.new(response.body)
40
+ end
41
+
42
+ sig do
43
+ params(
44
+ type: String,
45
+ totp_issuer: T.nilable(String),
46
+ totp_user: T.nilable(String),
47
+ phone_number: T.nilable(String),
48
+ ).void
49
+ end
50
+ # rubocop:disable Metrics/CyclomaticComplexity
51
+ # rubocop:disable Metrics/PerceivedComplexity
52
+
53
+ def validate_args(
54
+ type:,
55
+ totp_issuer: nil,
56
+ totp_user: nil,
57
+ phone_number: nil
58
+ )
59
+ if type != 'sms' && type != 'totp' && type != 'generic_otp'
60
+ raise ArgumentError, "Type argument must be either 'sms' or 'totp'"
61
+ end
62
+ if (type == 'totp' && totp_issuer.nil?) || (type == 'totp' && totp_user.nil?)
63
+ raise ArgumentError, 'Incomplete arguments. Need to specify both totp_issuer and totp_user when type is totp'
64
+ end
65
+ return unless type == 'sms' && phone_number.nil?
66
+
67
+ raise ArgumentError, 'Incomplete arguments. Need to specify phone_number when type is sms'
68
+ end
69
+ # rubocop:enable Metrics/CyclomaticComplexity
70
+ # rubocop:enable Metrics/PerceivedComplexity
71
+
72
+ sig do
73
+ params(
74
+ type: String,
75
+ totp_issuer: T.nilable(String),
76
+ totp_user: T.nilable(String),
77
+ phone_number: T.nilable(String),
78
+ ).returns(WorkOS::Factor)
79
+ end
80
+ # rubocop:disable Metrics/MethodLength
81
+ def enroll_factor(
82
+ type:,
83
+ totp_issuer: nil,
84
+ totp_user: nil,
85
+ phone_number: nil
86
+ )
87
+ validate_args(
88
+ type: type,
89
+ totp_issuer: totp_issuer,
90
+ totp_user: totp_user,
91
+ phone_number: phone_number,
92
+ )
93
+ response = execute_request(request: post_request(
94
+ auth: true,
95
+ body: {
96
+ type: type,
97
+ totp_issuer: totp_issuer,
98
+ totp_user: totp_user,
99
+ phone_number: phone_number,
100
+ },
101
+ path: '/auth/factors/enroll',
102
+ ))
103
+ WorkOS::Factor.new(response.body)
104
+ end
105
+ # rubocop:enable Metrics/MethodLength
106
+
107
+ sig do
108
+ params(
109
+ authentication_factor_id: T.nilable(String),
110
+ sms_template: T.nilable(String),
111
+ ).returns(WorkOS::Challenge)
112
+ end
113
+ def challenge_factor(
114
+ authentication_factor_id: nil,
115
+ sms_template: nil
116
+ )
117
+ if authentication_factor_id.nil?
118
+ raise ArgumentError, "Incomplete arguments: 'authentication_factor_id' is a required argument"
119
+ end
120
+
121
+ request = post_request(
122
+ auth: true,
123
+ body: {
124
+ sms_template: sms_template,
125
+ authentication_factor_id: authentication_factor_id,
126
+ },
127
+ path: '/auth/factors/challenge',
128
+ )
129
+
130
+ response = execute_request(request: request)
131
+ WorkOS::Challenge.new(response.body)
132
+ end
133
+
134
+ sig do
135
+ params(
136
+ authentication_challenge_id: T.nilable(String),
137
+ code: T.nilable(String),
138
+ ).returns(WorkOS::VerifyFactor)
139
+ end
140
+ def verify_factor(
141
+ authentication_challenge_id: nil,
142
+ code: nil
143
+ )
144
+
145
+ if authentication_challenge_id.nil? || code.nil?
146
+ raise ArgumentError, "Incomplete arguments: 'authentication_challenge_id' and 'code' are required arguments"
147
+ end
148
+
149
+ options = {
150
+ "authentication_challenge_id": authentication_challenge_id,
151
+ "code": code,
152
+ }
153
+
154
+ response = execute_request(
155
+ request: post_request(
156
+ path: '/auth/factors/verify',
157
+ auth: true,
158
+ body: options,
159
+ ),
160
+ )
161
+ WorkOS::VerifyFactor.new(response.body)
162
+ end
163
+ end
164
+ end
165
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+ # typed: strict
3
+
4
+ module WorkOS
5
+ module Types
6
+ # This ChallgengeStruct acts as a typed interface
7
+ # for the Factor class
8
+ class ChallengeStruct < T::Struct
9
+ const :id, String
10
+ const :object, String
11
+ const :expires_at, T.nilable(String)
12
+ const :code, T.nilable(String)
13
+ const :authentication_factor_id, String
14
+ const :created_at, String
15
+ const :updated_at, String
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+ # typed: strict
3
+
4
+ module WorkOS
5
+ module Types
6
+ # This FactorStruct acts as a typed interface
7
+ # for the Factor class
8
+ class FactorStruct < T::Struct
9
+ const :id, String
10
+ const :environment_id, String
11
+ const :object, String
12
+ const :type, String
13
+ const :totp, T.nilable(T::Hash[Symbol, Object])
14
+ const :sms, T.nilable(T::Hash[Symbol, Object])
15
+ const :created_at, String
16
+ const :updated_at, String
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+ # typed: strict
3
+
4
+ module WorkOS
5
+ module Types
6
+ # This VerifyFactorStruct acts as a typed interface
7
+ # for the Factor class
8
+ class VerifyFactorStruct < T::Struct
9
+ const :challenge, T.nilable(T::Hash[Symbol, Object])
10
+ const :valid, T.nilable(TrueClass)
11
+ const :code, T.nilable(String)
12
+ const :message, T.nilable(String)
13
+ end
14
+ end
15
+ end
data/lib/workos/types.rb CHANGED
@@ -16,5 +16,8 @@ module WorkOS
16
16
  require_relative 'types/provider_enum'
17
17
  require_relative 'types/directory_user_struct'
18
18
  require_relative 'types/webhook_struct'
19
+ require_relative 'types/factor_struct'
20
+ require_relative 'types/challenge_struct'
21
+ require_relative 'types/verify_factor_struct'
19
22
  end
20
23
  end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+ # typed: false
3
+
4
+ module WorkOS
5
+ # The VerifyFactor class provides a lightweight wrapper around
6
+ # a WorkOS DirectoryUser resource. This class is not meant to be instantiated
7
+ # in DirectoryUser space, and is instantiated internally but exposed.
8
+ class VerifyFactor
9
+ extend T::Sig
10
+
11
+ attr_accessor :challenge, :valid
12
+
13
+ sig { params(json: String).void }
14
+ def initialize(json)
15
+ raw = parse_json(json)
16
+ @challenge = T.let(raw.challenge, Hash)
17
+ @valid = raw.valid
18
+ end
19
+
20
+ def to_json(*)
21
+ {
22
+ challenge: challenge,
23
+ valid: valid,
24
+ }
25
+ end
26
+
27
+ private
28
+
29
+ sig { params(json_string: String).returns(WorkOS::Types::VerifyFactorStruct) }
30
+ def parse_json(json_string)
31
+ hash = JSON.parse(json_string, symbolize_names: true)
32
+
33
+ WorkOS::Types::VerifyFactorStruct.new(
34
+ challenge: hash[:challenge],
35
+ valid: hash[:valid],
36
+ )
37
+ end
38
+ end
39
+ end
@@ -2,5 +2,5 @@
2
2
  # typed: strong
3
3
 
4
4
  module WorkOS
5
- VERSION = '2.1.1'
5
+ VERSION = '2.2.0'
6
6
  end
@@ -108,7 +108,7 @@ module WorkOS
108
108
  sig do
109
109
  params(
110
110
  sig_header: String,
111
- ).returns(T::Array[T.untyped])
111
+ ).returns([String, String])
112
112
  end
113
113
  def get_timestamp_and_signature_hash(
114
114
  sig_header:
data/lib/workos.rb CHANGED
@@ -44,6 +44,11 @@ module WorkOS
44
44
  autoload :DirectoryUser, 'workos/directory_user'
45
45
  autoload :Webhook, 'workos/webhook'
46
46
  autoload :Webhooks, 'workos/webhooks'
47
+ autoload :MFA, 'workos/mfa'
48
+ autoload :Factor, 'workos/factor'
49
+ autoload :Challenge, 'workos/challenge'
50
+ autoload :VerifyFactor, 'workos/verify_factor'
51
+
47
52
 
48
53
  # Errors
49
54
  autoload :APIError, 'workos/errors'