workos 2.1.0 → 2.2.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.
- checksums.yaml +4 -4
- data/.semaphore/semaphore.yml +13 -39
- data/Gemfile.lock +2 -2
- data/README.md +4 -0
- data/lib/workos/challenge.rb +55 -0
- data/lib/workos/client.rb +2 -0
- data/lib/workos/connection.rb +1 -0
- data/lib/workos/deprecated_hash_wrapper.rb +76 -0
- data/lib/workos/directory.rb +1 -0
- data/lib/workos/directory_group.rb +5 -2
- data/lib/workos/directory_sync.rb +9 -8
- data/lib/workos/directory_user.rb +4 -1
- data/lib/workos/errors.rb +3 -1
- data/lib/workos/factor.rb +59 -0
- data/lib/workos/hash_provider.rb +19 -0
- data/lib/workos/mfa.rb +165 -0
- data/lib/workos/organization.rb +1 -0
- data/lib/workos/profile.rb +1 -0
- data/lib/workos/profile_and_token.rb +1 -0
- data/lib/workos/types/challenge_struct.rb +18 -0
- data/lib/workos/types/factor_struct.rb +19 -0
- data/lib/workos/types/passwordless_session_struct.rb +2 -0
- data/lib/workos/types/verify_factor_struct.rb +13 -0
- data/lib/workos/types.rb +3 -0
- data/lib/workos/verify_factor.rb +40 -0
- data/lib/workos/version.rb +1 -1
- data/lib/workos/webhook.rb +1 -0
- data/lib/workos/webhooks.rb +9 -7
- data/lib/workos.rb +7 -0
- data/spec/lib/workos/directory_sync_spec.rb +4 -0
- data/spec/lib/workos/mfa_spec.rb +262 -0
- data/spec/lib/workos/sso_spec.rb +0 -2
- data/spec/lib/workos/webhooks_spec.rb +1 -1
- data/spec/support/fixtures/vcr_cassettes/mfa/challenge_factor_generic_valid.yml +82 -0
- data/spec/support/fixtures/vcr_cassettes/mfa/challenge_factor_sms_valid.yml +82 -0
- data/spec/support/fixtures/vcr_cassettes/mfa/challenge_factor_totp_valid.yml +82 -0
- data/spec/support/fixtures/vcr_cassettes/mfa/delete_factor.yml +80 -0
- data/spec/support/fixtures/vcr_cassettes/mfa/enroll_factor_generic_valid.yml +82 -0
- data/spec/support/fixtures/vcr_cassettes/mfa/enroll_factor_sms_valid.yml +82 -0
- data/spec/support/fixtures/vcr_cassettes/mfa/enroll_factor_totp_valid.yml +82 -0
- data/spec/support/fixtures/vcr_cassettes/mfa/get_factor_invalid.yml +82 -0
- data/spec/support/fixtures/vcr_cassettes/mfa/get_factor_valid.yml +82 -0
- data/spec/support/fixtures/vcr_cassettes/mfa/verify_factor_generic_expired.yml +84 -0
- data/spec/support/fixtures/vcr_cassettes/mfa/verify_factor_generic_invalid.yml +84 -0
- data/spec/support/fixtures/vcr_cassettes/mfa/verify_factor_generic_valid.yml +82 -0
- data/spec/support/fixtures/vcr_cassettes/mfa/verify_factor_generic_valid_is_false.yml +82 -0
- metadata +40 -3
|
@@ -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,13 @@
|
|
|
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::Boolean
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
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,40 @@
|
|
|
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
|
+
include HashProvider
|
|
10
|
+
extend T::Sig
|
|
11
|
+
|
|
12
|
+
attr_accessor :challenge, :valid
|
|
13
|
+
|
|
14
|
+
sig { params(json: String).void }
|
|
15
|
+
def initialize(json)
|
|
16
|
+
raw = parse_json(json)
|
|
17
|
+
@challenge = T.let(raw.challenge, Hash)
|
|
18
|
+
@valid = raw.valid
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def to_json(*)
|
|
22
|
+
{
|
|
23
|
+
challenge: challenge,
|
|
24
|
+
valid: valid,
|
|
25
|
+
}
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
sig { params(json_string: String).returns(WorkOS::Types::VerifyFactorStruct) }
|
|
31
|
+
def parse_json(json_string)
|
|
32
|
+
hash = JSON.parse(json_string, symbolize_names: true)
|
|
33
|
+
|
|
34
|
+
WorkOS::Types::VerifyFactorStruct.new(
|
|
35
|
+
challenge: hash[:challenge],
|
|
36
|
+
valid: hash[:valid],
|
|
37
|
+
)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
data/lib/workos/version.rb
CHANGED
data/lib/workos/webhook.rb
CHANGED
data/lib/workos/webhooks.rb
CHANGED
|
@@ -65,7 +65,7 @@ module WorkOS
|
|
|
65
65
|
tolerance: Integer,
|
|
66
66
|
).returns(T::Boolean)
|
|
67
67
|
end
|
|
68
|
-
# rubocop:disable Metrics/MethodLength
|
|
68
|
+
# rubocop:disable Metrics/MethodLength, Metrics/AbcSize
|
|
69
69
|
def verify_header(
|
|
70
70
|
payload:,
|
|
71
71
|
sig_header:,
|
|
@@ -86,7 +86,9 @@ module WorkOS
|
|
|
86
86
|
)
|
|
87
87
|
end
|
|
88
88
|
|
|
89
|
-
|
|
89
|
+
timestamp_to_time = Time.at(timestamp.to_i / 1000)
|
|
90
|
+
|
|
91
|
+
if timestamp_to_time < Time.now - tolerance
|
|
90
92
|
raise WorkOS::SignatureVerificationError.new(
|
|
91
93
|
message: 'Timestamp outside the tolerance zone',
|
|
92
94
|
)
|
|
@@ -101,12 +103,12 @@ module WorkOS
|
|
|
101
103
|
|
|
102
104
|
true
|
|
103
105
|
end
|
|
104
|
-
# rubocop:enable Metrics/MethodLength
|
|
106
|
+
# rubocop:enable Metrics/MethodLength, Metrics/AbcSize
|
|
105
107
|
|
|
106
108
|
sig do
|
|
107
109
|
params(
|
|
108
110
|
sig_header: String,
|
|
109
|
-
).returns(
|
|
111
|
+
).returns([String, String])
|
|
110
112
|
end
|
|
111
113
|
def get_timestamp_and_signature_hash(
|
|
112
114
|
sig_header:
|
|
@@ -122,12 +124,12 @@ module WorkOS
|
|
|
122
124
|
timestamp = timestamp.sub('t=', '')
|
|
123
125
|
signature_hash = signature_hash.sub('v1=', '')
|
|
124
126
|
|
|
125
|
-
[
|
|
127
|
+
[timestamp, signature_hash]
|
|
126
128
|
end
|
|
127
129
|
|
|
128
130
|
sig do
|
|
129
131
|
params(
|
|
130
|
-
timestamp:
|
|
132
|
+
timestamp: String,
|
|
131
133
|
payload: String,
|
|
132
134
|
secret: String,
|
|
133
135
|
).returns(String)
|
|
@@ -137,7 +139,7 @@ module WorkOS
|
|
|
137
139
|
payload:,
|
|
138
140
|
secret:
|
|
139
141
|
)
|
|
140
|
-
unhashed_string = "#{timestamp
|
|
142
|
+
unhashed_string = "#{timestamp}.#{payload}"
|
|
141
143
|
digest = OpenSSL::Digest.new('sha256')
|
|
142
144
|
OpenSSL::HMAC.hexdigest(digest, secret, unhashed_string)
|
|
143
145
|
end
|
data/lib/workos.rb
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
require 'workos/version'
|
|
5
5
|
require 'sorbet-runtime'
|
|
6
6
|
require 'json'
|
|
7
|
+
require 'workos/hash_provider'
|
|
7
8
|
|
|
8
9
|
# Use the WorkOS module to authenticate your
|
|
9
10
|
# requests to the WorkOS API. The gem will read
|
|
@@ -44,6 +45,12 @@ module WorkOS
|
|
|
44
45
|
autoload :DirectoryUser, 'workos/directory_user'
|
|
45
46
|
autoload :Webhook, 'workos/webhook'
|
|
46
47
|
autoload :Webhooks, 'workos/webhooks'
|
|
48
|
+
autoload :MFA, 'workos/mfa'
|
|
49
|
+
autoload :Factor, 'workos/factor'
|
|
50
|
+
autoload :Challenge, 'workos/challenge'
|
|
51
|
+
autoload :VerifyFactor, 'workos/verify_factor'
|
|
52
|
+
autoload :DeprecatedHashWrapper, 'workos/deprecated_hash_wrapper'
|
|
53
|
+
|
|
47
54
|
|
|
48
55
|
# Errors
|
|
49
56
|
autoload :APIError, 'workos/errors'
|
|
@@ -204,6 +204,7 @@ describe WorkOS::DirectorySync do
|
|
|
204
204
|
)
|
|
205
205
|
|
|
206
206
|
expect(groups.data.size).to eq(10)
|
|
207
|
+
expect(groups.data[0].name).to eq(groups.data[0]['name'])
|
|
207
208
|
end
|
|
208
209
|
end
|
|
209
210
|
end
|
|
@@ -332,6 +333,7 @@ describe WorkOS::DirectorySync do
|
|
|
332
333
|
)
|
|
333
334
|
|
|
334
335
|
expect(users.data.size).to eq(4)
|
|
336
|
+
expect(users.data[0].first_name).to eq(users.data[0]['first_name'])
|
|
335
337
|
end
|
|
336
338
|
end
|
|
337
339
|
end
|
|
@@ -440,6 +442,7 @@ describe WorkOS::DirectorySync do
|
|
|
440
442
|
)
|
|
441
443
|
|
|
442
444
|
expect(group['name']).to eq('Walrus')
|
|
445
|
+
expect(group.name).to eq('Walrus')
|
|
443
446
|
end
|
|
444
447
|
end
|
|
445
448
|
end
|
|
@@ -464,6 +467,7 @@ describe WorkOS::DirectorySync do
|
|
|
464
467
|
)
|
|
465
468
|
|
|
466
469
|
expect(user['first_name']).to eq('Logan')
|
|
470
|
+
expect(user.first_name).to eq('Logan')
|
|
467
471
|
end
|
|
468
472
|
end
|
|
469
473
|
end
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# typed: false
|
|
3
|
+
|
|
4
|
+
describe WorkOS::MFA do
|
|
5
|
+
it_behaves_like 'client'
|
|
6
|
+
|
|
7
|
+
describe 'enroll_factor valid requests' do
|
|
8
|
+
context 'enroll factor using valid generic argument' do
|
|
9
|
+
it 'returns a valid factor object' do
|
|
10
|
+
VCR.use_cassette 'mfa/enroll_factor_generic_valid' do
|
|
11
|
+
factor = described_class.enroll_factor(
|
|
12
|
+
type: 'generic_otp',
|
|
13
|
+
)
|
|
14
|
+
expect(factor.type == 'generic_otp')
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
context 'enroll factor using valid totp arguments' do
|
|
20
|
+
it 'returns a valid factor object' do
|
|
21
|
+
VCR.use_cassette 'mfa/enroll_factor_totp_valid' do
|
|
22
|
+
factor = described_class.enroll_factor(
|
|
23
|
+
type: 'totp',
|
|
24
|
+
totp_issuer: 'WorkOS',
|
|
25
|
+
totp_user: 'some_user',
|
|
26
|
+
)
|
|
27
|
+
expect(factor.totp.instance_of?(Hash))
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
context 'enroll factor using valid sms arguments' do
|
|
33
|
+
it 'returns a valid factor object' do
|
|
34
|
+
VCR.use_cassette 'mfa/enroll_factor_sms_valid' do
|
|
35
|
+
factor = described_class.enroll_factor(
|
|
36
|
+
type: 'sms',
|
|
37
|
+
phone_number: '55555555555',
|
|
38
|
+
)
|
|
39
|
+
expect(factor.sms.instance_of?(Hash))
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
describe 'enroll_factor invalid responses' do
|
|
46
|
+
context 'enroll factor throws error if type is not sms or totp' do
|
|
47
|
+
it 'returns an error' do
|
|
48
|
+
expect do
|
|
49
|
+
described_class.enroll_factor(
|
|
50
|
+
type: 'invalid',
|
|
51
|
+
phone_number: '+15005550006',
|
|
52
|
+
)
|
|
53
|
+
end.to raise_error(
|
|
54
|
+
ArgumentError,
|
|
55
|
+
"Type argument must be either 'sms' or 'totp'",
|
|
56
|
+
)
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
context 'enroll factor throws error if type is not sms or totp' do
|
|
61
|
+
it 'returns an error' do
|
|
62
|
+
expect do
|
|
63
|
+
described_class.enroll_factor(
|
|
64
|
+
type: 'totp',
|
|
65
|
+
totp_issuer: 'WorkOS',
|
|
66
|
+
)
|
|
67
|
+
end.to raise_error(
|
|
68
|
+
ArgumentError,
|
|
69
|
+
'Incomplete arguments. Need to specify both totp_issuer and totp_user when type is totp',
|
|
70
|
+
)
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
context 'enroll factor throws error if type sms and phone number is nil' do
|
|
74
|
+
it 'returns an error' do
|
|
75
|
+
expect do
|
|
76
|
+
described_class.enroll_factor(
|
|
77
|
+
type: 'sms',
|
|
78
|
+
)
|
|
79
|
+
end.to raise_error(
|
|
80
|
+
ArgumentError,
|
|
81
|
+
'Incomplete arguments. Need to specify phone_number when type is sms',
|
|
82
|
+
)
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
describe 'challenge factor with valid request arguments' do
|
|
88
|
+
context 'challenge with totp' do
|
|
89
|
+
it 'returns challenge factor object for totp' do
|
|
90
|
+
VCR.use_cassette 'mfa/challenge_factor_totp_valid' do
|
|
91
|
+
challenge_factor = described_class.challenge_factor(
|
|
92
|
+
authentication_factor_id: 'auth_factor_01FZ4TS0MWPZR7GATS7KCXANQZ',
|
|
93
|
+
)
|
|
94
|
+
expect(challenge_factor.authentication_factor_id.class.instance_of?(String))
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
context 'challenge with sms' do
|
|
100
|
+
it 'returns a challenge factor object for sms' do
|
|
101
|
+
VCR.use_cassette 'mfa/challenge_factor_sms_valid' do
|
|
102
|
+
challenge_factor = described_class.challenge_factor(
|
|
103
|
+
authentication_factor_id: 'auth_factor_01FZ4TS14D1PHFNZ9GF6YD8M1F',
|
|
104
|
+
sms_template: 'Your code is {{code}}',
|
|
105
|
+
)
|
|
106
|
+
expect(challenge_factor.authentication_factor_id.instance_of?(String))
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
context 'challenge with generic' do
|
|
112
|
+
it 'returns a valid challenge factor object for generic otp' do
|
|
113
|
+
VCR.use_cassette 'mfa/challenge_factor_generic_valid' do
|
|
114
|
+
challenge_factor = described_class.challenge_factor(
|
|
115
|
+
authentication_factor_id: 'auth_factor_01FZ4WMXXA09XF6NK1XMKNWB3M',
|
|
116
|
+
)
|
|
117
|
+
expect(challenge_factor.code.instance_of?(String))
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
describe 'challenge factor with invalid arguments' do
|
|
124
|
+
context 'challenge with totp mssing authentication_factor_id' do
|
|
125
|
+
it 'returns argument error' do
|
|
126
|
+
expect do
|
|
127
|
+
described_class.challenge_factor
|
|
128
|
+
end.to raise_error(
|
|
129
|
+
ArgumentError,
|
|
130
|
+
"Incomplete arguments: 'authentication_factor_id' is a required argument",
|
|
131
|
+
)
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
describe 'challenge factor with valid requests' do
|
|
137
|
+
context 'verify generic otp' do
|
|
138
|
+
it 'returns a true boolean if the challenge has not been verifed yet' do
|
|
139
|
+
VCR.use_cassette 'mfa/verify_factor_generic_valid' do
|
|
140
|
+
verify_factor = described_class.verify_factor(
|
|
141
|
+
authentication_challenge_id: 'auth_challenge_01FZ4YVRBMXP5ZM0A7BP4AJ12J',
|
|
142
|
+
code: '897792',
|
|
143
|
+
)
|
|
144
|
+
expect(verify_factor.valid == 'true')
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
context 'verify generic otp invalid response' do
|
|
150
|
+
it 'returns a true boolean if the challenge has not been verifed yet' do
|
|
151
|
+
VCR.use_cassette 'mfa/verify_factor_generic_valid_is_false' do
|
|
152
|
+
verify_factor = described_class.verify_factor(
|
|
153
|
+
authentication_challenge_id: 'auth_challenge_01FZ4YVRBMXP5ZM0A7BP4AJ12J',
|
|
154
|
+
code: '897792',
|
|
155
|
+
)
|
|
156
|
+
expect(verify_factor.valid == 'false')
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
context 'verify generic otp' do
|
|
162
|
+
it 'returns error that the challenge has already been verfied' do
|
|
163
|
+
VCR.use_cassette 'mfa/verify_factor_generic_invalid' do
|
|
164
|
+
expect do
|
|
165
|
+
described_class.verify_factor(
|
|
166
|
+
authentication_challenge_id: 'auth_challenge_01FZ4YVRBMXP5ZM0A7BP4AJ12J',
|
|
167
|
+
code: '897792',
|
|
168
|
+
)
|
|
169
|
+
end.to raise_error(WorkOS::InvalidRequestError)
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
context 'verify generic otp' do
|
|
174
|
+
it 'returns error that the challenge has expired' do
|
|
175
|
+
VCR.use_cassette 'mfa/verify_factor_generic_expired' do
|
|
176
|
+
expect do
|
|
177
|
+
described_class.verify_factor(
|
|
178
|
+
authentication_challenge_id: 'auth_challenge_01FZ4YVRBMXP5ZM0A7BP4AJ12J',
|
|
179
|
+
code: '897792',
|
|
180
|
+
)
|
|
181
|
+
end.to raise_error(WorkOS::InvalidRequestError)
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
describe 'verify_factor with invalid argument' do
|
|
189
|
+
context 'missing code argument' do
|
|
190
|
+
it 'returns argument error' do
|
|
191
|
+
expect do
|
|
192
|
+
described_class.verify_factor(
|
|
193
|
+
authentication_challenge_id: 'auth_challenge_01FZ4YVRBMXP5ZM0A7BP4AJ12J',
|
|
194
|
+
)
|
|
195
|
+
end.to raise_error(
|
|
196
|
+
ArgumentError,
|
|
197
|
+
"Incomplete arguments: 'authentication_challenge_id' and 'code' are required arguments",
|
|
198
|
+
)
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
context 'missing authentication_challenge_id argument' do
|
|
203
|
+
it 'returns and error' do
|
|
204
|
+
expect do
|
|
205
|
+
described_class.verify_factor(
|
|
206
|
+
code: '897792',
|
|
207
|
+
)
|
|
208
|
+
end.to raise_error(
|
|
209
|
+
ArgumentError,
|
|
210
|
+
"Incomplete arguments: 'authentication_challenge_id' and 'code' are required arguments",
|
|
211
|
+
)
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
context 'missing code and authentication_challenge_id arguments' do
|
|
216
|
+
it 'returns argument error' do
|
|
217
|
+
expect do
|
|
218
|
+
described_class.verify_factor
|
|
219
|
+
end.to raise_error(
|
|
220
|
+
ArgumentError,
|
|
221
|
+
"Incomplete arguments: 'authentication_challenge_id' and 'code' are required arguments",
|
|
222
|
+
)
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
describe 'tests returning and deleting a factor' do
|
|
228
|
+
context 'returns a factor' do
|
|
229
|
+
it 'uses get_factor to return factor' do
|
|
230
|
+
VCR.use_cassette 'mfa/get_factor_valid' do
|
|
231
|
+
factor = described_class.get_factor(
|
|
232
|
+
id: 'auth_factor_01FZ4WMXXA09XF6NK1XMKNWB3M',
|
|
233
|
+
)
|
|
234
|
+
expect(factor.id.instance_of?(String))
|
|
235
|
+
end
|
|
236
|
+
end
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
context 'invalid factor request' do
|
|
240
|
+
it 'uses get_factor and throws error if id is wrong' do
|
|
241
|
+
VCR.use_cassette 'mfa/get_factor_invalid' do
|
|
242
|
+
expect do
|
|
243
|
+
described_class.get_factor(
|
|
244
|
+
id: 'auth_factor_invalid',
|
|
245
|
+
)
|
|
246
|
+
end.to raise_error(WorkOS::APIError)
|
|
247
|
+
end
|
|
248
|
+
end
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
context 'deletes facotr' do
|
|
252
|
+
it 'uses delete_factor to delete factor' do
|
|
253
|
+
VCR.use_cassette 'mfa/delete_factor' do
|
|
254
|
+
response = described_class.delete_factor(
|
|
255
|
+
id: 'auth_factor_01FZ4WMXXA09XF6NK1XMKNWB3M',
|
|
256
|
+
)
|
|
257
|
+
expect(response).to be(true)
|
|
258
|
+
end
|
|
259
|
+
end
|
|
260
|
+
end
|
|
261
|
+
end
|
|
262
|
+
end
|
data/spec/lib/workos/sso_spec.rb
CHANGED
|
@@ -20,7 +20,6 @@ describe WorkOS::SSO do
|
|
|
20
20
|
end
|
|
21
21
|
it 'returns a valid URL' do
|
|
22
22
|
authorization_url = described_class.authorization_url(**args)
|
|
23
|
-
|
|
24
23
|
expect(URI.parse(authorization_url)).to be_a URI
|
|
25
24
|
end
|
|
26
25
|
|
|
@@ -315,7 +314,6 @@ describe WorkOS::SSO do
|
|
|
315
314
|
verified_email: true,
|
|
316
315
|
},
|
|
317
316
|
}
|
|
318
|
-
|
|
319
317
|
expect(profile.to_json).to eq(expectation)
|
|
320
318
|
end
|
|
321
319
|
end
|
|
@@ -177,7 +177,7 @@ describe WorkOS::Webhooks do
|
|
|
177
177
|
expect do
|
|
178
178
|
described_class.construct_event(
|
|
179
179
|
payload: @payload,
|
|
180
|
-
sig_header: "t
|
|
180
|
+
sig_header: "t=#{@timestamp.to_i - (200 * 1000)}, v1=#{@signature_hash}",
|
|
181
181
|
secret: @secret,
|
|
182
182
|
)
|
|
183
183
|
end.to raise_error(
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
---
|
|
2
|
+
http_interactions:
|
|
3
|
+
- request:
|
|
4
|
+
method: post
|
|
5
|
+
uri: https://api.workos.com/auth/factors/challenge
|
|
6
|
+
body:
|
|
7
|
+
encoding: UTF-8
|
|
8
|
+
string: '{"sms_template":null,"authentication_factor_id":"auth_factor_01FZ4WMXXA09XF6NK1XMKNWB3M"}'
|
|
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/3.0.2; arm64-darwin21; v2.1.1
|
|
18
|
+
Authorization:
|
|
19
|
+
- Bearer <API_KEY>
|
|
20
|
+
response:
|
|
21
|
+
status:
|
|
22
|
+
code: 201
|
|
23
|
+
message: Created
|
|
24
|
+
headers:
|
|
25
|
+
Date:
|
|
26
|
+
- Sun, 27 Mar 2022 05:15:26 GMT
|
|
27
|
+
Content-Type:
|
|
28
|
+
- application/json; charset=utf-8
|
|
29
|
+
Content-Length:
|
|
30
|
+
- '290'
|
|
31
|
+
Connection:
|
|
32
|
+
- keep-alive
|
|
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
|
+
Vary:
|
|
56
|
+
- Origin, Accept-Encoding
|
|
57
|
+
Access-Control-Allow-Credentials:
|
|
58
|
+
- 'true'
|
|
59
|
+
X-Request-Id:
|
|
60
|
+
- 6f320a1f-92d8-496c-9c30-69355787d21e
|
|
61
|
+
Etag:
|
|
62
|
+
- W/"122-QtSiaXex7UKEyydEC3oPpuHzGNw"
|
|
63
|
+
Via:
|
|
64
|
+
- 1.1 vegur
|
|
65
|
+
Cf-Cache-Status:
|
|
66
|
+
- DYNAMIC
|
|
67
|
+
Report-To:
|
|
68
|
+
- '{"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v3?s=2kePZ4Q7kGQ1by5iXZxRevmSkQq4vTbW1vXTqFev99eLBrrfEHOLK2%2FE0ItWB2GAKoQvhPBk3rhS%2FKg0rtK3ZH4DGTf%2FEQKGFPT6BxtCqQE2L%2ByfEv1AgU152ZwIBPVYjQ%3D%3D"}],"group":"cf-nel","max_age":604800}'
|
|
69
|
+
Nel:
|
|
70
|
+
- '{"success_fraction":0,"report_to":"cf-nel","max_age":604800}'
|
|
71
|
+
Server:
|
|
72
|
+
- cloudflare
|
|
73
|
+
Cf-Ray:
|
|
74
|
+
- 6f25a5f0dddf088d-SEA
|
|
75
|
+
Alt-Svc:
|
|
76
|
+
- h3=":443"; ma=86400, h3-29=":443"; ma=86400
|
|
77
|
+
body:
|
|
78
|
+
encoding: UTF-8
|
|
79
|
+
string: '{"object":"authentication_challenge","id":"auth_challenge_01FZ4WSWV2SCEDX3GKY1NA9YTN","created_at":"2022-03-27T05:15:26.432Z","updated_at":"2022-03-27T05:15:26.432Z","expires_at":"2022-03-27T05:25:26.434Z","code":"541295","authentication_factor_id":"auth_factor_01FZ4WMXXA09XF6NK1XMKNWB3M"}'
|
|
80
|
+
http_version:
|
|
81
|
+
recorded_at: Sun, 27 Mar 2022 05:15:26 GMT
|
|
82
|
+
recorded_with: VCR 5.0.0
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
---
|
|
2
|
+
http_interactions:
|
|
3
|
+
- request:
|
|
4
|
+
method: post
|
|
5
|
+
uri: https://api.workos.com/auth/factors/challenge
|
|
6
|
+
body:
|
|
7
|
+
encoding: UTF-8
|
|
8
|
+
string: '{"sms_template":"Your code is {{code}}","authentication_factor_id":"auth_factor_01FZ4TS14D1PHFNZ9GF6YD8M1F"}'
|
|
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/3.0.2; arm64-darwin21; v2.1.1
|
|
18
|
+
Authorization:
|
|
19
|
+
- Bearer <API_KEY>
|
|
20
|
+
response:
|
|
21
|
+
status:
|
|
22
|
+
code: 201
|
|
23
|
+
message: Created
|
|
24
|
+
headers:
|
|
25
|
+
Date:
|
|
26
|
+
- Sun, 27 Mar 2022 05:03:26 GMT
|
|
27
|
+
Content-Type:
|
|
28
|
+
- application/json; charset=utf-8
|
|
29
|
+
Content-Length:
|
|
30
|
+
- '274'
|
|
31
|
+
Connection:
|
|
32
|
+
- keep-alive
|
|
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
|
+
Vary:
|
|
56
|
+
- Origin, Accept-Encoding
|
|
57
|
+
Access-Control-Allow-Credentials:
|
|
58
|
+
- 'true'
|
|
59
|
+
X-Request-Id:
|
|
60
|
+
- b9c66c87-adf4-4a9a-854f-d838f966ea5c
|
|
61
|
+
Etag:
|
|
62
|
+
- W/"112-VpD9nscbxE6VOcsJlK2RHEzlq3s"
|
|
63
|
+
Via:
|
|
64
|
+
- 1.1 vegur
|
|
65
|
+
Cf-Cache-Status:
|
|
66
|
+
- DYNAMIC
|
|
67
|
+
Report-To:
|
|
68
|
+
- '{"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v3?s=yNaSbl1oXtixmxb7lJOn7tDKzD0mN8jSFHJmTsZfD6YTlSGeMrdBgfi3LUFeDv1ldKcdNe5eZ%2BkRrVM986RUizGOhL2xzdl2AkJEdudIRaaJCMdWbjQDmGLbC4OPFajMIA%3D%3D"}],"group":"cf-nel","max_age":604800}'
|
|
69
|
+
Nel:
|
|
70
|
+
- '{"success_fraction":0,"report_to":"cf-nel","max_age":604800}'
|
|
71
|
+
Server:
|
|
72
|
+
- cloudflare
|
|
73
|
+
Cf-Ray:
|
|
74
|
+
- 6f25945b9ee639b4-SEA
|
|
75
|
+
Alt-Svc:
|
|
76
|
+
- h3=":443"; ma=86400, h3-29=":443"; ma=86400
|
|
77
|
+
body:
|
|
78
|
+
encoding: UTF-8
|
|
79
|
+
string: '{"object":"authentication_challenge","id":"auth_challenge_01FZ4W3XG1VD8ZD5TXSYQMFMSR","created_at":"2022-03-27T05:03:26.203Z","updated_at":"2022-03-27T05:03:26.203Z","expires_at":"2022-03-27T05:13:26.204Z","authentication_factor_id":"auth_factor_01FZ4TS14D1PHFNZ9GF6YD8M1F"}'
|
|
80
|
+
http_version:
|
|
81
|
+
recorded_at: Sun, 27 Mar 2022 05:03:26 GMT
|
|
82
|
+
recorded_with: VCR 5.0.0
|