graphql-ruby icon indicating copy to clipboard operation
graphql-ruby copied to clipboard

ObjectCache#private_context_fingerprint_for called for public: true fields/objects

Open alextrueman opened this issue 5 months ago • 7 comments

Describe the bug

GraphQL Enterprise ObjectCache calls private_context_fingerprint_for method for fields configured with cacheable(public: true), causing crashes when no user authentication context is present. Public cached fields should not require user context or trigger private fingerprinting logic.

Versions

graphql version: 2.5.6 graphql-pro version: 1.29.7 graphql-enterprise version: 1.5.7 rails (or other framework): 7.2.2 ruby: 3.1.6 other applicable versions (graphql-batch, etc)

GraphQL schema

Include relevant types and fields (in Ruby is best, in GraphQL IDL is ok). Any custom extensions, etc?

class GraphqlSchema < GraphQL::Schema
  default_max_page_size 50

  mutation Types::MutationType
  query Types::QueryType

  use GraphQL::Enterprise::ObjectCache, redis: Redis.new(url: Config.redis_url)
  
  def self.private_context_fingerprint_for(context)
    current_user = context[:current_user]

    if current_user.nil?
      # This should never happen, but just in case:
      raise("Invariant: No current_user in context! Can't create a private context fingerprint")
    end

    "user:#{current_user.id}:#{current_user.updated_at.to_i}"
  end
  ....
end

class BaseObject < GraphQL::Schema::Object
    include ActionPolicy::GraphQL::Behaviour
    include GraphQL::Enterprise::ObjectCache::ObjectIntegration
    cacheable(public: true)
end

class BaseField < GraphQL::Schema::Field
    include GraphQL::Enterprise::ObjectCache::FieldIntegration
    cacheable(public: true)
end

class Types::QueryType < Types::BaseObject
  graphql_name "Query"

  field :locations,
        [Types::LocationType],
        null: false,
        description: "nearby locations",
        oauth: "public",
        cacheable: {
          public: true
        } do
    argument :coordinates, Types::LngLatType, "coordinates for relative results", required: true
  end
end

class Types::LocationType < Types::BaseObject
  graphql_name "Location"
  description "location"
  cacheable(public: true)

  implements GraphQL::Types::Relay::Node

  field :address,
        Types::AddressType,
        null: false,
        description: "address of location",
        preload: :address,
        cacheable: {
          public: true
        }
end

GraphQL query

Example GraphQL query and response (if query execution is involved)

query {
  locations(coordinates: [-122.4194, 37.7749]) {
    address {
      street
    }
  }
}
error

Steps to reproduce

Add cacheable(public: true) and query any public query

Expected behavior

Public cached fields should not require user context and should not trigger private_context_fingerprint_for method.

Actual behavior

Public cached fields trigger private_context_fingerprint_for method.

Place full backtrace here (if a Ruby exception is involved):

Click to view exception backtrace
Failure/Error: raise("Invariant: No current_user in context! Can't create a private context fingerprint")
     
     RuntimeError:
       Invariant: No current_user in context! Can't create a private context fingerprint
     # ./app/graphql/graphql_schema.rb:88:in `private_context_fingerprint_for'

Additional context

When cacheable(false) is set on BaseField/BaseObject, individual fields/objects cannot easily override this to enable caching. This makes it difficult to incrementally adopt caching in existing projects with many fields, as the base-level false setting appears to take precedence over field-specific cacheable: true configurations.

Expected Behavior: Field-level cacheable: true should override base-level cacheable(false)

P.S. Not sure, should I create separate issue for this or it's planned?

Thanks in advance!

alextrueman avatar Jun 14 '25 15:06 alextrueman