osso 0.0.3.3 → 0.0.3.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -1
  3. data/Gemfile.lock +3 -1
  4. data/db/schema.rb +1 -0
  5. data/lib/osso.rb +1 -0
  6. data/lib/osso/graphql/mutation.rb +15 -0
  7. data/lib/osso/graphql/mutations.rb +10 -0
  8. data/lib/osso/graphql/mutations/base_mutation.rb +24 -0
  9. data/lib/osso/graphql/mutations/configure_identity_provider.rb +30 -0
  10. data/lib/osso/graphql/mutations/create_identity_provider.rb +28 -0
  11. data/lib/osso/graphql/mutations/set_saml_provider.rb +27 -0
  12. data/lib/osso/graphql/query.rb +25 -0
  13. data/lib/osso/graphql/resolvers.rb +12 -0
  14. data/lib/osso/graphql/resolvers/enterprise_account.rb +25 -0
  15. data/lib/osso/graphql/resolvers/enterprise_accounts.rb +17 -0
  16. data/lib/osso/graphql/resolvers/oauth_clients.rb +15 -0
  17. data/lib/osso/graphql/schema.rb +42 -0
  18. data/lib/osso/graphql/types.rb +14 -0
  19. data/lib/osso/graphql/types/base_enum.rb +10 -0
  20. data/lib/osso/graphql/types/base_object.rb +12 -0
  21. data/lib/osso/graphql/types/enterprise_account.rb +33 -0
  22. data/lib/osso/graphql/types/identity_provider.rb +40 -0
  23. data/lib/osso/graphql/types/identity_provider_service.rb +12 -0
  24. data/lib/osso/graphql/types/oauth_client.rb +20 -0
  25. data/lib/osso/graphql/types/user.rb +17 -0
  26. data/lib/osso/helpers/auth.rb +51 -49
  27. data/lib/osso/helpers/helpers.rb +3 -1
  28. data/lib/osso/routes/admin.rb +1 -5
  29. data/lib/osso/routes/oauth.rb +2 -1
  30. data/lib/osso/version.rb +1 -1
  31. data/osso-rb.gemspec +1 -0
  32. data/spec/routes/admin_spec.rb +1 -0
  33. data/spec/spec_helper.rb +4 -0
  34. data/spec/support/views/admin.erb +5 -0
  35. metadata +38 -2
  36. data/spec/support/vcr_cassettes/okta_saml_callback.yml +0 -59
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6b1788a4933cd6a5e4fef31d6e92c8c63cc9fd9e6cf6fb838b71f65161e9845b
4
- data.tar.gz: 82508ceb2a7af276c64a6a1ae2ba3febc118e71d4c89fcb194db1d0ec35c686a
3
+ metadata.gz: 7da5a91a5eda62f96a11772d72afc0e1477e586218233466d2dcd37a89e2aabf
4
+ data.tar.gz: 839e333e245142b6fc29738242293f205bae28a4e768e9f35b87d0572708f9f4
5
5
  SHA512:
6
- metadata.gz: 363d13744052824deea9a26478549daac1dfd49d850577329f6c87efbe4f0de23d1aca40ee1aa39b8cd4324503267e5e81b192086532db796c122ca79e2a9c14
7
- data.tar.gz: 9313221346428718b1d06b2ef10c9dcb453cd02716f4ccfaa956e956d19b420290c0818215cebba204aa843acb5a668dfdb6d11e285de1f3f847a786968c78a0
6
+ metadata.gz: 8ede35265fed16afeb20d28608c8b74053ed5e5f11e6a393df87395f5d0ec30f7a58a84834a99bd6c8a16cbe21eaee5f22a4fdec4c0bbf3a7e5d49a760b53f85
7
+ data.tar.gz: 3c37e0256ee1758ce915c25ce3d7b83e070ef2cadba85c41649c13b3ede1fa0371eeb10757ce990e47a8669edc3b65424749cf62b5fa8d46f21a132285302e08
data/.gitignore CHANGED
@@ -7,4 +7,4 @@
7
7
  /spec/reports/
8
8
  /tmp/
9
9
  *.gem
10
- *.DS_Store
10
+ .DS_Store
@@ -1,8 +1,9 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- osso (0.0.3.3)
4
+ osso (0.0.3.4)
5
5
  activesupport (>= 6.0.3.2)
6
+ graphql
6
7
  jwt
7
8
  omniauth-multi-provider
8
9
  omniauth-saml
@@ -48,6 +49,7 @@ GEM
48
49
  activesupport (>= 5.0.0)
49
50
  faker (2.13.0)
50
51
  i18n (>= 1.6, < 2)
52
+ graphql (1.11.1)
51
53
  hashdiff (1.0.1)
52
54
  hashie (4.1.0)
53
55
  httpclient (2.8.3)
@@ -0,0 +1 @@
1
+ # frozen_string_literal: true
@@ -6,4 +6,5 @@ module Osso
6
6
  require_relative 'osso/lib/oauth2_token'
7
7
  require_relative 'osso/models/models'
8
8
  require_relative 'osso/routes/routes'
9
+ require_relative 'osso/graphql/schema'
9
10
  end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'mutations'
4
+
5
+ module Osso
6
+ module GraphQL
7
+ module Types
8
+ class MutationType < BaseObject
9
+ field :configure_identity_provider, mutation: Mutations::ConfigureIdentityProvider
10
+ field :create_identity_provider, mutation: Mutations::CreateIdentityProvider
11
+ field :set_saml_provider, mutation: Mutations::SetSamlProvider
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+ module Osso
3
+ module Mutations
4
+ end
5
+ end
6
+
7
+ require_relative 'mutations/base_mutation'
8
+ require_relative 'mutations/configure_identity_provider'
9
+ require_relative 'mutations/create_identity_provider'
10
+ require_relative 'mutations/set_saml_provider'
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Osso
4
+ module GraphQL
5
+ module Mutations
6
+ class BaseMutation < ::GraphQL::Schema::RelayClassicMutation
7
+ # This is used for generating payload types
8
+ object_class Types::BaseObject
9
+ # # This is used for return fields on the mutation's payload
10
+ # field_class Types::BaseField
11
+ # # This is used for generating the `input: { ... }` object type
12
+ # input_object_class Types::BaseInputObject
13
+
14
+ def return_data(data)
15
+ data.merge(errors: [])
16
+ end
17
+
18
+ def return_error(error)
19
+ error.merge(data: nil)
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Osso
4
+ module GraphQL
5
+ module Mutations
6
+ class ConfigureIdentityProvider < BaseMutation
7
+ null false
8
+ argument :id, ID, required: true
9
+ argument :service, Types::IdentityProviderService, required: true
10
+ argument :sso_url, String, required: true
11
+ argument :sso_cert, String, required: true
12
+
13
+ field :identity_provider, Types::IdentityProvider, null: true
14
+ field :errors, [String], null: false
15
+
16
+ def resolve(id:, sso_url:, sso_cert:, service:)
17
+ provider = Osso::Models::SamlProvider.find(id)
18
+ provider.update(
19
+ idp_cert: sso_cert,
20
+ idp_sso_target_url: sso_url,
21
+ )
22
+
23
+ return_data(identity_provider: provider)
24
+ # rescue StandardError => e
25
+ # return_error(errors: e.full_message)
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Osso
4
+ module GraphQL
5
+ module Mutations
6
+ class CreateIdentityProvider < BaseMutation
7
+ null false
8
+ argument :enterprise_account_id, ID, required: true
9
+ argument :provider_service, Types::IdentityProviderService, required: true
10
+
11
+ field :identity_provider, Types::IdentityProvider, null: false
12
+ field :errors, [String], null: false
13
+
14
+ def resolve(enterprise_account_id:, provider_service:)
15
+ enterprise_account = Osso::Models::EnterpriseAccount.find(enterprise_account_id)
16
+ identity_provider = enterprise_account.saml_providers.create!(
17
+ provider: provider_service || 'OKTA',
18
+ domain: enterprise_account.domain,
19
+ )
20
+
21
+ return_data(identity_provider: identity_provider)
22
+ rescue StandardError => e
23
+ return_error(errors: e.full_message)
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Osso
4
+ module GraphQL
5
+ module Mutations
6
+ class SetSamlProvider < BaseMutation
7
+ null false
8
+
9
+ argument :provider, Types::IdentityProviderService, required: true
10
+ argument :id, ID, required: true
11
+
12
+ field :identity_provider, Types::IdentityProvider, null: false
13
+ field :errors, [String], null: false
14
+
15
+ def resolve(provider:, id:)
16
+ saml_provider = Osso::Models::SamlProvider.find(id)
17
+ saml_provider.provider = provider
18
+ saml_provider.save!
19
+ {
20
+ saml_provider: saml_provider,
21
+ errors: [],
22
+ }
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Osso
4
+ module GraphQL
5
+ module Types
6
+ class QueryType < ::GraphQL::Schema::Object
7
+ field :enterprise_accounts, null: true, resolver: Resolvers::EnterpriseAccounts
8
+ field :oauth_clients, null: true, resolver: Resolvers::OAuthClients
9
+
10
+ field :enterprise_account, null: false, resolver: Resolvers::EnterpriseAccount do
11
+ argument :domain, String, required: true
12
+ end
13
+
14
+ field(
15
+ :identity_provider,
16
+ Types::IdentityProvider,
17
+ null: true,
18
+ resolve: ->(_obj, args, _context) { Osso::Models::SamlProvider.find(args[:id]) },
19
+ ) do
20
+ argument :id, ID, required: true
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Osso
4
+ module GraphQL
5
+ module Resolvers
6
+ end
7
+ end
8
+ end
9
+
10
+ require_relative 'resolvers/enterprise_account'
11
+ require_relative 'resolvers/enterprise_accounts'
12
+ require_relative 'resolvers/oauth_clients'
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Osso
4
+ module GraphQL
5
+ module Resolvers
6
+ class EnterpriseAccount < ::GraphQL::Schema::Resolver
7
+ type Types::EnterpriseAccount, null: false
8
+
9
+ def resolve(args)
10
+ return unless admin? || enterprise_authorized?(args[:domain])
11
+
12
+ Osso::Models::EnterpriseAccount.find_by(domain: args[:domain])
13
+ end
14
+
15
+ def admin?
16
+ context[:scope] == :admin
17
+ end
18
+
19
+ def enterprise_authorized?(domain)
20
+ context[:scope] == domain
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Osso
4
+ module GraphQL
5
+ module Resolvers
6
+ class EnterpriseAccounts < ::GraphQL::Schema::Resolver
7
+ type [Types::EnterpriseAccount], null: true
8
+
9
+ def resolve
10
+ return Osso::Models::EnterpriseAccount.all if context[:scope] == :admin
11
+
12
+ Array(Osso::Models::EnterpriseAccount.find_by(domain: context[:scope]))
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Osso
4
+ module GraphQL
5
+ module Resolvers
6
+ class OAuthClients < ::GraphQL::Schema::Resolver
7
+ type [Types::OAuthClient], null: true
8
+
9
+ def resolve
10
+ return Osso::Models::OAuthClient.all if context[:scope] == :admin
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,42 @@
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
+ module Osso
15
+ module GraphQL
16
+ class Schema < ::GraphQL::Schema
17
+ use ::GraphQL::Pagination::Connections
18
+ query Types::QueryType
19
+ mutation Types::MutationType
20
+
21
+ def self.id_from_object(object, _type_definition = nil, _query_ctx = nil)
22
+ GraphQL::Schema::UniqueWithinType.encode(object.class.name, object.id)
23
+ end
24
+
25
+ def self.object_from_id(id, _query_ctx = nil)
26
+ class_name, item_id = GraphQL::Schema::UniqueWithinType.decode(id)
27
+ Object.const_get(class_name).find(item_id)
28
+ end
29
+
30
+ def self.resolve_type(_type, obj, _ctx)
31
+ case obj
32
+ when Osso::Models::EnterpriseAccount
33
+ Types::EnterpriseAccount
34
+ when Osso::Models::SamlProvider
35
+ Types::IdentityProvider
36
+ else
37
+ raise("Unexpected object: #{obj}")
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Osso
4
+ module Types
5
+ end
6
+ end
7
+
8
+ require_relative 'types/base_object'
9
+ require_relative 'types/base_enum'
10
+ require_relative 'types/identity_provider_service'
11
+ require_relative 'types/identity_provider'
12
+ require_relative 'types/enterprise_account'
13
+ require_relative 'types/oauth_client'
14
+ require_relative 'types/user'
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Osso
4
+ module GraphQL
5
+ module Types
6
+ class BaseEnum < ::GraphQL::Schema::Enum
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'graphql'
4
+
5
+ module Osso
6
+ module GraphQL
7
+ module Types
8
+ class BaseObject < ::GraphQL::Schema::Object
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'graphql'
4
+
5
+ module Osso
6
+ module GraphQL
7
+ module Types
8
+ class EnterpriseAccount < Types::BaseObject
9
+ description 'An Account for a company that wishes to use SAML via Osso'
10
+ implements ::GraphQL::Types::Relay::Node
11
+
12
+ global_id_field :gid
13
+ field :id, ID, null: false
14
+ field :name, String, null: false
15
+ field :domain, String, null: false
16
+ field :identity_providers, [Types::IdentityProvider], null: true
17
+ field :status, String, null: false
18
+
19
+ def name
20
+ object.domain.gsub('.com', '')
21
+ end
22
+
23
+ def status
24
+ 'active'
25
+ end
26
+
27
+ def identity_providers
28
+ object.saml_providers
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'graphql'
4
+
5
+ module Osso
6
+ module GraphQL
7
+ module Types
8
+ class IdentityProvider < Types::BaseObject
9
+ description 'Represents a SAML based IDP instance for an EnterpriseAccount'
10
+ implements ::GraphQL::Types::Relay::Node
11
+
12
+ global_id_field :gid
13
+ field :id, ID, null: false
14
+ field :enterprise_account_id, ID, null: false
15
+ field :service, Types::IdentityProviderService, null: true
16
+ field :domain, String, null: false
17
+ field :acs_url, String, null: false
18
+ field :sso_url, String, null: true
19
+ field :sso_cert, String, null: true
20
+ field :configured, Boolean, null: false
21
+
22
+ def service
23
+ @object.provider
24
+ end
25
+
26
+ def configured
27
+ @object.idp_sso_target_url && @object.idp_cert
28
+ end
29
+
30
+ def sso_cert
31
+ @object.idp_cert
32
+ end
33
+
34
+ def sso_url
35
+ @object.idp_sso_target_url
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Osso
4
+ module GraphQL
5
+ module Types
6
+ class IdentityProviderService < BaseEnum
7
+ value('AZURE', 'Microsoft Azure Identity Provider', value: 'Osso::Models::AzureSamlProvider')
8
+ value('OKTA', 'Okta Identity Provider', value: 'Osso::Models::OktaSamlProvider')
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'graphql'
4
+
5
+ module Osso
6
+ module GraphQL
7
+ module Types
8
+ class OAuthClient < Types::BaseObject
9
+ description 'An OAuth client used to consume Osso SAML users'
10
+ implements ::GraphQL::Types::Relay::Node
11
+
12
+ global_id_field :gid
13
+ field :id, ID, null: false
14
+ field :name, String, null: false
15
+ field :client_id, String, null: false
16
+ field :client_secret, String, null: false
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'graphql'
4
+ require_relative 'base_object'
5
+
6
+ module Osso
7
+ module GraphQL
8
+ module Types
9
+ class User < Types::BaseObject
10
+ description 'A User of the application'
11
+
12
+ field :id, ID, null: false
13
+ field :name, String, null: true
14
+ end
15
+ end
16
+ end
17
+ end
@@ -1,67 +1,69 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module Helpers
4
- module Auth
5
- attr_accessor :current_scope
6
-
7
- def enterprise_protected!(domain = nil)
8
- return if admin_authorized?
9
- return if enterprise_authorized?(domain)
3
+ module Osso
4
+ module Helpers
5
+ module Auth
6
+ attr_accessor :current_scope
7
+
8
+ def enterprise_protected!(domain = nil)
9
+ return if admin_authorized?
10
+ return if enterprise_authorized?(domain)
10
11
 
11
- redirect ENV['JWT_URL']
12
- end
12
+ redirect ENV['JWT_URL']
13
+ end
13
14
 
14
- def enterprise_authorized?(domain)
15
- payload, _args = JWT.decode(
16
- token,
17
- ENV['JWT_HMAC_SECRET'],
18
- true,
19
- { algorithm: 'HS256' },
20
- )
15
+ def enterprise_authorized?(domain)
16
+ payload, _args = JWT.decode(
17
+ token,
18
+ ENV['JWT_HMAC_SECRET'],
19
+ true,
20
+ { algorithm: 'HS256' },
21
+ )
21
22
 
22
- @current_scope = payload['scope']
23
+ @current_scope = payload['scope']
23
24
 
24
- true
25
- rescue JWT::DecodeError
26
- false
27
- end
25
+ true
26
+ rescue JWT::DecodeError
27
+ false
28
+ end
28
29
 
29
- def admin_protected!
30
- return if admin_authorized?
30
+ def admin_protected!
31
+ return if admin_authorized?
31
32
 
32
- redirect ENV['JWT_URL']
33
- end
33
+ redirect ENV['JWT_URL']
34
+ end
34
35
 
35
- def admin_authorized?
36
- payload, _args = JWT.decode(
37
- token,
38
- ENV['JWT_HMAC_SECRET'],
39
- true,
40
- { algorithm: 'HS256' },
41
- )
36
+ def admin_authorized?
37
+ payload, _args = JWT.decode(
38
+ token,
39
+ ENV['JWT_HMAC_SECRET'],
40
+ true,
41
+ { algorithm: 'HS256' },
42
+ )
42
43
 
43
- if payload['scope'] == 'admin'
44
- @current_scope = :admin
45
- return true
46
- end
44
+ if payload['scope'] == 'admin'
45
+ @current_scope = :admin
46
+ return true
47
+ end
47
48
 
48
- false
49
- rescue JWT::DecodeError
50
- false
51
- end
49
+ false
50
+ rescue JWT::DecodeError
51
+ false
52
+ end
52
53
 
53
- def token
54
- request.env['admin_token'] || session['admin_token'] || request['admin_token']
55
- end
54
+ def token
55
+ request.env['admin_token'] || session['admin_token'] || request['admin_token']
56
+ end
56
57
 
57
- def chomp_token
58
- return unless request['admin_token'].present?
58
+ def chomp_token
59
+ return unless request['admin_token'].present?
59
60
 
60
- session['admin_token'] = request['admin_token']
61
+ session['admin_token'] = request['admin_token']
61
62
 
62
- return if request.post?
63
+ return if request.post?
63
64
 
64
- redirect request.path
65
+ redirect request.path
66
+ end
65
67
  end
66
68
  end
67
- end
69
+ end
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module Helpers
3
+ module Osso
4
+ module Helpers
5
+ end
4
6
  end
5
7
 
6
8
  require_relative 'auth'
@@ -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.3'
4
+ VERSION = '0.0.3.4'
5
5
  end
@@ -15,6 +15,7 @@ Gem::Specification.new do |spec|
15
15
  spec.license = 'MIT'
16
16
 
17
17
  spec.add_runtime_dependency 'activesupport', '>= 6.0.3.2'
18
+ spec.add_runtime_dependency 'graphql'
18
19
  spec.add_runtime_dependency 'jwt'
19
20
  spec.add_runtime_dependency 'omniauth-multi-provider'
20
21
  spec.add_runtime_dependency 'omniauth-saml'
@@ -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,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: osso
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3.3
4
+ version: 0.0.3.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sam Bauch
@@ -24,6 +24,20 @@ dependencies:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: 6.0.3.2
27
+ - !ruby/object:Gem::Dependency
28
+ name: graphql
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
27
41
  - !ruby/object:Gem::Dependency
28
42
  name: jwt
29
43
  requirement: !ruby/object:Gem::Requirement
@@ -218,6 +232,7 @@ files:
218
232
  - bin/console
219
233
  - bin/setup
220
234
  - config/database.yml
235
+ - db/schema.rb
221
236
  - lib/.DS_Store
222
237
  - lib/osso.rb
223
238
  - lib/osso/Rakefile
@@ -237,6 +252,27 @@ files:
237
252
  - lib/osso/db/migrate/20200502135008_add_oauth_client_id_to_enterprise_account.rb
238
253
  - lib/osso/db/migrate/20200601131227_drop_null_constraint_from_saml_providers_provider.rb
239
254
  - lib/osso/db/schema.rb
255
+ - lib/osso/graphql/.DS_Store
256
+ - lib/osso/graphql/mutation.rb
257
+ - lib/osso/graphql/mutations.rb
258
+ - lib/osso/graphql/mutations/base_mutation.rb
259
+ - lib/osso/graphql/mutations/configure_identity_provider.rb
260
+ - lib/osso/graphql/mutations/create_identity_provider.rb
261
+ - lib/osso/graphql/mutations/set_saml_provider.rb
262
+ - lib/osso/graphql/query.rb
263
+ - lib/osso/graphql/resolvers.rb
264
+ - lib/osso/graphql/resolvers/enterprise_account.rb
265
+ - lib/osso/graphql/resolvers/enterprise_accounts.rb
266
+ - lib/osso/graphql/resolvers/oauth_clients.rb
267
+ - lib/osso/graphql/schema.rb
268
+ - lib/osso/graphql/types.rb
269
+ - lib/osso/graphql/types/base_enum.rb
270
+ - lib/osso/graphql/types/base_object.rb
271
+ - lib/osso/graphql/types/enterprise_account.rb
272
+ - lib/osso/graphql/types/identity_provider.rb
273
+ - lib/osso/graphql/types/identity_provider_service.rb
274
+ - lib/osso/graphql/types/oauth_client.rb
275
+ - lib/osso/graphql/types/user.rb
240
276
  - lib/osso/helpers/auth.rb
241
277
  - lib/osso/helpers/helpers.rb
242
278
  - lib/osso/lib/app_config.rb
@@ -275,7 +311,7 @@ files:
275
311
  - spec/routes/auth_spec.rb
276
312
  - spec/routes/oauth_spec.rb
277
313
  - spec/spec_helper.rb
278
- - spec/support/vcr_cassettes/okta_saml_callback.yml
314
+ - spec/support/views/admin.erb
279
315
  homepage: https://github.com/enterprise-oss/osso-rb
280
316
  licenses:
281
317
  - 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