Merge branch 'gdpr-delete' of github.com:coralproject/talk into gdpr-delete

* 'gdpr-delete' of github.com:coralproject/talk:
  added emails around account deletion
This commit is contained in:
okbel
2018-05-03 15:48:49 -03:00
3 changed files with 112 additions and 9 deletions
@@ -1,10 +1,12 @@
const path = require('path');
const moment = require('moment');
const { CronJob } = require('cron');
const { get } = require('lodash');
const { ErrMissingEmail } = require('errors');
module.exports = connectors => {
const {
services: { Mailer },
services: { Mailer, I18n },
models: { User },
graph: { Context },
} = connectors;
@@ -28,6 +30,13 @@ module.exports = connectors => {
// Create the context we'll use to perform user deletions.
const ctx = Context.forSystem();
// Grab some settings.
const { loaders: { Settings } } = ctx;
const {
organizationName,
organizationContactEmail,
} = await Settings.load(['organizationName', 'organizationContactEmail']);
// rescheduledDeletionDate is the date in the future that we'll set the
// user's account to be deleted on if this delete fails.
const rescheduledDeletionDate = moment()
@@ -61,6 +70,26 @@ module.exports = connectors => {
break;
}
// Get the user's email address.
const reply = await ctx.graphql(
`
query GetUserEmailAddress($user_id: ID!) {
user(id: $user_id) {
email
}
}
`,
{ user_id: user.id }
);
if (reply.errors) {
throw reply.errors;
}
const email = get(reply, 'data.user.email');
if (!email) {
throw new ErrMissingEmail();
}
ctx.log.info(
{
userID: user.id,
@@ -91,6 +120,20 @@ module.exports = connectors => {
}
ctx.log.info({ userID: user.id }, 'user was deleted successfully');
// Send the download link via the user's attached email account.
await Mailer.send({
template: 'plain',
locals: {
body: I18n.t(
'email.deleted.body',
organizationName,
organizationContactEmail
),
},
subject: I18n.t('email.deleted.subject'),
email,
});
}
} catch (err) {
ctx.log.error({ err }, 'could not handle user deletions');
@@ -104,31 +104,64 @@ async function sendDownloadLink(ctx) {
// requestDeletion will schedule the current user to have their account deleted
// by setting the `scheduledDeletionDate` on the user
// ${scheduledDeletionDelayHours} hours from now.
async function requestDeletion({ user, connectors: { models: { User } } }) {
async function requestDeletion({
user,
loaders: { Settings },
connectors: { models: { User }, services: { Users, I18n } },
}) {
// Ensure the user doesn't already have a deletion scheduled.
if (get(user, 'metadata.scheduledDeletionDate')) {
throw new ErrDeletionAlreadyScheduled();
}
// Get the date in the future ${scheduledDeletionDelayHours} hours from now.
const scheduledDeletionDate = moment()
.add(scheduledDeletionDelayHours, 'hours')
.toDate();
const scheduledDeletionDate = moment().add(
scheduledDeletionDelayHours,
'hours'
);
// Amend the scheduledDeletionDate on the user.
await User.update(
{ id: user.id },
{ $set: { 'metadata.scheduledDeletionDate': scheduledDeletionDate } }
{
$set: {
'metadata.scheduledDeletionDate': scheduledDeletionDate.toDate(),
},
}
);
return scheduledDeletionDate;
const { organizationName } = await Settings.load('organizationName');
// Send the download link via the user's attached email account.
await Users.sendEmail(user, {
template: 'plain',
locals: {
body: I18n.t(
'email.delete.body',
organizationName,
scheduledDeletionDate.format('MMM Do YYYY, h:mm:ss a')
),
},
subject: I18n.t('email.delete.subject'),
});
return scheduledDeletionDate.toDate();
}
// cancelDeletion will unset the scheduled deletion date on the user account
// that is used to indicate that the user was scheduled for deletion.
async function cancelDeletion({ user, connectors: { models: { User } } }) {
async function cancelDeletion({
user,
loaders: { Settings },
connectors: { models: { User }, services: { I18n, Users } },
}) {
// Ensure the user has a deletion scheduled.
if (!get(user, 'metadata.scheduledDeletionDate', null)) {
const scheduledDeletionDate = get(
user,
'metadata.scheduledDeletionDate',
null
);
if (!scheduledDeletionDate) {
throw new ErrDeletionNotScheduled();
}
@@ -137,6 +170,21 @@ async function cancelDeletion({ user, connectors: { models: { User } } }) {
{ id: user.id },
{ $unset: { 'metadata.scheduledDeletionDate': 1 } }
);
const { organizationName } = await Settings.load('organizationName');
// Send the download link via the user's attached email account.
await Users.sendEmail(user, {
template: 'plain',
locals: {
body: I18n.t(
'email.cancelDelete.body',
organizationName,
moment(scheduledDeletionDate).format('MMM Do YYYY, h:mm:ss a')
),
},
subject: I18n.t('email.cancelDelete.subject'),
});
}
// downloadUser will return the download file url that can be used to directly
@@ -14,5 +14,17 @@ en:
subject: "Your comments are ready for download from {0}"
download_link_ready: "Click here to download your comments from {0} as of {1}:"
download_archive: "Download Archive"
delete:
subject: "Commenter Account Deletion Request"
body: "You have submitted a request to delete your commenter account on {0}. Your account will be deleted on {1}. If you change your mind, you can reactivate your account by logging in and cancelling the request."
deleted:
subject: "Commenter Account Deleted"
body: |
Your commenter account for {0} is now deleted. Were sorry to see you go!
If youd like to re-join the discussion in the future, you can sign up for a new account.
If youd like to give us feedback on why you left and what we can do to make the commenting experience better, please email us at {1}.
cancelDelete:
subject: "Commenter Account Reactivated"
body: "You have cancelled your account deletion request for {0}. Your account is now reactivated."
error:
DOWNLOAD_TOKEN_INVALID: "Your download link is not valid."