Skip to content

Commit

Permalink
Merge 22c8b5b into 42cbd8c
Browse files Browse the repository at this point in the history
  • Loading branch information
mcelicalderon committed Sep 9, 2019
2 parents 42cbd8c + 22c8b5b commit b3ca980
Show file tree
Hide file tree
Showing 8 changed files with 156 additions and 5 deletions.
2 changes: 1 addition & 1 deletion app/controllers/graphql_devise/graphql_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ def auth
GraphqlDevise::Schema.execute(params[:query], execute_params(params))
end

render json: result
render json: result unless performed?
end

attr_accessor :client_id, :token, :resource
Expand Down
9 changes: 9 additions & 0 deletions app/graphql/graphql_devise/mutations/base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ def controller
context[:controller]
end

def set_auth_headers(resource)
auth_headers = resource.create_new_auth_token
response.headers.merge!(auth_headers)
end

def resource_class
context[:resource_class]
end
Expand All @@ -39,6 +44,10 @@ def recoverable_enabled?
resource_class.devise_modules.include?(:recoverable)
end

def confirmable_enabled?
resource_class.devise_modules.include?(:confirmable)
end

def current_resource
context[:current_resource]
end
Expand Down
52 changes: 52 additions & 0 deletions app/graphql/graphql_devise/mutations/check_password_token.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
module GraphqlDevise
module Mutations
class CheckPasswordToken < Base
argument :reset_password_token, String, required: true
argument :redirect_url, String, required: false

def resolve(reset_password_token:, redirect_url: nil)
resource = resource_class.with_reset_password_token(reset_password_token)
raise_user_error(I18n.t('graphql_devise.passwords.reset_token_not_found')) if resource.blank?

if resource.reset_password_period_valid?
token_info = client_and_token(resource.create_token)

resource.skip_confirmation! if confirmable_enabled? && !resource.confirmed_at
resource.allow_password_change = true if recoverable_enabled?

resource.save!

yield resource if block_given?

redirect_header_options = { reset_password: true }
redirect_headers = controller.send(
:build_redirect_headers,
token_info.fetch(:token),
token_info.fetch(:client_id),
redirect_header_options
)

if redirect_url.present?
controller.redirect_to(resource.build_auth_url(redirect_url, redirect_headers))
else
set_auth_headers(resource)
end

{ authenticable: resource }
else
raise_user_error(I18n.t('graphql_devise.passwords.reset_token_expired'))
end
end

private

def client_and_token(token)
if Gem::Version.new(DeviseTokenAuth::VERSION) <= Gem::Version.new('1.1.0')
{ client_id: token.first, token: token.last }
else
{ client_id: token.client, token: token.token }
end
end
end
end
end
2 changes: 2 additions & 0 deletions config/locales/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ en:
update_password_error: "Unable to update user password"
missing_passwords: "You must fill out the fields labeled 'Password' and 'Password confirmation'."
password_not_required: "This account does not require a password. Sign in using your '%{provider}' account instead."
reset_token_not_found: "No user found for the specified reset token."
reset_token_expired: "Reset password token is no longer valid."
sessions:
bad_credentials: "Invalid login credentials. Please try again."
not_confirmed: "A confirmation email was sent to your account at '%{email}'. You must follow the instructions in the email before your account can be activated"
Expand Down
10 changes: 6 additions & 4 deletions lib/graphql_devise/rails/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,11 @@ def mount_graphql_devise_for(resource, opts = {})
GraphqlDevise::Types::AuthenticableType

default_mutations = {
login: GraphqlDevise::Mutations::Login,
logout: GraphqlDevise::Mutations::Logout,
sign_up: GraphqlDevise::Mutations::SignUp,
update_password: GraphqlDevise::Mutations::UpdatePassword
login: GraphqlDevise::Mutations::Login,
logout: GraphqlDevise::Mutations::Logout,
sign_up: GraphqlDevise::Mutations::SignUp,
update_password: GraphqlDevise::Mutations::UpdatePassword,
check_password_token: GraphqlDevise::Mutations::CheckPasswordToken
}.freeze

default_mutations.each do |action, mutation|
Expand All @@ -41,6 +42,7 @@ def mount_graphql_devise_for(resource, opts = {})

devise_scope mapping_name.to_sym do
post "#{path}/graphql_auth", to: 'graphql_devise/graphql#auth'
get "#{path}/graphql_auth", to: 'graphql_devise/graphql#auth'
end
end
end
Expand Down
1 change: 1 addition & 0 deletions spec/rails_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,5 @@

config.include(Requests::JsonHelpers, type: :request)
config.include(Requests::AuthHelpers, type: :request)
config.include(ActiveSupport::Testing::TimeHelpers)
end
81 changes: 81 additions & 0 deletions spec/requests/mutations/check_password_token_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
require 'rails_helper'

RSpec.describe 'Check Password Token Requests' do
include_context 'with graphql query request'

let(:user) { create(:user, :confirmed) }
let(:redirect_url) { 'https://google.com' }
let(:query) do
<<-GRAPHQL
mutation {
userCheckPasswordToken(
resetPasswordToken: "#{token}",
redirectUrl: "#{redirect_url}"
) {
authenticable { email }
}
}
GRAPHQL
end

context 'when reset password token is valid' do
let(:token) { user.send(:set_reset_password_token) }

context 'when redirect_url is not provided' do
let(:redirect_url) { nil }

it 'returns authenticable and credentials in the headers' do
get_request

expect(response).to include_auth_headers
expect(json_response[:data][:userCheckPasswordToken]).to match(
authenticable: { email: user.email }
)
end
end

context 'when redirect url is provided' do
it 'redirects to redirect url' do
expect do
get_request

user.reload
end.to change { user.tokens.keys.count }.from(0).to(1).and(
change(user, :allow_password_change).from(false).to(true)
)

expect(response).to redirect_to %r{\Ahttps://google.com}
expect(response.body).to include("client=#{user.reload.tokens.keys.first}")
expect(response.body).to include('access-token=')
expect(response.body).to include('uid=')
expect(response.body).to include('expiry=')
end
end

context 'when token has expired' do
it 'returns an expired token error' do
travel_to 10.hours.ago do
token
end

get_request

expect(json_response[:errors]).to contain_exactly(
hash_including(message: 'Reset password token is no longer valid.', extensions: { code: 'USER_ERROR' })
)
end
end
end

context 'when reset password token is not found' do
let(:token) { user.send(:set_reset_password_token) + 'invalid' }

it 'redirects to redirect url' do
get_request

expect(json_response[:errors]).to contain_exactly(
hash_including(message: 'No user found for the specified reset token.', extensions: { code: 'USER_ERROR' })
)
end
end
end
4 changes: 4 additions & 0 deletions spec/support/contexts/graphql_request.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,8 @@
def post_request
post '/api/v1/graphql_auth', *graphql_params
end

def get_request
get '/api/v1/graphql_auth', *graphql_params
end
end

0 comments on commit b3ca980

Please sign in to comment.