mirror of
https://github.com/wassname/talk.git
synced 2026-09-09 11:38:08 +08:00
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:
@@ -1,10 +1,12 @@
|
|||||||
const path = require('path');
|
const path = require('path');
|
||||||
const moment = require('moment');
|
const moment = require('moment');
|
||||||
const { CronJob } = require('cron');
|
const { CronJob } = require('cron');
|
||||||
|
const { get } = require('lodash');
|
||||||
|
const { ErrMissingEmail } = require('errors');
|
||||||
|
|
||||||
module.exports = connectors => {
|
module.exports = connectors => {
|
||||||
const {
|
const {
|
||||||
services: { Mailer },
|
services: { Mailer, I18n },
|
||||||
models: { User },
|
models: { User },
|
||||||
graph: { Context },
|
graph: { Context },
|
||||||
} = connectors;
|
} = connectors;
|
||||||
@@ -28,6 +30,13 @@ module.exports = connectors => {
|
|||||||
// Create the context we'll use to perform user deletions.
|
// Create the context we'll use to perform user deletions.
|
||||||
const ctx = Context.forSystem();
|
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
|
// rescheduledDeletionDate is the date in the future that we'll set the
|
||||||
// user's account to be deleted on if this delete fails.
|
// user's account to be deleted on if this delete fails.
|
||||||
const rescheduledDeletionDate = moment()
|
const rescheduledDeletionDate = moment()
|
||||||
@@ -61,6 +70,26 @@ module.exports = connectors => {
|
|||||||
break;
|
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(
|
ctx.log.info(
|
||||||
{
|
{
|
||||||
userID: user.id,
|
userID: user.id,
|
||||||
@@ -91,6 +120,20 @@ module.exports = connectors => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ctx.log.info({ userID: user.id }, 'user was deleted successfully');
|
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) {
|
} catch (err) {
|
||||||
ctx.log.error({ err }, 'could not handle user deletions');
|
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
|
// requestDeletion will schedule the current user to have their account deleted
|
||||||
// by setting the `scheduledDeletionDate` on the user
|
// by setting the `scheduledDeletionDate` on the user
|
||||||
// ${scheduledDeletionDelayHours} hours from now.
|
// ${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.
|
// Ensure the user doesn't already have a deletion scheduled.
|
||||||
if (get(user, 'metadata.scheduledDeletionDate')) {
|
if (get(user, 'metadata.scheduledDeletionDate')) {
|
||||||
throw new ErrDeletionAlreadyScheduled();
|
throw new ErrDeletionAlreadyScheduled();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the date in the future ${scheduledDeletionDelayHours} hours from now.
|
// Get the date in the future ${scheduledDeletionDelayHours} hours from now.
|
||||||
const scheduledDeletionDate = moment()
|
const scheduledDeletionDate = moment().add(
|
||||||
.add(scheduledDeletionDelayHours, 'hours')
|
scheduledDeletionDelayHours,
|
||||||
.toDate();
|
'hours'
|
||||||
|
);
|
||||||
|
|
||||||
// Amend the scheduledDeletionDate on the user.
|
// Amend the scheduledDeletionDate on the user.
|
||||||
await User.update(
|
await User.update(
|
||||||
{ id: user.id },
|
{ 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
|
// cancelDeletion will unset the scheduled deletion date on the user account
|
||||||
// that is used to indicate that the user was scheduled for deletion.
|
// 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.
|
// 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();
|
throw new ErrDeletionNotScheduled();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -137,6 +170,21 @@ async function cancelDeletion({ user, connectors: { models: { User } } }) {
|
|||||||
{ id: user.id },
|
{ id: user.id },
|
||||||
{ $unset: { 'metadata.scheduledDeletionDate': 1 } }
|
{ $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
|
// 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}"
|
subject: "Your comments are ready for download from {0}"
|
||||||
download_link_ready: "Click here to download your comments from {0} as of {1}:"
|
download_link_ready: "Click here to download your comments from {0} as of {1}:"
|
||||||
download_archive: "Download Archive"
|
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. We’re sorry to see you go!
|
||||||
|
If you’d like to re-join the discussion in the future, you can sign up for a new account.
|
||||||
|
If you’d 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:
|
error:
|
||||||
DOWNLOAD_TOKEN_INVALID: "Your download link is not valid."
|
DOWNLOAD_TOKEN_INVALID: "Your download link is not valid."
|
||||||
|
|||||||
Reference in New Issue
Block a user