diff --git a/README.md b/README.md index 0590d1e..ec6ae99 100644 --- a/README.md +++ b/README.md @@ -100,6 +100,9 @@ GraphQL::Auth.configure do |config| # config.app_url = ENV['APP_URL'] # config.user_type = '::Types::Auth::User' + # config.sign_up_input_type = '::Types::Auth::Inputs::SignUp' + # config.update_account_input_type = '::Types::Auth::Inputs::UpdateAccount' + # config.update_password_input_type = '::Types::Auth::Inputs::UpdatePassword' # Devise allowed actions # Don't forget to enable the lockable setting in your Devise user model if you plan on using the lock_account feature diff --git a/app/graphql/mutations/auth/sign_up.rb b/app/graphql/mutations/auth/sign_up.rb index cd90cc2..242d272 100644 --- a/app/graphql/mutations/auth/sign_up.rb +++ b/app/graphql/mutations/auth/sign_up.rb @@ -3,25 +3,17 @@ class Mutations::Auth::SignUp < GraphQL::Schema::Mutation include ::Graphql::TokenHelper - argument :email, String, required: true do - description "New user's email" - end - - argument :password, String, required: true do - description "New user's password" - end - - argument :password_confirmation, String, required: true do - description "New user's password confirmation" + argument :input, GraphQL::Auth.configuration.sign_up_input_type.constantize, required: true do + description "Sign up input" end field :errors, [::Types::Auth::Error], null: false field :success, Boolean, null: false field :user, GraphQL::Auth.configuration.user_type.constantize, null: true - def resolve(args) + def resolve(input:) response = context[:response] - user = User.new args + user = User.new input.to_h if user.save generate_access_token(user, response) diff --git a/app/graphql/mutations/auth/update_account.rb b/app/graphql/mutations/auth/update_account.rb index 368487b..b31ecad 100644 --- a/app/graphql/mutations/auth/update_account.rb +++ b/app/graphql/mutations/auth/update_account.rb @@ -1,23 +1,15 @@ # frozen_string_literal: true class Mutations::Auth::UpdateAccount < GraphQL::Schema::Mutation - argument :current_password, String, required: true do - description "User's current password" - end - - argument :password, String, required: true do - description "User's new password" - end - - argument :password_confirmation, String, required: true do - description "User's new password confirmation" + argument :input, GraphQL::Auth.configuration.update_account_input_type.constantize, required: true do + description "Update account input" end field :errors, [::Types::Auth::Error], null: false field :success, Boolean, null: false field :user, GraphQL::Auth.configuration.user_type.constantize, null: true - def resolve(args) + def resolve(input:) user = context[:current_user] if user.blank? @@ -30,7 +22,7 @@ def resolve(args) } end - user.update_with_password args + user.update_without_password input.to_h if user.errors.any? { diff --git a/app/graphql/mutations/auth/update_password.rb b/app/graphql/mutations/auth/update_password.rb new file mode 100644 index 0000000..f021cc2 --- /dev/null +++ b/app/graphql/mutations/auth/update_password.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +class Mutations::Auth::UpdatePassword < GraphQL::Schema::Mutation + argument :input, GraphQL::Auth.configuration.update_password_input_type.constantize, required: true do + description "Update password input" + end + + field :errors, [::Types::Auth::Error], null: false + field :success, Boolean, null: false + field :user, GraphQL::Auth.configuration.user_type.constantize, null: true + + def resolve(input:) + user = context[:current_user] + + if user.blank? + return { + errors: [ + { field: :_error, message: I18n.t('devise.failure.unauthenticated') } + ], + success: false, + user: nil + } + end + + user.update_with_password input.to_h + + if user.errors.any? + { + errors: user.errors.messages.map do |field, messages| + { field: field.to_s.camelize(:lower), message: messages.first.capitalize } + end, + success: false, + user: nil + } + else + { + errors: [], + success: true, + user: user + } + end + end +end diff --git a/app/graphql/types/auth/inputs/sign_up.rb b/app/graphql/types/auth/inputs/sign_up.rb new file mode 100644 index 0000000..f50f825 --- /dev/null +++ b/app/graphql/types/auth/inputs/sign_up.rb @@ -0,0 +1,16 @@ +class Types::Auth::Inputs::SignUp < Types::BaseInputObject + graphql_name 'SignUpInput' + description 'Sign up arguments' + + argument :email, String, required: true do + description "New user's email" + end + + argument :password, String, required: true do + description "New user's password" + end + + argument :password_confirmation, String, required: true do + description "New user's password confirmation" + end +end diff --git a/app/graphql/types/auth/inputs/update_account.rb b/app/graphql/types/auth/inputs/update_account.rb new file mode 100644 index 0000000..fa7dda6 --- /dev/null +++ b/app/graphql/types/auth/inputs/update_account.rb @@ -0,0 +1,8 @@ +class Types::Auth::Inputs::UpdateAccount < Types::BaseInputObject + graphql_name 'UpdateAccountInput' + description 'Update account arguments' + + argument :email, String, required: true do + description "User's email" + end +end diff --git a/app/graphql/types/auth/inputs/update_password.rb b/app/graphql/types/auth/inputs/update_password.rb new file mode 100644 index 0000000..4b7d332 --- /dev/null +++ b/app/graphql/types/auth/inputs/update_password.rb @@ -0,0 +1,16 @@ +class Types::Auth::Inputs::UpdatePassword < Types::BaseInputObject + graphql_name 'UpdatePasswordInput' + description 'Update password arguments' + + argument :current_password, String, required: true do + description "User's current password" + end + + argument :password, String, required: true do + description "User's new password" + end + + argument :password_confirmation, String, required: true do + description "User's new password confirmation" + end +end diff --git a/app/graphql/types/graphql_auth.rb b/app/graphql/types/graphql_auth.rb index 85bea84..9cbcfa7 100644 --- a/app/graphql/types/graphql_auth.rb +++ b/app/graphql/types/graphql_auth.rb @@ -11,6 +11,7 @@ module Types::GraphqlAuth field :forgot_password, mutation: ::Mutations::Auth::ForgotPassword field :reset_password, mutation: ::Mutations::Auth::ResetPassword + field :update_password, mutation: ::Mutations::Auth::UpdatePassword field :update_account, mutation: GraphQL::Auth.configuration.update_account_mutation.constantize @@ -23,4 +24,4 @@ module Types::GraphqlAuth if GraphQL::Auth.configuration.allow_unlock_account field :unlock_account, mutation: Mutations::Auth::UnlockAccount end -end \ No newline at end of file +end diff --git a/app/helpers/graphql/auth_helper.rb b/app/helpers/graphql/auth_helper.rb index daa3f23..8e8a1a3 100644 --- a/app/helpers/graphql/auth_helper.rb +++ b/app/helpers/graphql/auth_helper.rb @@ -21,6 +21,7 @@ def current_user decrypted_token = GraphQL::Auth::JwtManager.decode(authorization_token) user = User.find_by id: decrypted_token['user'] + return nil if user.blank? || account_locked?(user) # update token if user is found with token diff --git a/lib/generators/graphql_auth/templates/graphql_auth.rb.erb b/lib/generators/graphql_auth/templates/graphql_auth.rb.erb index 6dcd457..32c9d81 100644 --- a/lib/generators/graphql_auth/templates/graphql_auth.rb.erb +++ b/lib/generators/graphql_auth/templates/graphql_auth.rb.erb @@ -4,6 +4,9 @@ GraphQL::Auth.configure do |config| # config.app_url = ENV['APP_URL'] # config.user_type = '::Types::Auth::User' + # config.sign_up_input_type = '::Types::Auth::Inputs::SignUp' + # config.update_account_input_type = '::Types::Auth::Inputs::UpdateAccount' + # config.update_password_input_type = '::Types::Auth::Inputs::UpdatePassword' # Devise allowed actions # Don't forget to enable the lockable setting in your Devise user model if you plan on using the lock_account feature diff --git a/lib/graphql-auth/configuration.rb b/lib/graphql-auth/configuration.rb index 6a8b781..34af553 100644 --- a/lib/graphql-auth/configuration.rb +++ b/lib/graphql-auth/configuration.rb @@ -5,6 +5,9 @@ class Configuration :jwt_secret_key, :app_url, :user_type, + :sign_up_input_type, + :update_account_input_type, + :update_password_input_type, :allow_sign_up, :allow_lock_account, :allow_unlock_account, @@ -17,6 +20,9 @@ def initialize @app_url = ENV['APP_URL'] @user_type = '::Types::Auth::User' + @sign_up_input_type = '::Types::Auth::Inputs::SignUp' + @update_account_input_type = '::Types::Auth::Inputs::UpdateAccount' + @update_password_input_type = '::Types::Auth::Inputs::UpdatePassword' # Devise allowed actions @allow_sign_up = true diff --git a/lib/graphql-auth/version.rb b/lib/graphql-auth/version.rb index bd1f349..b3f9e9e 100644 --- a/lib/graphql-auth/version.rb +++ b/lib/graphql-auth/version.rb @@ -2,4 +2,4 @@ module GraphQL module Auth VERSION = '0.6.1' end -end \ No newline at end of file +end diff --git a/spec/dummy/config/initializers/graphql_auth.rb b/spec/dummy/config/initializers/graphql_auth.rb index 40920c5..8b47d26 100644 --- a/spec/dummy/config/initializers/graphql_auth.rb +++ b/spec/dummy/config/initializers/graphql_auth.rb @@ -4,14 +4,17 @@ # config.app_url = ENV['APP_URL'] # config.user_type = '::Types::Auth::User' + # config.sign_up_input_type = '::Types::Auth::Inputs::SignUp' + # config.update_account_input_type = '::Types::Auth::Inputs::UpdateAccount' + # config.update_password_input_type = '::Types::Auth::Inputs::UpdatePassword' # Devise allowed actions # Don't forget to enable the lockable setting in your Devise user model if you plan on using the lock_account feature - config.allow_sign_up = true + # config.allow_sign_up = true config.allow_lock_account = true config.allow_unlock_account = true # Allow custom mutations for signup and update account # config.sign_up_mutation = '::Mutations::Auth::SignUp' - # config.udpate_account_mutation = '::Mutations::Auth::UpdateAccount' -end \ No newline at end of file + # config.update_account_mutation = '::Mutations::Auth::UpdateAccount' +end diff --git a/spec/graphql/mutations/auth/sign_up_spec.rb b/spec/graphql/mutations/auth/sign_up_spec.rb index ec5da41..a09e818 100644 --- a/spec/graphql/mutations/auth/sign_up_spec.rb +++ b/spec/graphql/mutations/auth/sign_up_spec.rb @@ -13,8 +13,8 @@ let(:query_string) do <<-GRAPHQL - mutation($email: String!, $password: String!, $passwordConfirmation: String!) { - signUp(email: $email, password: $password, passwordConfirmation: $passwordConfirmation) { + mutation($input: SignUpInput!) { + signUp(input: $input) { success user { email @@ -40,9 +40,11 @@ context 'when valid parameters are given' do let(:variables) do { - 'email' => 'email@example.com', - 'password' => 'password', - 'passwordConfirmation' => 'password' + 'input' => { + 'email' => 'email@example.com', + 'password' => 'password', + 'passwordConfirmation' => 'password' + } } end @@ -55,9 +57,11 @@ context 'when invalid parameters are given' do let(:variables) do { - 'email' => 'emailexample.com', - 'password' => 'password', - 'passwordConfirmation' => 'password2' + 'input' => { + 'email' => 'email@example.com', + 'password' => 'password', + 'passwordConfirmation' => 'password2' + } } end diff --git a/spec/graphql/mutations/auth/update_account_spec.rb b/spec/graphql/mutations/auth/update_account_spec.rb deleted file mode 100644 index f7b6a1b..0000000 --- a/spec/graphql/mutations/auth/update_account_spec.rb +++ /dev/null @@ -1,157 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe Mutations::Auth::UpdateAccount, type: :request do - let!(:user) do - User.create!( - email: 'email@example.com', - password: 'password' - ) - end - - let(:result) do - GraphqlSchema.execute( - query_string, - variables: variables, - context: context - ) - end - - let(:query_string) do - <<-GRAPHQL - mutation($currentPassword: String!, $password: String!, $passwordConfirmation: String!) { - updateAccount(currentPassword: $currentPassword, password: $password, passwordConfirmation: $passwordConfirmation) { - success - user { - email - } - errors { - field - message - } - } - } - GRAPHQL - end - - subject { result } - - context 'when the user is logged in' do - let(:context) do - { - current_user: user, - response: ResponseMock.new(headers: {}), - } - end - - context 'when valid parameters are given' do - let(:variables) do - { - 'currentPassword' => 'password', - 'password' => 'newpassword', - 'passwordConfirmation' => 'newpassword' - } - end - - it 'succeeds to update the account' do - subject - - expect(result['data']['updateAccount']['success']).to be_truthy - expect(result['data']['updateAccount']['user']['email']).to eq(user.email) - end - end - - context 'when invalid parameters are given' do - let(:variables) do - { - 'currentPassword' => 'badpassword', - 'password' => 'newpassword', - 'passwordConfirmation' => 'newpassword' - } - end - - it 'fails to update the account' do - subject - expect(result['data']['updateAccount']['success']).to be_falsey - end - end - - context 'when user is locked' do - before do - user.lock_access! - end - - let(:variables) do - { - 'currentPassword' => 'badpassword', - 'password' => 'newpassword', - 'passwordConfirmation' => 'newpassword' - } - end - - it 'fails to update the account' do - subject - expect(result['data']['updateAccount']['success']).to be_falsey - end - end - end - - context 'when the user not is logged in' do - let(:context) do - { - current_user: nil, - response: ResponseMock.new(headers: {}), - } - end - - context 'when valid parameters are given' do - let(:variables) do - { - 'currentPassword' => 'password', - 'password' => 'newpassword', - 'passwordConfirmation' => 'newpassword' - } - end - - it 'fails to update the account' do - subject - expect(result['data']['updateAccount']['success']).to be_falsey - end - end - - context 'when invalid parameters are given' do - let(:variables) do - { - 'currentPassword' => 'badpassword', - 'password' => 'newpassword', - 'passwordConfirmation' => 'newpassword' - } - end - - it 'fails to update the account' do - subject - expect(result['data']['updateAccount']['success']).to be_falsey - end - end - - context 'when user is locked' do - before do - user.lock_access! - end - - let(:variables) do - { - 'currentPassword' => 'badpassword', - 'password' => 'newpassword', - 'passwordConfirmation' => 'newpassword' - } - end - - it 'fails to update the account' do - subject - expect(result['data']['updateAccount']['success']).to be_falsey - end - end - end -end diff --git a/spec/graphql/mutations/auth/update_password_spec.rb b/spec/graphql/mutations/auth/update_password_spec.rb new file mode 100644 index 0000000..e10247a --- /dev/null +++ b/spec/graphql/mutations/auth/update_password_spec.rb @@ -0,0 +1,148 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Mutations::Auth::UpdatePassword, type: :request do + let!(:user) do + User.create!( + email: 'email@example.com', + password: 'password' + ) + end + + let(:result) do + GraphqlSchema.execute( + query_string, + variables: variables, + context: context + ) + end + + let(:query_string) do + <<-GRAPHQL + mutation($input: UpdatePasswordInput!) { + updatePassword(input: $input) { + success + user { + email + } + errors { + field + message + } + } + } + GRAPHQL + end + + subject { result } + + context 'when the user is logged in' do + let(:context) do + { + current_user: user, + response: ResponseMock.new(headers: {}), + } + end + + context 'when valid parameters are given' do + let(:variables) do + { + 'input' => { + 'currentPassword' => 'password', + 'password' => 'newpassword', + 'passwordConfirmation' => 'newpassword' + } + } + end + + it 'succeeds to update the password' do + subject + + expect(result['data']['updatePassword']['success']).to be_truthy + expect(result['data']['updatePassword']['user']['email']).to eq(user.email) + end + end + + context 'when invalid parameters are given' do + let(:variables) do + { + 'input' => { + 'currentPassword' => 'badpassword', + 'password' => 'newpassword', + 'passwordConfirmation' => 'newpassword' + } + } + end + + it 'fails to update the password' do + subject + expect(result['data']['updatePassword']['success']).to be_falsey + end + end + end + + context 'when the user not is logged in' do + let(:context) do + { + current_user: nil, + response: ResponseMock.new(headers: {}), + } + end + + context 'when valid parameters are given' do + let(:variables) do + { + 'input' => { + 'currentPassword' => 'password', + 'password' => 'newpassword', + 'passwordConfirmation' => 'newpassword' + } + } + end + + it 'fails to update the password' do + subject + expect(result['data']['updatePassword']['success']).to be_falsey + end + end + + context 'when invalid parameters are given' do + let(:variables) do + { + 'input' => { + 'currentPassword' => 'badpassword', + 'password' => 'newpassword', + 'passwordConfirmation' => 'newpassword' + } + } + end + + it 'fails to update the password' do + subject + expect(result['data']['updatePassword']['success']).to be_falsey + end + end + + context 'when user is locked' do + before do + user.lock_access! + end + + let(:variables) do + { + 'input' => { + 'currentPassword' => 'password', + 'password' => 'newpassword', + 'passwordConfirmation' => 'newpassword' + } + } + end + + it 'fails to update the password' do + subject + expect(result['data']['updatePassword']['success']).to be_falsey + end + end + end +end