Commit d10f2d65 authored by Eric Eastwood's avatar Eric Eastwood

Add ability to delete account from UI

Part of gitlab-org/gitter/webapp#1900
parent 807d3f89
# 19.3.0 - *upcoming*
- ..
- Add ability to delete account, https://gitlab.com/gitlab-org/gitter/webapp/merge_requests/1169
Developer facing:
......@@ -9,6 +9,8 @@ Developer facing:
- Update utility script docs to be more copy-pasta friendly, https://gitlab.com/gitlab-org/gitter/webapp/merge_requests/1173
- Fix `skip` parameter in the room search API endpoint `/v1/rooms?q=foo&skip=15&limit=3`
- Thanks to [@nsuchy](https://gitlab.com/nsuchy) for the contribution, https://gitlab.com/gitlab-org/gitter/webapp/merge_requests/1175
- Add room `lcUri` to room deletion log warning for easier grepping, https://gitlab.com/gitlab-org/gitter/webapp/merge_requests/1168
# 19.2.0 - 2018-5-23
......
......@@ -34,6 +34,8 @@ Sign out of Gitter and sign back in to update your avatar (or any other info).
## How do I delete my account?
Whilst we're very sad to see you go, we're more than happy to help you out. Please contact support@gitter.im with your Gitter username and we'll take care of that for you.
You can delete your account by using the profile menu dropdown in the top-right -> **Delete Account**
We'd love to hear why so we can improve our service going forward.
![](https://i.imgur.com/j3Gowl7.png)
We can't recover your data after deletion but you can re-create your account at any time by signing back in.
......@@ -4,7 +4,7 @@
"browser": true
},
"parserOptions": {
"ecmaVersion": 5,
"ecmaVersion": 6,
"sourceType": "script"
},
"plugins": [
......
'use strict';
var Backbone = require('backbone');
var DelayLock = Backbone.Model.extend({
defaults: {
locked: true,
// give enough time to read the warnings
secondsLeft: 8,
error: null
},
initialize: function() {
this.tick();
},
tick: function() {
var self = this;
if (!this.get('locked')) return;
setTimeout(function() {
var seconds = self.get('secondsLeft') - 1;
self.set('secondsLeft', seconds);
if (seconds <= 0) {
self.set('locked', false);
} else {
self.tick();
}
}, 1000);
}
});
module.exports = DelayLock;
......@@ -345,6 +345,10 @@ onready(function() { // eslint-disable-line max-statements
case 'toggle-dark-theme':
toggleDarkTheme(!!message.theme.length);
break;
case 'account.delete-start':
appEvents.trigger('account.delete-start');
break;
}
}, false);
......@@ -352,7 +356,7 @@ onready(function() { // eslint-disable-line max-statements
var allRoomsCollection = troupeCollections.troupes;
new RoomCollectionTracker(allRoomsCollection);
allRoomsCollection.on('remove', function(model) {
const onRoomRemoveHandler = function(model) {
if (model.id === context.getTroupeId()) {
//context.troupe().set('roomMember', false);
var newLocation = '/home';
......@@ -362,6 +366,14 @@ onready(function() { // eslint-disable-line max-statements
pushState(newFrame, title, newLocation);
roomSwitcher.change(newFrame);
}
};
allRoomsCollection.on('remove', onRoomRemoveHandler);
// We remove `onRoomRemoveHandler` so we don't try to redirect to the user home
// before the `logout()` kicks in (see `delete-account-view.js`)
appEvents.on('account.delete-start', function() {
allRoomsCollection.off('remove', onRoomRemoveHandler);
});
......
......@@ -19,6 +19,7 @@ var chatCollection = require('./collections/instances/chats-cached');
var ChatToolbarInputLayout = require('./views/layouts/chat-toolbar-input');
var DropTargetView = require('./views/app/dropTargetView');
var Router = require('./routes/router');
var userRoutes = require('./routes/user-routes');
var roomRoutes = require('./routes/room-routes');
var notificationRoutes = require('./routes/notification-routes');
......@@ -228,6 +229,10 @@ onready(function() { // eslint-disable-line max-statements
frameUtils.postMessage({ type: 'ajaxError' });
});
appEvents.on('account.delete-start', function() {
frameUtils.postMessage({ type: 'account.delete-start' });
});
var notifyRemoveError = function(message) {
appEvents.triggerParent('user_notification', {
title: 'Failed to remove user',
......@@ -261,6 +266,7 @@ onready(function() { // eslint-disable-line max-statements
dialogRegion: appView.dialogRegion,
routes: [
notificationRoutes(),
userRoutes(),
roomRoutes({
rosterCollection: itemCollections.roster
}),
......
'use strict';
var Backbone = require('backbone');
var context = require('../utils/context');
var clientEnv = require('gitter-client-env');
var apiClient = require('../components/api-client');
function createRoutes(options) {
return {
'delete-account': function() {
var dialogRegion = this.dialogRegion;
require.ensure(['../views/modals/delete-room-view'], function(require) {
var DeleteModal = require('../views/modals/delete-account-view');
dialogRegion.show(new DeleteModal({}));
});
},
}
}
module.exports = createRoutes;
"use strict";
var Marionette = require('backbone.marionette');
var log = require('../../utils/log');
var logout = require('../../utils/logout');
var context = require('../../utils/context');
var appEvents = require('../../utils/appevents');
var ModalView = require('./modal');
var apiClient = require('../../components/api-client');
var DelayLock = require('../../models/delay-lock-model');
var template = require('./tmpl/delete-account-view.hbs');
var View = Marionette.ItemView.extend({
tagName: 'p',
attributes: { style: '' },
modelEvents: {
'change': 'render'
},
initialize: function() {
this.listenTo(this, 'menuItemClicked', this.menuItemClicked);
},
template: template,
menuItemClicked: function(button) {
switch(button) {
case 'delete':
// Notify others, that they shouldn't redirect while we are trying to logout
appEvents.trigger('account.delete-start');
apiClient.user.delete()
.then(() => {
return logout();
})
.catch((err) => {
log.error('Error while deleting account', { exception: err });
this.model.set('error', `Error while deleting account: ${err} (status: ${err.status})`);
});
break;
case 'cancel':
this.dialog.hide();
break;
}
}
});
var Modal = ModalView.extend({
initialize: function(options) {
options = options || {};
options.title = 'Careful Now...';
var username = context.user().get('username');
options.menuItems = [{
disabled: true,
action: 'delete',
text: `Delete ${username}`,
className: 'modal--default__footer__btn--negative'
}];
var lock = new DelayLock();
this.listenTo(lock, 'change:locked', function() {
this.setButtonState('delete', true);
});
ModalView.prototype.initialize.call(this, options);
this.view = new View({
model: lock
});
}
});
module.exports = Modal;
......@@ -6,6 +6,7 @@ var ModalView = require('./modal');
var context = require('../../utils/context');
var apiClient = require('../../components/api-client');
var appEvents = require('../../utils/appevents');
var DelayLock = require('../../models/delay-lock-model');
var template = require('./tmpl/delete-room-view.hbs');
var View = Marionette.ItemView.extend({
......@@ -33,32 +34,6 @@ var View = Marionette.ItemView.extend({
}
});
var DelayLock = Backbone.Model.extend({
defaults: {
locked: true,
// give enough time to read the warnings
secondsLeft: 8
},
initialize: function() {
this.tick();
},
tick: function() {
var self = this;
if (!this.get('locked')) return;
setTimeout(function() {
var seconds = self.get('secondsLeft') - 1;
self.set('secondsLeft', seconds);
if (seconds <= 0) {
self.set('locked', false);
} else {
self.tick();
}
}, 1000);
}
});
var Modal = ModalView.extend({
initialize: function(options) {
options = options || {};
......
Doing this will:
<ul>
<li>Clear your GitHub tokens (doesn't apply to GitLab/Twitter)</li>
<li>Clear your email</li>
<li>Mark your account as removed</li>
</ul>
This will <strong>NOT</strong>:
<ul>
<li>Delete your messages</li>
</ul>
You can sign back in at any time to re-create your account.
<br>
<br>
Are you sure you want to do this?{{#if locked }} <em>(unlocking in {{secondsLeft }})</em>{{/if}}
{{#if error }}
<br>
<br>
<span class="error-text">{{error}}</span>
{{/if}}
......@@ -60,6 +60,8 @@ function getProfileCollection() {
result.add({ name: 'Terms of Service', stub: 'https://about.gitlab.com/terms/', target: '_blank' });
result.add({ name: 'Delete Account', stub: '#delete-account' });
if(isWebApp) {
result.add({ name: 'Sign Out', stub: '/logout' });
}
......
......@@ -29,7 +29,7 @@
}
.dropdown.profile-menu__list {
width: 200px;
width: 210px;
left: auto;
}
......
......@@ -77,6 +77,10 @@ input {
font-family: inherit;
}
.error-text {
color: @error-red;
}
body {
// This hack appears in an old Chrome issue to fix font rendering similar to what we're experiencing
......
......@@ -5,6 +5,7 @@ var githubGitterUserSearch = require("../../../services/github-gitter-user-searc
var gitterUserSearch = require("../../../services/user-search-service");
var StatusError = require('statuserror');
var mongoUtils = require('gitter-web-persistence-utils/lib/mongo-utils');
var userRemovalService = require('gitter-web-rooms/lib/user-removal-service');
module.exports = {
id: 'resourceUser',
......@@ -45,6 +46,15 @@ module.exports = {
return restSerializer.serializeObject(req.resourceUser, strategy);
},
destroy: function(req) {
if(!req.user) throw new StatusError(401);
return userRemovalService.removeByUsername(req.user.username)
.then(function() {
return { success: true };
});
},
load: function(req, id) {
if(!req.user) throw new StatusError(401);
......
......@@ -60,4 +60,17 @@ describe('user-api', function() {
});
});
it('DELETE /v1/user/:userId', function() {
return request(app)
.delete('/v1/user/me')
.set('x-access-token', fixture.user1.accessToken)
.expect(200)
.then(function(result) {
assert.strictEqual(result.status, 200);
assert.deepEqual(result.body, {
success: true
});
});
});
});
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment