Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions app/graphql/mutations/users/delete.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# frozen_string_literal: true

module Mutations
module Users
class Delete < BaseMutation
description 'Delete an existing user.'

field :user, Types::UserType, null: true, description: 'The deleted user.'

argument :user_id, Types::GlobalIdType[::User], required: true,
description: 'The user to delete.'

def resolve(user_id:)
user = SagittariusSchema.object_from_id(user_id)

if user.nil?
return { user: nil,
errors: [create_error(:user_not_found, 'Invalid user')] }
end

::Users::DeleteService.new(
current_authentication,
user
).execute.to_mutation_response(success_key: :user)
end
end
end
end
1 change: 1 addition & 0 deletions app/graphql/types/mutation_type.rb
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ class MutationType < Types::BaseObject
mount_mutation Mutations::Users::Mfa::BackupCodes::Rotate
mount_mutation Mutations::Users::Mfa::Totp::GenerateSecret
mount_mutation Mutations::Users::Mfa::Totp::ValidateSecret
mount_mutation Mutations::Users::Delete
mount_mutation Mutations::Users::EmailVerification
mount_mutation Mutations::Users::Login
mount_mutation Mutations::Users::Logout
Expand Down
1 change: 1 addition & 0 deletions app/models/audit_event.rb
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ class AuditEvent < ApplicationRecord
email_verified: 35,
password_reset_requested: 36,
password_reset: 37,
user_deleted: 38,
}.with_indifferent_access

# rubocop:disable Lint/StructNewOverride
Expand Down
1 change: 1 addition & 0 deletions app/policies/user_policy.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ class UserPolicy < BasePolicy
enable :read_user_identity
enable :update_attachment_avatar
enable :read_email
enable :delete_user
end

rule { user_is_self }.policy do
Expand Down
42 changes: 42 additions & 0 deletions app/services/users/delete_service.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# frozen_string_literal: true

module Users
class DeleteService
include Sagittarius::Database::Transactional

attr_reader :current_authentication, :user

def initialize(current_authentication, user)
@current_authentication = current_authentication
@user = user
end

def execute
unless Ability.allowed?(current_authentication, :delete_user, user)
return ServiceResponse.error(message: 'Missing permission', error_code: :missing_permission)
end

transactional do |t|
user.delete

if user.persisted?
t.rollback_and_return! ServiceResponse.error(
message: 'Failed to delete user',
error_code: :invalid_user,
details: user.errors
)
end

AuditService.audit(
:user_deleted,
author_id: current_authentication.user.id,
entity: user,
target: AuditEvent::GLOBAL_TARGET,
details: {}
)

ServiceResponse.success(message: 'Deleted user', payload: user)
end
end
end
end
20 changes: 20 additions & 0 deletions docs/graphql/mutation/usersdelete.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
---
title: usersDelete
---

Delete an existing user.

## Arguments

| Name | Type | Description |
|------|------|-------------|
| `clientMutationId` | [`String`](../scalar/string.md) | A unique identifier for the client performing the mutation. |
| `userId` | [`UserID!`](../scalar/userid.md) | The user to delete. |

## Fields

| Name | Type | Description |
|------|------|-------------|
| `clientMutationId` | [`String`](../scalar/string.md) | A unique identifier for the client performing the mutation. |
| `errors` | [`[Error!]!`](../object/error.md) | Errors encountered during execution of the mutation. |
| `user` | [`User`](../object/user.md) | The deleted user. |
7 changes: 7 additions & 0 deletions spec/graphql/mutations/users/delete_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# frozen_string_literal: true

require 'rails_helper'

RSpec.describe Mutations::Users::Delete do
it { expect(described_class.graphql_name).to eq('UsersDelete') }
end
63 changes: 63 additions & 0 deletions spec/requests/graphql/mutation/users/delete_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# frozen_string_literal: true

require 'rails_helper'

RSpec.describe 'usersDelete Mutation' do
include GraphqlHelpers

let(:mutation) do
<<~QUERY
mutation($input: UsersDeleteInput!) {
usersDelete(input: $input) {
#{error_query}
user {
id
username
admin
}
}
}
QUERY
end

let(:input) do
{
userId: user.to_global_id.to_s,
}
end

let(:user) { create(:user) }
let(:variables) { { input: input } }
let(:current_user) { create(:user, :admin) }

before do
post_graphql mutation, variables: variables, current_user: current_user
end

it 'deletes user' do
expect(graphql_data_at(:users_delete, :user, :id)).to be_present
expect(SagittariusSchema.object_from_id(graphql_data_at(:users_delete, :user, :id))).to be_nil

is_expected.to create_audit_event(
:user_deleted,
author_id: current_user.id,
entity_type: 'User',
entity_id: user.id,
details: {},
target_type: 'global',
target_id: 0
)
end

context 'when current user lacks permission' do
let(:current_user) { create(:user) }

it 'returns a missing permission error' do
expect(graphql_data_at(:users_delete, :user)).to be_nil
expect(graphql_data_at(:users_delete, :errors, :error_code)).to include('MISSING_PERMISSION')

expect(User.exists?(user.id)).to be true
is_expected.not_to create_audit_event(:user_deleted)
end
end
end
39 changes: 39 additions & 0 deletions spec/services/users/delete_service_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# frozen_string_literal: true

require 'rails_helper'

RSpec.describe Users::DeleteService do
subject(:service_response) do
described_class.new(create_authentication(current_user), user).execute
end

let(:user) { create(:user) }
let(:current_user) { create(:user, :admin) }

it 'deletes the user successfully' do
expect { service_response }.to change { User.exists?(user.id) }.from(true).to(false)
expect(service_response).to be_success
expect(service_response.payload).to eq(user)

is_expected.to create_audit_event(
:user_deleted,
author_id: current_user.id,
entity_type: 'User',
entity_id: user.id,
details: {},
target_type: 'global',
target_id: 0
)
end

context 'when current user lacks permission' do
let(:current_user) { create(:user) }

it 'returns a missing permission error' do
expect(service_response).not_to be_success
expect(service_response.payload[:error_code]).to eq(:missing_permission)
expect(User.exists?(user.id)).to be true
is_expected.not_to create_audit_event(:user_deleted)
end
end
end
Loading