osso 0.0.3 → 0.0.3.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c4d6c8de140e9aee1afb784383da381c1fe16eb9816eb1ed0d6439a851e5aef2
4
- data.tar.gz: e393a2c08eb2c64f9f151b06174aaf45b9d980081d3bc3a792984cf6e64f3b5f
3
+ metadata.gz: c702f0d417b5fdd0b2094303b1169b7095cf5b9b7a511086d51a4fd598ffd8e4
4
+ data.tar.gz: 5e26b9ce26e67a131a431f9bd9778d63bd6f6b1194aa06ffebc9e915312fa442
5
5
  SHA512:
6
- metadata.gz: 331e8828f50de204268d11922a5dc6973b65bb03ba214e7f354f95713e75eb41143554fbbf42903daced8e9bec8bbfd19824e2866673f3a27c6348a62da40992
7
- data.tar.gz: 42c3392315313792aa4012a582c395e7b304af4dfb720d0ebbbaa9407841b58924f910bcc963ff10a710f31e1a955f6643edfcb5894a2fe1c04e4d22f9a47dcb
6
+ metadata.gz: c58931d540f6f61140ae9708ec2c80b470274fb85c47cfc96eed440ae01da94adb1f5987be679f9aa20c17d20f22901bed550943447a1505d5073bb07730ba67
7
+ data.tar.gz: 56d28d4f79ae450c18759b400d2ce02aabad61775f930994ce84533da8ec589d43c8173bfe7f31a919d4c555d04da7c3852ebc5cc6c01f0946ecbabf83051c1d
data/.gitignore CHANGED
@@ -7,3 +7,4 @@
7
7
  /spec/reports/
8
8
  /tmp/
9
9
  *.gem
10
+ .DS_Store
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- osso (0.0.3)
4
+ osso (0.0.3.1)
5
5
  activesupport (>= 6.0.3.2)
6
6
  jwt
7
7
  omniauth-multi-provider
@@ -0,0 +1 @@
1
+ # frozen_string_literal: true
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'mutations'
4
+
5
+ module Types
6
+ class MutationType < BaseObject
7
+ field :configure_identity_provider, mutation: Mutations::ConfigureIdentityProvider
8
+ field :create_identity_provider, mutation: Mutations::CreateIdentityProvider
9
+ field :set_saml_provider, mutation: Mutations::SetSamlProvider
10
+ end
11
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mutations
4
+ end
5
+
6
+ require_relative 'mutations/base_mutation'
7
+ require_relative 'mutations/configure_identity_provider'
8
+ require_relative 'mutations/create_identity_provider'
9
+ require_relative 'mutations/set_saml_provider'
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mutations
4
+ class BaseMutation < GraphQL::Schema::RelayClassicMutation
5
+ # This is used for generating payload types
6
+ object_class Types::BaseObject
7
+ # # This is used for return fields on the mutation's payload
8
+ # field_class Types::BaseField
9
+ # # This is used for generating the `input: { ... }` object type
10
+ # input_object_class Types::BaseInputObject
11
+
12
+ def return_data(data)
13
+ data.merge(errors: [])
14
+ end
15
+
16
+ def return_error(error)
17
+ error.merge(data: nil)
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mutations
4
+ class ConfigureIdentityProvider < BaseMutation
5
+ null false
6
+ argument :id, ID, required: true
7
+ # argument :provider, Types::IdentityProviderService, required: true
8
+ argument :sso_url, String, required: true
9
+ argument :sso_cert, String, required: true
10
+
11
+ field :identity_provider, Types::IdentityProvider, null: true
12
+ field :errors, [String], null: false
13
+
14
+ def resolve(id:, sso_url:, sso_cert:)
15
+ provider = Osso::Models::SamlProvider.find(id)
16
+ provider.update(
17
+ idp_cert: sso_cert,
18
+ idp_sso_target_url: sso_url,
19
+ )
20
+
21
+ return_data(identity_provider: provider)
22
+ # rescue StandardError => e
23
+ # return_error(errors: e.full_message)
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mutations
4
+ class CreateIdentityProvider < BaseMutation
5
+ null false
6
+ argument :enterprise_account_id, ID, required: true
7
+ argument :provider_service, Types::IdentityProviderService, required: true
8
+
9
+ field :identity_provider, Types::IdentityProvider, null: false
10
+ field :errors, [String], null: false
11
+
12
+ def resolve(enterprise_account_id:, provider_service:)
13
+ enterprise_account = Osso::Models::EnterpriseAccount.find(enterprise_account_id)
14
+ identity_provider = enterprise_account.saml_providers.create!(
15
+ provider: provider_service || 'OKTA',
16
+ domain: enterprise_account.domain,
17
+ )
18
+
19
+ return_data(identity_provider: identity_provider)
20
+ rescue StandardError => e
21
+ return_error(errors: e.full_message)
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mutations
4
+ class SetSamlProvider < BaseMutation
5
+ null false
6
+
7
+ argument :provider, Types::IdentityProviderService, required: true
8
+ argument :id, ID, required: true
9
+
10
+ field :identity_provider, Types::IdentityProvider, null: false
11
+ field :errors, [String], null: false
12
+
13
+ def resolve(provider:, id:)
14
+ saml_provider = Osso::Models::SamlProvider.find(id)
15
+ saml_provider.provider = provider
16
+ saml_provider.save!
17
+ {
18
+ saml_provider: saml_provider,
19
+ errors: [],
20
+ }
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Types
4
+ class QueryType < GraphQL::Schema::Object
5
+ # field :node, field: GraphQL::Relay::Node.field
6
+ field :enterprise_account, null: false, resolver: Resolvers::EnterpriseAccount do
7
+ argument :domain, String, required: true
8
+ end
9
+ field :enterprise_accounts, null: true, resolver: Resolvers::EnterpriseAccounts
10
+ field :oauth_clients, null: true, resolver: Resolvers::OAuthClients
11
+
12
+ field(
13
+ :identity_provider,
14
+ Types::IdentityProvider,
15
+ null: true,
16
+ resolve: ->(_obj, args, _context) { Osso::Models::SamlProvider.find(args[:id]) },
17
+ ) do
18
+ argument :id, ID, required: true
19
+ end
20
+
21
+ # field(
22
+ # :viewer,
23
+ # Types::User,
24
+ # null: true,
25
+ # resolve: ->(_obj, _args, context) { context[:current_user] },
26
+ # )
27
+ end
28
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Resolvers
4
+ end
5
+
6
+ require_relative 'resolvers/enterprise_account'
7
+ require_relative 'resolvers/enterprise_accounts'
8
+ require_relative 'resolvers/oauth_clients'
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Resolvers
4
+ class EnterpriseAccount < GraphQL::Schema::Resolver
5
+ type Types::EnterpriseAccount, null: false
6
+
7
+ def resolve(args)
8
+ return unless admin? || enterprise_authorized?(args[:domain])
9
+
10
+ Osso::Models::EnterpriseAccount.find_by(domain: args[:domain])
11
+ end
12
+
13
+ def admin?
14
+ context[:scope] == :admin
15
+ end
16
+
17
+ def enterprise_authorized?(domain)
18
+ context[:scope] == domain
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Resolvers
4
+ class EnterpriseAccounts < GraphQL::Schema::Resolver
5
+ type [Types::EnterpriseAccount], null: true
6
+
7
+ def resolve
8
+ return Osso::Models::EnterpriseAccount.all if context[:scope] == :admin
9
+
10
+ Array(Osso::Models::EnterpriseAccount.find_by(domain: context[:scope]))
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Resolvers
4
+ class OAuthClients < GraphQL::Schema::Resolver
5
+ type [Types::OAuthClient], null: true
6
+
7
+ def resolve
8
+ return Osso::Models::OAuthClient.all if context[:scope] == :admin
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'graphql'
4
+ require_relative 'types'
5
+ require_relative 'resolvers'
6
+ require_relative 'mutation'
7
+ require_relative 'query'
8
+
9
+ GraphQL::Relay::BaseConnection.register_connection_implementation(
10
+ ActiveRecord::Relation,
11
+ GraphQL::Relay::RelationConnection,
12
+ )
13
+
14
+ class OssoSchema < GraphQL::Schema
15
+ query Types::QueryType
16
+ mutation Types::MutationType
17
+ use GraphQL::Pagination::Connections
18
+
19
+ def self.id_from_object(object, _type_definition = nil, _query_ctx = nil)
20
+ GraphQL::Schema::UniqueWithinType.encode(object.class.name, object.id)
21
+ end
22
+
23
+ def self.object_from_id(id, _query_ctx = nil)
24
+ class_name, item_id = GraphQL::Schema::UniqueWithinType.decode(id)
25
+ Object.const_get(class_name).find(item_id)
26
+ end
27
+
28
+ def self.resolve_type(_type, obj, _ctx)
29
+ case obj
30
+ when Osso::Models::EnterpriseAccount
31
+ Types::EnterpriseAccount
32
+ when Osso::Models::SamlProvider
33
+ Types::IdentityProvider
34
+ else
35
+ raise("Unexpected object: #{obj}")
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Types
4
+ end
5
+
6
+ require_relative 'types/base_object'
7
+ require_relative 'types/base_enum'
8
+ require_relative 'types/identity_provider_service'
9
+ require_relative 'types/identity_provider'
10
+ require_relative 'types/enterprise_account'
11
+ require_relative 'types/oauth_client'
12
+ require_relative 'types/user'
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Types
4
+ class BaseEnum < GraphQL::Schema::Enum
5
+ end
6
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'graphql'
4
+
5
+ module Types
6
+ class BaseObject < GraphQL::Schema::Object
7
+ end
8
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'graphql'
4
+
5
+ module Types
6
+ class EnterpriseAccount < Types::BaseObject
7
+ description 'An Account for a company that wishes to use SAML via Osso'
8
+ implements GraphQL::Types::Relay::Node
9
+
10
+ global_id_field :gid
11
+ field :id, ID, null: false
12
+ field :name, String, null: false
13
+ field :domain, String, null: false
14
+ field :identity_providers, [Types::IdentityProvider], null: true
15
+ field :status, String, null: false
16
+
17
+ def name
18
+ object.domain.gsub('.com', '')
19
+ end
20
+
21
+ def status
22
+ 'active'
23
+ end
24
+
25
+ def identity_providers
26
+ object.saml_providers
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'graphql'
4
+
5
+ module Types
6
+ class IdentityProvider < Types::BaseObject
7
+ description 'Represents a SAML based IDP instance for an EnterpriseAccount'
8
+ implements GraphQL::Types::Relay::Node
9
+
10
+ global_id_field :gid
11
+ field :id, ID, null: false
12
+ field :enterprise_Account_id, ID, null: false
13
+ field :service, Types::IdentityProviderService, null: true
14
+ field :domain, String, null: false
15
+ field :acs_url, String, null: false
16
+ field :sso_url, String, null: true
17
+ field :sso_cert, String, null: true
18
+ field :configured, Boolean, null: false
19
+
20
+ def service
21
+ @object.provider
22
+ end
23
+
24
+ def configured
25
+ @object.idp_sso_target_url && @object.idp_cert
26
+ end
27
+
28
+ def sso_cert
29
+ @object.idp_cert
30
+ end
31
+
32
+ def sso_url
33
+ @object.idp_sso_target_url
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Types
4
+ class IdentityProviderService < BaseEnum
5
+ value('AZURE', 'Microsoft Azure Identity Provider', value: 'Osso::Models::AzureSamlProvider')
6
+ value('OKTA', 'Okta Identity Provider', value: 'Osso::Models::OktaSamlProvider')
7
+ end
8
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'graphql'
4
+
5
+ module Types
6
+ class OAuthClient < Types::BaseObject
7
+ description 'An OAuth client used to consume Osso SAML users'
8
+ implements GraphQL::Types::Relay::Node
9
+
10
+ global_id_field :gid
11
+ field :id, ID, null: false
12
+ field :name, String, null: false
13
+ field :client_id, String, null: false
14
+ field :client_secret, String, null: false
15
+ end
16
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'graphql'
4
+ require_relative 'base_object'
5
+ module Types
6
+ class User < Types::BaseObject
7
+ description 'A User of the application'
8
+
9
+ field :id, ID, null: false
10
+ field :name, String, null: true
11
+ end
12
+ end
@@ -14,7 +14,7 @@ module Osso
14
14
  get '/' do
15
15
  admin_protected!
16
16
 
17
- erb :'public/index'
17
+ erb :admin
18
18
  end
19
19
 
20
20
  get '/enterprise' do
@@ -26,10 +26,6 @@ module Osso
26
26
  get '/enterprise/:domain' do
27
27
  enterprise_protected!(params[:domain])
28
28
 
29
- @enterprise = Models::EnterpriseAccount.where(
30
- domain: params[:domain],
31
- ).first_or_create
32
-
33
29
  erb :admin
34
30
  end
35
31
 
@@ -24,7 +24,8 @@ module Osso
24
24
  redirect "/auth/saml/#{@enterprise.provider.id}"
25
25
  end
26
26
 
27
- erb :multiple_providers
27
+ # TODO: multiple provider support
28
+ # erb :multiple_providers
28
29
 
29
30
  rescue Rack::OAuth2::Server::Authorize::BadRequest => e
30
31
  @error = e
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Osso
4
- VERSION = '0.0.3'
4
+ VERSION = '0.0.3.1'
5
5
  end
@@ -9,6 +9,7 @@ describe Osso::Admin do
9
9
  before do
10
10
  ENV['JWT_URL'] = jwt_url
11
11
  ENV['JWT_HMAC_SECRET'] = jwt_hmac_secret
12
+ described_class.set(:views, spec_views)
12
13
  end
13
14
 
14
15
  describe 'get /admin' do
@@ -40,6 +40,10 @@ module RSpecMixin
40
40
  def last_json_response
41
41
  JSON.parse(last_response.body, symbolize_names: true)
42
42
  end
43
+
44
+ def spec_views
45
+ File.dirname(__FILE__) + '/support/views'
46
+ end
43
47
  end
44
48
 
45
49
  RSpec.configure do |config|
@@ -0,0 +1,5 @@
1
+ <%#
2
+ NB: this file exists so that the admin routes have something to render in spec.
3
+ In real-world usage, those routes render an index.html file that includes the
4
+ React app.
5
+ %>
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: osso
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: 0.0.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sam Bauch
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-07-04 00:00:00.000000000 Z
11
+ date: 2020-07-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -218,6 +218,7 @@ files:
218
218
  - bin/console
219
219
  - bin/setup
220
220
  - config/database.yml
221
+ - db/schema.rb
221
222
  - lib/.DS_Store
222
223
  - lib/osso.rb
223
224
  - lib/osso/Rakefile
@@ -237,6 +238,27 @@ files:
237
238
  - lib/osso/db/migrate/20200502135008_add_oauth_client_id_to_enterprise_account.rb
238
239
  - lib/osso/db/migrate/20200601131227_drop_null_constraint_from_saml_providers_provider.rb
239
240
  - lib/osso/db/schema.rb
241
+ - lib/osso/graphql/.DS_Store
242
+ - lib/osso/graphql/mutation.rb
243
+ - lib/osso/graphql/mutations.rb
244
+ - lib/osso/graphql/mutations/base_mutation.rb
245
+ - lib/osso/graphql/mutations/configure_identity_provider.rb
246
+ - lib/osso/graphql/mutations/create_identity_provider.rb
247
+ - lib/osso/graphql/mutations/set_saml_provider.rb
248
+ - lib/osso/graphql/query.rb
249
+ - lib/osso/graphql/resolvers.rb
250
+ - lib/osso/graphql/resolvers/enterprise_account.rb
251
+ - lib/osso/graphql/resolvers/enterprise_accounts.rb
252
+ - lib/osso/graphql/resolvers/oauth_clients.rb
253
+ - lib/osso/graphql/schema.rb
254
+ - lib/osso/graphql/types.rb
255
+ - lib/osso/graphql/types/base_enum.rb
256
+ - lib/osso/graphql/types/base_object.rb
257
+ - lib/osso/graphql/types/enterprise_account.rb
258
+ - lib/osso/graphql/types/identity_provider.rb
259
+ - lib/osso/graphql/types/identity_provider_service.rb
260
+ - lib/osso/graphql/types/oauth_client.rb
261
+ - lib/osso/graphql/types/user.rb
240
262
  - lib/osso/helpers/auth.rb
241
263
  - lib/osso/helpers/helpers.rb
242
264
  - lib/osso/lib/app_config.rb
@@ -275,7 +297,7 @@ files:
275
297
  - spec/routes/auth_spec.rb
276
298
  - spec/routes/oauth_spec.rb
277
299
  - spec/spec_helper.rb
278
- - spec/support/vcr_cassettes/okta_saml_callback.yml
300
+ - spec/support/views/public/index.erb
279
301
  homepage: https://github.com/enterprise-oss/osso-rb
280
302
  licenses:
281
303
  - MIT
@@ -1,59 +0,0 @@
1
- ---
2
- http_interactions:
3
- - request:
4
- method: post
5
- uri: http://localhost:9292/auth/saml/:uuid/callback
6
- body:
7
- encoding: ASCII-8BIT
8
- string: SAMLResponse=PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48c2FtbDJwOlJlc3BvbnNlIERlc3RpbmF0aW9uPSJodHRwOi8vbG9jYWxob3N0OjkyOTIvYXV0aC9zYW1sLzY4ZTkwNTdjLTQ2MmYtNDM3Zi04NDRkLTk4NzI0ZmVmOWQ0My9jYWxsYmFjayIgSUQ9ImlkMTgyOTI5NzEwODgzMjU2OTIyMDE3Nzc0NDUyIiBJblJlc3BvbnNlVG89Il81NGM0ZjE5MS0xYmY3LTRiMDItOTZlYS0zNGQ4NTU1YWVjYWEiIElzc3VlSW5zdGFudD0iMjAyMC0wNC0xMVQxNjozMjoxOC40MjRaIiBWZXJzaW9uPSIyLjAiIHhtbG5zOnNhbWwycD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOnByb3RvY29sIiB4bWxuczp4cz0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEiPjxzYW1sMjpJc3N1ZXIgRm9ybWF0PSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6bmFtZWlkLWZvcm1hdDplbnRpdHkiIHhtbG5zOnNhbWwyPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXNzZXJ0aW9uIj5odHRwOi8vd3d3Lm9rdGEuY29tL2V4azUxMzI2YjNVMTk0MUhmNHg2PC9zYW1sMjpJc3N1ZXI%2BPGRzOlNpZ25hdHVyZSB4bWxuczpkcz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnIyI%2BPGRzOlNpZ25lZEluZm8%2BPGRzOkNhbm9uaWNhbGl6YXRpb25NZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzEwL3htbC1leGMtYzE0biMiLz48ZHM6U2lnbmF0dXJlTWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8wNC94bWxkc2lnLW1vcmUjcnNhLXNoYTI1NiIvPjxkczpSZWZlcmVuY2UgVVJJPSIjaWQxODI5Mjk3MTA4ODMyNTY5MjIwMTc3NzQ0NTIiPjxkczpUcmFuc2Zvcm1zPjxkczpUcmFuc2Zvcm0gQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjZW52ZWxvcGVkLXNpZ25hdHVyZSIvPjxkczpUcmFuc2Zvcm0gQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzEwL3htbC1leGMtYzE0biMiPjxlYzpJbmNsdXNpdmVOYW1lc3BhY2VzIFByZWZpeExpc3Q9InhzIiB4bWxuczplYz0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8xMC94bWwtZXhjLWMxNG4jIi8%2BPC9kczpUcmFuc2Zvcm0%2BPC9kczpUcmFuc2Zvcm1zPjxkczpEaWdlc3RNZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGVuYyNzaGEyNTYiLz48ZHM6RGlnZXN0VmFsdWU%2BZEpWb0Z6Y01zTjV3TmNJRWtJWkxqb1JjWDRpMzVYekI3RGg4ZE0wU1pMZz08L2RzOkRpZ2VzdFZhbHVlPjwvZHM6UmVmZXJlbmNlPjwvZHM6U2lnbmVkSW5mbz48ZHM6U2lnbmF0dXJlVmFsdWU%2BdnFES0RhVk8vYlRkRW5hNlMwNHdQV0hicXdNQ1JHdStjRVRxOE9FR2pzK1drTUVBQVYzeEFnNitUZTB2Z0haSjlTWnhwSW9iWEdadVQvVFFnbnAyczNMM1FvaUx5UVFwOExXQnl0b1A0UWhSV1dNY1N3UHlUYWI5TmRUT1RWeXJBckJxZHFFVXN0M0Z5cVpLSVFlbUpocWU4ckk4cmJwQTI2YUFqc2xNZkRIZHNMTFlFMjUwTXpPdG9wdVppNHpvdDZDSTl1NFVRK2dsOWdqSUJyai9sZ3BQME5mZ3RXM01QNVRQOGxsaEpmVXJ4MjhLWGZjSHpOa1RDU1BxVTRwLzZCRTV5dm5QcmJVTE1EWWZQWG5aV0t5d0JpRkNyaTgydmNOdElXVHUvcmkrejNRT0p6dFRiTkdKU0ZjWHdkWHpheG8xdS9SN3ZUQlJPOCtuZEFsL1VBPT08L2RzOlNpZ25hdHVyZVZhbHVlPjxkczpLZXlJbmZvPjxkczpYNTA5RGF0YT48ZHM6WDUwOUNlcnRpZmljYXRlPk1JSURwRENDQW95Z0F3SUJBZ0lHQVhFaUQ0TGxNQTBHQ1NxR1NJYjNEUUVCQ3dVQU1JR1NNUXN3Q1FZRFZRUUdFd0pWVXpFVE1CRUcKQTFVRUNBd0tRMkZzYVdadmNtNXBZVEVXTUJRR0ExVUVCd3dOVTJGdUlFWnlZVzVqYVhOamJ6RU5NQXNHQTFVRUNnd0VUMnQwWVRFVQpNQklHQTFVRUN3d0xVMU5QVUhKdmRtbGtaWEl4RXpBUkJnTlZCQU1NQ21SbGRpMHhOakl3TWpReEhEQWFCZ2txaGtpRzl3MEJDUUVXCkRXbHVabTlBYjJ0MFlTNWpiMjB3SGhjTk1qQXdNekk0TVRZMU1UVTBXaGNOTXpBd016STRNVFkxTWpVMFdqQ0JrakVMTUFrR0ExVUUKQmhNQ1ZWTXhFekFSQmdOVkJBZ01Da05oYkdsbWIzSnVhV0V4RmpBVUJnTlZCQWNNRFZOaGJpQkdjbUZ1WTJselkyOHhEVEFMQmdOVgpCQW9NQkU5cmRHRXhGREFTQmdOVkJBc01DMU5UVDFCeWIzWnBaR1Z5TVJNd0VRWURWUVFEREFwa1pYWXRNVFl5TURJME1Sd3dHZ1lKCktvWklodmNOQVFrQkZnMXBibVp2UUc5cmRHRXVZMjl0TUlJQklqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FROEFNSUlCQ2dLQ0FRRUEKd3NuUDRVVGZ2M2J4UjVKaDBhdDUxRHFqaitmS3hGem56RlczWEE1TmJGMlNsUkxqZVljdmozKzQ3VEMwZVA2eE9zTFdmbnZkbng0dgpkZDlVZm43akRDbzVwTDNKeWtNVkVoMkkwc3pGM1JMQythNTMyQXJjd2dVOVB4NDgrcldWd1BrQVNTN2w0TkhBTTQrZ09CSEpNUXQyCkFNb2hQVDBrVTQxUDhCRVB6ZndoTnlpRVhSNjZKTlpJSlVFOGZNM1ZwZ254bS9WU3dZekpmME5mT3lmeHY4SmN6RjB6a0RicEU3VGsKM1d3L1BGRkxvTXhXemFuV0dKUStibG5odjZVVjZINGZjZkFiY3dBcGxPZElWSGpTMmdoWUJ2WU5HYWh1RnhqaWEwKzZjc3laR3J0OApINFhtUjVEcitqWFk1SzFiMVZPQTBrMTkvRkNuSEhOL3NtbjI1d0lEQVFBQk1BMEdDU3FHU0liM0RRRUJDd1VBQTRJQkFRQmdEOU5FCjRPQ3VSMSt2dWNWOFMxVDZYWElMMmhCN2JYQkFaRVZIWjFhRXJSemt0Z1hBTWdWd0cyNjd2SWtENVZPWEJpVHk5eU5VNUxLNkczazIKemV3VTE5MHNMMWRNZnlQbm9WWnluOTRudndlOUErb24wdG1aZG1rMDB4aXJLazNGSmRhY25aTkU5RGwvYWZJcmNOZjZ4QW0wV3NVOQprYk1pUnd3dmpPNFRBaXlnRFF6YnJSQzhaZm1UM2hwQmEzYVRVekFjY3J2RVFjZ2FyTGs0cjdValhQN2EybUNOM1VJSWgrc25OMk1zCnZYSEwwcjZmTTN4Ym5peis1bGxlV3RQRnc3M3l5U0JjOHpua1daNFRuOExoMHI2bzVuQ1JZYnIyUkVVQjdaSWZpSXlCYlp4SXA0a3YKYStoYWJiblFERmlOVnpFZDhPUFhIaDRFcUx4T1BEUlc8L2RzOlg1MDlDZXJ0aWZpY2F0ZT48L2RzOlg1MDlEYXRhPjwvZHM6S2V5SW5mbz48L2RzOlNpZ25hdHVyZT48c2FtbDJwOlN0YXR1cyB4bWxuczpzYW1sMnA9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpwcm90b2NvbCI%2BPHNhbWwycDpTdGF0dXNDb2RlIFZhbHVlPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6c3RhdHVzOlN1Y2Nlc3MiLz48L3NhbWwycDpTdGF0dXM%2BPHNhbWwyOkFzc2VydGlvbiBJRD0iaWQxODI5Mjk3MTA4ODQyMDg3MzY4MzU5Njg1MiIgSXNzdWVJbnN0YW50PSIyMDIwLTA0LTExVDE2OjMyOjE4LjQyNFoiIFZlcnNpb249IjIuMCIgeG1sbnM6c2FtbDI9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphc3NlcnRpb24iIHhtbG5zOnhzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYSI%2BPHNhbWwyOklzc3VlciBGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpuYW1laWQtZm9ybWF0OmVudGl0eSIgeG1sbnM6c2FtbDI9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphc3NlcnRpb24iPmh0dHA6Ly93d3cub2t0YS5jb20vZXhrNTEzMjZiM1UxOTQxSGY0eDY8L3NhbWwyOklzc3Vlcj48ZHM6U2lnbmF0dXJlIHhtbG5zOmRzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjIj48ZHM6U2lnbmVkSW5mbz48ZHM6Q2Fub25pY2FsaXphdGlvbk1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMTAveG1sLWV4Yy1jMTRuIyIvPjxkczpTaWduYXR1cmVNZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGRzaWctbW9yZSNyc2Etc2hhMjU2Ii8%2BPGRzOlJlZmVyZW5jZSBVUkk9IiNpZDE4MjkyOTcxMDg4NDIwODczNjgzNTk2ODUyIj48ZHM6VHJhbnNmb3Jtcz48ZHM6VHJhbnNmb3JtIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnI2VudmVsb3BlZC1zaWduYXR1cmUiLz48ZHM6VHJhbnNmb3JtIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8xMC94bWwtZXhjLWMxNG4jIj48ZWM6SW5jbHVzaXZlTmFtZXNwYWNlcyBQcmVmaXhMaXN0PSJ4cyIgeG1sbnM6ZWM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMTAveG1sLWV4Yy1jMTRuIyIvPjwvZHM6VHJhbnNmb3JtPjwvZHM6VHJhbnNmb3Jtcz48ZHM6RGlnZXN0TWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8wNC94bWxlbmMjc2hhMjU2Ii8%2BPGRzOkRpZ2VzdFZhbHVlPjJ0ekZZTktpNTRBVTV0L3R1UUZJS0Q2aHdlWlRqc1FYSTFzWmFreUE3Y0k9PC9kczpEaWdlc3RWYWx1ZT48L2RzOlJlZmVyZW5jZT48L2RzOlNpZ25lZEluZm8%2BPGRzOlNpZ25hdHVyZVZhbHVlPkxJakdOK0RRRExldUE4clF4Q0Yvblp2c3ZCM0VLWjFaUHA3cUU4ZXZZVGY3R01lWGlVa3hXaTkvbGQ1d2p2YWFjaG5ZOXBMV0FObStFRGo4aUhGcnI1a1dINWJZZUI3b0JybVY1NDlTMmpJWW5pbmJBT055T2d3TVRpWXJUQkZ3czg1dHVYb2h0N3JBcVRrWVBjY2FnanJyQW5sYnNyK3BVb3FZYzZMOHZUczR3d2x0MzJiVXF0TThCOG5XS1NmWFBMTEo3VlVGeFhFQm1iV1JsbjJBcjgyN2dhT1dGemoyQXcxWGw2bUZFTllscDVib3hoSlVkRHJDMHNkaW01YXZmQU5jSXFrMjJFQkc5aFRZNUdnbzlrTjBSYkgvYm15MTc5K0lDaWo5R2hlZ2xmWGVpV3pVaWlLUkF1WS8zbEVLZnBYQ2lpaVRVbGdUcHFxdzhnNjNvZz09PC9kczpTaWduYXR1cmVWYWx1ZT48ZHM6S2V5SW5mbz48ZHM6WDUwOURhdGE%2BPGRzOlg1MDlDZXJ0aWZpY2F0ZT5NSUlEcERDQ0FveWdBd0lCQWdJR0FYRWlENExsTUEwR0NTcUdTSWIzRFFFQkN3VUFNSUdTTVFzd0NRWURWUVFHRXdKVlV6RVRNQkVHCkExVUVDQXdLUTJGc2FXWnZjbTVwWVRFV01CUUdBMVVFQnd3TlUyRnVJRVp5WVc1amFYTmpiekVOTUFzR0ExVUVDZ3dFVDJ0MFlURVUKTUJJR0ExVUVDd3dMVTFOUFVISnZkbWxrWlhJeEV6QVJCZ05WQkFNTUNtUmxkaTB4TmpJd01qUXhIREFhQmdrcWhraUc5dzBCQ1FFVwpEV2x1Wm05QWIydDBZUzVqYjIwd0hoY05NakF3TXpJNE1UWTFNVFUwV2hjTk16QXdNekk0TVRZMU1qVTBXakNCa2pFTE1Ba0dBMVVFCkJoTUNWVk14RXpBUkJnTlZCQWdNQ2tOaGJHbG1iM0p1YVdFeEZqQVVCZ05WQkFjTURWTmhiaUJHY21GdVkybHpZMjh4RFRBTEJnTlYKQkFvTUJFOXJkR0V4RkRBU0JnTlZCQXNNQzFOVFQxQnliM1pwWkdWeU1STXdFUVlEVlFRRERBcGtaWFl0TVRZeU1ESTBNUnd3R2dZSgpLb1pJaHZjTkFRa0JGZzFwYm1adlFHOXJkR0V1WTI5dE1JSUJJakFOQmdrcWhraUc5dzBCQVFFRkFBT0NBUThBTUlJQkNnS0NBUUVBCndzblA0VVRmdjNieFI1SmgwYXQ1MURxamorZkt4RnpuekZXM1hBNU5iRjJTbFJMamVZY3ZqMys0N1RDMGVQNnhPc0xXZm52ZG54NHYKZGQ5VWZuN2pEQ281cEwzSnlrTVZFaDJJMHN6RjNSTEMrYTUzMkFyY3dnVTlQeDQ4K3JXVndQa0FTUzdsNE5IQU00K2dPQkhKTVF0MgpBTW9oUFQwa1U0MVA4QkVQemZ3aE55aUVYUjY2Sk5aSUpVRThmTTNWcGdueG0vVlN3WXpKZjBOZk95Znh2OEpjekYwemtEYnBFN1RrCjNXdy9QRkZMb014V3phbldHSlErYmxuaHY2VVY2SDRmY2ZBYmN3QXBsT2RJVkhqUzJnaFlCdllOR2FodUZ4amlhMCs2Y3N5WkdydDgKSDRYbVI1RHIralhZNUsxYjFWT0EwazE5L0ZDbkhITi9zbW4yNXdJREFRQUJNQTBHQ1NxR1NJYjNEUUVCQ3dVQUE0SUJBUUJnRDlORQo0T0N1UjErdnVjVjhTMVQ2WFhJTDJoQjdiWEJBWkVWSFoxYUVyUnprdGdYQU1nVndHMjY3dklrRDVWT1hCaVR5OXlOVTVMSzZHM2syCnpld1UxOTBzTDFkTWZ5UG5vVlp5bjk0bnZ3ZTlBK29uMHRtWmRtazAweGlyS2szRkpkYWNuWk5FOURsL2FmSXJjTmY2eEFtMFdzVTkKa2JNaVJ3d3ZqTzRUQWl5Z0RRemJyUkM4WmZtVDNocEJhM2FUVXpBY2NydkVRY2dhckxrNHI3VWpYUDdhMm1DTjNVSUloK3NuTjJNcwp2WEhMMHI2Zk0zeGJuaXorNWxsZVd0UEZ3NzN5eVNCYzh6bmtXWjRUbjhMaDByNm81bkNSWWJyMlJFVUI3WklmaUl5QmJaeElwNGt2CmEraGFiYm5RREZpTlZ6RWQ4T1BYSGg0RXFMeE9QRFJXPC9kczpYNTA5Q2VydGlmaWNhdGU%2BPC9kczpYNTA5RGF0YT48L2RzOktleUluZm8%2BPC9kczpTaWduYXR1cmU%2BPHNhbWwyOlN1YmplY3QgeG1sbnM6c2FtbDI9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphc3NlcnRpb24iPjxzYW1sMjpOYW1lSUQgRm9ybWF0PSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoxLjE6bmFtZWlkLWZvcm1hdDp1bnNwZWNpZmllZCI%2Bc2FtQHZjYXJkbWUuY29tPC9zYW1sMjpOYW1lSUQ%2BPHNhbWwyOlN1YmplY3RDb25maXJtYXRpb24gTWV0aG9kPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6Y206YmVhcmVyIj48c2FtbDI6U3ViamVjdENvbmZpcm1hdGlvbkRhdGEgSW5SZXNwb25zZVRvPSJfNTRjNGYxOTEtMWJmNy00YjAyLTk2ZWEtMzRkODU1NWFlY2FhIiBOb3RPbk9yQWZ0ZXI9IjIwMjAtMDQtMTFUMTY6Mzc6MTguNDI0WiIgUmVjaXBpZW50PSJodHRwOi8vbG9jYWxob3N0OjkyOTIvYXV0aC9zYW1sLzY4ZTkwNTdjLTQ2MmYtNDM3Zi04NDRkLTk4NzI0ZmVmOWQ0My9jYWxsYmFjayIvPjwvc2FtbDI6U3ViamVjdENvbmZpcm1hdGlvbj48L3NhbWwyOlN1YmplY3Q%2BPHNhbWwyOkNvbmRpdGlvbnMgTm90QmVmb3JlPSIyMDIwLTA0LTExVDE2OjI3OjE4LjQyNFoiIE5vdE9uT3JBZnRlcj0iMjAyMC0wNC0xMVQxNjozNzoxOC40MjRaIiB4bWxuczpzYW1sMj0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFzc2VydGlvbiI%2BPHNhbWwyOkF1ZGllbmNlUmVzdHJpY3Rpb24%2BPHNhbWwyOkF1ZGllbmNlPjY4ZTkwNTdjLTQ2MmYtNDM3Zi04NDRkLTk4NzI0ZmVmOWQ0Mzwvc2FtbDI6QXVkaWVuY2U%2BPC9zYW1sMjpBdWRpZW5jZVJlc3RyaWN0aW9uPjwvc2FtbDI6Q29uZGl0aW9ucz48c2FtbDI6QXV0aG5TdGF0ZW1lbnQgQXV0aG5JbnN0YW50PSIyMDIwLTA0LTExVDE2OjMyOjE4LjQyNFoiIFNlc3Npb25JbmRleD0iXzU0YzRmMTkxLTFiZjctNGIwMi05NmVhLTM0ZDg1NTVhZWNhYSIgeG1sbnM6c2FtbDI9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphc3NlcnRpb24iPjxzYW1sMjpBdXRobkNvbnRleHQ%2BPHNhbWwyOkF1dGhuQ29udGV4dENsYXNzUmVmPnVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphYzpjbGFzc2VzOlBhc3N3b3JkUHJvdGVjdGVkVHJhbnNwb3J0PC9zYW1sMjpBdXRobkNvbnRleHRDbGFzc1JlZj48L3NhbWwyOkF1dGhuQ29udGV4dD48L3NhbWwyOkF1dGhuU3RhdGVtZW50PjxzYW1sMjpBdHRyaWJ1dGVTdGF0ZW1lbnQgeG1sbnM6c2FtbDI9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphc3NlcnRpb24iPjxzYW1sMjpBdHRyaWJ1dGUgTmFtZT0iZW1haWwiIE5hbWVGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphdHRybmFtZS1mb3JtYXQ6dW5zcGVjaWZpZWQiPjxzYW1sMjpBdHRyaWJ1dGVWYWx1ZSB4bWxuczp4cz0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEiIHhtbG5zOnhzaT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEtaW5zdGFuY2UiIHhzaTp0eXBlPSJ4czpzdHJpbmciPnNhbUB2Y2FyZG1lLmNvbTwvc2FtbDI6QXR0cmlidXRlVmFsdWU%2BPC9zYW1sMjpBdHRyaWJ1dGU%2BPHNhbWwyOkF0dHJpYnV0ZSBOYW1lPSJpZCIgTmFtZUZvcm1hdD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmF0dHJuYW1lLWZvcm1hdDp1bnNwZWNpZmllZCI%2BPHNhbWwyOkF0dHJpYnV0ZVZhbHVlIHhtbG5zOnhzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYSIgeG1sbnM6eHNpPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYS1pbnN0YW5jZSIgeHNpOnR5cGU9InhzOnN0cmluZyI%2BMDB1NTEwd2pwNDRmdDNEUm80eDY8L3NhbWwyOkF0dHJpYnV0ZVZhbHVlPjwvc2FtbDI6QXR0cmlidXRlPjwvc2FtbDI6QXR0cmlidXRlU3RhdGVtZW50Pjwvc2FtbDI6QXNzZXJ0aW9uPjwvc2FtbDJwOlJlc3BvbnNlPg%3D%3D&RelayState=
9
- headers:
10
- Host:
11
- - localhost:9292
12
- Connection:
13
- - keep-alive
14
- Cache-Control:
15
- - max-age=0
16
- Upgrade-Insecure-Requests:
17
- - '1'
18
- Origin:
19
- - 'null'
20
- User-Agent:
21
- - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML,
22
- like Gecko) Chrome/80.0.3987.163 Safari/537.36
23
- Sec-Fetch-Dest:
24
- - document
25
- Accept:
26
- - text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
27
- Sec-Fetch-Site:
28
- - cross-site
29
- Sec-Fetch-Mode:
30
- - navigate
31
- Accept-Encoding:
32
- - gzip, deflate, br
33
- Accept-Language:
34
- - en-US,en;q=0.9,fr;q=0.8
35
- Cookie:
36
- - _vcardme-api_session=YmdCdkxYdTlLWkhhWWNnZTQ5Yjdxc0gweWJjKys4TUtSWWVBd3c1U2tUT2E1b2t0MFQxd3lZQTJwSVcyZVFCOHZxaHRtTVZjVGYyay9ReVc0WElxR0hlVGM0MW5qQUxVZE8zYytTaXBlcDZraWRLSllkYjlvYUNMVkx6S3Aremh4ODB5eVBzMmFwS3dHOHNZaFFBYnJnPT0tLVRKUEdSMFV1SGRCZSt1LzNiYjRSZnc9PQ%3D%3D--4e212108b448172e97b0e205f75976780e843bd7;
37
- mp_321622acf650be9cac3978565e73be1a_mixpanel=%7B%22distinct_id%22%3A%20%2217122c6e1421dd-0fd311181d4582-396f7f07-384000-17122c6e14935d%22%2C%22%24device_id%22%3A%20%2217122c6e1421dd-0fd311181d4582-396f7f07-384000-17122c6e14935d%22%2C%22%24initial_referrer%22%3A%20%22%24direct%22%2C%22%24initial_referring_domain%22%3A%20%22%24direct%22%7D;
38
- rack.session=BAh7CUkiD3Nlc3Npb25faWQGOgZFVEkiRTdkYWNhMWRhNjc3MTY4OTY0ZWVj%0AZjg2YmIyOTM1M2EzOTcxZWIzN2ZjMzAxZGUxNmIyZGNiNDdhMGU0ZDdiMmQG%0AOwBGSSINc2FtbF91aWQGOwBUSSIUc2FtQHZjYXJkbWUuY29tBjsAVEkiF3Nh%0AbWxfc2Vzc2lvbl9pbmRleAY7AFRJIipfOTBjYjc4NjItYTc5My00M2ZjLWFh%0AOWItZDdiMjY0ZWE4NmM2BjsAVEkiFG9tbmlhdXRoLnBhcmFtcwY7AFR7AA%3D%3D%0A--429d063c69188264011d1384cbf24a4b486d2d3a
39
- Version:
40
- - HTTP/1.1
41
- Content-Type:
42
- - application/x-www-form-urlencoded
43
- Content-Length:
44
- - '10749'
45
- response:
46
- status:
47
- code: 200
48
- message: null
49
- headers:
50
- Content-Type:
51
- - application/json
52
- Content-Length:
53
- - '112'
54
- body:
55
- encoding: UTF-8
56
- string: '{"user":{"id":"2077ee4f-9e7d-46c2-bbf3-98c49c312281","email":"sam@vcardme.com","idp_id":"00u510wjp44ft3DRo4x6"}}'
57
- http_version: null
58
- recorded_at: Sat, 11 Apr 2020 16:32:18 GMT
59
- recorded_with: VCR 5.1.0