Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 12 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -343,38 +343,32 @@ and an error is returned in a REST format as the request never reaches your GQL

#### Authenticate in Your GQL Schema
For this you will need to add the `GraphqlDevise::SchemaPlugin` to your schema as described
[here](#mounting-operations-into-your-own-schema) and also set the authenticated resource
in a `before_action` hook.
[here](#mounting-operations-into-your-own-schema).

```ruby
# app/controllers/my_controller.rb

class MyController < ApplicationController
include GraphqlDevise::Concerns::SetUserByToken

before_action -> { set_resource_by_token(:user) }

def my_action
render json: DummySchema.execute(params[:query], context: graphql_context)
render json: DummySchema.execute(params[:query], context: graphql_context(:user))
end
end

# @resource.to_s.underscore.tr('/', '_').to_sym
```
The `set_resource_by_token` method receives a symbol identifying the resource you are trying
The `graphql_context` method receives a symbol identifying the resource you are trying
to authenticate. So if you mounted the `'User'` resource, the symbol is `:user`. You can use
this snippet to find the symbol for more complex scenarios
`resource_klass.to_s.underscore.tr('/', '_').to_sym`.
`resource_klass.to_s.underscore.tr('/', '_').to_sym`. `graphql_context` can also take an
array of resources if you mounted more than one into your schema. The gem will try to
authenticate a resource for each element on the array until it finds one.

The `graphql_context` method is simply a helper method that returns a hash like this
```ruby
{ current_resource: @resource, controller: self }
```
These are the two values the gem needs to check if a user is authenticated and to perform
other auth operations. All `set_resource_by_token` does is set the `@resource` variable if
the provided authentication headers are valid. If authentication fails, resource will be `nil`
and this is how `GraphqlDevise::SchemaPlugin` knows if a user is authenticated or not in
each query.
Internally in your own mutations and queries a key `current_resource` will be available in
the context if a resource was successfully authenticated or `nil` otherwise.

Keep in mind that sending multiple values to the `graphql_context` method means that depending
on who makes the request, the context value `current_resource` might contain instances of the
different models you might have mounted into the schema.

Please note that by using this mechanism your GQL schema will be in control of what queries are
restricted to authenticated users and you can only do this at the root level fields of your GQL
Expand Down
10 changes: 6 additions & 4 deletions app/controllers/graphql_devise/concerns/set_user_by_token.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@ module Concerns
SetUserByToken.module_eval do
attr_accessor :client_id, :token, :resource

alias_method :set_resource_by_token, :set_user_by_token
def set_resource_by_token(resource)
set_user_by_token(resource)
end

def graphql_context
def graphql_context(resource_name)
{
current_resource: @resource,
controller: self
resource_name: resource_name,
controller: self
}
end

Expand Down
25 changes: 22 additions & 3 deletions lib/graphql_devise/schema_plugin.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,23 +23,42 @@ def trace(event, trace_data)

field = traced_field(trace_data)
provided_value = authenticate_option(field, trace_data)
context = set_current_resource(context_from_data(trace_data))

if !provided_value.nil?
raise_on_missing_resource(context(trace_data), field) if provided_value
raise_on_missing_resource(context, field) if provided_value
elsif @authenticate_default
raise_on_missing_resource(context(trace_data), field)
raise_on_missing_resource(context, field)
end

yield
end

private

def set_current_resource(context)
controller = context[:controller]
resource_names = Array(context[:resource_name])
context[:current_resource] = resource_names.find do |resource_name|
unless Devise.mappings.key?(resource_name)
raise(
GraphqlDevise::Error,
"Invalid resource_name `#{resource_name}` provided to `graphql_context`. Possible values are: #{Devise.mappings.keys}."
)
end

found = controller.set_resource_by_token(resource_name)
break found if found
end

context
end

def raise_on_missing_resource(context, field)
@unauthenticated_proc.call(field.name) if context[:current_resource].blank?
end

def context(trace_data)
def context_from_data(trace_data)
query = if trace_data[:context]
trace_data[:context].query
else
Expand Down
10 changes: 6 additions & 4 deletions spec/dummy/app/controllers/api/v1/graphql_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,16 @@ module V1
class GraphqlController < ApplicationController
include GraphqlDevise::Concerns::SetUserByToken

before_action -> { set_resource_by_token(:user) }

def graphql
render json: DummySchema.execute(params[:query], context: graphql_context)
render json: DummySchema.execute(params[:query], context: graphql_context(:user))
end

def interpreter
render json: InterpreterSchema.execute(params[:query], context: graphql_context)
render json: InterpreterSchema.execute(params[:query], context: graphql_context(:user))
end

def failing_resource_name
render json: DummySchema.execute(params[:query], context: graphql_context([:user, :fail]))
end

private
Expand Down
1 change: 1 addition & 0 deletions spec/dummy/config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,5 @@

post '/api/v1/graphql', to: 'api/v1/graphql#graphql'
post '/api/v1/interpreter', to: 'api/v1/graphql#interpreter'
post '/api/v1/failing', to: 'api/v1/graphql#failing_resource_name'
end
9 changes: 9 additions & 0 deletions spec/requests/user_controller_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,15 @@
expect(json_response[:data][:publicField]).to eq('Field does not require authentication')
end
end

context 'when using the failing route' do
it 'raises an invalid resource_name error' do
expect { post_request('/api/v1/failing') }.to raise_error(
GraphqlDevise::Error,
'Invalid resource_name `fail` provided to `graphql_context`. Possible values are: [:user, :admin, :guest, :users_customer].'
)
end
end
end

describe 'privateField' do
Expand Down