workos 1.5.0 → 1.6.0
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/.rubocop.yml +1 -1
- data/Gemfile.lock +2 -2
- data/lib/workos/client.rb +3 -6
- data/lib/workos/errors.rb +4 -0
- data/lib/workos/organization.rb +4 -1
- data/lib/workos/organizations.rb +18 -4
- data/lib/workos/profile.rb +7 -2
- data/lib/workos/types/organization_struct.rb +1 -0
- data/lib/workos/types/profile_struct.rb +1 -0
- data/lib/workos/types/webhook_struct.rb +14 -0
- data/lib/workos/types.rb +1 -0
- data/lib/workos/version.rb +1 -1
- data/lib/workos/webhook.rb +47 -0
- data/lib/workos/webhooks.rb +168 -0
- data/lib/workos.rb +3 -0
- data/spec/lib/workos/audit_trail_spec.rb +2 -0
- data/spec/lib/workos/directory_sync_spec.rb +2 -0
- data/spec/lib/workos/organizations_spec.rb +2 -0
- data/spec/lib/workos/passwordless_spec.rb +2 -0
- data/spec/lib/workos/portal_spec.rb +2 -0
- data/spec/lib/workos/sso_spec.rb +4 -0
- data/spec/lib/workos/webhooks_spec.rb +190 -0
- data/spec/spec_helper.rb +3 -0
- data/spec/support/fixtures/vcr_cassettes/organization/create.yml +1 -1
- data/spec/support/fixtures/vcr_cassettes/organization/get.yml +1 -1
- data/spec/support/fixtures/vcr_cassettes/organization/list.yml +4 -4
- data/spec/support/fixtures/vcr_cassettes/organization/update.yml +1 -1
- data/spec/support/fixtures/vcr_cassettes/sso/profile.yml +1 -1
- data/spec/support/profile.txt +1 -1
- data/spec/support/shared_examples/client_spec.rb +16 -0
- data/spec/support/webhook_payload.txt +1 -0
- metadata +12 -5
- data/spec/support/fixtures/vcr_cassettes/organization/update_invalid.yml +0 -73
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: dd493c247c202074fa3d93d6f4db6f1cb4cffb19b63f140eeb37a3236255be1a
|
|
4
|
+
data.tar.gz: a266a358383a5242299a2df002b04486d6ef1203f0d3448736b6d5904d657643
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d05f397238b7e94d6f8e7e513523de96c87da44042c65638dbe8ce947076b5383f38491997a887a30e05a0c47e4193178dd39f0e4c62b5f14ebb1fcfb40b3299
|
|
7
|
+
data.tar.gz: 28158f92efaff774bbfaaf02e9be76552dd94bfebb996515824e278faf4011b4996846990074e30694f4306ba26c2cb76440294a020e5832b8bf25c3c504ccb3
|
data/.rubocop.yml
CHANGED
data/Gemfile.lock
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
workos (1.
|
|
4
|
+
workos (1.6.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.
|
|
63
|
+
sorbet-runtime (0.5.9094)
|
|
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/lib/workos/client.rb
CHANGED
|
@@ -9,12 +9,9 @@ module WorkOS
|
|
|
9
9
|
|
|
10
10
|
sig { returns(Net::HTTP) }
|
|
11
11
|
def client
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
@client.use_ssl = true
|
|
16
|
-
|
|
17
|
-
@client
|
|
12
|
+
Net::HTTP.new(WorkOS::API_HOSTNAME, 443).tap do |http_client|
|
|
13
|
+
http_client.use_ssl = true
|
|
14
|
+
end
|
|
18
15
|
end
|
|
19
16
|
|
|
20
17
|
sig do
|
data/lib/workos/errors.rb
CHANGED
|
@@ -55,4 +55,8 @@ module WorkOS
|
|
|
55
55
|
# InvalidRequestError is raised when a request is initiated with invalid
|
|
56
56
|
# parameters.
|
|
57
57
|
class InvalidRequestError < WorkOSError; end
|
|
58
|
+
|
|
59
|
+
# SignatureVerificationError is raised when the signature verification for a
|
|
60
|
+
# webhook fails
|
|
61
|
+
class SignatureVerificationError < WorkOSError; end
|
|
58
62
|
end
|
data/lib/workos/organization.rb
CHANGED
|
@@ -8,7 +8,7 @@ module WorkOS
|
|
|
8
8
|
class Organization
|
|
9
9
|
extend T::Sig
|
|
10
10
|
|
|
11
|
-
attr_accessor :id, :domains, :name, :created_at, :updated_at
|
|
11
|
+
attr_accessor :id, :domains, :name, :allow_profiles_outside_organization, :created_at, :updated_at
|
|
12
12
|
|
|
13
13
|
sig { params(json: String).void }
|
|
14
14
|
def initialize(json)
|
|
@@ -16,6 +16,7 @@ module WorkOS
|
|
|
16
16
|
|
|
17
17
|
@id = T.let(raw.id, String)
|
|
18
18
|
@name = T.let(raw.name, String)
|
|
19
|
+
@allow_profiles_outside_organization = T.let(raw.allow_profiles_outside_organization, T::Boolean)
|
|
19
20
|
@domains = T.let(raw.domains, Array)
|
|
20
21
|
@created_at = T.let(raw.created_at, String)
|
|
21
22
|
@updated_at = T.let(raw.updated_at, String)
|
|
@@ -25,6 +26,7 @@ module WorkOS
|
|
|
25
26
|
{
|
|
26
27
|
id: id,
|
|
27
28
|
name: name,
|
|
29
|
+
allow_profiles_outside_organization: allow_profiles_outside_organization,
|
|
28
30
|
domains: domains,
|
|
29
31
|
created_at: created_at,
|
|
30
32
|
updated_at: updated_at,
|
|
@@ -44,6 +46,7 @@ module WorkOS
|
|
|
44
46
|
WorkOS::Types::OrganizationStruct.new(
|
|
45
47
|
id: hash[:id],
|
|
46
48
|
name: hash[:name],
|
|
49
|
+
allow_profiles_outside_organization: hash[:allow_profiles_outside_organization],
|
|
47
50
|
domains: hash[:domains],
|
|
48
51
|
created_at: hash[:created_at],
|
|
49
52
|
updated_at: hash[:updated_at],
|
data/lib/workos/organizations.rb
CHANGED
|
@@ -80,16 +80,23 @@ module WorkOS
|
|
|
80
80
|
# @param [Array<String>] domains List of domains that belong to the
|
|
81
81
|
# organization
|
|
82
82
|
# @param [String] name A unique, descriptive name for the organization
|
|
83
|
+
# @param [Boolean, nil] allow_profiles_outside_organization Whether Connections
|
|
84
|
+
# within the Organization allow profiles that are outside of the Organization's configured User Email Domains.
|
|
83
85
|
sig do
|
|
84
86
|
params(
|
|
85
87
|
domains: T::Array[String],
|
|
86
88
|
name: String,
|
|
89
|
+
allow_profiles_outside_organization: T.nilable(T::Boolean),
|
|
87
90
|
).returns(WorkOS::Organization)
|
|
88
91
|
end
|
|
89
|
-
def create_organization(domains:, name:)
|
|
92
|
+
def create_organization(domains:, name:, allow_profiles_outside_organization: nil)
|
|
90
93
|
request = post_request(
|
|
91
94
|
auth: true,
|
|
92
|
-
body: {
|
|
95
|
+
body: {
|
|
96
|
+
domains: domains,
|
|
97
|
+
name: name,
|
|
98
|
+
allow_profiles_outside_organization: allow_profiles_outside_organization,
|
|
99
|
+
},
|
|
93
100
|
path: '/organizations',
|
|
94
101
|
)
|
|
95
102
|
|
|
@@ -105,17 +112,24 @@ module WorkOS
|
|
|
105
112
|
# @param [Array<String>] domains List of domains that belong to the
|
|
106
113
|
# organization
|
|
107
114
|
# @param [String] name A unique, descriptive name for the organization
|
|
115
|
+
# @param [Boolean, nil] allow_profiles_outside_organization Whether Connections
|
|
116
|
+
# within the Organization allow profiles that are outside of the Organization's configured User Email Domains.
|
|
108
117
|
sig do
|
|
109
118
|
params(
|
|
110
119
|
organization: String,
|
|
111
120
|
domains: T::Array[String],
|
|
112
121
|
name: String,
|
|
122
|
+
allow_profiles_outside_organization: T.nilable(T::Boolean),
|
|
113
123
|
).returns(WorkOS::Organization)
|
|
114
124
|
end
|
|
115
|
-
def update_organization(organization:, domains:, name:)
|
|
125
|
+
def update_organization(organization:, domains:, name:, allow_profiles_outside_organization: nil)
|
|
116
126
|
request = put_request(
|
|
117
127
|
auth: true,
|
|
118
|
-
body: {
|
|
128
|
+
body: {
|
|
129
|
+
domains: domains,
|
|
130
|
+
name: name,
|
|
131
|
+
allow_profiles_outside_organization: allow_profiles_outside_organization,
|
|
132
|
+
},
|
|
119
133
|
path: "/organizations/#{organization}",
|
|
120
134
|
)
|
|
121
135
|
|
data/lib/workos/profile.rb
CHANGED
|
@@ -11,9 +11,10 @@ module WorkOS
|
|
|
11
11
|
extend T::Sig
|
|
12
12
|
|
|
13
13
|
sig { returns(String) }
|
|
14
|
-
attr_accessor :id, :email, :first_name, :last_name, :
|
|
15
|
-
:connection_type, :idp_id, :raw_attributes
|
|
14
|
+
attr_accessor :id, :email, :first_name, :last_name, :organization_id,
|
|
15
|
+
:connection_id, :connection_type, :idp_id, :raw_attributes
|
|
16
16
|
|
|
17
|
+
# rubocop:disable Metrics/AbcSize
|
|
17
18
|
sig { params(profile_json: String).void }
|
|
18
19
|
def initialize(profile_json)
|
|
19
20
|
raw = parse_json(profile_json)
|
|
@@ -22,11 +23,13 @@ module WorkOS
|
|
|
22
23
|
@email = T.let(raw.email, String)
|
|
23
24
|
@first_name = raw.first_name
|
|
24
25
|
@last_name = raw.last_name
|
|
26
|
+
@organization_id = T.let(raw.organization_id, String)
|
|
25
27
|
@connection_id = T.let(raw.connection_id, String)
|
|
26
28
|
@connection_type = T.let(raw.connection_type, String)
|
|
27
29
|
@idp_id = raw.idp_id
|
|
28
30
|
@raw_attributes = raw.raw_attributes
|
|
29
31
|
end
|
|
32
|
+
# rubocop:enable Metrics/AbcSize
|
|
30
33
|
|
|
31
34
|
sig { returns(String) }
|
|
32
35
|
def full_name
|
|
@@ -39,6 +42,7 @@ module WorkOS
|
|
|
39
42
|
email: email,
|
|
40
43
|
first_name: first_name,
|
|
41
44
|
last_name: last_name,
|
|
45
|
+
organization_id: organization_id,
|
|
42
46
|
connection_id: connection_id,
|
|
43
47
|
connection_type: connection_type,
|
|
44
48
|
idp_id: idp_id,
|
|
@@ -57,6 +61,7 @@ module WorkOS
|
|
|
57
61
|
email: hash[:email],
|
|
58
62
|
first_name: hash[:first_name],
|
|
59
63
|
last_name: hash[:last_name],
|
|
64
|
+
organization_id: hash[:organization_id],
|
|
60
65
|
connection_id: hash[:connection_id],
|
|
61
66
|
connection_type: hash[:connection_type],
|
|
62
67
|
idp_id: hash[:idp_id],
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# typed: strict
|
|
3
|
+
|
|
4
|
+
module WorkOS
|
|
5
|
+
module Types
|
|
6
|
+
# This WebhookStruct acts as a typed interface
|
|
7
|
+
# for the Webhook class
|
|
8
|
+
class WebhookStruct < T::Struct
|
|
9
|
+
const :id, String
|
|
10
|
+
const :event, String
|
|
11
|
+
const :data, T::Hash[Symbol, Object]
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
data/lib/workos/types.rb
CHANGED
data/lib/workos/version.rb
CHANGED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# typed: true
|
|
3
|
+
|
|
4
|
+
module WorkOS
|
|
5
|
+
# The Webhook class provides a lightweight wrapper around
|
|
6
|
+
# a WorkOS Webhook resource. This class is not meant to be instantiated
|
|
7
|
+
# in user space, and is instantiated internally but exposed.
|
|
8
|
+
class Webhook
|
|
9
|
+
extend T::Sig
|
|
10
|
+
|
|
11
|
+
attr_accessor :id, :event, :data
|
|
12
|
+
|
|
13
|
+
sig { params(json: String).void }
|
|
14
|
+
def initialize(json)
|
|
15
|
+
raw = parse_json(json)
|
|
16
|
+
|
|
17
|
+
@id = T.let(raw.id, String)
|
|
18
|
+
@event = T.let(raw.event, String)
|
|
19
|
+
@data = raw.data
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def to_json(*)
|
|
23
|
+
{
|
|
24
|
+
id: id,
|
|
25
|
+
event: event,
|
|
26
|
+
data: data,
|
|
27
|
+
}
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
sig do
|
|
33
|
+
params(
|
|
34
|
+
json_string: String,
|
|
35
|
+
).returns(WorkOS::Types::WebhookStruct)
|
|
36
|
+
end
|
|
37
|
+
def parse_json(json_string)
|
|
38
|
+
hash = JSON.parse(json_string, symbolize_names: true)
|
|
39
|
+
|
|
40
|
+
WorkOS::Types::WebhookStruct.new(
|
|
41
|
+
id: hash[:id],
|
|
42
|
+
event: hash[:event],
|
|
43
|
+
data: hash[:data],
|
|
44
|
+
)
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# typed: true
|
|
3
|
+
|
|
4
|
+
require 'openssl'
|
|
5
|
+
|
|
6
|
+
module WorkOS
|
|
7
|
+
# The Webhooks module provides convenience methods for working with the WorkOS webhooks.
|
|
8
|
+
# You'll need to extract the signature header and payload from the webhook request
|
|
9
|
+
# sig_header = request.headers['WorkOS-Signature']
|
|
10
|
+
# payload = request.body.read
|
|
11
|
+
#
|
|
12
|
+
# The secret is the Webhook Secret from your WorkOS Dashboard
|
|
13
|
+
# The tolerance is for the timestamp validation
|
|
14
|
+
#
|
|
15
|
+
module Webhooks
|
|
16
|
+
class << self
|
|
17
|
+
extend T::Sig
|
|
18
|
+
|
|
19
|
+
DEFAULT_TOLERANCE = 180
|
|
20
|
+
|
|
21
|
+
# Initializes an Event object from a JSON payload
|
|
22
|
+
# rubocop:disable Layout/LineLength
|
|
23
|
+
#
|
|
24
|
+
# @param [String] payload The payload from the webhook sent by WorkOS. This is the RAW_POST_DATA of the request.
|
|
25
|
+
# @param [String] sig_header The signature from the webhook sent by WorkOS.
|
|
26
|
+
# @param [String] secret The webhook secret from the WorkOS dashboard.
|
|
27
|
+
# @param [Integer] tolerance The time tolerance in seconds for the webhook.
|
|
28
|
+
#
|
|
29
|
+
# @example
|
|
30
|
+
# WorkOS::Webhooks.construct_event(
|
|
31
|
+
# payload: "{"id": "wh_123","data":{"id":"directory_user_01FAEAJCR3ZBZ30D8BD1924TVG","state":"active","emails":[{"type":"work","value":"blair@foo-corp.com","primary":true}],"idp_id":"00u1e8mutl6wlH3lL4x7","object":"directory_user","username":"blair@foo-corp.com","last_name":"Lunceford","first_name":"Blair","directory_id":"directory_01F9M7F68PZP8QXP8G7X5QRHS7","raw_attributes":{"name":{"givenName":"Blair","familyName":"Lunceford","middleName":"Elizabeth","honorificPrefix":"Ms."},"title":"Developer Success Engineer","active":true,"emails":[{"type":"work","value":"blair@foo-corp.com","primary":true}],"groups":[],"locale":"en-US","schemas":["urn:ietf:params:scim:schemas:core:2.0:User","urn:ietf:params:scim:schemas:extension:enterprise:2.0:User"],"userName":"blair@foo-corp.com","addresses":[{"region":"CO","primary":true,"locality":"Steamboat Springs","postalCode":"80487"}],"externalId":"00u1e8mutl6wlH3lL4x7","displayName":"Blair Lunceford","urn:ietf:params:scim:schemas:extension:enterprise:2.0:User":{"manager":{"value":"2","displayName":"Kathleen Chung"},"division":"Engineering","department":"Customer Success"}}},"event":"dsync.user.created"}",
|
|
32
|
+
# sig_header: 't=1626125972272, v1=80f7ab7efadc306eb5797c588cee9410da9be4416782b497bf1e1bf4175fb928',
|
|
33
|
+
# secret: 'LJlTiC19GmCKWs8AE0IaOQcos',
|
|
34
|
+
# )
|
|
35
|
+
#
|
|
36
|
+
# => #<WorkOS::Webhook:0x00007fa64b980910 @event="dsync.user.created", @data={:id=>"directory_user_01FAEAJCR3ZBZ30D8BD1924TVG", :state=>"active", :emails=>[{:type=>"work", :value=>"blair@foo-corp.com", :primary=>true}], :idp_id=>"00u1e8mutl6wlH3lL4x7", :object=>"directory_user", :username=>"blair@foo-corp.com", :last_name=>"Lunceford", :first_name=>"Blair", :directory_id=>"directory_01F9M7F68PZP8QXP8G7X5QRHS7", :raw_attributes=>{:name=>{:givenName=>"Blair", :familyName=>"Lunceford", :middleName=>"Elizabeth", :honorificPrefix=>"Ms."}, :title=>"Developer Success Engineer", :active=>true, :emails=>[{:type=>"work", :value=>"blair@foo-corp.com", :primary=>true}], :groups=>[], :locale=>"en-US", :schemas=>["urn:ietf:params:scim:schemas:core:2.0:User", "urn:ietf:params:scim:schemas:extension:enterprise:2.0:User"], :userName=>"blair@foo-corp.com", :addresses=>[{:region=>"CO", :primary=>true, :locality=>"Steamboat Springs", :postalCode=>"80487"}], :externalId=>"00u1e8mutl6wlH3lL4x7", :displayName=>"Blair Lunceford", :"urn:ietf:params:scim:schemas:extension:enterprise:2.0:User"=>{:manager=>{:value=>"2", :displayName=>"Kathleen Chung"}, :division=>"Engineering", :department=>"Customer Success"}}}>
|
|
37
|
+
#
|
|
38
|
+
# @return [WorkOS::Webhook]
|
|
39
|
+
# rubocop:enable Layout/LineLength
|
|
40
|
+
sig do
|
|
41
|
+
params(
|
|
42
|
+
payload: String,
|
|
43
|
+
sig_header: String,
|
|
44
|
+
secret: String,
|
|
45
|
+
tolerance: Integer,
|
|
46
|
+
).returns(WorkOS::Webhook)
|
|
47
|
+
end
|
|
48
|
+
def construct_event(
|
|
49
|
+
payload:,
|
|
50
|
+
sig_header:,
|
|
51
|
+
secret:,
|
|
52
|
+
tolerance: DEFAULT_TOLERANCE
|
|
53
|
+
)
|
|
54
|
+
verify_header(payload: payload, sig_header: sig_header, secret: secret, tolerance: tolerance)
|
|
55
|
+
WorkOS::Webhook.new(payload)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
private
|
|
59
|
+
|
|
60
|
+
sig do
|
|
61
|
+
params(
|
|
62
|
+
payload: String,
|
|
63
|
+
sig_header: String,
|
|
64
|
+
secret: String,
|
|
65
|
+
tolerance: Integer,
|
|
66
|
+
).returns(T::Boolean)
|
|
67
|
+
end
|
|
68
|
+
# rubocop:disable Metrics/MethodLength
|
|
69
|
+
def verify_header(
|
|
70
|
+
payload:,
|
|
71
|
+
sig_header:,
|
|
72
|
+
secret:,
|
|
73
|
+
tolerance: DEFAULT_TOLERANCE
|
|
74
|
+
)
|
|
75
|
+
begin
|
|
76
|
+
timestamp, signature_hash = get_timestamp_and_signature_hash(sig_header: sig_header)
|
|
77
|
+
rescue StandardError
|
|
78
|
+
raise WorkOS::SignatureVerificationError.new(
|
|
79
|
+
message: 'Unable to extract timestamp and signature hash from header',
|
|
80
|
+
)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
if signature_hash.empty?
|
|
84
|
+
raise WorkOS::SignatureVerificationError.new(
|
|
85
|
+
message: 'No signature hash found with expected scheme v1',
|
|
86
|
+
)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
if timestamp < Time.now - tolerance
|
|
90
|
+
raise WorkOS::SignatureVerificationError.new(
|
|
91
|
+
message: 'Timestamp outside the tolerance zone',
|
|
92
|
+
)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
expected_sig = compute_signature(timestamp: timestamp, payload: payload, secret: secret)
|
|
96
|
+
unless secure_compare(str_a: expected_sig, str_b: signature_hash)
|
|
97
|
+
raise WorkOS::SignatureVerificationError.new(
|
|
98
|
+
message: 'Signature hash does not match the expected signature hash for payload',
|
|
99
|
+
)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
true
|
|
103
|
+
end
|
|
104
|
+
# rubocop:enable Metrics/MethodLength
|
|
105
|
+
|
|
106
|
+
sig do
|
|
107
|
+
params(
|
|
108
|
+
sig_header: String,
|
|
109
|
+
).returns(T::Array[T.untyped])
|
|
110
|
+
end
|
|
111
|
+
def get_timestamp_and_signature_hash(
|
|
112
|
+
sig_header:
|
|
113
|
+
)
|
|
114
|
+
timestamp, signature_hash = sig_header.split(', ')
|
|
115
|
+
|
|
116
|
+
if timestamp.nil? || signature_hash.nil?
|
|
117
|
+
raise WorkOS::SignatureVerificationError.new(
|
|
118
|
+
message: 'Unable to extract timestamp and signature hash from header',
|
|
119
|
+
)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
timestamp = timestamp.sub('t=', '')
|
|
123
|
+
signature_hash = signature_hash.sub('v1=', '')
|
|
124
|
+
|
|
125
|
+
[Time.at(timestamp.to_i), signature_hash]
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
sig do
|
|
129
|
+
params(
|
|
130
|
+
timestamp: Time,
|
|
131
|
+
payload: String,
|
|
132
|
+
secret: String,
|
|
133
|
+
).returns(String)
|
|
134
|
+
end
|
|
135
|
+
def compute_signature(
|
|
136
|
+
timestamp:,
|
|
137
|
+
payload:,
|
|
138
|
+
secret:
|
|
139
|
+
)
|
|
140
|
+
unhashed_string = "#{timestamp.to_i}.#{payload}"
|
|
141
|
+
digest = OpenSSL::Digest.new('sha256')
|
|
142
|
+
OpenSSL::HMAC.hexdigest(digest, secret, unhashed_string)
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# Constant time string comparison to prevent timing attacks
|
|
146
|
+
# Code borrowed from ActiveSupport
|
|
147
|
+
sig do
|
|
148
|
+
params(
|
|
149
|
+
str_a: String,
|
|
150
|
+
str_b: String,
|
|
151
|
+
).returns(T::Boolean)
|
|
152
|
+
end
|
|
153
|
+
def secure_compare(
|
|
154
|
+
str_a:,
|
|
155
|
+
str_b:
|
|
156
|
+
)
|
|
157
|
+
return false unless str_a.bytesize == str_b.bytesize
|
|
158
|
+
|
|
159
|
+
l = T.unsafe(str_a.unpack("C#{str_a.bytesize}"))
|
|
160
|
+
|
|
161
|
+
res = 0
|
|
162
|
+
str_b.each_byte { |byte| res |= byte ^ l.shift }
|
|
163
|
+
|
|
164
|
+
res.zero?
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
end
|
data/lib/workos.rb
CHANGED
|
@@ -42,11 +42,14 @@ module WorkOS
|
|
|
42
42
|
autoload :ProfileAndToken, 'workos/profile_and_token'
|
|
43
43
|
autoload :SSO, 'workos/sso'
|
|
44
44
|
autoload :DirectoryUser, 'workos/directory_user'
|
|
45
|
+
autoload :Webhook, 'workos/webhook'
|
|
46
|
+
autoload :Webhooks, 'workos/webhooks'
|
|
45
47
|
|
|
46
48
|
# Errors
|
|
47
49
|
autoload :APIError, 'workos/errors'
|
|
48
50
|
autoload :AuthenticationError, 'workos/errors'
|
|
49
51
|
autoload :InvalidRequestError, 'workos/errors'
|
|
52
|
+
autoload :SignatureVerificationError, 'workos/errors'
|
|
50
53
|
|
|
51
54
|
# Remove WORKOS_KEY at some point in the future. Keeping it here now for
|
|
52
55
|
# backwards compatibility.
|
data/spec/lib/workos/sso_spec.rb
CHANGED
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
require 'securerandom'
|
|
5
5
|
|
|
6
6
|
describe WorkOS::SSO do
|
|
7
|
+
it_behaves_like 'client'
|
|
8
|
+
|
|
7
9
|
describe '.authorization_url' do
|
|
8
10
|
context 'with a domain' do
|
|
9
11
|
let(:args) do
|
|
@@ -162,6 +164,7 @@ describe WorkOS::SSO do
|
|
|
162
164
|
id: 'prof_01EEJTY9SZ1R350RB7B73SNBKF',
|
|
163
165
|
idp_id: '116485463307139932699',
|
|
164
166
|
last_name: 'Loblaw',
|
|
167
|
+
organization_id: 'org_01FG53X8636WSNW2WEKB2C31ZB',
|
|
165
168
|
raw_attributes: {
|
|
166
169
|
email: 'bob.loblaw@workos.com',
|
|
167
170
|
family_name: 'Loblaw',
|
|
@@ -231,6 +234,7 @@ describe WorkOS::SSO do
|
|
|
231
234
|
id: 'prof_01DRA1XNSJDZ19A31F183ECQW5',
|
|
232
235
|
idp_id: '00u1klkowm8EGah2H357',
|
|
233
236
|
last_name: 'Demo',
|
|
237
|
+
organization_id: 'org_01FG53X8636WSNW2WEKB2C31ZB',
|
|
234
238
|
raw_attributes: {
|
|
235
239
|
email: 'demo@workos-okta.com',
|
|
236
240
|
first_name: 'WorkOS',
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# typed: false
|
|
3
|
+
|
|
4
|
+
require 'json'
|
|
5
|
+
require 'openssl'
|
|
6
|
+
|
|
7
|
+
describe WorkOS::Webhooks do
|
|
8
|
+
describe '.construct_event' do
|
|
9
|
+
before(:each) do
|
|
10
|
+
@payload = File.read("#{SPEC_ROOT}/support/webhook_payload.txt")
|
|
11
|
+
@secret = 'secret'
|
|
12
|
+
@timestamp = Time.at(Time.now.to_i * 1000)
|
|
13
|
+
unhashed_string = "#{@timestamp.to_i}.#{@payload}"
|
|
14
|
+
digest = OpenSSL::Digest.new('sha256')
|
|
15
|
+
@signature_hash = OpenSSL::HMAC.hexdigest(digest, @secret, unhashed_string)
|
|
16
|
+
@expectation = {
|
|
17
|
+
id: 'directory_user_01FAEAJCR3ZBZ30D8BD1924TVG',
|
|
18
|
+
state: 'active',
|
|
19
|
+
emails: [{
|
|
20
|
+
type: 'work',
|
|
21
|
+
value: 'blair@foo-corp.com',
|
|
22
|
+
primary: true,
|
|
23
|
+
}],
|
|
24
|
+
idp_id: '00u1e8mutl6wlH3lL4x7',
|
|
25
|
+
object: 'directory_user',
|
|
26
|
+
username: 'blair@foo-corp.com',
|
|
27
|
+
last_name: 'Lunceford',
|
|
28
|
+
first_name: 'Blair',
|
|
29
|
+
directory_id: 'directory_01F9M7F68PZP8QXP8G7X5QRHS7',
|
|
30
|
+
raw_attributes: {
|
|
31
|
+
name: {
|
|
32
|
+
givenName: 'Blair',
|
|
33
|
+
familyName: 'Lunceford',
|
|
34
|
+
middleName: 'Elizabeth',
|
|
35
|
+
honorificPrefix: 'Ms.',
|
|
36
|
+
},
|
|
37
|
+
title: 'Developer Success Engineer',
|
|
38
|
+
active: true,
|
|
39
|
+
emails: [{
|
|
40
|
+
type: 'work',
|
|
41
|
+
value: 'blair@foo-corp.com',
|
|
42
|
+
primary: true,
|
|
43
|
+
}],
|
|
44
|
+
groups: [],
|
|
45
|
+
locale: 'en-US',
|
|
46
|
+
schemas: [
|
|
47
|
+
'urn:ietf:params:scim:schemas:core:2.0:User',
|
|
48
|
+
'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User'
|
|
49
|
+
],
|
|
50
|
+
userName: 'blair@foo-corp.com',
|
|
51
|
+
addresses: [{
|
|
52
|
+
region: 'CO',
|
|
53
|
+
primary: true,
|
|
54
|
+
locality: 'Steamboat Springs',
|
|
55
|
+
postalCode: '80487',
|
|
56
|
+
}],
|
|
57
|
+
externalId: '00u1e8mutl6wlH3lL4x7',
|
|
58
|
+
displayName: 'Blair Lunceford',
|
|
59
|
+
"urn:ietf:params:scim:schemas:extension:enterprise:2.0:User": {
|
|
60
|
+
manager: {
|
|
61
|
+
value: '2',
|
|
62
|
+
displayName: 'Kathleen Chung',
|
|
63
|
+
},
|
|
64
|
+
division: 'Engineering',
|
|
65
|
+
department: 'Customer Success',
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
}
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
context 'with the correct payload, sig_header, and secret' do
|
|
72
|
+
it 'returns a webhook event' do
|
|
73
|
+
webhook = described_class.construct_event(
|
|
74
|
+
payload: @payload,
|
|
75
|
+
sig_header: "t=#{@timestamp.to_i}, v1=#{@signature_hash}",
|
|
76
|
+
secret: @secret,
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
expect(webhook.data).to eq(@expectation)
|
|
80
|
+
expect(webhook.event).to eq('dsync.user.created')
|
|
81
|
+
expect(webhook.id).to eq('wh_123')
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
context 'with the correct payload, sig_header, secret, and tolerance' do
|
|
86
|
+
it 'returns a webhook event' do
|
|
87
|
+
webhook = described_class.construct_event(
|
|
88
|
+
payload: @payload,
|
|
89
|
+
sig_header: "t=#{@timestamp.to_i}, v1=#{@signature_hash}",
|
|
90
|
+
secret: @secret,
|
|
91
|
+
tolerance: 300,
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
expect(webhook.data).to eq(@expectation)
|
|
95
|
+
expect(webhook.event).to eq('dsync.user.created')
|
|
96
|
+
expect(webhook.id).to eq('wh_123')
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
context 'with an empty header' do
|
|
101
|
+
it 'raises an error' do
|
|
102
|
+
expect do
|
|
103
|
+
described_class.construct_event(
|
|
104
|
+
payload: @payload,
|
|
105
|
+
sig_header: '',
|
|
106
|
+
secret: @secret,
|
|
107
|
+
)
|
|
108
|
+
end.to raise_error(
|
|
109
|
+
WorkOS::SignatureVerificationError,
|
|
110
|
+
'Unable to extract timestamp and signature hash from header',
|
|
111
|
+
)
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
context 'with an empty signature hash' do
|
|
116
|
+
it 'raises an error' do
|
|
117
|
+
expect do
|
|
118
|
+
described_class.construct_event(
|
|
119
|
+
payload: @payload,
|
|
120
|
+
sig_header: "t=#{@timestamp.to_i}, v1=",
|
|
121
|
+
secret: @secret,
|
|
122
|
+
)
|
|
123
|
+
end.to raise_error(
|
|
124
|
+
WorkOS::SignatureVerificationError,
|
|
125
|
+
'No signature hash found with expected scheme v1',
|
|
126
|
+
)
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
context 'with an incorrect signature hash' do
|
|
131
|
+
it 'raises an error' do
|
|
132
|
+
expect do
|
|
133
|
+
described_class.construct_event(
|
|
134
|
+
payload: @payload,
|
|
135
|
+
sig_header: "t=#{@timestamp.to_i}, v1=99999",
|
|
136
|
+
secret: @secret,
|
|
137
|
+
)
|
|
138
|
+
end.to raise_error(
|
|
139
|
+
WorkOS::SignatureVerificationError,
|
|
140
|
+
'Signature hash does not match the expected signature hash for payload',
|
|
141
|
+
)
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
context 'with an incorrect payload' do
|
|
146
|
+
it 'raises an error' do
|
|
147
|
+
expect do
|
|
148
|
+
described_class.construct_event(
|
|
149
|
+
payload: 'invalid',
|
|
150
|
+
sig_header: "t=#{@timestamp.to_i}, v1=#{@signature_hash}",
|
|
151
|
+
secret: @secret,
|
|
152
|
+
)
|
|
153
|
+
end.to raise_error(
|
|
154
|
+
WorkOS::SignatureVerificationError,
|
|
155
|
+
'Signature hash does not match the expected signature hash for payload',
|
|
156
|
+
)
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
context 'with an incorrect webhook secret' do
|
|
161
|
+
it 'raises an error' do
|
|
162
|
+
expect do
|
|
163
|
+
described_class.construct_event(
|
|
164
|
+
payload: @payload,
|
|
165
|
+
sig_header: "t=#{@timestamp.to_i}, v1=#{@signature_hash}",
|
|
166
|
+
secret: 'invalid',
|
|
167
|
+
)
|
|
168
|
+
end.to raise_error(
|
|
169
|
+
WorkOS::SignatureVerificationError,
|
|
170
|
+
'Signature hash does not match the expected signature hash for payload',
|
|
171
|
+
)
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
context 'with a timestamp outside tolerance' do
|
|
176
|
+
it 'raises an error' do
|
|
177
|
+
expect do
|
|
178
|
+
described_class.construct_event(
|
|
179
|
+
payload: @payload,
|
|
180
|
+
sig_header: "t=9999, v1=#{@signature_hash}",
|
|
181
|
+
secret: @secret,
|
|
182
|
+
)
|
|
183
|
+
end.to raise_error(
|
|
184
|
+
WorkOS::SignatureVerificationError,
|
|
185
|
+
'Timestamp outside the tolerance zone',
|
|
186
|
+
)
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
end
|
data/spec/spec_helper.rb
CHANGED
|
@@ -78,7 +78,7 @@ http_interactions:
|
|
|
78
78
|
body:
|
|
79
79
|
encoding: UTF-8
|
|
80
80
|
string: '{"object":"organization","id":"org_01FCPEJXEZR4DSBA625YMGQT9N","name":"Test
|
|
81
|
-
Organization","created_at":"2021-08-09T21:55:02.755Z","updated_at":"2021-08-09T21:55:02.755Z","domains":[{"object":"organization_domain","id":"org_domain_01FCPEJXF4CTFEDFEGDTRN1MZG","domain":"example.io"}]}'
|
|
81
|
+
Organization","allow_profiles_outside_organization":false,"created_at":"2021-08-09T21:55:02.755Z","updated_at":"2021-08-09T21:55:02.755Z","domains":[{"object":"organization_domain","id":"org_domain_01FCPEJXF4CTFEDFEGDTRN1MZG","domain":"example.io"}]}'
|
|
82
82
|
http_version:
|
|
83
83
|
recorded_at: Mon, 09 Aug 2021 21:55:02 GMT
|
|
84
84
|
recorded_with: VCR 5.0.0
|
|
@@ -78,7 +78,7 @@ http_interactions:
|
|
|
78
78
|
body:
|
|
79
79
|
encoding: ASCII-8BIT
|
|
80
80
|
string: '{"object":"organization","id":"org_01F9293WD2PDEEV4Y625XPZVG7","name":"Foo
|
|
81
|
-
Corp","created_at":"2021-06-25T19:07:33.155Z","updated_at":"2021-06-25T19:07:33.155Z","domains":[{"object":"organization_domain","id":"org_domain_01F9293WD8KZAS4ESBK57SZ4D8","domain":"foo-corp.com"}]}'
|
|
81
|
+
Corp","allow_profiles_outside_organization":false,"created_at":"2021-06-25T19:07:33.155Z","updated_at":"2021-06-25T19:07:33.155Z","domains":[{"object":"organization_domain","id":"org_domain_01F9293WD8KZAS4ESBK57SZ4D8","domain":"foo-corp.com"}]}'
|
|
82
82
|
http_version:
|
|
83
83
|
recorded_at: Mon, 09 Aug 2021 21:53:34 GMT
|
|
84
84
|
recorded_with: VCR 5.0.0
|
|
@@ -78,10 +78,10 @@ http_interactions:
|
|
|
78
78
|
body:
|
|
79
79
|
encoding: ASCII-8BIT
|
|
80
80
|
string: '{"object":"list","data":[{"object":"organization","id":"org_01FCPEJXEZR4DSBA625YMGQT9N","name":"Test
|
|
81
|
-
Organization","created_at":"2021-08-09T21:55:02.755Z","updated_at":"2021-08-09T21:55:02.755Z","domains":[{"object":"organization_domain","id":"org_domain_01FCPEJXF4CTFEDFEGDTRN1MZG","domain":"example.io"}]},{"object":"organization","id":"org_01F9293WD2PDEEV4Y625XPZVG7","name":"Foo
|
|
82
|
-
Corp","created_at":"2021-06-25T19:07:33.155Z","updated_at":"2021-06-25T19:07:33.155Z","domains":[{"object":"organization_domain","id":"org_domain_01F9293WD8KZAS4ESBK57SZ4D8","domain":"foo-corp.com"}]},{"object":"organization","id":"org_01F8873JSZWN1MJCDN537FEK1H","name":"Example
|
|
83
|
-
Organization","created_at":"2021-06-15T16:12:10.942Z","updated_at":"2021-06-15T16:12:10.942Z","domains":[]},{"object":"organization","id":"org_01F79Z8TGGTA8Q67Q50FDXAYN3","name":"gmail.com","created_at":"2021-06-03T22:18:01.100Z","updated_at":"2021-06-03T22:18:01.100Z","domains":[{"object":"organization_domain","id":"org_domain_01F79Z8TGS4VYFX0246RAWSZMP","domain":"gmail.com"}]},{"object":"organization","id":"org_01F6Q6TFP7RD2PF6J03ANNWDKV","name":"Test
|
|
84
|
-
Organization","created_at":"2021-05-27T15:24:25.670Z","updated_at":"2021-08-09T21:53:34.525Z","domains":[{"object":"organization_domain","id":"org_domain_01FCPEG7BAYMQ4CHMG41Y2VNHF","domain":"example.me"}]},{"object":"organization","id":"org_01F6Q6NGZS8R5ZTH3C1XR96JK7","name":"WorkOS","created_at":"2021-05-27T15:21:43.160Z","updated_at":"2021-05-27T15:21:43.160Z","domains":[{"object":"organization_domain","id":"org_domain_01F6Q6NGZZ54WSX1XCMCKSC4DA","domain":"workos.com"}]}],"listMetadata":{"before":"before-id","after":null}}'
|
|
81
|
+
Organization","allow_profiles_outside_organization":false,"created_at":"2021-08-09T21:55:02.755Z","updated_at":"2021-08-09T21:55:02.755Z","domains":[{"object":"organization_domain","id":"org_domain_01FCPEJXF4CTFEDFEGDTRN1MZG","domain":"example.io"}]},{"object":"organization","id":"org_01F9293WD2PDEEV4Y625XPZVG7","name":"Foo
|
|
82
|
+
Corp","allow_profiles_outside_organization":false,"created_at":"2021-06-25T19:07:33.155Z","updated_at":"2021-06-25T19:07:33.155Z","domains":[{"object":"organization_domain","id":"org_domain_01F9293WD8KZAS4ESBK57SZ4D8","domain":"foo-corp.com"}]},{"object":"organization","id":"org_01F8873JSZWN1MJCDN537FEK1H","name":"Example
|
|
83
|
+
Organization","allow_profiles_outside_organization":false,"created_at":"2021-06-15T16:12:10.942Z","updated_at":"2021-06-15T16:12:10.942Z","domains":[]},{"object":"organization","id":"org_01F79Z8TGGTA8Q67Q50FDXAYN3","name":"gmail.com","allow_profiles_outside_organization":false,"created_at":"2021-06-03T22:18:01.100Z","updated_at":"2021-06-03T22:18:01.100Z","domains":[{"object":"organization_domain","id":"org_domain_01F79Z8TGS4VYFX0246RAWSZMP","domain":"gmail.com"}]},{"object":"organization","id":"org_01F6Q6TFP7RD2PF6J03ANNWDKV","name":"Test
|
|
84
|
+
Organization","allow_profiles_outside_organization":false,"created_at":"2021-05-27T15:24:25.670Z","updated_at":"2021-08-09T21:53:34.525Z","domains":[{"object":"organization_domain","id":"org_domain_01FCPEG7BAYMQ4CHMG41Y2VNHF","domain":"example.me"}]},{"object":"organization","id":"org_01F6Q6NGZS8R5ZTH3C1XR96JK7","name":"WorkOS","allow_profiles_outside_organization":false,"created_at":"2021-05-27T15:21:43.160Z","updated_at":"2021-05-27T15:21:43.160Z","domains":[{"object":"organization_domain","id":"org_domain_01F6Q6NGZZ54WSX1XCMCKSC4DA","domain":"workos.com"}]}],"listMetadata":{"before":"before-id","after":null}}'
|
|
85
85
|
http_version:
|
|
86
86
|
recorded_at: Mon, 09 Aug 2021 21:55:03 GMT
|
|
87
87
|
recorded_with: VCR 5.0.0
|
|
@@ -78,7 +78,7 @@ http_interactions:
|
|
|
78
78
|
body:
|
|
79
79
|
encoding: ASCII-8BIT
|
|
80
80
|
string: '{"object":"organization","id":"org_01F6Q6TFP7RD2PF6J03ANNWDKV","name":"Test
|
|
81
|
-
Organization","created_at":"2021-05-27T15:24:25.670Z","updated_at":"2021-08-09T21:53:34.525Z","domains":[{"object":"organization_domain","id":"org_domain_01FCPEG7BAYMQ4CHMG41Y2VNHF","domain":"example.me"}]}'
|
|
81
|
+
Organization","allow_profiles_outside_organization":false,"created_at":"2021-05-27T15:24:25.670Z","updated_at":"2021-08-09T21:53:34.525Z","domains":[{"object":"organization_domain","id":"org_domain_01FCPEG7BAYMQ4CHMG41Y2VNHF","domain":"example.me"}]}'
|
|
82
82
|
http_version:
|
|
83
83
|
recorded_at: Mon, 09 Aug 2021 21:53:34 GMT
|
|
84
84
|
recorded_with: VCR 5.0.0
|
|
@@ -67,7 +67,7 @@ http_interactions:
|
|
|
67
67
|
body:
|
|
68
68
|
encoding: UTF-8
|
|
69
69
|
string:
|
|
70
|
-
'{"object":"profile","id":"prof_01EEJTY9SZ1R350RB7B73SNBKF","connection_id":"conn_01E83FVYZHY7DM4S9503JHV0R5","connection_type":"GoogleOAuth","idp_id":"116485463307139932699","email":"bob.loblaw@workos.com","first_name":"Bob","last_name":"Loblaw","raw_attributes":{"hd":"workos.com","id":"116485463307139932699","name":"Bob
|
|
70
|
+
'{"object":"profile","id":"prof_01EEJTY9SZ1R350RB7B73SNBKF","organization_id":"org_01FG53X8636WSNW2WEKB2C31ZB","connection_id":"conn_01E83FVYZHY7DM4S9503JHV0R5","connection_type":"GoogleOAuth","idp_id":"116485463307139932699","email":"bob.loblaw@workos.com","first_name":"Bob","last_name":"Loblaw","raw_attributes":{"hd":"workos.com","id":"116485463307139932699","name":"Bob
|
|
71
71
|
Loblaw","email":"bob.loblaw@workos.com","locale":"en","picture":"https://lh3.googleusercontent.com/a-/AOh14GyO2hLlgZvteDQ3Ldi3_-RteZLya0hWH7247Cam=s96-c","given_name":"Bob","family_name":"Loblaw","verified_email":true}}'
|
|
72
72
|
http_version:
|
|
73
73
|
recorded_at: Tue, 18 May 2021 22:55:21 GMT
|
data/spec/support/profile.txt
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"profile":{"object":"profile","id":"prof_01DRA1XNSJDZ19A31F183ECQW5","email":"demo@workos-okta.com","first_name":"WorkOS","connection_id":"conn_01EMH8WAK20T42N2NBMNBCYHAG","connection_type":"OktaSAML","last_name":"Demo","idp_id":"00u1klkowm8EGah2H357","raw_attributes":{"id":"prof_01DRA1XNSJDZ19A31F183ECQW5","email":"demo@workos-okta.com","first_name":"WorkOS","last_name":"Demo","idp_id":"00u1klkowm8EGah2H357"}},"access_token":"01DVX6QBS3EG6FHY2ESAA5Q65X"}
|
|
1
|
+
{"profile":{"object":"profile","id":"prof_01DRA1XNSJDZ19A31F183ECQW5","email":"demo@workos-okta.com","first_name":"WorkOS","organization_id":"org_01FG53X8636WSNW2WEKB2C31ZB","connection_id":"conn_01EMH8WAK20T42N2NBMNBCYHAG","connection_type":"OktaSAML","last_name":"Demo","idp_id":"00u1klkowm8EGah2H357","raw_attributes":{"id":"prof_01DRA1XNSJDZ19A31F183ECQW5","email":"demo@workos-okta.com","first_name":"WorkOS","last_name":"Demo","idp_id":"00u1klkowm8EGah2H357"}},"access_token":"01DVX6QBS3EG6FHY2ESAA5Q65X"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# typed: false
|
|
3
|
+
|
|
4
|
+
RSpec.shared_examples 'client' do
|
|
5
|
+
subject(:client) { described_class.client }
|
|
6
|
+
|
|
7
|
+
it { is_expected.to be_kind_of(Net::HTTP) }
|
|
8
|
+
|
|
9
|
+
it 'assigns use_ssl' do
|
|
10
|
+
expect(client.use_ssl?).to be true
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
it 'returns new instance' do
|
|
14
|
+
expect(described_class.client.object_id).to_not eq described_class.client.object_id
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"id": "wh_123","data":{"id":"directory_user_01FAEAJCR3ZBZ30D8BD1924TVG","state":"active","emails":[{"type":"work","value":"blair@foo-corp.com","primary":true}],"idp_id":"00u1e8mutl6wlH3lL4x7","object":"directory_user","username":"blair@foo-corp.com","last_name":"Lunceford","first_name":"Blair","directory_id":"directory_01F9M7F68PZP8QXP8G7X5QRHS7","raw_attributes":{"name":{"givenName":"Blair","familyName":"Lunceford","middleName":"Elizabeth","honorificPrefix":"Ms."},"title":"Developer Success Engineer","active":true,"emails":[{"type":"work","value":"blair@foo-corp.com","primary":true}],"groups":[],"locale":"en-US","schemas":["urn:ietf:params:scim:schemas:core:2.0:User","urn:ietf:params:scim:schemas:extension:enterprise:2.0:User"],"userName":"blair@foo-corp.com","addresses":[{"region":"CO","primary":true,"locality":"Steamboat Springs","postalCode":"80487"}],"externalId":"00u1e8mutl6wlH3lL4x7","displayName":"Blair Lunceford","urn:ietf:params:scim:schemas:extension:enterprise:2.0:User":{"manager":{"value":"2","displayName":"Kathleen Chung"},"division":"Engineering","department":"Customer Success"}}},"event":"dsync.user.created"}
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: workos
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.6.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- WorkOS
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2021-
|
|
11
|
+
date: 2021-09-23 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: sorbet-runtime
|
|
@@ -234,7 +234,10 @@ files:
|
|
|
234
234
|
- lib/workos/types/passwordless_session_struct.rb
|
|
235
235
|
- lib/workos/types/profile_struct.rb
|
|
236
236
|
- lib/workos/types/provider_enum.rb
|
|
237
|
+
- lib/workos/types/webhook_struct.rb
|
|
237
238
|
- lib/workos/version.rb
|
|
239
|
+
- lib/workos/webhook.rb
|
|
240
|
+
- lib/workos/webhooks.rb
|
|
238
241
|
- sorbet/config
|
|
239
242
|
- sorbet/rbi/gems/addressable.rbi
|
|
240
243
|
- sorbet/rbi/gems/ast.rbi
|
|
@@ -276,6 +279,7 @@ files:
|
|
|
276
279
|
- spec/lib/workos/passwordless_spec.rb
|
|
277
280
|
- spec/lib/workos/portal_spec.rb
|
|
278
281
|
- spec/lib/workos/sso_spec.rb
|
|
282
|
+
- spec/lib/workos/webhooks_spec.rb
|
|
279
283
|
- spec/spec_helper.rb
|
|
280
284
|
- spec/support/fixtures/vcr_cassettes/audit_trail/create_event.yml
|
|
281
285
|
- spec/support/fixtures/vcr_cassettes/audit_trail/create_event_custom_idempotency_key.yml
|
|
@@ -315,7 +319,6 @@ files:
|
|
|
315
319
|
- spec/support/fixtures/vcr_cassettes/organization/get_invalid.yml
|
|
316
320
|
- spec/support/fixtures/vcr_cassettes/organization/list.yml
|
|
317
321
|
- spec/support/fixtures/vcr_cassettes/organization/update.yml
|
|
318
|
-
- spec/support/fixtures/vcr_cassettes/organization/update_invalid.yml
|
|
319
322
|
- spec/support/fixtures/vcr_cassettes/passwordless/create_session.yml
|
|
320
323
|
- spec/support/fixtures/vcr_cassettes/passwordless/create_session_invalid.yml
|
|
321
324
|
- spec/support/fixtures/vcr_cassettes/passwordless/send_session.yml
|
|
@@ -336,6 +339,8 @@ files:
|
|
|
336
339
|
- spec/support/fixtures/vcr_cassettes/sso/list_connections/with_organization_id.yml
|
|
337
340
|
- spec/support/fixtures/vcr_cassettes/sso/profile.yml
|
|
338
341
|
- spec/support/profile.txt
|
|
342
|
+
- spec/support/shared_examples/client_spec.rb
|
|
343
|
+
- spec/support/webhook_payload.txt
|
|
339
344
|
- workos.gemspec
|
|
340
345
|
homepage: https://github.com/workos-inc/workos-ruby
|
|
341
346
|
licenses:
|
|
@@ -357,7 +362,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
357
362
|
- !ruby/object:Gem::Version
|
|
358
363
|
version: '0'
|
|
359
364
|
requirements: []
|
|
360
|
-
rubygems_version: 3.2.
|
|
365
|
+
rubygems_version: 3.2.27
|
|
361
366
|
signing_key:
|
|
362
367
|
specification_version: 4
|
|
363
368
|
summary: API client for WorkOS
|
|
@@ -369,6 +374,7 @@ test_files:
|
|
|
369
374
|
- spec/lib/workos/passwordless_spec.rb
|
|
370
375
|
- spec/lib/workos/portal_spec.rb
|
|
371
376
|
- spec/lib/workos/sso_spec.rb
|
|
377
|
+
- spec/lib/workos/webhooks_spec.rb
|
|
372
378
|
- spec/spec_helper.rb
|
|
373
379
|
- spec/support/fixtures/vcr_cassettes/audit_trail/create_event.yml
|
|
374
380
|
- spec/support/fixtures/vcr_cassettes/audit_trail/create_event_custom_idempotency_key.yml
|
|
@@ -408,7 +414,6 @@ test_files:
|
|
|
408
414
|
- spec/support/fixtures/vcr_cassettes/organization/get_invalid.yml
|
|
409
415
|
- spec/support/fixtures/vcr_cassettes/organization/list.yml
|
|
410
416
|
- spec/support/fixtures/vcr_cassettes/organization/update.yml
|
|
411
|
-
- spec/support/fixtures/vcr_cassettes/organization/update_invalid.yml
|
|
412
417
|
- spec/support/fixtures/vcr_cassettes/passwordless/create_session.yml
|
|
413
418
|
- spec/support/fixtures/vcr_cassettes/passwordless/create_session_invalid.yml
|
|
414
419
|
- spec/support/fixtures/vcr_cassettes/passwordless/send_session.yml
|
|
@@ -429,3 +434,5 @@ test_files:
|
|
|
429
434
|
- spec/support/fixtures/vcr_cassettes/sso/list_connections/with_organization_id.yml
|
|
430
435
|
- spec/support/fixtures/vcr_cassettes/sso/profile.yml
|
|
431
436
|
- spec/support/profile.txt
|
|
437
|
+
- spec/support/shared_examples/client_spec.rb
|
|
438
|
+
- spec/support/webhook_payload.txt
|
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
http_interactions:
|
|
3
|
-
- request:
|
|
4
|
-
method: put
|
|
5
|
-
uri: https://api.workos.com/organizations/org_01F29YJ068E52HGEB8ZQGC9MJG
|
|
6
|
-
body:
|
|
7
|
-
encoding: UTF-8
|
|
8
|
-
string: '{"domains":["example.com"],"name":"Test Organization 2"}'
|
|
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.1; x86_64-darwin19; v0.11.1
|
|
18
|
-
Authorization:
|
|
19
|
-
- Bearer <API_KEY>
|
|
20
|
-
response:
|
|
21
|
-
status:
|
|
22
|
-
code: 200
|
|
23
|
-
message: OK
|
|
24
|
-
headers:
|
|
25
|
-
Server:
|
|
26
|
-
- Cowboy
|
|
27
|
-
Connection:
|
|
28
|
-
- keep-alive
|
|
29
|
-
Vary:
|
|
30
|
-
- Origin, Accept-Encoding
|
|
31
|
-
Access-Control-Allow-Credentials:
|
|
32
|
-
- 'true'
|
|
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
|
-
X-Request-Id:
|
|
56
|
-
- 6ba2cbf9-76e3-4a6a-8ebe-d25edb366ed0
|
|
57
|
-
Content-Type:
|
|
58
|
-
- application/json; charset=utf-8
|
|
59
|
-
Content-Length:
|
|
60
|
-
- '205'
|
|
61
|
-
Etag:
|
|
62
|
-
- W/"cd-mlRMlyyz/LtzInRhAghs1B+BT4s"
|
|
63
|
-
Date:
|
|
64
|
-
- Wed, 05 May 2021 22:14:10 GMT
|
|
65
|
-
Via:
|
|
66
|
-
- 1.1 vegur
|
|
67
|
-
body:
|
|
68
|
-
encoding: UTF-8
|
|
69
|
-
string: '{"object":"organization","id":"org_01F29YJ068E52HGEB8ZQGC9MJG","name":"Test
|
|
70
|
-
Organization 2","domains":[{"object":"organization_domain","id":"org_domain_01F4Z9GY3089GV4SENXWZ9RBX1","domain":"example.com"}]}'
|
|
71
|
-
http_version:
|
|
72
|
-
recorded_at: Wed, 05 May 2021 22:14:10 GMT
|
|
73
|
-
recorded_with: VCR 5.0.0
|