]> git.openstreetmap.org Git - rails.git/commitdiff
Allow users to delete their own accounts
authorAndy Allan <git@gravitystorm.co.uk>
Thu, 9 Dec 2021 16:12:42 +0000 (16:12 +0000)
committerAndy Allan <git@gravitystorm.co.uk>
Wed, 9 Feb 2022 16:15:24 +0000 (16:15 +0000)
This PR allows users to delete their own accounts. The logic implemented matches
that currently used by the admins when they manually close accounts, although
there is room to be more complex in future e.g. completely removing accounts
with no content.

The error handling has been slightly adapted for namespaced controllers, by
anchoring the controller name with a leading forward slash.

app/abilities/ability.rb
app/controllers/account/deletions_controller.rb [new file with mode: 0644]
app/controllers/accounts_controller.rb
app/controllers/application_controller.rb
app/views/account/deletions/show.html.erb [new file with mode: 0644]
app/views/accounts/edit.html.erb
app/views/application/_settings_menu.html.erb
config/locales/en.yml
config/routes.rb
test/system/account_deletion_test.rb [new file with mode: 0644]

index b8e21b4867e430d13d1955b2296e78696c249eb3..e9fd6f7bb1cd1cefc79773605549548a07c6c0a5 100644 (file)
@@ -36,13 +36,14 @@ class Ability
     if user
       can :welcome, :site
       can [:revoke, :authorize], :oauth
+      can [:show], :deletion
 
       if Settings.status != "database_offline"
         can [:index, :new, :create, :show, :edit, :update, :destroy], ClientApplication
         can [:index, :new, :create, :show, :edit, :update, :destroy], :oauth2_application
         can [:index, :destroy], :oauth2_authorized_application
         can [:new, :show, :create, :destroy], :oauth2_authorization
-        can [:edit, :update], :account
+        can [:edit, :update, :destroy], :account
         can [:show], :dashboard
         can [:new, :create, :edit, :update, :comment, :subscribe, :unsubscribe], DiaryEntry
         can [:make_friend, :remove_friend], Friendship
diff --git a/app/controllers/account/deletions_controller.rb b/app/controllers/account/deletions_controller.rb
new file mode 100644 (file)
index 0000000..2e3c777
--- /dev/null
@@ -0,0 +1,12 @@
+module Account
+  class DeletionsController < ApplicationController
+    layout "site"
+
+    before_action :authorize_web
+    before_action :set_locale
+
+    authorize_resource :class => false
+
+    def show; end
+  end
+end
index 06eb031c46a2f81a18ea21f5712439a655aa9fe3..63da1293ff731ecfe85d0430f9c6f0f1819a2008 100644 (file)
@@ -51,4 +51,14 @@ class AccountsController < ApplicationController
       redirect_to auth_url(params[:user][:auth_provider], params[:user][:auth_uid]), :status => :temporary_redirect
     end
   end
+
+  def destroy
+    current_user.soft_destroy!
+
+    session.delete(:user)
+    session_expires_automatically
+
+    flash[:notice] = t ".success"
+    redirect_to root_path
+  end
 end
index 07b23ce21c814561c8266cff403a46874bb4cbb8..bb32e7e6e5cbbe25da6787efc37dce39266e8c8d 100644 (file)
@@ -349,7 +349,7 @@ class ApplicationController < ActionController::Base
     elsif current_user
       set_locale
       respond_to do |format|
-        format.html { redirect_to :controller => "errors", :action => "forbidden" }
+        format.html { redirect_to :controller => "/errors", :action => "forbidden" }
         format.any { report_error t("application.permission_denied"), :forbidden }
       end
     elsif request.get?
diff --git a/app/views/account/deletions/show.html.erb b/app/views/account/deletions/show.html.erb
new file mode 100644 (file)
index 0000000..99c1686
--- /dev/null
@@ -0,0 +1,38 @@
+<% content_for :heading do %>
+  <h1><%= t ".title" %></h1>
+<% end %>
+
+<%= render :partial => "settings_menu" %>
+
+<div class="alert alert-danger row mx-0 p-3 align-items-center">
+  <div class="col-auto">
+    <picture>
+      <source srcset="<%= image_path "notice.svg" %>" type="image/svg+xml"></source>
+      <%= image_tag("notice.png", :srcset => image_path("notice.svg")) %>
+    </picture>
+  </div>
+  <div class="col">
+    <p class="mb-0"><%= t ".warning" %></p>
+  </div>
+</div>
+
+<p><%= t ".delete_introduction" %></p>
+
+<ul>
+  <li><%= t ".delete_profile" %></li>
+  <li><%= t ".delete_display_name" %></li>
+</ul>
+
+<p><%= t ".retain_caveats" %></p>
+
+<ul>
+  <li><%= t ".retain_edits" %></li>
+  <li><%= t ".retain_traces" %></li>
+  <li><%= t ".retain_diary_entries" %></li>
+  <li><%= t ".retain_notes" %></li>
+  <li><%= t ".retain_changeset_discussions" %></li>
+  <li><%= t ".retain_email" %></li>
+</ul>
+
+<%= link_to t(".delete_account"), account_path, { :method => :delete, :class => "btn btn-danger", :data => { :confirm => t(".confirm_delete") } } %>
+<%= link_to t(".cancel"), edit_account_path, :class => "btn btn-link" %>
index 0dcab17b0d3ba20ffaa9f3febdb21f6e4dd31e96..eb977d0942d900f96bc49cf28a979a2a8257e2c9 100644 (file)
     </span>
   </div>
 
-  <%= f.primary t(".save changes button") %>
+  <div class="row justify-content-between">
+    <div class="col-auto btn-wrapper">
+      <%= f.primary t(".save changes button") %>
+    </div>
+    <div class="col-auto btn-wrapper">
+      <%= link_to t(".delete_account"), account_deletion_path, :class => "btn btn-outline-danger" %>
+    </div>
+  </div>
 <% end %>
 
 <% unless current_user.data_public? %>
index 05cee91858e5a5a9851e215ee41613d10a70a0f7..9ce9755a2c5f3e452e016f2ce0fb6e6551250c40 100644 (file)
@@ -3,7 +3,7 @@
 <% content_for :heading do %>
   <ul class="nav nav-tabs flex-column flex-sm-row">
     <li class="nav-item">
-      <%= link_to t(".account_settings"), edit_account_path, :class => "nav-link #{'active' if controller_name == 'accounts'}" %>
+      <%= link_to t(".account_settings"), edit_account_path, :class => "nav-link #{'active' if %w[accounts deletions].include?(controller_name)}" %>
     </li>
     <li class="nav-item">
       <%= link_to t(".oauth1_settings"), oauth_clients_path(current_user), :class => "nav-link #{'active' if controller_name == 'oauth_clients'}" %>
index 2cbc8091800c1106b0bdc828890ec1651d30c6a9..f04c3dd42574551c35cbb6e80444b04a31f8446a 100644 (file)
@@ -239,6 +239,24 @@ en:
       entry:
         comment: Comment
         full: Full note
+  account:
+    deletions:
+      show:
+        title: Delete My Account
+        warning: Warning! The account deletion process is final, and cannot be reversed.
+        delete_account: Delete Account
+        delete_introduction: "You can delete your OpenStreetMap account using the button below. Please note the following details:"
+        delete_profile: Your profile information, including your avatar, description and home location will be removed.
+        delete_display_name: Your display name will be removed, and can be reused by other accounts.
+        retain_caveats: "However, some information about you will be retained on OpenStreetMap, even after your account is deleted:"
+        retain_edits: Your edits to the map database, if any, will be retained.
+        retain_traces: Your uploaded traces, if any, will be retained.
+        retain_diary_entries: Your diary entries and diary comments, if any, will be retained but hidden from view.
+        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.
+        confirm_delete: Are you sure?
+        cancel: Cancel
   accounts:
     edit:
       title: "Edit account"
@@ -268,9 +286,12 @@ en:
         link text: "what is this?"
       save changes button: Save Changes
       make edits public button: Make all my edits public
+      delete_account: Delete Account...
     update:
       success_confirm_needed: "User information updated successfully. Check your email for a note to confirm your new email address."
       success: "User information updated successfully."
+    destroy:
+      success: "Account Deleted."
   browse:
     created: "Created"
     closed: "Closed"
index ade101821541f4b58ef63ab086e87b75ea5cacb7..e45d56701ef6dde5c99abc131d0872371acf250b 100644 (file)
@@ -240,7 +240,11 @@ OpenStreetMap::Application.routes.draw do
   get "/user/:display_name/account", :to => redirect(:path => "/account/edit")
   post "/user/:display_name/set_status" => "users#set_status", :as => :set_status_user
 
-  resource :account, :only => [:edit, :update]
+  resource :account, :only => [:edit, :update, :destroy]
+
+  namespace :account do
+    resource :deletion, :only => [:show]
+  end
   resource :dashboard, :only => [:show]
   resource :preferences, :only => [:show, :edit, :update]
   resource :profile, :only => [:edit, :update]
diff --git a/test/system/account_deletion_test.rb b/test/system/account_deletion_test.rb
new file mode 100644 (file)
index 0000000..5a55c58
--- /dev/null
@@ -0,0 +1,44 @@
+require "application_system_test_case"
+
+class AccountDeletionTest < ApplicationSystemTestCase
+  def setup
+    @user = create(:user, :display_name => "test user")
+    sign_in_as(@user)
+  end
+
+  test "the status is deleted and the personal data removed" do
+    visit edit_account_path
+
+    click_on "Delete Account..."
+    accept_confirm do
+      click_on "Delete Account"
+    end
+
+    assert_current_path root_path
+    @user.reload
+    assert_equal "deleted", @user.status
+    assert_equal "user_#{@user.id}", @user.display_name
+  end
+
+  test "the user is signed out after deletion" do
+    visit edit_account_path
+
+    click_on "Delete Account..."
+    accept_confirm do
+      click_on "Delete Account"
+    end
+
+    assert_content "Log In"
+  end
+
+  test "the user is shown a confirmation flash message" do
+    visit edit_account_path
+
+    click_on "Delete Account..."
+    accept_confirm do
+      click_on "Delete Account"
+    end
+
+    assert_content "Account Deleted"
+  end
+end