Merge pull request #4313 from AntonKhorev/account-delete-delay

Account deletion cool-down period
This commit is contained in:
Andy Allan 2023-11-22 15:07:34 +00:00 committed by GitHub
commit 5fdddf2a8a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 199 additions and 12 deletions

View file

@ -61,7 +61,7 @@ Metrics/BlockNesting:
# Offense count: 26
# Configuration parameters: CountComments, CountAsOne.
Metrics/ClassLength:
Max: 286
Max: 297
# Offense count: 59
# Configuration parameters: AllowedMethods, AllowedPatterns.

View file

@ -53,12 +53,16 @@ class AccountsController < ApplicationController
end
def destroy
current_user.soft_destroy!
if current_user.deletion_allowed?
current_user.soft_destroy!
session.delete(:user)
session_expires_automatically
session.delete(:user)
session_expires_automatically
flash[:notice] = t ".success"
redirect_to root_path
flash[:notice] = t ".success"
redirect_to root_path
else
head :bad_request
end
end
end

View file

@ -14,11 +14,12 @@
#
# Indexes
#
# changesets_bbox_idx (min_lat,max_lat,min_lon,max_lon) USING gist
# changesets_closed_at_idx (closed_at)
# changesets_created_at_idx (created_at)
# changesets_user_id_created_at_idx (user_id,created_at)
# changesets_user_id_id_idx (user_id,id)
# changesets_bbox_idx (min_lat,max_lat,min_lon,max_lon) USING gist
# changesets_closed_at_idx (closed_at)
# changesets_created_at_idx (created_at)
# changesets_user_id_created_at_idx (user_id,created_at)
# changesets_user_id_id_idx (user_id,id)
# index_changesets_on_user_id_and_closed_at (user_id,closed_at)
#
# Foreign Keys
#

View file

@ -419,6 +419,18 @@ class User < ApplicationRecord
end
end
def deletion_allowed_at
unless Settings.user_account_deletion_delay.nil?
last_changeset = changesets.reorder(:closed_at => :desc).first
return last_changeset.closed_at.utc + Settings.user_account_deletion_delay.hours if last_changeset
end
creation_time.utc
end
def deletion_allowed?
deletion_allowed_at <= Time.now.utc
end
private
def encrypt_password

View file

@ -31,5 +31,13 @@
<li><%= t ".retain_email" %></li>
</ul>
<%= link_to t(".delete_account"), account_path, { :method => :delete, :class => "btn btn-danger", :data => { :confirm => t(".confirm_delete") } } %>
<% if current_user.deletion_allowed? %>
<%= link_to t(".delete_account"), account_path, { :method => :delete, :class => "btn btn-danger", :data => { :confirm => t(".confirm_delete") } } %>
<% else %>
<div class="alert alert-warning">
<%= t ".recent_editing_html", :time => friendly_date(current_user.deletion_allowed_at) %>
</div>
<button class="btn btn-secondary" disabled><%= t(".delete_account") %></button>
<% end %>
<%= link_to t(".cancel"), edit_account_path, :class => "btn btn-link" %>

View file

@ -79,6 +79,7 @@ Config.setup do |config|
required(:max_number_of_relation_members).filled(:int?)
required(:max_issues_count).filled(:int?)
required(:api_timeout).filled(:int?)
required(:user_account_deletion_delay).maybe(:number?)
required(:imagery_blacklist).maybe(:array?)
required(:status).filled(:str?, :included_in? => ALLOWED_STATUS)
required(:avatar_storage).filled(:str?)

View file

@ -256,6 +256,7 @@ en:
retain_notes: Your map notes and note comments, if any, will be retained but hidden from view.
retain_changeset_discussions: Your changeset discussions, if any, will be retained.
retain_email: Your email address will be retained.
recent_editing_html: "As you have edited recently your account cannot currently be deleted. Deletion will be possible in %{time}."
confirm_delete: Are you sure?
cancel: Cancel
accounts:

View file

@ -53,6 +53,8 @@ api_timeout: 300
web_timeout: 30
# Periods (in hours) which are allowed for user blocks
user_block_periods: [0, 1, 3, 6, 12, 24, 48, 96, 168, 336, 731, 4383, 8766, 87660]
# Account deletion cooldown period (in hours) since last changeset close; null to disable, 0 to make sure there aren't any open changesets when the deletion happens
user_account_deletion_delay: null
# Rate limit for message sending
max_messages_per_hour: 60
# Rate limit for friending

View file

@ -0,0 +1,7 @@
class AddClosedAtIndexToChangesets < ActiveRecord::Migration[7.1]
disable_ddl_transaction!
def change
add_index :changesets, [:user_id, :closed_at], :algorithm => :concurrently
end
end

View file

@ -2499,6 +2499,13 @@ CREATE INDEX index_changeset_comments_on_changeset_id_and_created_at ON public.c
CREATE INDEX index_changeset_comments_on_created_at ON public.changeset_comments USING btree (created_at);
--
-- Name: index_changesets_on_user_id_and_closed_at; Type: INDEX; Schema: public; Owner: -
--
CREATE INDEX index_changesets_on_user_id_and_closed_at ON public.changesets USING btree (user_id, closed_at);
--
-- Name: index_changesets_subscribers_on_changeset_id; Type: INDEX; Schema: public; Owner: -
--
@ -3499,6 +3506,7 @@ INSERT INTO "schema_migrations" (version) VALUES
('23'),
('22'),
('21'),
('20231117170422'),
('20231101222146'),
('20231029151516'),
('20231010194809'),

View file

@ -152,4 +152,23 @@ class AccountsControllerTest < ActionDispatch::IntegrationTest
# Make sure we have a button to "go public"
assert_select "form.button_to[action='/user/go_public']", true
end
def test_destroy_allowed
user = create(:user)
session_for(user)
delete account_path
assert_response :redirect
end
def test_destroy_not_allowed
with_user_account_deletion_delay(24) do
user = create(:user)
create(:changeset, :user => user, :created_at => Time.now.utc)
session_for(user)
delete account_path
assert_response :bad_request
end
end
end

View file

@ -282,4 +282,62 @@ class UserTest < ActiveSupport::TestCase
oauth_access_token.reload
assert_predicate oauth_access_token, :revoked?
end
def test_deletion_allowed_when_no_changesets
with_user_account_deletion_delay(10000) do
user = create(:user)
assert_predicate user, :deletion_allowed?
end
end
def test_deletion_allowed_without_delay
with_user_account_deletion_delay(nil) do
user = create(:user)
create(:changeset, :user => user)
user.reload
assert_predicate user, :deletion_allowed?
end
end
def test_deletion_allowed_past_delay
with_user_account_deletion_delay(10) do
user = create(:user)
create(:changeset, :user => user, :created_at => Time.now.utc - 12.hours, :closed_at => Time.now.utc - 10.hours)
user.reload
assert_predicate user, :deletion_allowed?
end
end
def test_deletion_allowed_during_delay
with_user_account_deletion_delay(10) do
user = create(:user)
create(:changeset, :user => user, :created_at => Time.now.utc - 11.hours, :closed_at => Time.now.utc - 9.hours)
user.reload
assert_not_predicate user, :deletion_allowed?
assert_equal Time.now.utc + 1.hour, user.deletion_allowed_at
end
end
def test_deletion_allowed_past_zero_delay
with_user_account_deletion_delay(0) do
user = create(:user)
create(:changeset, :user => user, :created_at => Time.now.utc, :closed_at => Time.now.utc + 1.hour)
travel 90.minutes do
user.reload
assert_predicate user, :deletion_allowed?
end
end
end
def test_deletion_allowed_during_zero_delay
with_user_account_deletion_delay(0) do
user = create(:user)
create(:changeset, :user => user, :created_at => Time.now.utc, :closed_at => Time.now.utc + 1.hour)
travel 30.minutes do
user.reload
assert_not_predicate user, :deletion_allowed?
assert_equal Time.now.utc + 30.minutes, user.deletion_allowed_at
end
end
end
end

View file

@ -41,4 +41,59 @@ class AccountDeletionTest < ApplicationSystemTestCase
assert_content "Account Deleted"
end
test "can delete with any delay setting value if the user has no changesets" do
with_user_account_deletion_delay(10000) do
travel 1.hour do
visit edit_account_path
click_link "Delete Account..."
assert_no_content "cannot currently be deleted"
end
end
end
test "can delete with delay disabled" do
with_user_account_deletion_delay(nil) do
create(:changeset, :user => @user)
travel 1.hour do
visit edit_account_path
click_link "Delete Account..."
assert_no_content "cannot currently be deleted"
end
end
end
test "can delete when last changeset is old enough" do
with_user_account_deletion_delay(10) do
create(:changeset, :user => @user, :created_at => Time.now.utc, :closed_at => Time.now.utc + 1.hour)
travel 12.hours do
visit edit_account_path
click_link "Delete Account..."
assert_no_content "cannot currently be deleted"
end
end
end
test "can't delete when last changeset isn't old enough" do
with_user_account_deletion_delay(10) do
create(:changeset, :user => @user, :created_at => Time.now.utc, :closed_at => Time.now.utc + 1.hour)
travel 10.hours do
visit edit_account_path
click_link "Delete Account..."
assert_content "cannot currently be deleted"
assert_content "in about 1 hour"
end
end
end
end

View file

@ -371,5 +371,16 @@ module ActiveSupport
el << tag_el
end
end
def with_user_account_deletion_delay(value)
freeze_time
default_value = Settings.user_account_deletion_delay
Settings.user_account_deletion_delay = value
yield
Settings.user_account_deletion_delay = default_value
unfreeze_time
end
end
end