Skip to content

Commit

Permalink
Allow users to request account closure
Browse files Browse the repository at this point in the history
  • Loading branch information
chrismytton committed Aug 29, 2023
1 parent 648ad8e commit 9b0d9f4
Show file tree
Hide file tree
Showing 22 changed files with 282 additions and 12 deletions.
9 changes: 9 additions & 0 deletions app/controllers/admin_user_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,15 @@ def modify_comment_visibility
redirect_back(fallback_location: admin_users_url)
end

def account_closure_requests
@title = 'Account closure requests'

# Find requests where the account associated with the request is not closed
@account_closure_requests = AccountClosureRequest.
joins(:user).
where(users: { closed_at: nil })
end

private

def user_params
Expand Down
2 changes: 1 addition & 1 deletion app/controllers/admin_users_account_closing_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ def create
'Something went wrong. The user account could not be closed.'
end

redirect_to admin_user_path(@closed_user)
redirect_back_or_to admin_user_path(@closed_user)
end

private
Expand Down
3 changes: 3 additions & 0 deletions app/controllers/application_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ class RouteNotFound < StandardError
# Standard headers, footers and navigation for whole site
layout "default"

# Defaults are :alert and :notice, but Alaveteli uses :error as well
add_flash_types :error

include FastGettext::Translation # make functions like _, n_, N_ etc available)
include AlaveteliPro::PostRedirectHandler

Expand Down
34 changes: 34 additions & 0 deletions app/controllers/users/close_account_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
class Users::CloseAccountController < ApplicationController

Check warning on line 1 in app/controllers/users/close_account_controller.rb

View workflow job for this annotation

GitHub Actions / build

[rubocop] reported by reviewdog 🐶 Style/Documentation: Missing top-level documentation comment for class Users::CloseAccountController. Raw Output: app/controllers/users/close_account_controller.rb:1:1: C: Style/Documentation: Missing top-level documentation comment for class Users::CloseAccountController. class Users::CloseAccountController < ApplicationController ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
before_action :authenticate_user!

def new
# Display a form that explains the process to the users
end

def create
# If they haven't checked the "confirm" checkbox, then redirect them back to the form

Check warning on line 9 in app/controllers/users/close_account_controller.rb

View workflow job for this annotation

GitHub Actions / build

[rubocop] reported by reviewdog 🐶 Layout/LineLength: Line is too long. [89/80] (https://rubystyle.guide#max-line-length) Raw Output: app/controllers/users/close_account_controller.rb:9:81: C: Layout/LineLength: Line is too long. [89/80] (https://rubystyle.guide#max-line-length) # If they haven't checked the "confirm" checkbox, then redirect them back to the form ^^^^^^^^^
return redirect_to users_close_account_path, error: "You must confirm that you want to close your account" if params[:confirm] == "0"

Check warning on line 10 in app/controllers/users/close_account_controller.rb

View workflow job for this annotation

GitHub Actions / build

[rubocop] reported by reviewdog 🐶 [Correctable] Layout/LineLength: Line is too long. [137/80] (https://rubystyle.guide#max-line-length) Raw Output: app/controllers/users/close_account_controller.rb:10:81: C: [Correctable] Layout/LineLength: Line is too long. [137/80] (https://rubystyle.guide#max-line-length) return redirect_to users_close_account_path, error: "You must confirm that you want to close your account" if params[:confirm] == "0" ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Check warning on line 10 in app/controllers/users/close_account_controller.rb

View workflow job for this annotation

GitHub Actions / build

[rubocop] reported by reviewdog 🐶 [Correctable] Style/IfUnlessModifier: Modifier form of if makes the line too long. (https://rubystyle.guide#if-as-a-modifier) Raw Output: app/controllers/users/close_account_controller.rb:10:112: C: [Correctable] Style/IfUnlessModifier: Modifier form of if makes the line too long. (https://rubystyle.guide#if-as-a-modifier) return redirect_to users_close_account_path, error: "You must confirm that you want to close your account" if params[:confirm] == "0" ^^

# Otherwise, create a record of the user's request to close their account
current_user.create_account_closure_request!

# Send the user an acknowledgement email
UserMailer.account_closure_requested(current_user).deliver_now

# TODO: Should the user be logged out here?

redirect_to root_path, notice: "Your account closure request has been received. We will be in touch."

Check warning on line 20 in app/controllers/users/close_account_controller.rb

View workflow job for this annotation

GitHub Actions / build

[rubocop] reported by reviewdog 🐶 [Correctable] Layout/LineLength: Line is too long. [105/80] (https://rubystyle.guide#max-line-length) Raw Output: app/controllers/users/close_account_controller.rb:20:81: C: [Correctable] Layout/LineLength: Line is too long. [105/80] (https://rubystyle.guide#max-line-length) redirect_to root_path, notice: "Your account closure request has been received. We will be in touch." ^^^^^^^^^^^^^^^^^^^^^^^^^
end

private

def authenticate_user!
return if authenticated?

ask_to_login(
web: _('To close your account on {{site_name}}', site_name: site_name),
email: _('Then you can close your account on {{site_name}}', site_name: site_name),

Check warning on line 30 in app/controllers/users/close_account_controller.rb

View workflow job for this annotation

GitHub Actions / build

[rubocop] reported by reviewdog 🐶 [Correctable] Layout/LineLength: Line is too long. [89/80] (https://rubystyle.guide#max-line-length) Raw Output: app/controllers/users/close_account_controller.rb:30:81: C: [Correctable] Layout/LineLength: Line is too long. [89/80] (https://rubystyle.guide#max-line-length) email: _('Then you can close your account on {{site_name}}', site_name: site_name), ^^^^^^^^^
email_subject: _('Close your account on {{site_name}}', site_name: site_name)

Check warning on line 31 in app/controllers/users/close_account_controller.rb

View workflow job for this annotation

GitHub Actions / build

[rubocop] reported by reviewdog 🐶 [Correctable] Layout/LineLength: Line is too long. [83/80] (https://rubystyle.guide#max-line-length) Raw Output: app/controllers/users/close_account_controller.rb:31:81: C: [Correctable] Layout/LineLength: Line is too long. [83/80] (https://rubystyle.guide#max-line-length) email_subject: _('Close your account on {{site_name}}', site_name: site_name) ^^^
)
end
end
7 changes: 7 additions & 0 deletions app/mailers/user_mailer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -45,4 +45,11 @@ def changeemail_already_used(old_email, new_email)
to: new_email,
subject: _("Unable to change email address on {{site_name}}", site_name: site_name))
end

def account_closure_requested(user)
@name = user.name

set_reply_to_headers(user)
mail_user(user, _("Your account closure request on {{site_name}}", site_name: site_name))

Check warning on line 53 in app/mailers/user_mailer.rb

View workflow job for this annotation

GitHub Actions / build

[rubocop] reported by reviewdog 🐶 [Correctable] Layout/LineLength: Line is too long. [93/80] (https://rubystyle.guide#max-line-length) Raw Output: app/mailers/user_mailer.rb:53:81: C: [Correctable] Layout/LineLength: Line is too long. [93/80] (https://rubystyle.guide#max-line-length) mail_user(user, _("Your account closure request on {{site_name}}", site_name: site_name)) ^^^^^^^^^^^^^
end
end
13 changes: 13 additions & 0 deletions app/models/account_closure_request.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# == Schema Information
# Schema version: 20230718062820
#
# Table name: account_closure_requests
#
# id :bigint not null, primary key
# user_id :bigint not null
# created_at :datetime not null
# updated_at :datetime not null
#
class AccountClosureRequest < ApplicationRecord
belongs_to :user
end
2 changes: 1 addition & 1 deletion app/models/outgoing_message.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# == Schema Information
# Schema version: 20230412084830
# Schema version: 20230718062820
#
# Table name: outgoing_messages
#
Expand Down
4 changes: 4 additions & 0 deletions app/models/user.rb
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,10 @@ class User < ApplicationRecord
inverse_of: :user,
dependent: :destroy

has_one :account_closure_request,
inverse_of: :user,
dependent: :destroy

scope :active, -> { not_banned.not_closed }
scope :banned, -> { where.not(ban_text: '') }
scope :not_banned, -> { where(ban_text: '') }
Expand Down
1 change: 1 addition & 0 deletions app/views/admin_general/_admin_navbar.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
<ul class="dropdown-menu" role="menu">
<li><%= link_to 'Users', admin_users_path %></li>
<li><%= link_to 'Tracks', admin_tracks_path %></li>
<li><%= link_to 'Account closure requests', account_closure_requests_admin_users_path %></li>
</ul>
</li>

Expand Down
9 changes: 9 additions & 0 deletions app/views/admin_user/_close_account_form.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<%= form_tag admin_users_account_closing_index_path(user_id: user.id), class: 'span3 form form-inline' do %>
<% disabled = user.closed? %>
<% submit_class = %w(btn btn-danger) %>
<% submit_class << 'disabled' if disabled %>
<%= submit_tag 'Close',
class: submit_class,
disabled: disabled,
data: { confirm: 'Are you sure? This is irreversible.' } %>
<% end %>
4 changes: 4 additions & 0 deletions app/views/admin_user/_scopes.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@
<%= nav_li(admin_sign_ins_path) do %>
<%= link_to 'Sign Ins', admin_sign_ins_path %>
<% end %>
<%= nav_li(account_closure_requests_admin_users_path) do %>
<%= link_to 'Account closure requests', account_closure_requests_admin_users_path %>
<% end %>
</ul>
</div>
</div>
35 changes: 35 additions & 0 deletions app/views/admin_user/account_closure_requests.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<%= render 'scopes' %>

<div class="row">
<div class="span12">
<% if @account_closure_requests.any? %>
<table class="table table-striped">
<thead>
<tr>
<th>Request ID</th>
<th>User</th>
<th>Created at</th>
<th>Action</th>
</tr>
</thead>
<tbody>
<% @account_closure_requests.each do |request| %>
<tr id="account-closure-request-<%= request.id %>">
<td><%= request.id %></td>
<td>
<%= link_to request.user.name, admin_user_path(request.user) %>
</td>
<td><%= request.created_at.to_fs(:long) %></td>
<td>
<%= render 'close_account_form', { user: request.user } %>
</td>
</tr>
<% end %>
</tbody>
</table>

<% else %>
<p>No users have requested to close their accounts.</p>
<% end %>
</div>
</div>
10 changes: 1 addition & 9 deletions app/views/admin_user/edit.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -39,15 +39,7 @@
</p>
</div>
<hr>
<%= form_tag admin_users_account_closing_index_path(user_id: @admin_user.id), class: 'span3 form form-inline' do %>
<% disabled = @admin_user.closed? %>
<% submit_class = %w(btn btn-danger) %>
<% submit_class << 'disabled' if disabled %>
<%= submit_tag 'Close',
class: submit_class,
disabled: disabled,
data: { confirm: 'Are you sure? This is irreversible.' } %>
<% end %>
<%= render 'close_account_form', { user: @admin_user } %>
</div>

<div class="danger-zone__item">
Expand Down
3 changes: 2 additions & 1 deletion app/views/user/_show_user_info.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@
<%= link_to _('Change profile photo'), set_profile_photo_path %> |
<% end %>
<%= link_to _('Change your password'), new_password_change_path %> |
<%= link_to _('Change your email'), signchangeemail_path %>
<%= link_to _('Change your email'), signchangeemail_path %> |
<%= link_to _('Close your account'), users_close_account_path %>
<% if AlaveteliConfiguration.enable_two_factor_auth %>
|
<% if @display_user.otp_enabled? %>
Expand Down
7 changes: 7 additions & 0 deletions app/views/user_mailer/account_closure_requested.text.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<%= raw @name %>,

<%= _("You've requested to close your account on {{site_name}}." \
'We will process your request and will be in touch once it has been actioned.',
:site_name => site_name.html_safe) %>

-- <%= _('the {{site_name}} team', :site_name => site_name.html_safe) %>
26 changes: 26 additions & 0 deletions app/views/users/close_account/new.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<% @title = "Close your account" %>
<h1><%= @title %></h1>

<p>If you no longer wish to use your WhatDoTheyKnow account, you can ask us to close your account.</p>

<p>Closing your account will:</p>

<ul>
<li>Set your user name to &#91;Name Removed&#93;</li>
<li>Reset your email address to something random</li>
<li>Clear your about me text</li>
<li>Disable all email alerts</li>
<li>Hide any requests you have made from your profile page</li>
<li>Make an automatic attempt to remove your name from your requests</li>
</ul>

<p>Closing your account will prevent you from logging in. If you have any requests that are ongoing, you will not be able to send any follow up messages to public authorities.</p>

<%= form_with url: users_close_account_path, method: :post do |form| %>
<p>
<%= form.check_box :confirm, class: "checkbox" %>
I understand that closing my account will mean that I <b>will not</b> be able to login or follow up on my requests, and that this cannot be undone.
</p>

<%= form.submit "Close my account", class: "button alert", data: { confirm: "Are you sure you want to close your account?" } %>
<% end %>
4 changes: 4 additions & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,9 @@ def matches?(request)
get 'email_alerts/disable/:token',
to: 'email_alerts#destroy',
as: :disable_email_alerts

get 'close_account', to: 'close_account#new', as: :close_account
post 'close_account', to: 'close_account#create'
end

namespace :users, path: 'profile' do
Expand Down Expand Up @@ -704,6 +707,7 @@ def matches?(request)
get 'active', :on => :collection
get 'banned', :on => :collection
get 'closed', :on => :collection
get 'account_closure_requests', :on => :collection
get 'show_bounce_message', :on => :member
post 'clear_bounce', :on => :member
post 'clear_profile_photo', :on => :member
Expand Down
8 changes: 8 additions & 0 deletions db/migrate/20230718062820_create_account_closure_requests.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
class CreateAccountClosureRequests < ActiveRecord::Migration[7.0]
def change
create_table :account_closure_requests do |t|
t.references :user, null: false, foreign_key: true
t.timestamps
end
end
end
13 changes: 13 additions & 0 deletions spec/controllers/admin_user_controller_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -449,4 +449,17 @@
}.to change(ActsAsXapian::ActsAsXapianJob, :count).by(1)
end
end

describe "GET #account_closure_requests" do
it "assigns @account_closure_requests with requests where accounts are not closed" do
user_with_closed_account = FactoryBot.create(:user, closed_at: Time.now)
user_with_open_account = FactoryBot.create(:user)
request_for_closed_account = FactoryBot.create(:account_closure_request, user: user_with_closed_account)

Check warning on line 457 in spec/controllers/admin_user_controller_spec.rb

View workflow job for this annotation

GitHub Actions / build

[rubocop] reported by reviewdog 🐶 [Correctable] Layout/LineLength: Line is too long. [110/80] (https://rubystyle.guide#max-line-length) Raw Output: spec/controllers/admin_user_controller_spec.rb:457:81: C: [Correctable] Layout/LineLength: Line is too long. [110/80] (https://rubystyle.guide#max-line-length) request_for_closed_account = FactoryBot.create(:account_closure_request, user: user_with_closed_account) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
request_for_open_account = FactoryBot.create(:account_closure_request, user: user_with_open_account)

Check warning on line 458 in spec/controllers/admin_user_controller_spec.rb

View workflow job for this annotation

GitHub Actions / build

[rubocop] reported by reviewdog 🐶 [Correctable] Layout/LineLength: Line is too long. [106/80] (https://rubystyle.guide#max-line-length) Raw Output: spec/controllers/admin_user_controller_spec.rb:458:81: C: [Correctable] Layout/LineLength: Line is too long. [106/80] (https://rubystyle.guide#max-line-length) request_for_open_account = FactoryBot.create(:account_closure_request, user: user_with_open_account) ^^^^^^^^^^^^^^^^^^^^^^^^^^

get :account_closure_requests

expect(assigns(:account_closure_requests)).to eq([request_for_open_account])
end
end
end
46 changes: 46 additions & 0 deletions spec/controllers/users/close_account_controller_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# spec/controllers/users/close_account_controller_spec.rb
require 'spec_helper'

RSpec.describe Users::CloseAccountController, type: :controller do
describe "POST #create" do
let(:user) { FactoryBot.create(:user) }

before do
sign_in user
end

after do
user.account_closure_request&.destroy
end

it "shows the user a confirmation page" do
get :new
assert_response :success
expect(response).to render_template(:new)
end

it "asks the user to check the confirmation checkbox" do
post :create, params: { confirm: "0" }
assert_response :redirect
expect(response).to redirect_to(users_close_account_path)
expect(flash[:error]).to eq("You must confirm that you want to close your account")
end

it "creates a record of the user's request to close their account" do
post :create, params: { confirm: "1" }

user.reload
expect(user.account_closure_request).to be_present

# Check email has been sent
expect(ActionMailer::Base.deliveries.count).to eq(1)
email = ActionMailer::Base.deliveries.last
expect(email.to).to eq([user.email])
expect(email.subject).to eq("Your account closure request on #{site_name}")

assert_response :redirect
expect(response).to redirect_to(root_path)
expect(flash[:notice]).to eq("Your account closure request has been received. We will be in touch.")
end
end
end
14 changes: 14 additions & 0 deletions spec/factories/account_closure_requests.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# == Schema Information
# Schema version: 20230718062820
#
# Table name: account_closure_requests
#
# id :bigint not null, primary key
# user_id :bigint not null
# created_at :datetime not null
# updated_at :datetime not null
#
FactoryBot.define do
factory :account_closure_request do
end
end
40 changes: 40 additions & 0 deletions spec/integration/admin_user_account_closing_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
require 'spec_helper'
require 'integration/alaveteli_dsl'

RSpec.describe 'Admin Account Closure Requests' do
before do
allow(AlaveteliConfiguration).to receive(:skip_admin_auth).and_return(false)

confirm(:admin_user)
@admin = login(:admin_user)
@user = FactoryBot.create(:user)
@account_closure_request = FactoryBot.create(:account_closure_request, user: @user)
end

context 'viewing account closure requests' do
it 'displays link to "Account closure requests" on admin homepage' do
using_session(@admin) do
visit admin_general_index_path
expect(page).to have_link('Account closure requests')
end
end

it 'can close an account from the "Account closure requests" page' do
using_session(@admin) do
expect(@user).to_not be_closed

visit account_closure_requests_admin_users_path
within("tr#account-closure-request-#{@account_closure_request.id}") do
click_button 'Close'
end
expect(page).to have_text('The user account was closed.')

@user.reload
expect(@user).to be_closed

visit account_closure_requests_admin_users_path
expect(page).to_not have_text(@user.name)
end
end
end
end

0 comments on commit 9b0d9f4

Please sign in to comment.