diff --git a/app/controllers/graphql_controller.rb b/app/controllers/graphql_controller.rb index c572322..9966435 100644 --- a/app/controllers/graphql_controller.rb +++ b/app/controllers/graphql_controller.rb @@ -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 + return render plain: 'Invalid password', status: :authorized unless user.authenticate(password) + + user + } + end + def ensure_hash(variables) if variables.blank? {} diff --git a/app/models/graph/types/film.rb b/app/models/graph/types/film.rb index 3f5d3a5..959537a 100644 --- a/app/models/graph/types/film.rb +++ b/app/models/graph/types/film.rb @@ -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 diff --git a/app/models/graph/types/query.rb b/app/models/graph/types/query.rb index 4f40483..53df069 100644 --- a/app/models/graph/types/query.rb +++ b/app/models/graph/types/query.rb @@ -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 diff --git a/app/models/graph/types/species.rb b/app/models/graph/types/species.rb index c86ef04..60de9ff 100644 --- a/app/models/graph/types/species.rb +++ b/app/models/graph/types/species.rb @@ -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 diff --git a/app/models/graph/types/starship.rb b/app/models/graph/types/starship.rb index ae36ebd..8e746df 100644 --- a/app/models/graph/types/starship.rb +++ b/app/models/graph/types/starship.rb @@ -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 diff --git a/app/models/graph/types/user.rb b/app/models/graph/types/user.rb new file mode 100644 index 0000000..cf3e742 --- /dev/null +++ b/app/models/graph/types/user.rb @@ -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 diff --git a/app/models/graph/types/vehicle.rb b/app/models/graph/types/vehicle.rb index 53ed16c..cb973b6 100644 --- a/app/models/graph/types/vehicle.rb +++ b/app/models/graph/types/vehicle.rb @@ -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 diff --git a/db/migrate/20170212223839_add_username_to_users.rb b/db/migrate/20170212223839_add_username_to_users.rb new file mode 100644 index 0000000..9ca59f4 --- /dev/null +++ b/db/migrate/20170212223839_add_username_to_users.rb @@ -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 diff --git a/db/schema.rb b/db/schema.rb index 6ea8d94..950678a 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -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" @@ -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| diff --git a/test/controllers/graphql_controller_test.rb b/test/controllers/graphql_controller_test.rb index 1ca1af5..b580015 100644 --- a/test/controllers/graphql_controller_test.rb +++ b/test/controllers/graphql_controller_test.rb @@ -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, @@ -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", @@ -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 @@ -167,7 +211,7 @@ def full_graphql_query cargoCapacity consumables costInCredits - created_at + createdAt crew hyperdriveRating length @@ -183,7 +227,7 @@ def full_graphql_query cargoCapacity consumables costInCredits - created_at + createdAt crew length manufacturer diff --git a/test/fixtures/users.yml b/test/fixtures/users.yml index 0d210b9..c2466f3 100644 --- a/test/fixtures/users.yml +++ b/test/fixtures/users.yml @@ -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) %>