osso 0.0.3 → 0.0.3.1

Sign up to get free protection for your applications and to get access to all the features.
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