Skip to content
This repository was archived by the owner on Sep 24, 2019. It is now read-only.
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
19 changes: 18 additions & 1 deletion app/controllers/graphql_controller.rb
Original file line number Diff line number Diff line change
@@ -1,13 +1,30 @@
class GraphqlController < ApplicationController
before_action :authenticate

def execute
query_string = params[:query].to_s
variables = ensure_hash(params[:variables])
result = ::Graph::Schema.execute(query_string, variables: variables)
context = {
user: @user
}

result = Graph::Schema.execute(query_string, variables: variables, context: context)
render json: result
end

private

def authenticate
@user = authenticate_with_http_basic { |username, password|
user = User.where(username: username).first

return render plain: 'Invalid username', status: :unauthorized unless user
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

probably dont want to differentiate invalid user vs password?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I used to think the same until I read a UX article about this.

It's easy to know if a username is valid or not by attempting to register with that username, so by returning Invalid username or password we're not really making it any more secure, we're just making the UX worse.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TIL 👍

return render plain: 'Invalid password', status: :authorized unless user.authenticate(password)

user
}
end

def ensure_hash(variables)
if variables.blank?
{}
Expand Down
7 changes: 5 additions & 2 deletions app/models/graph/types/film.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,11 @@ module Types
field :releaseDate, !types.String,
"The ISO 8601 date format of film release at original creator country.", property: :release_date

field :created_at, !types.String, "The ISO 8601 date format of the time that this resource was created."
field :updated_at, !types.String, "The ISO 8601 date format of the time that this resource was edited."
field :createdAt, !types.String,
"The ISO 8601 date format of the time that this resource was created.", property: :created_at

field :updatedAt, !types.String, "The ISO 8601 date format of the time that this resource was edited.",
property: :updated_at
end
end
end
6 changes: 6 additions & 0 deletions app/models/graph/types/query.rb
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,12 @@ module Types
}
end

field :viewer, Graph::Types::User, 'The currently authenticated user (if any)' do
resolve ->(_, _, ctx) {
ctx[:user]
}
end

# Relay
field :node, GraphQL::Relay::Node.field
field :nodes, GraphQL::Relay::Node.plural_field
Expand Down
2 changes: 1 addition & 1 deletion app/models/graph/types/species.rb
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ module Types
end

field :language, types.String, "The language commonly spoken by this species."
field :homeworld, Planet, "A planet that this species originates from type." do
field :homeworld, Graph::Types::Planet, "A planet that this species originates from type." do
resolve -> (species, _, _) do
Graph::AssociationLoader.for(::Species, :homeworld).load(species)
end
Expand Down
7 changes: 5 additions & 2 deletions app/models/graph/types/starship.rb
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,11 @@ module Types
field :consumables, types.String, "The maximum length of time that this starship can provide consumables for its entire crew without having to resupply."
connection :pilots, Graph::Types::Person.connection_type

field :created_at, !types.String, "The ISO 8601 date format of the time that this resource was created."
field :updated_at, !types.String, "The ISO 8601 date format of the time that this resource was updated."
field :createdAt, !types.String,
"The ISO 8601 date format of the time that this resource was created.", property: :created_at

field :updatedAt, !types.String,
"The ISO 8601 date format of the time that this resource was updated.", property: :updated_at
end
end
end
15 changes: 15 additions & 0 deletions app/models/graph/types/user.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
module Graph
module Types
User = GraphQL::ObjectType.define do
name "User"
description "A user that can rate films."

field :name, !types.String
field :username, !types.String
field :createdAt, !types.String,
"The ISO 8601 date format of the time that this resource was created.", property: :created_at
field :updatedAt, !types.String,
"The ISO 8601 date format of the time that this resource was updated.", property: :updated_at
end
end
end
7 changes: 5 additions & 2 deletions app/models/graph/types/vehicle.rb
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,11 @@ module Types
field :consumables, types.String, "The maximum length of time that this vehicle can provide consumables for its entire crew without having to resupply."
connection :pilots, Graph::Types::Person.connection_type

field :created_at, !types.String, "The ISO 8601 date format of the time that this resource was created."
field :updated_at, !types.String, "The ISO 8601 date format of the time that this resource was updated."
field :createdAt, !types.String,
"The ISO 8601 date format of the time that this resource was created.", property: :created_at

field :updatedAt, !types.String,
"The ISO 8601 date format of the time that this resource was updated.", property: :updated_at
end
end
end
8 changes: 8 additions & 0 deletions db/migrate/20170212223839_add_username_to_users.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
class AddUsernameToUsers < ActiveRecord::Migration[5.0]
def change
change_table :users do |t|
t.string :username
end
add_index :users, :username, unique: true
end
end
4 changes: 3 additions & 1 deletion db/schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.

ActiveRecord::Schema.define(version: 20170212195638) do
ActiveRecord::Schema.define(version: 20170212223839) do

create_table "films", force: :cascade do |t|
t.string "title"
Expand Down Expand Up @@ -153,6 +153,8 @@
t.string "password_digest"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "username"
t.index ["username"], name: "index_users_on_username", unique: true
end

create_table "vehicles", force: :cascade do |t|
Expand Down
52 changes: 48 additions & 4 deletions test/controllers/graphql_controller_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ class GraphQLControllerTest < ActionDispatch::IntegrationTest
"cargoCapacity" => 100000.0,
"consumables" => "2 months",
"costInCredits" => 100000.0,
"created_at" => "2014-12-10 16:59:45 UTC",
"createdAt" => "2014-12-10 16:59:45 UTC",
"crew" => "4",
"hyperdriveRating" => 0.5,
"length" => 34.37,
Expand All @@ -75,7 +75,7 @@ class GraphQLControllerTest < ActionDispatch::IntegrationTest
"cargoCapacity" => 10.0,
"consumables" => "none",
"costInCredits" => nil,
"created_at" => "2014-12-15 12:22:12 UTC",
"createdAt" => "2014-12-15 12:22:12 UTC",
"crew" => "2",
"length" => 4.5,
"manufacturer" => "Incom corporation",
Expand Down Expand Up @@ -109,6 +109,50 @@ class GraphQLControllerTest < ActionDispatch::IntegrationTest
assert_equal expected, JSON.parse(response.body)
end

test "#execute allows authentication via basic auth" do
query = """
{
viewer {
username
}
}
"""

expected = {
"data" => {
"viewer" => {
"username" => "xuorig"
}
}
}

post graphql_url, params: { query: query }, headers: {
"Authorization" => ActionController::HttpAuthentication::Basic.encode_credentials("xuorig", "averysecurepassword"),
}

assert_equal expected, JSON.parse(response.body)
end

test "#execute authentication is not required" do
query = """
{
viewer {
username
}
}
"""

expected = {
"data" => {
"viewer" => nil
}
}

post graphql_url, params: { query: query }

assert_equal expected, JSON.parse(response.body)
end

private

def full_graphql_query
Expand Down Expand Up @@ -167,7 +211,7 @@ def full_graphql_query
cargoCapacity
consumables
costInCredits
created_at
createdAt
crew
hyperdriveRating
length
Expand All @@ -183,7 +227,7 @@ def full_graphql_query
cargoCapacity
consumables
costInCredits
created_at
createdAt
crew
length
manufacturer
Expand Down
5 changes: 3 additions & 2 deletions test/fixtures/users.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
test-user:
name: xuorig
xuorig:
name: Marc-André Giroux
username: xuorig
password_digest: <%= BCrypt::Password.create('averysecurepassword', cost: BCrypt::Engine.cost) %>