Merge branch 'master' of github.com:coralproject/talk into email-auth-gdpr

* 'master' of github.com:coralproject/talk:
  fix
  handle confirmed email better
  standardized styles, fixed mutation bug
  password patches
  fixed merge error
  Fix build fail
  Missed an s
  docs patch
  Add feature overview to GDPR docs
  InputField conflict
  moving mutations to /hocs folder
  moving mutations to /hocs folder
  withUpdateEmailAddress in the plugins hoc
This commit is contained in:
okbel
2018-05-04 09:46:58 -03:00
14 changed files with 117 additions and 97 deletions
+11 -1
View File
@@ -12,9 +12,19 @@ can enable the following plugins:
- [talk-plugin-profile-data](/talk/plugin/talk-plugin-profile-data) - to facilitate account download and deletion
Even if you don't reside in a location where GDPR will apply, it is recommended
to enable these features anyways to provide your users with control over their
to enable these features as a best practice to provide your users with control over their
own data.
## GPDR Feature Overview
Integrating our GDPR tools will give your users and organizations the following benefits:
- **Download my comment data**: Users can request a download of their comments. An email with a link is emailed to them to download a CSV with each comment they've made, what story it was made on, and the comment's ID and timestamp.
- **Delete my acccount**: Users can request deletion of their account. Deleted account requests are pending for 24 hours to allow the user to download their comments, or to change their mind and reactivate their account before the expiry. Account deletions remove all of their comments from the site, all their comments and actions from the database, and their account info from our system.
- **Add an email to an Oauth/external account**: Users are prompted to add an email to their non-Talk account (Facebook, Google, external, etc) so that they can take part in GDPR and other features requiring email communication.
- **Change my username**: Users can update their username. This is capped at once every 2 weeks.
- **Change my email**: Users can change their email.
## Custom Authentication Solutions
As many of the newsrooms who have integrated Talk have followed our guides on
@@ -1,22 +1,27 @@
.container {
position: relative;
color: #202020;
padding: 10px;
border-radius: 2px;
border: solid 1px transparent;
box-sizing: border-box;
justify-content: space-between;
&.editing {
padding: 10px;
border-color: #979797;
background-color: #EDEDED;
.actions {
top: 10px;
right: 10px;
}
}
}
.actions {
position: absolute;
top: 10px;
right: 10px;
top: 0px;
right: 0px;
display: flex;
flex-direction: column;
align-items: center;
@@ -31,11 +36,11 @@
display: block;
padding-top: 4px;
text-align: right;
width: 280px;
width: 230px;
}
.detailLink {
color: #00538A;
color: #00538A;
text-decoration: none;
font-size: 0.9em;
&:hover {
@@ -59,7 +64,7 @@
> i {
font-size: 17px;
}
&:hover {
background-color: #399ee2;
color: white;
@@ -5,6 +5,7 @@
.detailItemContainer {
display: flex;
flex-direction: column;
}
.columnDisplay {
@@ -16,6 +17,10 @@
}
.detailItemContent {
display: flex;
}
.detailInput {
border: solid 1px #787D80;
border-radius: 2px;
background-color: white;
@@ -57,6 +62,7 @@
flex: 1;
height: 100%;
box-sizing: border-box;
padding: 0 6px;
}
.detailItemMessage {
@@ -64,7 +70,6 @@
display: flex;
align-items: center;
padding-left: 6px;
padding-top: 16px;
.warningIcon, .checkIcon {
font-size: 17px;
@@ -30,41 +30,45 @@ const InputField = ({
return (
<div className={styles.detailItem}>
<div
className={cn(styles.detailItemContainer, {
[styles.columnDisplay]: columnDisplay,
})}
>
<div className={cn(styles.detailItemContainer)}>
{label && (
<label className={styles.detailLabel} id={id}>
{label}
</label>
)}
<div
className={cn(
styles.detailItemContent,
{ [styles.error]: hasError && showError },
{ [styles.disabled]: disabled }
)}
className={cn(styles.detailItemContent, {
[styles.columnDisplay]: columnDisplay,
})}
>
{icon && <Icon name={icon} className={styles.detailIcon} />}
<input
id={id}
type={type}
name={name}
className={styles.detailValue}
onChange={onChange}
autoComplete="off"
data-validation-type={validationType}
disabled={disabled}
{...inputValue}
/>
</div>
<div className={styles.detailItemMessage}>
{!hasError &&
showSuccess &&
value && <Icon className={styles.checkIcon} name="check_circle" />}
{hasError && showError && <ErrorMessage>{errorMsg}</ErrorMessage>}
<div
className={cn(
styles.detailInput,
{ [styles.error]: hasError && showError },
{ [styles.disabled]: disabled }
)}
>
{icon && <Icon name={icon} className={styles.detailIcon} />}
<input
id={id}
type={type}
name={name}
className={styles.detailValue}
onChange={onChange}
autoComplete="off"
data-validation-type={validationType}
disabled={disabled}
{...inputValue}
/>
</div>
<div className={styles.detailItemMessage}>
{!hasError &&
showSuccess &&
value && (
<Icon className={styles.checkIcon} name="check_circle" />
)}
{hasError && showError && <ErrorMessage>{errorMsg}</ErrorMessage>}
</div>
</div>
</div>
{children}
@@ -0,0 +1,35 @@
import update from 'immutability-helper';
import get from 'lodash/get';
import findIndex from 'lodash/findIndex';
export default {
mutations: {
UpdateEmailAddress: () => ({
updateQueries: {
CoralEmbedStream_Profile: previousData => {
// Find the local profile (if they have one).
const localIndex = findIndex(get(previousData, 'me.profiles', []), {
provider: 'local',
});
if (localIndex < 0) {
return previousData;
}
// Mutate the confirmedAt, because we changed the email address, they
// can't possibly be confirmed now as well.
return update(previousData, {
me: {
profiles: {
[localIndex]: {
confirmedAt: {
$set: null,
},
},
},
},
});
},
},
}),
},
};
@@ -2,6 +2,7 @@ import ChangePassword from './containers/ChangePassword';
import AddEmailAddressDialog from './containers/AddEmailAddressDialog';
import Profile from './containers/Profile';
import translations from './translations.yml';
import graphql from './graphql';
export default {
translations,
@@ -10,4 +11,5 @@ export default {
profileSettings: [ChangePassword],
stream: [AddEmailAddressDialog],
},
...graphql,
};
@@ -42,6 +42,7 @@ async function updateUserEmailAddress(ctx, email, confirmPassword) {
},
{
$set: { 'profiles.$.id': email },
$unset: { 'profiles.$.metadata.confirmed_at': 1 },
}
);
@@ -1,23 +0,0 @@
.button {
color: #787D80;
border-radius: 2px;
border: 1px solid #787d80;
background-color: transparent;
height: 30px;
font-size: 0.9em;
line-height: normal;
&:hover {
background-color: #eaeaea;
}
&.secondary {
background-color: #787D80;
color: white;
}
> i {
font-size: 1.2em;
vertical-align: middle;
}
}
@@ -1,8 +1,6 @@
import React from 'react';
import PropTypes from 'prop-types';
import cn from 'classnames';
import moment from 'moment';
import styles from './DeleteMyAccount.css';
import { Button } from 'plugin-api/beta/client/components/ui';
import DeleteMyAccountDialog from './DeleteMyAccountDialog';
import { getErrorMessages } from 'coral-framework/utils';
@@ -59,28 +57,13 @@ class DeleteMyAccount extends React.Component {
scheduledDeletionDate={scheduledDeletionDate}
organizationContactEmail={organizationContactEmail}
/>
<h3
className={cn(
styles.title,
'talk-plugin-auth--delete-my-account-description'
)}
>
<h3 className="talk-plugin-auth--delete-my-account-description">
{t('delete_request.delete_my_account')}
</h3>
<p
className={cn(
styles.description,
'talk-plugin-auth--delete-my-account-description'
)}
>
<p className="talk-plugin-auth--delete-my-account-description">
{t('delete_request.delete_my_account_description')}
</p>
<p
className={cn(
styles.description,
'talk-plugin-auth--delete-my-account-description'
)}
>
<p className="talk-plugin-auth--delete-my-account-description">
{scheduledDeletionDate &&
t(
'delete_request.already_submitted_request_description',
@@ -88,18 +71,11 @@ class DeleteMyAccount extends React.Component {
)}
</p>
{scheduledDeletionDate ? (
<Button
className={cn(styles.button, styles.secondary)}
onClick={this.cancelAccountDeletion}
>
<Button onClick={this.cancelAccountDeletion}>
{t('delete_request.cancel_account_deletion_request')}
</Button>
) : (
<Button
className={cn(styles.button)}
icon="delete"
onClick={this.showDialog}
>
<Button icon="delete" onClick={this.showDialog}>
{t('delete_request.delete_my_account')}
</Button>
)}
@@ -97,7 +97,7 @@ DeleteMyAccountDialog.propTypes = {
showDialog: PropTypes.bool.isRequired,
closeDialog: PropTypes.func.isRequired,
requestAccountDeletion: PropTypes.func.isRequired,
scheduledDeletionDate: PropTypes.any.isRequired,
scheduledDeletionDate: PropTypes.any,
organizationContactEmail: PropTypes.string.isRequired,
};
@@ -3,8 +3,8 @@ import PropTypes from 'prop-types';
import { bindActionCreators } from 'redux';
import { compose, gql } from 'react-apollo';
import DownloadCommentHistory from '../components/DownloadCommentHistory';
import { connect, withFragments } from 'plugin-api/beta/client/hocs';
import { withRequestDownloadLink } from '../hocs';
import { connect, withFragments } from 'plugin-api/beta/client/hocs';
import { notify } from 'coral-framework/actions/notification';
class DownloadCommentHistoryContainer extends Component {
@@ -10,7 +10,7 @@ export default {
),
},
mutations: {
RequestDownloadLink: () => ({
DownloadCommentHistory: () => ({
updateQueries: {
CoralEmbedStream_Profile: previousData =>
update(previousData, {
@@ -1,19 +1,21 @@
import { withMutation } from 'plugin-api/beta/client/hocs';
import { gql } from 'react-apollo';
import update from 'immutability-helper';
import moment from 'moment';
import update from 'immutability-helper';
export const withRequestDownloadLink = withMutation(
gql`
mutation RequestDownloadLink {
mutation DownloadCommentHistory {
requestDownloadLink {
...RequestDownloadLinkResponse
errors {
translation_key
}
}
}
`,
{
props: ({ mutate }) => ({
requestDownloadLink: () => mutate({}),
requestDownloadLink: () => mutate({ variables: {} }),
}),
}
);
+4 -1
View File
@@ -32,6 +32,7 @@ const i18n = require('./i18n');
const Wordlist = require('./wordlist');
const DomainList = require('./domain_list');
const Limit = require('./limit');
const { get } = require('lodash');
const EMAIL_CONFIRM_JWT_SUBJECT = 'email_confirm';
const PASSWORD_RESET_JWT_SUBJECT = 'password_reset';
@@ -969,7 +970,9 @@ class Users {
throw new ErrNotFound();
}
if (profile.metadata && profile.metadata.confirmed_at !== null) {
// Check to see if the profile has already been confirmed.
const confirmedAt = get(profile, 'metadata.confirmed_at', null);
if (confirmedAt && confirmedAt < Date.now()) {
throw new ErrEmailAlreadyVerified();
}