From d6324c4dfba42845914d303ed83aecb892cdb945 Mon Sep 17 00:00:00 2001 From: Aaron A <_aaron@tutanota.com> Date: Wed, 30 Oct 2019 22:25:36 -0700 Subject: [PATCH 01/14] Updated documentation for the mutation and query graphql methods. --- README.md | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 46992d4d..1c2cc557 100644 --- a/README.md +++ b/README.md @@ -156,21 +156,24 @@ The install generator can do this for you because it executes DTA installer. See [Installation](#Installation) for details. ### Making Requests -Here is a list of the available mutations and queries assuming your mounted model -is `User`. +Here is a list of the available mutations and queriess assuming your mounted model is `User`. #### Mutations -1. userLogin -1. userLogout -1. userSignUp -1. userUpdatePassword -1. userSendPasswordReset +1. `userLogin(email: String!, password: String!): UserLoginPayload` +1. `userLogout: UserLogoutPayload` +1. `userSignUp(email: String!, password: String!, passwordConfirmation: String!, confirmSuccessUrl: String): UserSignUpPayload` + + The parameter `confirmSuccessUrl` is optional unless you are using the `confirmable` plugin from Devise in your `resource`'s model. If you have `confirmable` set up, you will have to provide it unless you have `config.default_confirm_success_url` set in `config/initializers/devise_token_auth.rb`. +1. `userUpdatePassword(password: String!, passwordConfirmation: String!, currentPassword: String): UserUpdatePasswordPayload` + + The parameter `currentPassword` is optional if you have `config.check_current_password_before_update` set to false (disabled by default) or the `resource` model supports the `recoverable` Devise plugin and the `resource`'s `allow_password_change` attribute is set to true. +1. `userSendResetPassword(email: String!, redirectUrl: String!): UserSendReserPasswordPayload` #### Queries -1. userConfirmAccount -1. userCheckPasswordToken +1. `userConfirmAccount(confirmationToken: String!, redirectUrl: String!): User` +1. `userCheckPasswordToken(resetPasswordToken: String!, redirectUrl: String): User` -The reason for having 2 queries is that these 2 are going to be accessed when clicking on +The reason for having two queries is that these two are going to be accessed when clicking on the confirmation and reset password email urls. There is no limitation for making mutation requests using the `GET` method on the Rails side, but looks like there might be a limitation on the [Apollo Client](https://www.apollographql.com/docs/apollo-server/v1/requests/#get-requests). From 898a6529a43032ba0df6380c11941dbbe9dd7754 Mon Sep 17 00:00:00 2001 From: Aaron A <_aaron@tutanota.com> Date: Wed, 30 Oct 2019 22:35:07 -0700 Subject: [PATCH 02/14] Added ResendConfirmation GraphQL method. --- README.md | 25 +++++---- .../mutations/resend_confirmation.rb | 30 +++++++++++ .../mailer/confirmation_instructions.html.erb | 2 +- config/locales/en.yml | 7 +++ lib/graphql_devise/rails/routes.rb | 3 +- .../mutations/resend_confirmation_spec.rb | 51 +++++++++++++++++++ 6 files changed, 106 insertions(+), 12 deletions(-) create mode 100644 app/graphql/graphql_devise/mutations/resend_confirmation.rb create mode 100644 spec/requests/mutations/resend_confirmation_spec.rb diff --git a/README.md b/README.md index 46992d4d..77a7172e 100644 --- a/README.md +++ b/README.md @@ -156,21 +156,26 @@ The install generator can do this for you because it executes DTA installer. See [Installation](#Installation) for details. ### Making Requests -Here is a list of the available mutations and queries assuming your mounted model -is `User`. +Here is a list of the available mutations and queriess assuming your mounted model is `User`. #### Mutations -1. userLogin -1. userLogout -1. userSignUp -1. userUpdatePassword -1. userSendPasswordReset +1. `userLogin(email: String!, password: String!): UserLoginPayload` +1. `userLogout: UserLogoutPayload` +1. `userSignUp(email: String!, password: String!, passwordConfirmation: String!, confirmSuccessUrl: String): UserSignUpPayload` + The parameter `confirmSuccessUrl` is optional unless you are using the `confirmable` plugin from Devise in your `resource`'s model. If you have `confirmable` set up, you will have to provide it unless you have `config.default_confirm_success_url` set in `config/initializers/devise_token_auth.rb`. +1. `userUpdatePassword(password: String!, passwordConfirmation: String!, currentPassword: String): UserUpdatePasswordPayload` + + The parameter `currentPassword` is optional if you have `config.check_current_password_before_update` set to false (disabled by default) or the `resource` model supports the `recoverable` Devise plugin and the `resource`'s `allow_password_change` attribute is set to true. +1. `userSendResetPassword(email: String!, redirectUrl: String!): UserSendReserPasswordPayload` +1. `userResendConfirmation(email: String!, redirectUrl: String!): UserResendConfirmationPayload` + + The `UserResendConfirmationPayload` will return the `authenticable` resource that was sent the confirmation instructions but also has a `message: String!` that can be used to notify a user what to do after the instructions were sent to them and a `success: Boolean!` to indicate success. #### Queries -1. userConfirmAccount -1. userCheckPasswordToken +1. `userConfirmAccount(confirmationToken: String!, redirectUrl: String!): User` +1. `userCheckPasswordToken(resetPasswordToken: String!, redirectUrl: String): User` -The reason for having 2 queries is that these 2 are going to be accessed when clicking on +The reason for having two queries is that these two are going to be accessed when clicking on the confirmation and reset password email urls. There is no limitation for making mutation requests using the `GET` method on the Rails side, but looks like there might be a limitation on the [Apollo Client](https://www.apollographql.com/docs/apollo-server/v1/requests/#get-requests). diff --git a/app/graphql/graphql_devise/mutations/resend_confirmation.rb b/app/graphql/graphql_devise/mutations/resend_confirmation.rb new file mode 100644 index 00000000..17075cd0 --- /dev/null +++ b/app/graphql/graphql_devise/mutations/resend_confirmation.rb @@ -0,0 +1,30 @@ +module GraphqlDevise + module Mutations + class ResendConfirmation < Base + argument :email, String, required: true, prepare: ->(email, _) { email.downcase } + argument :redirect_url, String, required: true + + field :success, Boolean, null: false + field :message, String, null: false + + def resolve(email:, redirect_url:) + resource = controller.find_resource(:uid, email) + + if resource + yield resource if block_given? + resource.send_confirmation_instructions({ + redirect_url: redirect_url, + template_path: ['graphql_devise/mailer'] + }) + + { + success: true, + message: I18n.t('graphql_devise.confirmations.send_instructions', email: email) + } + else + raise_user_error(I18n.t('graphql_devise.confirmations.user_not_found', email: email)) + end + end + end + end +end diff --git a/app/views/graphql_devise/mailer/confirmation_instructions.html.erb b/app/views/graphql_devise/mailer/confirmation_instructions.html.erb index bc4e4b8b..e2e75806 100644 --- a/app/views/graphql_devise/mailer/confirmation_instructions.html.erb +++ b/app/views/graphql_devise/mailer/confirmation_instructions.html.erb @@ -1,5 +1,5 @@

<%= t(:welcome).capitalize + ' ' + @email %>!

-

<%= t '.confirm_link_msg' %>

+

<%= t '.confirm_link_msg' %>

<%= link_to t('.confirm_account_link'), url_for(controller: 'graphql_devise/graphql', action: :auth, **confirmation_query(resource_name: @resource.class.to_s, redirect_url: message['redirect-url'], token: @token)) %>

diff --git a/config/locales/en.yml b/config/locales/en.yml index d2155c4d..13f92ac1 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -19,6 +19,13 @@ en: 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" confirmations: invalid_token: "Invalid confirmation token. Please try again" + user_not_found: "Unable to find user with email '%{email}'." + sended: "An email has been sent to '%{email}' containing instructions for confirming your account." + send_instructions: "You will receive an email with instructions for how to confirm your email address in a few minutes." + send_paranoid_instructions: "If your email address exists in our database, you will receive an email with instructions for how to confirm your email address in a few minutes." mailer: + confirmation_instructions: + confirm_link_msg: "You can confirm your account email through the link below:" + confirm_account_link: "Confirm my account" unlock_instructions: account_lock_msg: "Your account has been locked due to an excessive number of unsuccessful sign in attempts." diff --git a/lib/graphql_devise/rails/routes.rb b/lib/graphql_devise/rails/routes.rb index 246a2b9b..ad4c7de1 100644 --- a/lib/graphql_devise/rails/routes.rb +++ b/lib/graphql_devise/rails/routes.rb @@ -14,7 +14,8 @@ def mount_graphql_devise_for(resource, opts = {}) logout: GraphqlDevise::Mutations::Logout, sign_up: GraphqlDevise::Mutations::SignUp, update_password: GraphqlDevise::Mutations::UpdatePassword, - send_password_reset: GraphqlDevise::Mutations::SendPasswordReset + send_password_reset: GraphqlDevise::Mutations::SendPasswordReset, + resend_confirmation: GraphqlDevise::Mutations::ResendConfirmation }.freeze default_queries = { confirm_account: GraphqlDevise::Resolvers::ConfirmAccount, diff --git a/spec/requests/mutations/resend_confirmation_spec.rb b/spec/requests/mutations/resend_confirmation_spec.rb new file mode 100644 index 00000000..81030dd2 --- /dev/null +++ b/spec/requests/mutations/resend_confirmation_spec.rb @@ -0,0 +1,51 @@ +require 'rails_helper' + +RSpec.describe 'Resend confirmation' do + include_context 'with graphql query request' + + let(:user) { create(:user, confirmed_at: nil) } + let(:email) { user.email } + let(:redirect) { Faker::Internet.url } + let(:query) do + <<-GRAPHQL + mutation { + userResendConfirmation( + email:"#{email}", + redirectUrl:"#{redirect}" + ) { + message + success + } + } + GRAPHQL + end + + context 'when params are correct' do + it 'sends an email to the user with confirm url' do + expect { post_request }.to( + change(ActionMailer::Base.deliveries, :count).by(1) + ) + + email = Nokogiri::HTML(ActionMailer::Base.deliveries.last.body.encoded) + link = email.css('a').first + + # TODO: Move to feature spec + expect do + get link['href'] + user.reload + end.to change(user, :confirmed_at).from(NilClass).to(Time) + end + end + + context 'when the email isn''t in the system' do + let(:email) { 'nothere@gmail.com' } + before { post_request } + + it 'returns an error' do + expect(json_response[:errors]).to contain_exactly( + hash_including( + message: "Unable to find user with email '#{email}'.", extensions: { code: 'USER_ERROR' }) + ) + end + end +end \ No newline at end of file From 7e27c659f6f7c0ab541d3d2b6260e27749e4349d Mon Sep 17 00:00:00 2001 From: Aaron A <_aaron@tutanota.com> Date: Thu, 31 Oct 2019 17:17:30 -0700 Subject: [PATCH 03/14] Fixed a misspelling --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1c2cc557..984ea0d0 100644 --- a/README.md +++ b/README.md @@ -156,7 +156,7 @@ The install generator can do this for you because it executes DTA installer. See [Installation](#Installation) for details. ### Making Requests -Here is a list of the available mutations and queriess assuming your mounted model is `User`. +Here is a list of the available mutations and queries assuming your mounted model is `User`. #### Mutations 1. `userLogin(email: String!, password: String!): UserLoginPayload` From ac9c1ab3f91fd5070b140af50dcf459a75fea174 Mon Sep 17 00:00:00 2001 From: David Revelo <00dav00@users.noreply.github.com> Date: Mon, 4 Nov 2019 06:35:02 -0500 Subject: [PATCH 04/14] Add github templates for issues (#36) --- .github/ISSUE_TEMPLATE/bug_report.md | 81 +++++++++++++++++++++++++++ .github/ISSUE_TEMPLATE/enhancement.md | 38 +++++++++++++ .github/ISSUE_TEMPLATE/question.md | 16 ++++++ 3 files changed, 135 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/enhancement.md create mode 100644 .github/ISSUE_TEMPLATE/question.md diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 00000000..083faa5e --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,81 @@ +--- +name: Bug report +about: Report a bug in the gem +labels: 'issue: bug, needs triage' +--- + + + +### Describe the bug + +(Describe the problem you are experiencing here.) + +### Environment + + + +(paste the link(s) to the Gemfile and Gemfile.lock files.) + +### Steps to reproduce + + + +(Write your steps here:) + +1. +2. +3. + +### Expected behavior + + + +(Write what you thought would happen.) + +### Actual behavior + + + +(Write what happened. Please add stacktraces or http responses!) + +### Reproducible demo + + + +(Paste the link to an example project and exact instructions to reproduce the issue.) + + diff --git a/.github/ISSUE_TEMPLATE/enhancement.md b/.github/ISSUE_TEMPLATE/enhancement.md new file mode 100644 index 00000000..287030f2 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/enhancement.md @@ -0,0 +1,38 @@ +--- +name: Enhancement +about: Suggest an idea for improving the gem +labels: 'issue: enhancement' +--- + +### What is the problem the enhancement will solve? + + + +(Write the problem here.) + +### Describe the solution you have in mind + + + +(Describe your proposed solution here.) + +### Describe alternatives you've considered + + + +(Write your findings here.) + +### Additional context + + + +(Write your answer here.) diff --git a/.github/ISSUE_TEMPLATE/question.md b/.github/ISSUE_TEMPLATE/question.md new file mode 100644 index 00000000..a9f4aac9 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/question.md @@ -0,0 +1,16 @@ +--- +name: Question +about: Inquiries about the gem +labels: 'issue: question' +--- + + +### Question + + + +(Write your question here.) From 1e09349ebbc36b1d1057c91bcfa814064db25d6c Mon Sep 17 00:00:00 2001 From: Aaron A <_aaron@tutanota.com> Date: Mon, 4 Nov 2019 09:43:46 -0800 Subject: [PATCH 05/14] Merged master into resend_confirmation. --- README.md | 15 ++++++ .../mutations/resend_confirmation.rb | 30 +++++++++++ .../mailer/confirmation_instructions.html.erb | 2 +- config/locales/en.yml | 7 +++ lib/graphql_devise/rails/routes.rb | 3 +- .../mutations/resend_confirmation_spec.rb | 51 +++++++++++++++++++ 6 files changed, 106 insertions(+), 2 deletions(-) create mode 100644 app/graphql/graphql_devise/mutations/resend_confirmation.rb create mode 100644 spec/requests/mutations/resend_confirmation_spec.rb diff --git a/README.md b/README.md index 984ea0d0..d28a0f18 100644 --- a/README.md +++ b/README.md @@ -156,19 +156,34 @@ The install generator can do this for you because it executes DTA installer. See [Installation](#Installation) for details. ### Making Requests +<<<<<<< HEAD Here is a list of the available mutations and queries assuming your mounted model is `User`. +======= +Here is a list of the available mutations and queriess assuming your mounted model is `User`. +>>>>>>> Added ResendConfirmation GraphQL method. #### Mutations 1. `userLogin(email: String!, password: String!): UserLoginPayload` 1. `userLogout: UserLogoutPayload` 1. `userSignUp(email: String!, password: String!, passwordConfirmation: String!, confirmSuccessUrl: String): UserSignUpPayload` +<<<<<<< HEAD The parameter `confirmSuccessUrl` is optional unless you are using the `confirmable` plugin from Devise in your `resource`'s model. If you have `confirmable` set up, you will have to provide it unless you have `config.default_confirm_success_url` set in `config/initializers/devise_token_auth.rb`. 1. `userUpdatePassword(password: String!, passwordConfirmation: String!, currentPassword: String): UserUpdatePasswordPayload` The parameter `currentPassword` is optional if you have `config.check_current_password_before_update` set to false (disabled by default) or the `resource` model supports the `recoverable` Devise plugin and the `resource`'s `allow_password_change` attribute is set to true. 1. `userSendResetPassword(email: String!, redirectUrl: String!): UserSendReserPasswordPayload` +======= +>>>>>>> Added ResendConfirmation GraphQL method. + The parameter `confirmSuccessUrl` is optional unless you are using the `confirmable` plugin from Devise in your `resource`'s model. If you have `confirmable` set up, you will have to provide it unless you have `config.default_confirm_success_url` set in `config/initializers/devise_token_auth.rb`. +1. `userUpdatePassword(password: String!, passwordConfirmation: String!, currentPassword: String): UserUpdatePasswordPayload` + + The parameter `currentPassword` is optional if you have `config.check_current_password_before_update` set to false (disabled by default) or the `resource` model supports the `recoverable` Devise plugin and the `resource`'s `allow_password_change` attribute is set to true. +1. `userSendResetPassword(email: String!, redirectUrl: String!): UserSendReserPasswordPayload` +1. `userResendConfirmation(email: String!, redirectUrl: String!): UserResendConfirmationPayload` + + The `UserResendConfirmationPayload` will return the `authenticable` resource that was sent the confirmation instructions but also has a `message: String!` that can be used to notify a user what to do after the instructions were sent to them and a `success: Boolean!` to indicate success. #### Queries 1. `userConfirmAccount(confirmationToken: String!, redirectUrl: String!): User` 1. `userCheckPasswordToken(resetPasswordToken: String!, redirectUrl: String): User` diff --git a/app/graphql/graphql_devise/mutations/resend_confirmation.rb b/app/graphql/graphql_devise/mutations/resend_confirmation.rb new file mode 100644 index 00000000..17075cd0 --- /dev/null +++ b/app/graphql/graphql_devise/mutations/resend_confirmation.rb @@ -0,0 +1,30 @@ +module GraphqlDevise + module Mutations + class ResendConfirmation < Base + argument :email, String, required: true, prepare: ->(email, _) { email.downcase } + argument :redirect_url, String, required: true + + field :success, Boolean, null: false + field :message, String, null: false + + def resolve(email:, redirect_url:) + resource = controller.find_resource(:uid, email) + + if resource + yield resource if block_given? + resource.send_confirmation_instructions({ + redirect_url: redirect_url, + template_path: ['graphql_devise/mailer'] + }) + + { + success: true, + message: I18n.t('graphql_devise.confirmations.send_instructions', email: email) + } + else + raise_user_error(I18n.t('graphql_devise.confirmations.user_not_found', email: email)) + end + end + end + end +end diff --git a/app/views/graphql_devise/mailer/confirmation_instructions.html.erb b/app/views/graphql_devise/mailer/confirmation_instructions.html.erb index bc4e4b8b..e2e75806 100644 --- a/app/views/graphql_devise/mailer/confirmation_instructions.html.erb +++ b/app/views/graphql_devise/mailer/confirmation_instructions.html.erb @@ -1,5 +1,5 @@

<%= t(:welcome).capitalize + ' ' + @email %>!

-

<%= t '.confirm_link_msg' %>

+

<%= t '.confirm_link_msg' %>

<%= link_to t('.confirm_account_link'), url_for(controller: 'graphql_devise/graphql', action: :auth, **confirmation_query(resource_name: @resource.class.to_s, redirect_url: message['redirect-url'], token: @token)) %>

diff --git a/config/locales/en.yml b/config/locales/en.yml index d2155c4d..13f92ac1 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -19,6 +19,13 @@ en: 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" confirmations: invalid_token: "Invalid confirmation token. Please try again" + user_not_found: "Unable to find user with email '%{email}'." + sended: "An email has been sent to '%{email}' containing instructions for confirming your account." + send_instructions: "You will receive an email with instructions for how to confirm your email address in a few minutes." + send_paranoid_instructions: "If your email address exists in our database, you will receive an email with instructions for how to confirm your email address in a few minutes." mailer: + confirmation_instructions: + confirm_link_msg: "You can confirm your account email through the link below:" + confirm_account_link: "Confirm my account" unlock_instructions: account_lock_msg: "Your account has been locked due to an excessive number of unsuccessful sign in attempts." diff --git a/lib/graphql_devise/rails/routes.rb b/lib/graphql_devise/rails/routes.rb index 246a2b9b..ad4c7de1 100644 --- a/lib/graphql_devise/rails/routes.rb +++ b/lib/graphql_devise/rails/routes.rb @@ -14,7 +14,8 @@ def mount_graphql_devise_for(resource, opts = {}) logout: GraphqlDevise::Mutations::Logout, sign_up: GraphqlDevise::Mutations::SignUp, update_password: GraphqlDevise::Mutations::UpdatePassword, - send_password_reset: GraphqlDevise::Mutations::SendPasswordReset + send_password_reset: GraphqlDevise::Mutations::SendPasswordReset, + resend_confirmation: GraphqlDevise::Mutations::ResendConfirmation }.freeze default_queries = { confirm_account: GraphqlDevise::Resolvers::ConfirmAccount, diff --git a/spec/requests/mutations/resend_confirmation_spec.rb b/spec/requests/mutations/resend_confirmation_spec.rb new file mode 100644 index 00000000..81030dd2 --- /dev/null +++ b/spec/requests/mutations/resend_confirmation_spec.rb @@ -0,0 +1,51 @@ +require 'rails_helper' + +RSpec.describe 'Resend confirmation' do + include_context 'with graphql query request' + + let(:user) { create(:user, confirmed_at: nil) } + let(:email) { user.email } + let(:redirect) { Faker::Internet.url } + let(:query) do + <<-GRAPHQL + mutation { + userResendConfirmation( + email:"#{email}", + redirectUrl:"#{redirect}" + ) { + message + success + } + } + GRAPHQL + end + + context 'when params are correct' do + it 'sends an email to the user with confirm url' do + expect { post_request }.to( + change(ActionMailer::Base.deliveries, :count).by(1) + ) + + email = Nokogiri::HTML(ActionMailer::Base.deliveries.last.body.encoded) + link = email.css('a').first + + # TODO: Move to feature spec + expect do + get link['href'] + user.reload + end.to change(user, :confirmed_at).from(NilClass).to(Time) + end + end + + context 'when the email isn''t in the system' do + let(:email) { 'nothere@gmail.com' } + before { post_request } + + it 'returns an error' do + expect(json_response[:errors]).to contain_exactly( + hash_including( + message: "Unable to find user with email '#{email}'.", extensions: { code: 'USER_ERROR' }) + ) + end + end +end \ No newline at end of file From 083570f471459956a1fc37fba3c7cc9be39e0aac Mon Sep 17 00:00:00 2001 From: Aaron A <_aaron@tutanota.com> Date: Wed, 30 Oct 2019 22:35:07 -0700 Subject: [PATCH 06/14] Added ResendConfirmation GraphQL method. --- README.md | 7 ------- 1 file changed, 7 deletions(-) diff --git a/README.md b/README.md index d28a0f18..447ed5bd 100644 --- a/README.md +++ b/README.md @@ -156,25 +156,18 @@ The install generator can do this for you because it executes DTA installer. See [Installation](#Installation) for details. ### Making Requests -<<<<<<< HEAD Here is a list of the available mutations and queries assuming your mounted model is `User`. -======= -Here is a list of the available mutations and queriess assuming your mounted model is `User`. ->>>>>>> Added ResendConfirmation GraphQL method. #### Mutations 1. `userLogin(email: String!, password: String!): UserLoginPayload` 1. `userLogout: UserLogoutPayload` 1. `userSignUp(email: String!, password: String!, passwordConfirmation: String!, confirmSuccessUrl: String): UserSignUpPayload` -<<<<<<< HEAD The parameter `confirmSuccessUrl` is optional unless you are using the `confirmable` plugin from Devise in your `resource`'s model. If you have `confirmable` set up, you will have to provide it unless you have `config.default_confirm_success_url` set in `config/initializers/devise_token_auth.rb`. 1. `userUpdatePassword(password: String!, passwordConfirmation: String!, currentPassword: String): UserUpdatePasswordPayload` The parameter `currentPassword` is optional if you have `config.check_current_password_before_update` set to false (disabled by default) or the `resource` model supports the `recoverable` Devise plugin and the `resource`'s `allow_password_change` attribute is set to true. 1. `userSendResetPassword(email: String!, redirectUrl: String!): UserSendReserPasswordPayload` -======= ->>>>>>> Added ResendConfirmation GraphQL method. The parameter `confirmSuccessUrl` is optional unless you are using the `confirmable` plugin from Devise in your `resource`'s model. If you have `confirmable` set up, you will have to provide it unless you have `config.default_confirm_success_url` set in `config/initializers/devise_token_auth.rb`. 1. `userUpdatePassword(password: String!, passwordConfirmation: String!, currentPassword: String): UserUpdatePasswordPayload` From 537e3f275d966f27a418cba681e85ee6b77c288a Mon Sep 17 00:00:00 2001 From: Aaron A <_aaron@tutanota.com> Date: Mon, 4 Nov 2019 09:50:46 -0800 Subject: [PATCH 07/14] Pulled upstream master and then rebased master on send_confirmation --- README.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/README.md b/README.md index 60b4cdc8..23e592c0 100644 --- a/README.md +++ b/README.md @@ -172,15 +172,12 @@ Here is a list of the available mutations and queriess assuming your mounted mod The parameter `currentPassword` is optional if you have `config.check_current_password_before_update` set to false (disabled by default) or the `resource` model supports the `recoverable` Devise plugin and the `resource`'s `allow_password_change` attribute is set to true. 1. `userSendResetPassword(email: String!, redirectUrl: String!): UserSendReserPasswordPayload` -<<<<<<< HEAD The parameter `confirmSuccessUrl` is optional unless you are using the `confirmable` plugin from Devise in your `resource`'s model. If you have `confirmable` set up, you will have to provide it unless you have `config.default_confirm_success_url` set in `config/initializers/devise_token_auth.rb`. 1. `userUpdatePassword(password: String!, passwordConfirmation: String!, currentPassword: String): UserUpdatePasswordPayload` The parameter `currentPassword` is optional if you have `config.check_current_password_before_update` set to false (disabled by default) or the `resource` model supports the `recoverable` Devise plugin and the `resource`'s `allow_password_change` attribute is set to true. 1. `userSendResetPassword(email: String!, redirectUrl: String!): UserSendReserPasswordPayload` -======= ->>>>>>> 898a6529a43032ba0df6380c11941dbbe9dd7754 1. `userResendConfirmation(email: String!, redirectUrl: String!): UserResendConfirmationPayload` The `UserResendConfirmationPayload` will return the `authenticable` resource that was sent the confirmation instructions but also has a `message: String!` that can be used to notify a user what to do after the instructions were sent to them and a `success: Boolean!` to indicate success. From 223621646c71becced5b56fecfc770cc2a537a57 Mon Sep 17 00:00:00 2001 From: Aaron A <_aaron@tutanota.com> Date: Mon, 4 Nov 2019 09:57:10 -0800 Subject: [PATCH 08/14] Pulled upstream master and rebased master in resend_confirmation --- README.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/README.md b/README.md index 23e592c0..447ed5bd 100644 --- a/README.md +++ b/README.md @@ -156,11 +156,7 @@ The install generator can do this for you because it executes DTA installer. See [Installation](#Installation) for details. ### Making Requests -<<<<<<< HEAD Here is a list of the available mutations and queries assuming your mounted model is `User`. -======= -Here is a list of the available mutations and queriess assuming your mounted model is `User`. ->>>>>>> 898a6529a43032ba0df6380c11941dbbe9dd7754 #### Mutations 1. `userLogin(email: String!, password: String!): UserLoginPayload` From d1539bf3d4814a332c469e0b4dd2cb8e9bcd4668 Mon Sep 17 00:00:00 2001 From: Aaron A <_aaron@tutanota.com> Date: Mon, 4 Nov 2019 10:30:10 -0800 Subject: [PATCH 09/14] Removed rebased code to seperate the Resend Confirmation changes. --- .github/ISSUE_TEMPLATE/bug_report.md | 81 --------------------------- .github/ISSUE_TEMPLATE/enhancement.md | 38 ------------- .github/ISSUE_TEMPLATE/question.md | 16 ------ README.md | 28 ++++----- 4 files changed, 10 insertions(+), 153 deletions(-) delete mode 100644 .github/ISSUE_TEMPLATE/bug_report.md delete mode 100644 .github/ISSUE_TEMPLATE/enhancement.md delete mode 100644 .github/ISSUE_TEMPLATE/question.md diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index 083faa5e..00000000 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,81 +0,0 @@ ---- -name: Bug report -about: Report a bug in the gem -labels: 'issue: bug, needs triage' ---- - - - -### Describe the bug - -(Describe the problem you are experiencing here.) - -### Environment - - - -(paste the link(s) to the Gemfile and Gemfile.lock files.) - -### Steps to reproduce - - - -(Write your steps here:) - -1. -2. -3. - -### Expected behavior - - - -(Write what you thought would happen.) - -### Actual behavior - - - -(Write what happened. Please add stacktraces or http responses!) - -### Reproducible demo - - - -(Paste the link to an example project and exact instructions to reproduce the issue.) - - diff --git a/.github/ISSUE_TEMPLATE/enhancement.md b/.github/ISSUE_TEMPLATE/enhancement.md deleted file mode 100644 index 287030f2..00000000 --- a/.github/ISSUE_TEMPLATE/enhancement.md +++ /dev/null @@ -1,38 +0,0 @@ ---- -name: Enhancement -about: Suggest an idea for improving the gem -labels: 'issue: enhancement' ---- - -### What is the problem the enhancement will solve? - - - -(Write the problem here.) - -### Describe the solution you have in mind - - - -(Describe your proposed solution here.) - -### Describe alternatives you've considered - - - -(Write your findings here.) - -### Additional context - - - -(Write your answer here.) diff --git a/.github/ISSUE_TEMPLATE/question.md b/.github/ISSUE_TEMPLATE/question.md deleted file mode 100644 index a9f4aac9..00000000 --- a/.github/ISSUE_TEMPLATE/question.md +++ /dev/null @@ -1,16 +0,0 @@ ---- -name: Question -about: Inquiries about the gem -labels: 'issue: question' ---- - - -### Question - - - -(Write your question here.) diff --git a/README.md b/README.md index 447ed5bd..d2fac95e 100644 --- a/README.md +++ b/README.md @@ -159,29 +159,21 @@ See [Installation](#Installation) for details. Here is a list of the available mutations and queries assuming your mounted model is `User`. #### Mutations -1. `userLogin(email: String!, password: String!): UserLoginPayload` -1. `userLogout: UserLogoutPayload` -1. `userSignUp(email: String!, password: String!, passwordConfirmation: String!, confirmSuccessUrl: String): UserSignUpPayload` - - The parameter `confirmSuccessUrl` is optional unless you are using the `confirmable` plugin from Devise in your `resource`'s model. If you have `confirmable` set up, you will have to provide it unless you have `config.default_confirm_success_url` set in `config/initializers/devise_token_auth.rb`. -1. `userUpdatePassword(password: String!, passwordConfirmation: String!, currentPassword: String): UserUpdatePasswordPayload` - - The parameter `currentPassword` is optional if you have `config.check_current_password_before_update` set to false (disabled by default) or the `resource` model supports the `recoverable` Devise plugin and the `resource`'s `allow_password_change` attribute is set to true. -1. `userSendResetPassword(email: String!, redirectUrl: String!): UserSendReserPasswordPayload` - - The parameter `confirmSuccessUrl` is optional unless you are using the `confirmable` plugin from Devise in your `resource`'s model. If you have `confirmable` set up, you will have to provide it unless you have `config.default_confirm_success_url` set in `config/initializers/devise_token_auth.rb`. -1. `userUpdatePassword(password: String!, passwordConfirmation: String!, currentPassword: String): UserUpdatePasswordPayload` - - The parameter `currentPassword` is optional if you have `config.check_current_password_before_update` set to false (disabled by default) or the `resource` model supports the `recoverable` Devise plugin and the `resource`'s `allow_password_change` attribute is set to true. -1. `userSendResetPassword(email: String!, redirectUrl: String!): UserSendReserPasswordPayload` +1. userLogin +1. userLogout +1. userSignUp +1. userUpdatePassword +1. userSendResetPassword +1. userUpdatePassword +1. userSendResetPassword 1. `userResendConfirmation(email: String!, redirectUrl: String!): UserResendConfirmationPayload` The `UserResendConfirmationPayload` will return the `authenticable` resource that was sent the confirmation instructions but also has a `message: String!` that can be used to notify a user what to do after the instructions were sent to them and a `success: Boolean!` to indicate success. #### Queries -1. `userConfirmAccount(confirmationToken: String!, redirectUrl: String!): User` -1. `userCheckPasswordToken(resetPasswordToken: String!, redirectUrl: String): User` +1. userConfirmAccount +1. userCheckPasswordToken -The reason for having two queries is that these two are going to be accessed when clicking on +The reason for having 2 queries is that these 2 are going to be accessed when clicking on the confirmation and reset password email urls. There is no limitation for making mutation requests using the `GET` method on the Rails side, but looks like there might be a limitation on the [Apollo Client](https://www.apollographql.com/docs/apollo-server/v1/requests/#get-requests). From 53e7bd4c8f41b6b6b11f890c606ba669ef44eb33 Mon Sep 17 00:00:00 2001 From: Aaron A <_aaron@tutanota.com> Date: Mon, 4 Nov 2019 10:36:53 -0800 Subject: [PATCH 10/14] Removed rebased code to seperate the Resend Confirmation changes. --- README.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index d2fac95e..f697ffa8 100644 --- a/README.md +++ b/README.md @@ -156,16 +156,15 @@ The install generator can do this for you because it executes DTA installer. See [Installation](#Installation) for details. ### Making Requests -Here is a list of the available mutations and queries assuming your mounted model is `User`. +Here is a list of the available mutations and queries assuming your mounted model +is `User`. #### Mutations 1. userLogin 1. userLogout 1. userSignUp 1. userUpdatePassword -1. userSendResetPassword -1. userUpdatePassword -1. userSendResetPassword +1. userSendPasswordReset 1. `userResendConfirmation(email: String!, redirectUrl: String!): UserResendConfirmationPayload` The `UserResendConfirmationPayload` will return the `authenticable` resource that was sent the confirmation instructions but also has a `message: String!` that can be used to notify a user what to do after the instructions were sent to them and a `success: Boolean!` to indicate success. From 0ced80c5b164ecfcf7398e078ea472fcd1baa430 Mon Sep 17 00:00:00 2001 From: Aaron A <_aaron@tutanota.com> Date: Sat, 9 Nov 2019 12:14:24 -0800 Subject: [PATCH 11/14] Code review fixes and test enhancements per @00dav00's request. --- .../mutations/resend_confirmation.rb | 9 ++-- config/locales/en.yml | 3 -- .../mutations/resend_confirmation_spec.rb | 52 +++++++++++++++---- 3 files changed, 48 insertions(+), 16 deletions(-) diff --git a/app/graphql/graphql_devise/mutations/resend_confirmation.rb b/app/graphql/graphql_devise/mutations/resend_confirmation.rb index 17075cd0..f5363dd7 100644 --- a/app/graphql/graphql_devise/mutations/resend_confirmation.rb +++ b/app/graphql/graphql_devise/mutations/resend_confirmation.rb @@ -4,7 +4,7 @@ class ResendConfirmation < Base argument :email, String, required: true, prepare: ->(email, _) { email.downcase } argument :redirect_url, String, required: true - field :success, Boolean, null: false + field :authenticable, User, null: false field :message, String, null: false def resolve(email:, redirect_url:) @@ -12,14 +12,17 @@ def resolve(email:, redirect_url:) if resource yield resource if block_given? + + raise_user_error("Email #{I18n.t('errors.messages.already_confirmed')}") if resource.confirmed? + resource.send_confirmation_instructions({ redirect_url: redirect_url, template_path: ['graphql_devise/mailer'] }) { - success: true, - message: I18n.t('graphql_devise.confirmations.send_instructions', email: email) + authenticable: resource, + message: I18n.t('devise.confirmations.send_instructions', email: email) } else raise_user_error(I18n.t('graphql_devise.confirmations.user_not_found', email: email)) diff --git a/config/locales/en.yml b/config/locales/en.yml index 13f92ac1..ec737016 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -20,9 +20,6 @@ en: confirmations: invalid_token: "Invalid confirmation token. Please try again" user_not_found: "Unable to find user with email '%{email}'." - sended: "An email has been sent to '%{email}' containing instructions for confirming your account." - send_instructions: "You will receive an email with instructions for how to confirm your email address in a few minutes." - send_paranoid_instructions: "If your email address exists in our database, you will receive an email with instructions for how to confirm your email address in a few minutes." mailer: confirmation_instructions: confirm_link_msg: "You can confirm your account email through the link below:" diff --git a/spec/requests/mutations/resend_confirmation_spec.rb b/spec/requests/mutations/resend_confirmation_spec.rb index 81030dd2..24c612c2 100644 --- a/spec/requests/mutations/resend_confirmation_spec.rb +++ b/spec/requests/mutations/resend_confirmation_spec.rb @@ -5,6 +5,7 @@ let(:user) { create(:user, confirmed_at: nil) } let(:email) { user.email } + let(:id) { user.id } let(:redirect) { Faker::Internet.url } let(:query) do <<-GRAPHQL @@ -14,38 +15,69 @@ redirectUrl:"#{redirect}" ) { message - success + authenticable { + id + email + } } } GRAPHQL end context 'when params are correct' do - it 'sends an email to the user with confirm url' do - expect { post_request }.to( - change(ActionMailer::Base.deliveries, :count).by(1) + it 'sends an email to the user with confirmation url and returns a success message' do + expect { post_request }.to(change(ActionMailer::Base.deliveries, :count).by(1)) + expect(json_response[:data][:userResendConfirmation]).to include( + authenticable: { + id: id, + email: email + }, + message: "You will receive an email with instructions for how to confirm your email address in a few minutes." ) - + email = Nokogiri::HTML(ActionMailer::Base.deliveries.last.body.encoded) link = email.css('a').first + confirm_link_msg_text = email.css('p')[1].inner_html + confirm_account_link_text = link.inner_html + + expect(confirm_link_msg_text).to eq("You can confirm your account email through the link below:") + expect(confirm_account_link_text).to eq("Confirm my account") # TODO: Move to feature spec expect do get link['href'] user.reload - end.to change(user, :confirmed_at).from(NilClass).to(Time) + end.to change(user, :confirmed_at).from(NilClass).to(ActiveSupport::TimeWithZone) + end + + context 'when the user has already been confirmed' do + before { user.confirm } + + it 'does *NOT* send an email and raises an error' do + expect { post_request }.to(change(ActionMailer::Base.deliveries, :count).by(0)) + expect(json_response[:data][:userResendConfirmation]).to be_nil + expect(json_response[:errors]).to contain_exactly( + hash_including( + message: "Email was already confirmed, please try signing in", + extensions: { code: 'USER_ERROR' } + ) + ) + end end end context 'when the email isn''t in the system' do let(:email) { 'nothere@gmail.com' } - before { post_request } - it 'returns an error' do + it 'does *NOT* send an email and raises an error' do + expect { post_request }.to(change(ActionMailer::Base.deliveries, :count).by(0)) + expect(json_response[:data][:userResendConfirmation]).to be_nil expect(json_response[:errors]).to contain_exactly( hash_including( - message: "Unable to find user with email '#{email}'.", extensions: { code: 'USER_ERROR' }) + message: "Unable to find user with email '#{email}'.", + extensions: { code: 'USER_ERROR' } + ) ) end end -end \ No newline at end of file +end From 4e83a8bb39afbe20bfab9d7f38acb4703ccbc84f Mon Sep 17 00:00:00 2001 From: Aaron A <_aaron@tutanota.com> Date: Thu, 21 Nov 2019 15:52:57 -0800 Subject: [PATCH 12/14] Code review requests per @mcelicalderon and @00dav00 --- .../graphql_devise/mutations/resend_confirmation.rb | 1 - spec/requests/mutations/resend_confirmation_spec.rb | 8 ++++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/app/graphql/graphql_devise/mutations/resend_confirmation.rb b/app/graphql/graphql_devise/mutations/resend_confirmation.rb index f5363dd7..a0e1a5b0 100644 --- a/app/graphql/graphql_devise/mutations/resend_confirmation.rb +++ b/app/graphql/graphql_devise/mutations/resend_confirmation.rb @@ -4,7 +4,6 @@ class ResendConfirmation < Base argument :email, String, required: true, prepare: ->(email, _) { email.downcase } argument :redirect_url, String, required: true - field :authenticable, User, null: false field :message, String, null: false def resolve(email:, redirect_url:) diff --git a/spec/requests/mutations/resend_confirmation_spec.rb b/spec/requests/mutations/resend_confirmation_spec.rb index 24c612c2..4888644b 100644 --- a/spec/requests/mutations/resend_confirmation_spec.rb +++ b/spec/requests/mutations/resend_confirmation_spec.rb @@ -26,7 +26,7 @@ context 'when params are correct' do it 'sends an email to the user with confirmation url and returns a success message' do - expect { post_request }.to(change(ActionMailer::Base.deliveries, :count).by(1)) + expect { post_request }.to change(ActionMailer::Base.deliveries, :count).by 1 expect(json_response[:data][:userResendConfirmation]).to include( authenticable: { id: id, @@ -54,7 +54,7 @@ before { user.confirm } it 'does *NOT* send an email and raises an error' do - expect { post_request }.to(change(ActionMailer::Base.deliveries, :count).by(0)) + expect { post_request }.to change(ActionMailer::Base.deliveries, :count).by 0 expect(json_response[:data][:userResendConfirmation]).to be_nil expect(json_response[:errors]).to contain_exactly( hash_including( @@ -66,11 +66,11 @@ end end - context 'when the email isn''t in the system' do + context "when the email isn't in the system" do let(:email) { 'nothere@gmail.com' } it 'does *NOT* send an email and raises an error' do - expect { post_request }.to(change(ActionMailer::Base.deliveries, :count).by(0)) + expect { post_request }.to not_change(ActionMailer::Base.deliveries, :count) expect(json_response[:data][:userResendConfirmation]).to be_nil expect(json_response[:errors]).to contain_exactly( hash_including( From 58014a44f82daaef16d4e0917d98ac00369320de Mon Sep 17 00:00:00 2001 From: Aaron A <_aaron@tutanota.com> Date: Fri, 22 Nov 2019 16:08:48 -0800 Subject: [PATCH 13/14] Moved reliance for Resend Instructions from Devise to Graphql Devise. --- app/graphql/graphql_devise/mutations/resend_confirmation.rb | 4 ++-- config/locales/en.yml | 4 ++++ spec/dummy/config/locales/devise.en.yml | 1 - 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/app/graphql/graphql_devise/mutations/resend_confirmation.rb b/app/graphql/graphql_devise/mutations/resend_confirmation.rb index a0e1a5b0..f9c591ae 100644 --- a/app/graphql/graphql_devise/mutations/resend_confirmation.rb +++ b/app/graphql/graphql_devise/mutations/resend_confirmation.rb @@ -12,7 +12,7 @@ def resolve(email:, redirect_url:) if resource yield resource if block_given? - raise_user_error("Email #{I18n.t('errors.messages.already_confirmed')}") if resource.confirmed? + raise_user_error(I18n.t('errors.messages.already_confirmed')) if resource.confirmed? resource.send_confirmation_instructions({ redirect_url: redirect_url, @@ -21,7 +21,7 @@ def resolve(email:, redirect_url:) { authenticable: resource, - message: I18n.t('devise.confirmations.send_instructions', email: email) + message: I18n.t('graphql_devise.confirmations.send_instructions', email: email) } else raise_user_error(I18n.t('graphql_devise.confirmations.user_not_found', email: email)) diff --git a/config/locales/en.yml b/config/locales/en.yml index ec737016..217525d2 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -20,9 +20,13 @@ en: confirmations: invalid_token: "Invalid confirmation token. Please try again" user_not_found: "Unable to find user with email '%{email}'." + send_instructions: "You will receive an email with instructions for how to confirm your email address in a few minutes." mailer: confirmation_instructions: confirm_link_msg: "You can confirm your account email through the link below:" confirm_account_link: "Confirm my account" unlock_instructions: account_lock_msg: "Your account has been locked due to an excessive number of unsuccessful sign in attempts." + errors: + messages: + already_confirmed: "Email was already confirmed, please try signing in" diff --git a/spec/dummy/config/locales/devise.en.yml b/spec/dummy/config/locales/devise.en.yml index ca3d6199..3c527373 100644 --- a/spec/dummy/config/locales/devise.en.yml +++ b/spec/dummy/config/locales/devise.en.yml @@ -55,7 +55,6 @@ en: unlocked: "Your account has been unlocked successfully. Please sign in to continue." errors: messages: - already_confirmed: "was already confirmed, please try signing in" confirmation_period_expired: "needs to be confirmed within %{period}, please request a new one" expired: "has expired, please request a new one" not_found: "not found" From 247819e17d8d377a0acb4ee56760456afce55877 Mon Sep 17 00:00:00 2001 From: Aaron A <_aaron@tutanota.com> Date: Fri, 22 Nov 2019 16:36:51 -0800 Subject: [PATCH 14/14] Some RSpec styling changes per @mcelicalderon's request. --- spec/requests/mutations/resend_confirmation_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/requests/mutations/resend_confirmation_spec.rb b/spec/requests/mutations/resend_confirmation_spec.rb index 4888644b..8dcf5b04 100644 --- a/spec/requests/mutations/resend_confirmation_spec.rb +++ b/spec/requests/mutations/resend_confirmation_spec.rb @@ -26,7 +26,7 @@ context 'when params are correct' do it 'sends an email to the user with confirmation url and returns a success message' do - expect { post_request }.to change(ActionMailer::Base.deliveries, :count).by 1 + expect { post_request }.to change(ActionMailer::Base.deliveries, :count).by(1) expect(json_response[:data][:userResendConfirmation]).to include( authenticable: { id: id, @@ -54,7 +54,7 @@ before { user.confirm } it 'does *NOT* send an email and raises an error' do - expect { post_request }.to change(ActionMailer::Base.deliveries, :count).by 0 + expect { post_request }.to not_change(ActionMailer::Base.deliveries, :count) expect(json_response[:data][:userResendConfirmation]).to be_nil expect(json_response[:errors]).to contain_exactly( hash_including(