graphql_devise 0.13.6 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e90de970ae686dd8437156a6d830b922c1fe4369c10206532073e5bb3f8f75f8
4
- data.tar.gz: 3a74fe59c81889eb9f5a4bb42710d4cb7e086b9a9bdbd0e9bd09a370ccd7f435
3
+ metadata.gz: 623b4df681f9e3ae95598c67885cbc261d4e73c5d6bd2cb070ea9c39b4305dbb
4
+ data.tar.gz: 4efd72cc4b7b61d44b03cdf062db5a47c3bbce831c9101801b1a0a6d6af91701
5
5
  SHA512:
6
- metadata.gz: 0f608b88cf17acc4e8c4d7d54fb4d578afb38d2c7f7a73b9df2cee7b9661cdb6a35b1b45e4a6d7c05e022a334f6c7ed8bf1427b301422c2e27f191a830dde621
7
- data.tar.gz: 5d5bc1eab5158c5134f18a7f2f85e0653139ee13d67c45efb7050274d41ed6f1a5c2dee0c97c57ca987bd7f74556cbca9ae478b7782aee28f5967308d7bd3c92
6
+ metadata.gz: 0510a69ab752c9f5047f1d4dd3a7f6721c974b5d3af979b73cacd9a5456cd0cdc695766fce66e6b2ed70879a9faa24104fb71140ec12e900c51e4925bf4fc850
7
+ data.tar.gz: afb8d1f441dde3094e11f9ff12e9555d7d0ada530d086a3836f371855ce1f784e48be0e93d3c0c20bef5e0bae5328cd7a4e17168e747edb57a43b03fbfec5ee4
@@ -1,5 +1,13 @@
1
1
  # Changelog
2
2
 
3
+ ## [v0.14.0](https://github.com/graphql-devise/graphql_devise/tree/v0.14.0) (2021-01-19)
4
+
5
+ [Full Changelog](https://github.com/graphql-devise/graphql_devise/compare/v0.13.6...v0.14.0)
6
+
7
+ **Implemented enhancements:**
8
+
9
+ - Alternate reset password flow, only 2 steps, no redirect [\#146](https://github.com/graphql-devise/graphql_devise/pull/146) ([mcelicalderon](https://github.com/mcelicalderon))
10
+
3
11
  ## [v0.13.6](https://github.com/graphql-devise/graphql_devise/tree/v0.13.6) (2020-12-22)
4
12
 
5
13
  [Full Changelog](https://github.com/graphql-devise/graphql_devise/compare/v0.13.5...v0.13.6)
@@ -2,7 +2,13 @@
2
2
 
3
3
  <p><%= t('.request_reset_link_msg') %></p>
4
4
 
5
- <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>
5
+ <p>
6
+ <% if message['schema_url'].present? %>
7
+ <%= 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}" %>
8
+ <% else %>
9
+ <%= link_to t('.password_change_link'), "#{message['redirect-url'].to_s}?#{{ reset_password_token: @token }.to_query}" %>
10
+ <% end %>
11
+ </p>
6
12
 
7
13
  <p><%= t('.ignore_mail_msg') %></p>
8
14
  <p><%= t('.no_changes_msg') %></p>
@@ -9,6 +9,7 @@ en:
9
9
  registrations:
10
10
  missing_confirm_redirect_url: "Missing 'confirm_success_url' parameter. Required when confirmable module is enabled."
11
11
  passwords:
12
+ password_recovery_disabled: "You must enable password recovery for this model."
12
13
  update_password_error: "Unable to update user password"
13
14
  missing_passwords: "You must fill out the fields labeled 'Password' and 'Password confirmation'."
14
15
  password_not_required: "This account does not require a password. Sign in using your '%{provider}' account instead."
@@ -5,18 +5,22 @@ require 'graphql_devise/mutations/login'
5
5
  require 'graphql_devise/mutations/logout'
6
6
  require 'graphql_devise/mutations/resend_confirmation'
7
7
  require 'graphql_devise/mutations/send_password_reset'
8
+ require 'graphql_devise/mutations/send_password_reset_with_token'
8
9
  require 'graphql_devise/mutations/sign_up'
9
10
  require 'graphql_devise/mutations/update_password'
11
+ require 'graphql_devise/mutations/update_password_with_token'
10
12
 
11
13
  module GraphqlDevise
12
14
  module DefaultOperations
13
15
  MUTATIONS = {
14
- login: { klass: GraphqlDevise::Mutations::Login, authenticatable: true },
15
- logout: { klass: GraphqlDevise::Mutations::Logout, authenticatable: true },
16
- sign_up: { klass: GraphqlDevise::Mutations::SignUp, authenticatable: true },
17
- update_password: { klass: GraphqlDevise::Mutations::UpdatePassword, authenticatable: true },
18
- send_password_reset: { klass: GraphqlDevise::Mutations::SendPasswordReset, authenticatable: false },
19
- resend_confirmation: { klass: GraphqlDevise::Mutations::ResendConfirmation, authenticatable: false }
16
+ login: { klass: GraphqlDevise::Mutations::Login, authenticatable: true },
17
+ logout: { klass: GraphqlDevise::Mutations::Logout, authenticatable: true },
18
+ sign_up: { klass: GraphqlDevise::Mutations::SignUp, authenticatable: true },
19
+ update_password: { klass: GraphqlDevise::Mutations::UpdatePassword, authenticatable: true },
20
+ update_password_with_token: { klass: GraphqlDevise::Mutations::UpdatePasswordWithToken, authenticatable: true },
21
+ send_password_reset: { klass: GraphqlDevise::Mutations::SendPasswordReset, authenticatable: false },
22
+ send_password_reset_with_token: { klass: GraphqlDevise::Mutations::SendPasswordResetWithToken, authenticatable: false },
23
+ resend_confirmation: { klass: GraphqlDevise::Mutations::ResendConfirmation, authenticatable: false }
20
24
  }.freeze
21
25
  end
22
26
  end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphqlDevise
4
+ module Mutations
5
+ class SendPasswordResetWithToken < Base
6
+ argument :email, String, required: true
7
+ argument :redirect_url, String, required: true
8
+
9
+ field :message, String, null: false
10
+
11
+ def resolve(email:, redirect_url:)
12
+ check_redirect_url_whitelist!(redirect_url)
13
+
14
+ resource = find_resource(:email, get_case_insensitive_field(:email, email))
15
+
16
+ if resource
17
+ yield resource if block_given?
18
+
19
+ resource.send_reset_password_instructions(
20
+ email: email,
21
+ provider: 'email',
22
+ redirect_url: redirect_url,
23
+ template_path: ['graphql_devise/mailer']
24
+ )
25
+
26
+ if resource.errors.empty?
27
+ { message: I18n.t('graphql_devise.passwords.send_instructions') }
28
+ else
29
+ raise_user_error_list(I18n.t('graphql_devise.invalid_resource'), errors: resource.errors.full_messages)
30
+ end
31
+ else
32
+ raise_user_error(I18n.t('graphql_devise.user_not_found'))
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphqlDevise
4
+ module Mutations
5
+ class UpdatePasswordWithToken < Base
6
+ argument :password, String, required: true
7
+ argument :password_confirmation, String, required: true
8
+ argument :reset_password_token, String, required: true
9
+
10
+ field :credentials,
11
+ GraphqlDevise::Types::CredentialType,
12
+ null: true,
13
+ description: 'Authentication credentials. Resource must be signed_in for credentials to be returned.'
14
+
15
+ def resolve(reset_password_token:, **attrs)
16
+ raise_user_error(I18n.t('graphql_devise.passwords.password_recovery_disabled')) unless recoverable_enabled?
17
+
18
+ resource = resource_class.with_reset_password_token(reset_password_token)
19
+ raise_user_error(I18n.t('graphql_devise.passwords.reset_token_not_found')) if resource.blank?
20
+ raise_user_error(I18n.t('graphql_devise.passwords.reset_token_expired')) unless resource.reset_password_period_valid?
21
+
22
+ if resource.update(attrs)
23
+ yield resource if block_given?
24
+
25
+ response_payload = { authenticatable: resource }
26
+ response_payload[:credentials] = set_auth_headers(resource) if controller.signed_in?(resource_name)
27
+
28
+ response_payload
29
+ else
30
+ raise_user_error_list(
31
+ I18n.t('graphql_devise.passwords.update_password_error'),
32
+ errors: resource.errors.full_messages
33
+ )
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module GraphqlDevise
4
- VERSION = '0.13.6'.freeze
4
+ VERSION = '0.14.0'.freeze
5
5
  end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mutations
4
+ class ResetAdminPasswordWithToken < GraphqlDevise::Mutations::UpdatePasswordWithToken
5
+ field :authenticatable, Types::AdminType, null: false
6
+
7
+ def resolve(reset_password_token:, **attrs)
8
+ super do |admin|
9
+ controller.sign_in(admin)
10
+ end
11
+ end
12
+ end
13
+ end
@@ -15,7 +15,8 @@ Rails.application.routes.draw do
15
15
  authenticatable_type: Types::CustomAdminType,
16
16
  skip: [:sign_up, :check_password_token],
17
17
  operations: {
18
- confirm_account: Resolvers::ConfirmAdminAccount
18
+ confirm_account: Resolvers::ConfirmAdminAccount,
19
+ update_password_with_token: Mutations::ResetAdminPasswordWithToken
19
20
  },
20
21
  at: '/api/v1/admin/graphql_auth'
21
22
  )
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails_helper'
4
+
5
+ RSpec.describe 'Send Password Reset Requests' do
6
+ include_context 'with graphql query request'
7
+
8
+ let!(:user) { create(:user, :confirmed, email: 'jwinnfield@wallaceinc.com') }
9
+ let(:email) { user.email }
10
+ let(:redirect_url) { 'https://google.com' }
11
+ let(:query) do
12
+ <<-GRAPHQL
13
+ mutation {
14
+ userSendPasswordResetWithToken(
15
+ email: "#{email}",
16
+ redirectUrl: "#{redirect_url}"
17
+ ) {
18
+ message
19
+ }
20
+ }
21
+ GRAPHQL
22
+ end
23
+
24
+ context 'when redirect_url is not whitelisted' do
25
+ let(:redirect_url) { 'https://not-safe.com' }
26
+
27
+ it 'returns a not whitelisted redirect url error' do
28
+ expect { post_request }.to not_change(ActionMailer::Base.deliveries, :count)
29
+
30
+ expect(json_response[:errors]).to containing_exactly(
31
+ hash_including(
32
+ message: "Redirect to '#{redirect_url}' not allowed.",
33
+ extensions: { code: 'USER_ERROR' }
34
+ )
35
+ )
36
+ end
37
+ end
38
+
39
+ context 'when params are correct' do
40
+ context 'when using the gem schema' do
41
+ it 'sends password reset email' do
42
+ expect { post_request }.to change(ActionMailer::Base.deliveries, :count).by(1)
43
+
44
+ expect(json_response[:data][:userSendPasswordResetWithToken]).to include(
45
+ message: 'You will receive an email with instructions on how to reset your password in a few minutes.'
46
+ )
47
+
48
+ email = Nokogiri::HTML(ActionMailer::Base.deliveries.last.body.encoded)
49
+ link = email.css('a').first
50
+
51
+ expect(link['href']).to include(redirect_url + '?reset_password_token')
52
+ end
53
+ end
54
+ end
55
+
56
+ context 'when email address uses different casing' do
57
+ let(:email) { 'jWinnfield@wallaceinc.com' }
58
+
59
+ it 'honors devise configuration for case insensitive fields' do
60
+ expect { post_request }.to change(ActionMailer::Base.deliveries, :count).by(1)
61
+ expect(json_response[:data][:userSendPasswordResetWithToken]).to include(
62
+ message: 'You will receive an email with instructions on how to reset your password in a few minutes.'
63
+ )
64
+ end
65
+ end
66
+
67
+ context 'when user email is not found' do
68
+ let(:email) { 'nothere@gmail.com' }
69
+
70
+ before { post_request }
71
+
72
+ it 'returns an error' do
73
+ expect(json_response[:errors]).to contain_exactly(
74
+ hash_including(message: 'User was not found or was not logged in.', extensions: { code: 'USER_ERROR' })
75
+ )
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,119 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails_helper'
4
+
5
+ RSpec.describe 'Update Password With Token' do
6
+ include_context 'with graphql query request'
7
+
8
+ let(:password) { '12345678' }
9
+ let(:password_confirmation) { password }
10
+
11
+ context 'when using the user model' do
12
+ let(:user) { create(:user, :confirmed) }
13
+ let(:query) do
14
+ <<-GRAPHQL
15
+ mutation {
16
+ userUpdatePasswordWithToken(
17
+ resetPasswordToken: "#{token}",
18
+ password: "#{password}",
19
+ passwordConfirmation: "#{password_confirmation}"
20
+ ) {
21
+ authenticatable { email }
22
+ credentials { accessToken }
23
+ }
24
+ }
25
+ GRAPHQL
26
+ end
27
+
28
+ context 'when reset password token is valid' do
29
+ let(:token) { user.send(:set_reset_password_token) }
30
+
31
+ it 'updates the password' do
32
+ expect do
33
+ post_request
34
+ user.reload
35
+ end.to change(user, :encrypted_password)
36
+
37
+ expect(user).to be_valid_password(password)
38
+ expect(json_response[:data][:userUpdatePasswordWithToken][:credentials]).to be_nil
39
+ expect(json_response[:data][:userUpdatePasswordWithToken][:authenticatable]).to include(email: user.email)
40
+ end
41
+
42
+ context 'when token has expired' do
43
+ it 'returns an expired token error' do
44
+ travel_to 10.hours.ago do
45
+ token
46
+ end
47
+
48
+ post_request
49
+
50
+ expect(json_response[:errors]).to contain_exactly(
51
+ hash_including(message: 'Reset password token is no longer valid.', extensions: { code: 'USER_ERROR' })
52
+ )
53
+ end
54
+ end
55
+
56
+ context 'when password confirmation does not match' do
57
+ let(:password_confirmation) { 'does not match' }
58
+
59
+ it 'returns an error' do
60
+ post_request
61
+
62
+ expect(json_response[:errors]).to contain_exactly(
63
+ hash_including(
64
+ message: 'Unable to update user password',
65
+ extensions: { code: 'USER_ERROR', detailed_errors: ["Password confirmation doesn't match Password"] }
66
+ )
67
+ )
68
+ end
69
+ end
70
+ end
71
+
72
+ context 'when reset password token is not found' do
73
+ let(:token) { user.send(:set_reset_password_token) + 'invalid' }
74
+
75
+ it 'returns an error' do
76
+ post_request
77
+
78
+ expect(json_response[:errors]).to contain_exactly(
79
+ hash_including(message: 'No user found for the specified reset token.', extensions: { code: 'USER_ERROR' })
80
+ )
81
+ end
82
+ end
83
+ end
84
+
85
+ context 'when using the admin model' do
86
+ let(:admin) { create(:admin, :confirmed) }
87
+ let(:query) do
88
+ <<-GRAPHQL
89
+ mutation {
90
+ adminUpdatePasswordWithToken(
91
+ resetPasswordToken: "#{token}",
92
+ password: "#{password}",
93
+ passwordConfirmation: "#{password_confirmation}"
94
+ ) {
95
+ authenticatable { email }
96
+ credentials { uid }
97
+ }
98
+ }
99
+ GRAPHQL
100
+ end
101
+
102
+ context 'when reset password token is valid' do
103
+ let(:token) { admin.send(:set_reset_password_token) }
104
+
105
+ it 'updates the password' do
106
+ expect do
107
+ post_request
108
+ admin.reload
109
+ end.to change(admin, :encrypted_password)
110
+
111
+ expect(admin).to be_valid_password(password)
112
+ expect(json_response[:data][:adminUpdatePasswordWithToken]).to include(
113
+ credentials: { uid: admin.email },
114
+ authenticatable: { email: admin.email }
115
+ )
116
+ end
117
+ end
118
+ end
119
+ end
@@ -89,7 +89,7 @@ RSpec.describe 'Check Password Token Requests' do
89
89
  context 'when reset password token is not found' do
90
90
  let(:token) { user.send(:set_reset_password_token) + 'invalid' }
91
91
 
92
- it 'redirects to redirect url' do
92
+ it 'returns an error message' do
93
93
  get_request
94
94
 
95
95
  expect(json_response[:errors]).to contain_exactly(
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: graphql_devise
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.13.6
4
+ version: 0.14.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mario Celi
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: exe
11
11
  cert_chain: []
12
- date: 2020-12-22 00:00:00.000000000 Z
12
+ date: 2021-01-19 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: devise_token_auth
@@ -338,8 +338,10 @@ files:
338
338
  - lib/graphql_devise/mutations/logout.rb
339
339
  - lib/graphql_devise/mutations/resend_confirmation.rb
340
340
  - lib/graphql_devise/mutations/send_password_reset.rb
341
+ - lib/graphql_devise/mutations/send_password_reset_with_token.rb
341
342
  - lib/graphql_devise/mutations/sign_up.rb
342
343
  - lib/graphql_devise/mutations/update_password.rb
344
+ - lib/graphql_devise/mutations/update_password_with_token.rb
343
345
  - lib/graphql_devise/rails/routes.rb
344
346
  - lib/graphql_devise/resolvers/base.rb
345
347
  - lib/graphql_devise/resolvers/check_password_token.rb
@@ -362,6 +364,7 @@ files:
362
364
  - spec/dummy/app/graphql/interpreter_schema.rb
363
365
  - spec/dummy/app/graphql/mutations/login.rb
364
366
  - spec/dummy/app/graphql/mutations/register_confirmed_user.rb
367
+ - spec/dummy/app/graphql/mutations/reset_admin_password_with_token.rb
365
368
  - spec/dummy/app/graphql/mutations/sign_up.rb
366
369
  - spec/dummy/app/graphql/mutations/update_user.rb
367
370
  - spec/dummy/app/graphql/resolvers/confirm_admin_account.rb
@@ -439,8 +442,10 @@ files:
439
442
  - spec/requests/mutations/logout_spec.rb
440
443
  - spec/requests/mutations/resend_confirmation_spec.rb
441
444
  - spec/requests/mutations/send_password_reset_spec.rb
445
+ - spec/requests/mutations/send_password_reset_with_token_spec.rb
442
446
  - spec/requests/mutations/sign_up_spec.rb
443
447
  - spec/requests/mutations/update_password_spec.rb
448
+ - spec/requests/mutations/update_password_with_token_spec.rb
444
449
  - spec/requests/queries/check_password_token_spec.rb
445
450
  - spec/requests/queries/confirm_account_spec.rb
446
451
  - spec/requests/user_controller_spec.rb
@@ -505,6 +510,7 @@ test_files:
505
510
  - spec/dummy/app/graphql/interpreter_schema.rb
506
511
  - spec/dummy/app/graphql/mutations/login.rb
507
512
  - spec/dummy/app/graphql/mutations/register_confirmed_user.rb
513
+ - spec/dummy/app/graphql/mutations/reset_admin_password_with_token.rb
508
514
  - spec/dummy/app/graphql/mutations/sign_up.rb
509
515
  - spec/dummy/app/graphql/mutations/update_user.rb
510
516
  - spec/dummy/app/graphql/resolvers/confirm_admin_account.rb
@@ -582,8 +588,10 @@ test_files:
582
588
  - spec/requests/mutations/logout_spec.rb
583
589
  - spec/requests/mutations/resend_confirmation_spec.rb
584
590
  - spec/requests/mutations/send_password_reset_spec.rb
591
+ - spec/requests/mutations/send_password_reset_with_token_spec.rb
585
592
  - spec/requests/mutations/sign_up_spec.rb
586
593
  - spec/requests/mutations/update_password_spec.rb
594
+ - spec/requests/mutations/update_password_with_token_spec.rb
587
595
  - spec/requests/queries/check_password_token_spec.rb
588
596
  - spec/requests/queries/confirm_account_spec.rb
589
597
  - spec/requests/user_controller_spec.rb