Issue overriding the "authorized?" method in BaseField
codingwaysarg opened this issue · 2 comments
Describe the bug
Im overriding the authorized? method based on the documentation, in order to support an "authenticate" key for my fields.
module Types
class BaseField < GraphQL::Schema::Field
include ApolloFederation::Field
argument_class Types::BaseArgument
def initialize(*args, authenticate: true, **kwargs, &block)
@authenticate = authenticate
super(*args, **kwargs, &block)
end
attr_reader :authenticate
def authorized?(object, args, context)
super && (!@authenticate || context[:current_user].present?)
end
end
end
Then, in order to test, my query_type looks like this:
module Types
class QueryType < Types::BaseObject
field :current_user, Types::UserType, null: true, authenticate: false
def current_user
User.first
end
end
end
As you can see, i'm passing authenticate: false to the field.
The thing is that, when I call this query:
query {
currentUser {
id
}
}
it looks like the "id" field inside my user_type is still checking for the authenticate option, even its parent explicitly says authenticate: false.
I understand thats because in the initialize method, the argument authenticate is "true" by default, because that is what i want. I would like all fields to require authentication by default, except the ones i mark with authenticate: false, but I dont know if fields inside a type can also behave with the same option set on the parent.
Versions
graphql
version: 2.3.1
rails
(or other framework): 7.1.3.2
apollo-federation
: 3.8.5
GraphQL schema
Include relevant types and fields (in Ruby is best, in GraphQL IDL is ok). Any custom extensions, etc?
module Types
class UserType < Types::BaseObject
key fields: :id
field :id, ID, null: false
field :email, String, null: false
field :company_id, ID, null: false
field :company, Types::CompanyType, null: false
def company
{ __typename: 'Company', id: object[:company_id] }
end
end
end
# frozen_string_literal: true
class ApiSchema < GraphQL::Schema
GraphQL::Types::Relay::PageInfo.include ApolloFederation::Object
GraphQL::Types::Relay::PageInfo.shareable
include ApolloFederation::Schema
federation version: '2.0'
mutation(Types::MutationType)
query(Types::QueryType)
use GraphQL::Dataloader
max_query_string_tokens(5000)
validate_max_errors(100)
def self.unauthorized_object(error)
raise GraphQL::ExecutionError, "An object of type #{error.type.graphql_name} was hidden due to permissions"
end
def self.unauthorized_field(error)
raise GraphQL::ExecutionError, "The field #{error.field.graphql_name} on an object of type #{error.type.graphql_name} was hidden due to permissions"
end
end
module Types
class BaseObject < GraphQL::Schema::Object
include ApolloFederation::Object
edge_type_class(Types::BaseEdge)
connection_type_class(Types::BaseConnection)
field_class Types::BaseField
underscore_reference_keys true
end
end
GraphQL query
Example GraphQL query and response (if query execution is involved)
query {
currentUser {
id
}
}
{
"errors": [
{
"message": "The field id on an object of type User was hidden due to permissions",
"path": [
"currentUser",
"id"
],
"extensions": {
"serviceName": "users",
"code": "DOWNSTREAM_SERVICE_ERROR",
"stacktrace": [
"GraphQLError: The field id on an object of type User was hidden due to permissions",
" at Object.err (/home/jpmermoz/Code/Other/customer-api-federation/node_modules/@apollo/federation-internals/dist/error.js:11:32)",
" at downstreamServiceError (/home/jpmermoz/Code/Other/customer-api-federation/node_modules/@apollo/gateway/dist/executeQueryPlan.js:523:120)",
" at /home/jpmermoz/Code/Other/customer-api-federation/node_modules/@apollo/gateway/dist/executeQueryPlan.js:341:59",
" at Array.map (<anonymous>)",
" at sendOperation (/home/jpmermoz/Code/Other/customer-api-federation/node_modules/@apollo/gateway/dist/executeQueryPlan.js:341:44)",
" at process.processTicksAndRejections (node:internal/process/task_queues:95:5)",
" at async /home/jpmermoz/Code/Other/customer-api-federation/node_modules/@apollo/gateway/dist/executeQueryPlan.js:255:49",
" at async executeNode (/home/jpmermoz/Code/Other/customer-api-federation/node_modules/@apollo/gateway/dist/executeQueryPlan.js:200:17)",
" at async executeNode (/home/jpmermoz/Code/Other/customer-api-federation/node_modules/@apollo/gateway/dist/executeQueryPlan.js:174:40)",
" at async /home/jpmermoz/Code/Other/customer-api-federation/node_modules/@apollo/gateway/dist/executeQueryPlan.js:96:35"
]
}
}
],
"data": {
"currentUser": null
}
}
Steps to reproduce
Steps to reproduce the behavior
Expected behavior
Return the user with its ID
Actual behavior
I have permissions for the user field, but I cannot query fields inside it, like the ID field.
Hi! Thanks for the detailed report and sorry for the trouble.
Given the setup you shared, if you want the ID field to not require authentication, you have to configure it with authenticate: false
, for example:
field :id, ID, authenticate: false # don't require logging in for this field
Alternatively, you could update your BaseField definition to handle this special case, for example:
def authorized?(object, args, context)
# Allow this field if either:
# - authenticate: false was configured,
# - or, a current_user is present,
# - or, it's an ID field
super && (!@authenticate || context[:current_user].present? || graphql_name == "id")
end
(Maybe that solution isn't exactly what you want, but hopefully that gives the idea ...)
Would one of those approaches work for you?
Hi, thank you for your quick answer!
I was looking for a more "generic" solution that wouldnt involve puting authenticate: false
on every field.
I ended up doing something like this:
module Types
class BaseField < GraphQL::Schema::Field
include ApolloFederation::Field
argument_class Types::BaseArgument
def initialize(*args, authenticate: true, **kwargs, &block)
@authenticate = authenticate
super(*args, **kwargs, &block)
end
attr_reader :authenticate
def authorized?(object, args, context)
return super unless root_level_field?
super && (!@authenticate || context[:current_user].present?)
end
def root_level_field?
%w(Types::QueryType Types::MutationType).include?(owner.name)
end
end
end
Of course this will work only for root level fields (defined in query_type or mutation_type) but is enough for know.
Thank you!