Skip to content

Commit

Permalink
Add sendPasswordResetWithToken mutation
Browse files Browse the repository at this point in the history
  • Loading branch information
mcelicalderon committed Jan 19, 2021
1 parent 8d05630 commit 88598b8
Show file tree
Hide file tree
Showing 5 changed files with 132 additions and 9 deletions.
Expand Up @@ -2,7 +2,13 @@

<p><%= t('.request_reset_link_msg') %></p>

<p><%= link_to t('.password_change_link'), "#{message['schema_url']}?#{password_reset_query(token: @token, redirect_url: message['redirect-url'], resource_name: @resource.class.to_s).to_query}" %></p>
<p>
<% if message['schema_url'].present? %>
<%= link_to t('.password_change_link'), "#{message['schema_url']}?#{password_reset_query(token: @token, redirect_url: message['redirect-url'], resource_name: @resource.class.to_s).to_query}" %>
<% else %>
<%= link_to t('.password_change_link'), "#{message['redirect-url'].to_s}?#{{ reset_password_token: @token }.to_query}" %>
<% end %>
</p>

<p><%= t('.ignore_mail_msg') %></p>
<p><%= t('.no_changes_msg') %></p>
16 changes: 9 additions & 7 deletions lib/graphql_devise/default_operations/mutations.rb
Expand Up @@ -5,20 +5,22 @@
require 'graphql_devise/mutations/logout'
require 'graphql_devise/mutations/resend_confirmation'
require 'graphql_devise/mutations/send_password_reset'
require 'graphql_devise/mutations/send_password_reset_with_token'
require 'graphql_devise/mutations/sign_up'
require 'graphql_devise/mutations/update_password'
require 'graphql_devise/mutations/update_password_with_token'

module GraphqlDevise
module DefaultOperations
MUTATIONS = {
login: { klass: GraphqlDevise::Mutations::Login, authenticatable: true },
logout: { klass: GraphqlDevise::Mutations::Logout, authenticatable: true },
sign_up: { klass: GraphqlDevise::Mutations::SignUp, authenticatable: true },
update_password: { klass: GraphqlDevise::Mutations::UpdatePassword, authenticatable: true },
update_password_with_token: { klass: GraphqlDevise::Mutations::UpdatePasswordWithToken, authenticatable: true },
send_password_reset: { klass: GraphqlDevise::Mutations::SendPasswordReset, authenticatable: false },
resend_confirmation: { klass: GraphqlDevise::Mutations::ResendConfirmation, authenticatable: false }
login: { klass: GraphqlDevise::Mutations::Login, authenticatable: true },
logout: { klass: GraphqlDevise::Mutations::Logout, authenticatable: true },
sign_up: { klass: GraphqlDevise::Mutations::SignUp, authenticatable: true },
update_password: { klass: GraphqlDevise::Mutations::UpdatePassword, authenticatable: true },
update_password_with_token: { klass: GraphqlDevise::Mutations::UpdatePasswordWithToken, authenticatable: true },
send_password_reset: { klass: GraphqlDevise::Mutations::SendPasswordReset, authenticatable: false },
send_password_reset_with_token: { klass: GraphqlDevise::Mutations::SendPasswordResetWithToken, authenticatable: false },
resend_confirmation: { klass: GraphqlDevise::Mutations::ResendConfirmation, authenticatable: false }
}.freeze
end
end
37 changes: 37 additions & 0 deletions lib/graphql_devise/mutations/send_password_reset_with_token.rb
@@ -0,0 +1,37 @@
# frozen_string_literal: true

module GraphqlDevise
module Mutations
class SendPasswordResetWithToken < Base
argument :email, String, required: true
argument :redirect_url, String, required: true

field :message, String, null: false

def resolve(email:, redirect_url:)
check_redirect_url_whitelist!(redirect_url)

resource = find_resource(:email, get_case_insensitive_field(:email, email))

if resource
yield resource if block_given?

resource.send_reset_password_instructions(
email: email,
provider: 'email',
redirect_url: redirect_url,
template_path: ['graphql_devise/mailer']
)

if resource.errors.empty?
{ message: I18n.t('graphql_devise.passwords.send_instructions') }
else
raise_user_error_list(I18n.t('graphql_devise.invalid_resource'), errors: resource.errors.full_messages)
end
else
raise_user_error(I18n.t('graphql_devise.user_not_found'))
end
end
end
end
end
2 changes: 1 addition & 1 deletion lib/graphql_devise/mutations/update_password_with_token.rb
Expand Up @@ -10,7 +10,7 @@ class UpdatePasswordWithToken < Base
field :credentials,
GraphqlDevise::Types::CredentialType,
null: true,
description: 'Authentication credentials. Resource must be signed_in in order for credentials to be returned.'
description: 'Authentication credentials. Resource must be signed_in for credentials to be returned.'

def resolve(reset_password_token:, **attrs)
raise_user_error(I18n.t('graphql_devise.passwords.password_recovery_disabled')) unless recoverable_enabled?
Expand Down
78 changes: 78 additions & 0 deletions spec/requests/mutations/send_password_reset_with_token_spec.rb
@@ -0,0 +1,78 @@
# frozen_string_literal: true

require 'rails_helper'

RSpec.describe 'Send Password Reset Requests' do
include_context 'with graphql query request'

let!(:user) { create(:user, :confirmed, email: 'jwinnfield@wallaceinc.com') }
let(:email) { user.email }
let(:redirect_url) { 'https://google.com' }
let(:query) do
<<-GRAPHQL
mutation {
userSendPasswordResetWithToken(
email: "#{email}",
redirectUrl: "#{redirect_url}"
) {
message
}
}
GRAPHQL
end

context 'when redirect_url is not whitelisted' do
let(:redirect_url) { 'https://not-safe.com' }

it 'returns a not whitelisted redirect url error' do
expect { post_request }.to not_change(ActionMailer::Base.deliveries, :count)

expect(json_response[:errors]).to containing_exactly(
hash_including(
message: "Redirect to '#{redirect_url}' not allowed.",
extensions: { code: 'USER_ERROR' }
)
)
end
end

context 'when params are correct' do
context 'when using the gem schema' do
it 'sends password reset email' do
expect { post_request }.to change(ActionMailer::Base.deliveries, :count).by(1)

expect(json_response[:data][:userSendPasswordResetWithToken]).to include(
message: 'You will receive an email with instructions on how to reset your password in a few minutes.'
)

email = Nokogiri::HTML(ActionMailer::Base.deliveries.last.body.encoded)
link = email.css('a').first

expect(link['href']).to include(redirect_url + '?reset_password_token')
end
end
end

context 'when email address uses different casing' do
let(:email) { 'jWinnfield@wallaceinc.com' }

it 'honors devise configuration for case insensitive fields' do
expect { post_request }.to change(ActionMailer::Base.deliveries, :count).by(1)
expect(json_response[:data][:userSendPasswordResetWithToken]).to include(
message: 'You will receive an email with instructions on how to reset your password in a few minutes.'
)
end
end

context 'when user email is not found' do
let(:email) { 'nothere@gmail.com' }

before { post_request }

it 'returns an error' do
expect(json_response[:errors]).to contain_exactly(
hash_including(message: 'User was not found or was not logged in.', extensions: { code: 'USER_ERROR' })
)
end
end
end

0 comments on commit 88598b8

Please sign in to comment.