Merge pull request #4313 from AntonKhorev/account-delete-delay
Account deletion cool-down period
This commit is contained in:
commit
5fdddf2a8a
14 changed files with 199 additions and 12 deletions
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
#
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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" %>
|
||||
|
|
|
@ -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?)
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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'),
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue