mirror of
https://github.com/wassname/talk.git
synced 2026-09-09 11:38:08 +08:00
[CORL-439] Allow admins to configure GDPR features (#2497)
* allow tenant admins to modify commenter account features * style account features configuration * prevent changing username or downloading comments based on tenant settings * update fixtures and snaps * add i18n strings * update snap
This commit is contained in:
committed by
Kim Gardner
parent
46f0d08cf7
commit
fcf3640adc
@@ -15,6 +15,7 @@ interface Props {
|
||||
offLabel?: React.ReactNode;
|
||||
format?: ((value: any, name: string) => any) | null;
|
||||
parse?: ((value: any, name: string) => any) | null;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const OnOffField: FunctionComponent<Props> = ({
|
||||
@@ -25,8 +26,9 @@ const OnOffField: FunctionComponent<Props> = ({
|
||||
invert = false,
|
||||
parse = parseStringBool,
|
||||
format,
|
||||
className,
|
||||
}) => (
|
||||
<div>
|
||||
<div className={className}>
|
||||
<Field
|
||||
name={name}
|
||||
type="radio"
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
.radioButtons {
|
||||
display: flex;
|
||||
padding-left: var(--spacing-4);
|
||||
align-items: flex-start;
|
||||
& > *:not(:last-child) {
|
||||
margin-right: var(--spacing-5);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
import { Localized } from "fluent-react/compat";
|
||||
import React, { FunctionComponent } from "react";
|
||||
|
||||
import {
|
||||
FieldSet,
|
||||
Flex,
|
||||
FormField,
|
||||
HorizontalGutter,
|
||||
InputLabel,
|
||||
Typography,
|
||||
} from "coral-ui/components";
|
||||
import OnOffField from "../../OnOffField";
|
||||
|
||||
import Header from "../../Header";
|
||||
|
||||
import styles from "./AccountFeaturesConfig.css";
|
||||
|
||||
interface Props {
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
const AccountFeaturesConfig: FunctionComponent<Props> = ({ disabled }) => (
|
||||
<div>
|
||||
<HorizontalGutter size="oneAndAHalf">
|
||||
<Localized id="configure-account-features-title">
|
||||
<Header container="legend">Commenter account mangement features</Header>
|
||||
</Localized>
|
||||
<Localized id="configure-account-features-explanation">
|
||||
<Typography variant="bodyCopy">
|
||||
You can enable and disable certain features for your commenters to use
|
||||
within their Profile. These features also assist towards GDPR
|
||||
compliance.
|
||||
</Typography>
|
||||
</Localized>
|
||||
<HorizontalGutter container={<FieldSet />}>
|
||||
<Localized id="configure-account-features-allow">
|
||||
<Typography variant="heading4">Allow users to:</Typography>
|
||||
</Localized>
|
||||
<FormField container="fieldset">
|
||||
<Flex justifyContent="space-between">
|
||||
<HorizontalGutter size="half">
|
||||
<Localized id="configure-account-features-change-usernames">
|
||||
<InputLabel container="legend">
|
||||
Change their usernames
|
||||
</InputLabel>
|
||||
</Localized>
|
||||
<Localized id="configure-account-features-change-usernames-details">
|
||||
<Typography>
|
||||
Usernames can be changed once every 14 days.
|
||||
</Typography>
|
||||
</Localized>
|
||||
</HorizontalGutter>
|
||||
<OnOffField
|
||||
name="accountFeatures.changeUsername"
|
||||
disabled={disabled || false}
|
||||
className={styles.radioButtons}
|
||||
onLabel={
|
||||
<Localized id="configure-account-features-yes">
|
||||
<span>Yes</span>
|
||||
</Localized>
|
||||
}
|
||||
offLabel={
|
||||
<Localized id="configure-account-features-no">
|
||||
<span>No</span>
|
||||
</Localized>
|
||||
}
|
||||
/>
|
||||
</Flex>
|
||||
</FormField>
|
||||
</HorizontalGutter>
|
||||
<HorizontalGutter size="double">
|
||||
<FormField container="fieldset">
|
||||
<Flex justifyContent="space-between">
|
||||
<HorizontalGutter size="half">
|
||||
<Localized id="configure-account-features-download-comments">
|
||||
<InputLabel container="legend">
|
||||
Download their comments
|
||||
</InputLabel>
|
||||
</Localized>
|
||||
<Localized id="configure-account-features-download-comments-details">
|
||||
<Typography>
|
||||
Commenters can download a csv of their comment history.
|
||||
</Typography>
|
||||
</Localized>
|
||||
</HorizontalGutter>
|
||||
<OnOffField
|
||||
name="accountFeatures.downloadComments"
|
||||
disabled={disabled || false}
|
||||
className={styles.radioButtons}
|
||||
onLabel={
|
||||
<Localized id="configure-account-features-yes">
|
||||
<span>Yes</span>
|
||||
</Localized>
|
||||
}
|
||||
offLabel={
|
||||
<Localized id="configure-account-features-no">
|
||||
<span>No</span>
|
||||
</Localized>
|
||||
}
|
||||
/>
|
||||
</Flex>
|
||||
</FormField>
|
||||
</HorizontalGutter>
|
||||
<HorizontalGutter size="double">
|
||||
<FormField container="fieldset">
|
||||
<Flex justifyContent="space-between">
|
||||
<HorizontalGutter size="half">
|
||||
<Localized id="configure-account-features-delete-account">
|
||||
<InputLabel container="legend">Delete their account</InputLabel>
|
||||
</Localized>
|
||||
<Localized id="configure-account-features-delete-account-details">
|
||||
<Typography>
|
||||
Removes all of their comment data, username, and email address
|
||||
from the site and the database.
|
||||
</Typography>
|
||||
</Localized>
|
||||
</HorizontalGutter>
|
||||
<OnOffField
|
||||
name="accountFeatures.deleteAccount"
|
||||
disabled={disabled || false}
|
||||
className={styles.radioButtons}
|
||||
onLabel={
|
||||
<Localized id="configure-account-features-yes">
|
||||
<span>Yes</span>
|
||||
</Localized>
|
||||
}
|
||||
offLabel={
|
||||
<Localized id="configure-account-features-no">
|
||||
<span>No</span>
|
||||
</Localized>
|
||||
}
|
||||
/>
|
||||
</Flex>
|
||||
</FormField>
|
||||
</HorizontalGutter>
|
||||
</HorizontalGutter>
|
||||
</div>
|
||||
);
|
||||
|
||||
export default AccountFeaturesConfig;
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
import React from "react";
|
||||
import { graphql } from "react-relay";
|
||||
|
||||
import { AccountFeaturesConfigContainer_settings as SettingsData } from "coral-admin/__generated__/AccountFeaturesConfigContainer_settings.graphql";
|
||||
import { DeepNullable, DeepPartial } from "coral-common/types";
|
||||
import { withFragmentContainer } from "coral-framework/lib/relay";
|
||||
|
||||
import { GQLSettings } from "coral-framework/schema";
|
||||
import AccountFeaturesConfig from "./AccountFeaturesConfig";
|
||||
|
||||
export type FormProps = DeepNullable<Pick<GQLSettings, "accountFeatures">>;
|
||||
export type OnInitValuesFct = (values: DeepPartial<FormProps>) => void;
|
||||
|
||||
interface Props {
|
||||
settings: SettingsData;
|
||||
onInitValues: (values: SettingsData) => void;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
class AccountFeaturesConfigContainer extends React.Component<Props> {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
props.onInitValues(props.settings);
|
||||
}
|
||||
|
||||
public render() {
|
||||
const { disabled } = this.props;
|
||||
return <AccountFeaturesConfig disabled={disabled} />;
|
||||
}
|
||||
}
|
||||
|
||||
const enhanced = withFragmentContainer<Props>({
|
||||
settings: graphql`
|
||||
fragment AccountFeaturesConfigContainer_settings on Settings {
|
||||
accountFeatures {
|
||||
changeUsername
|
||||
deleteAccount
|
||||
downloadComments
|
||||
}
|
||||
}
|
||||
`,
|
||||
})(AccountFeaturesConfigContainer);
|
||||
|
||||
export default enhanced;
|
||||
@@ -5,6 +5,7 @@ import React from "react";
|
||||
import { graphql } from "react-relay";
|
||||
|
||||
import { AuthConfigContainer_auth as AuthData } from "coral-admin/__generated__/AuthConfigContainer_auth.graphql";
|
||||
import { AuthConfigContainer_settings as SettingsData } from "coral-admin/__generated__/AuthConfigContainer_settings.graphql";
|
||||
import { DeepNullable, DeepPartial } from "coral-common/types";
|
||||
import { pureMerge } from "coral-common/utils";
|
||||
import { CoralContext, withContext } from "coral-framework/lib/bootstrap";
|
||||
@@ -17,10 +18,14 @@ import {
|
||||
import { getMessage } from "coral-framework/lib/i18n";
|
||||
import { withFragmentContainer } from "coral-framework/lib/relay";
|
||||
import { GQLSettings } from "coral-framework/schema";
|
||||
import { HorizontalGutter } from "coral-ui/components";
|
||||
|
||||
import AccountFeaturesConfigContainer from "./AccountFeaturesConfigContainer";
|
||||
import AuthConfig from "./AuthConfig";
|
||||
|
||||
export type FormProps = DeepNullable<Pick<GQLSettings, "auth">>;
|
||||
export type FormProps = DeepNullable<
|
||||
Pick<GQLSettings, "auth" | "accountFeatures">
|
||||
>;
|
||||
export type OnInitValuesFct = (values: DeepPartial<FormProps>) => void;
|
||||
|
||||
interface Props {
|
||||
@@ -29,6 +34,7 @@ interface Props {
|
||||
submitting?: boolean;
|
||||
addSubmitHook: AddSubmitHook<FormProps>;
|
||||
auth: AuthData;
|
||||
settings: SettingsData;
|
||||
}
|
||||
|
||||
class AuthConfigContainer extends React.Component<Props> {
|
||||
@@ -96,16 +102,28 @@ class AuthConfigContainer extends React.Component<Props> {
|
||||
|
||||
public render() {
|
||||
return (
|
||||
<AuthConfig
|
||||
disabled={this.props.submitting}
|
||||
auth={this.props.auth}
|
||||
onInitValues={this.handleOnInitValues}
|
||||
/>
|
||||
<HorizontalGutter size="double">
|
||||
<AccountFeaturesConfigContainer
|
||||
onInitValues={this.handleOnInitValues}
|
||||
settings={this.props.settings}
|
||||
disabled={this.props.submitting}
|
||||
/>
|
||||
<AuthConfig
|
||||
disabled={this.props.submitting}
|
||||
auth={this.props.auth}
|
||||
onInitValues={this.handleOnInitValues}
|
||||
/>
|
||||
</HorizontalGutter>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const enhanced = withFragmentContainer<Props>({
|
||||
settings: graphql`
|
||||
fragment AuthConfigContainer_settings on Settings {
|
||||
...AccountFeaturesConfigContainer_settings
|
||||
}
|
||||
`,
|
||||
auth: graphql`
|
||||
fragment AuthConfigContainer_auth on Auth {
|
||||
...FacebookConfigContainer_auth
|
||||
|
||||
@@ -25,6 +25,7 @@ class AuthConfigRoute extends React.Component<Props> {
|
||||
}
|
||||
return (
|
||||
<AuthConfigContainer
|
||||
settings={this.props.data.settings}
|
||||
auth={this.props.data.settings.auth}
|
||||
form={this.props.form}
|
||||
submitting={this.props.submitting}
|
||||
@@ -37,6 +38,7 @@ const enhanced = withRouteConfig<Props>({
|
||||
query: graphql`
|
||||
query AuthConfigRouteContainerQuery {
|
||||
settings {
|
||||
...AuthConfigContainer_settings
|
||||
auth {
|
||||
...AuthConfigContainer_auth
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -141,6 +141,11 @@ export const settings = createFixture<GQLSettings>({
|
||||
},
|
||||
},
|
||||
},
|
||||
accountFeatures: {
|
||||
downloadComments: true,
|
||||
changeUsername: true,
|
||||
deleteAccount: true,
|
||||
},
|
||||
});
|
||||
|
||||
export const settingsWithEmptyAuth = createFixture<GQLSettings>(
|
||||
|
||||
@@ -168,7 +168,7 @@ const ChangeUsernameContainer: FunctionComponent<Props> = ({
|
||||
{!showEditForm && (
|
||||
<Flex alignItems="baseline">
|
||||
<Typography variant="header2">{viewer.username}</Typography>
|
||||
{canChangeLocalAuth && (
|
||||
{canChangeLocalAuth && settings.accountFeatures.changeUsername && (
|
||||
<Localized id="profile-changeUsername-edit">
|
||||
<Button size="small" color="primary" onClick={toggleEditForm}>
|
||||
edit
|
||||
@@ -374,6 +374,9 @@ const enhanced = withFragmentContainer<Props>({
|
||||
`,
|
||||
settings: graphql`
|
||||
fragment ChangeUsernameContainer_settings on Settings {
|
||||
accountFeatures {
|
||||
changeUsername
|
||||
}
|
||||
auth {
|
||||
integrations {
|
||||
local {
|
||||
|
||||
@@ -22,8 +22,12 @@ const SettingsContainer: FunctionComponent<Props> = ({ viewer, settings }) => (
|
||||
<HorizontalGutter spacing={5} className={styles.root}>
|
||||
<IgnoreUserSettingsContainer viewer={viewer} />
|
||||
<ChangePasswordContainer settings={settings} />
|
||||
<DownloadCommentsContainer viewer={viewer} />
|
||||
<DeleteAccountContainer viewer={viewer} settings={settings} />
|
||||
{settings.accountFeatures.downloadComments && (
|
||||
<DownloadCommentsContainer viewer={viewer} />
|
||||
)}
|
||||
{settings.accountFeatures.deleteAccount && (
|
||||
<DeleteAccountContainer viewer={viewer} settings={settings} />
|
||||
)}
|
||||
</HorizontalGutter>
|
||||
);
|
||||
|
||||
@@ -37,6 +41,10 @@ const enhanced = withFragmentContainer<Props>({
|
||||
`,
|
||||
settings: graphql`
|
||||
fragment SettingsContainer_settings on Settings {
|
||||
accountFeatures {
|
||||
downloadComments
|
||||
deleteAccount
|
||||
}
|
||||
...ChangePasswordContainer_settings
|
||||
...DeleteAccountContainer_settings
|
||||
}
|
||||
|
||||
@@ -96,6 +96,11 @@ export const settings = createFixture<GQLSettings>({
|
||||
charCount: {
|
||||
enabled: false,
|
||||
},
|
||||
accountFeatures: {
|
||||
downloadComments: true,
|
||||
changeUsername: true,
|
||||
deleteAccount: true,
|
||||
},
|
||||
});
|
||||
|
||||
export const settingsWithoutLocalAuth = createFixture<GQLSettings>(
|
||||
|
||||
@@ -1079,6 +1079,24 @@ type StoryConfiguration {
|
||||
disableLazy: Boolean!
|
||||
}
|
||||
|
||||
"""
|
||||
CommenterAccountFeatures stores the configuration for commenter account features.
|
||||
"""
|
||||
type CommenterAccountFeatures {
|
||||
"""
|
||||
changeUsername when true, non-sso user may change username every 14 days
|
||||
"""
|
||||
changeUsername: Boolean!
|
||||
"""
|
||||
downloadComments when true, user may download their comment history
|
||||
"""
|
||||
downloadComments: Boolean!
|
||||
"""
|
||||
deleteAccount when true, non-sso user may permanently delete their account
|
||||
"""
|
||||
deleteAccount: Boolean!
|
||||
}
|
||||
|
||||
"""
|
||||
Settings stores the global settings for a given Tenant.
|
||||
"""
|
||||
@@ -1194,6 +1212,11 @@ type Settings {
|
||||
"""
|
||||
reaction: ReactionConfiguration!
|
||||
|
||||
"""
|
||||
accountFeatures stores the configuration for commenter account features.
|
||||
"""
|
||||
accountFeatures: CommenterAccountFeatures!
|
||||
|
||||
"""
|
||||
stories stores the configuration around stories.
|
||||
"""
|
||||
@@ -3108,6 +3131,24 @@ input ReactionConfigurationInput {
|
||||
color: String
|
||||
}
|
||||
|
||||
"""
|
||||
CommenterAccountFeatures stores the configuration for commenter account features.
|
||||
"""
|
||||
input CommenterAccountFeaturesInput {
|
||||
"""
|
||||
changeUsername when true, non-sso user may change username every 14 days
|
||||
"""
|
||||
changeUsername: Boolean
|
||||
"""
|
||||
downloadComments when true, user may download their comment history
|
||||
"""
|
||||
downloadComments: Boolean
|
||||
"""
|
||||
deleteAccount when true, non-sso user may permanently delete their account
|
||||
"""
|
||||
deleteAccount: Boolean
|
||||
}
|
||||
|
||||
"""
|
||||
SettingsInput is the partial type of the Settings type for performing mutations.
|
||||
"""
|
||||
@@ -3206,6 +3247,11 @@ input SettingsInput {
|
||||
reaction specifies the configuration for reactions.
|
||||
"""
|
||||
reaction: ReactionConfigurationInput
|
||||
|
||||
"""
|
||||
accountFeatures specifies the configuration for accounts.
|
||||
"""
|
||||
accountFeatures: CommenterAccountFeaturesInput
|
||||
}
|
||||
|
||||
"""
|
||||
|
||||
@@ -49,6 +49,15 @@ export interface AuthIntegrations {
|
||||
facebook: FacebookAuthIntegration;
|
||||
}
|
||||
|
||||
/**
|
||||
* AccountFeatures are features enabled for commenter accounts
|
||||
*/
|
||||
export interface AccountFeatures {
|
||||
changeUsername: boolean;
|
||||
deleteAccount: boolean;
|
||||
downloadComments: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Auth is the set of configured authentication integrations.
|
||||
*/
|
||||
@@ -112,4 +121,9 @@ export type Settings = GlobalModerationSettings &
|
||||
* disableCommenting will disable commenting site-wide.
|
||||
*/
|
||||
disableCommenting: DisableCommenting;
|
||||
|
||||
/**
|
||||
* AccountFeatures are features enabled for commenter accounts
|
||||
*/
|
||||
accountFeatures: AccountFeatures;
|
||||
};
|
||||
|
||||
@@ -192,6 +192,11 @@ export async function createTenant(
|
||||
},
|
||||
disableLazy: false,
|
||||
},
|
||||
accountFeatures: {
|
||||
changeUsername: false,
|
||||
deleteAccount: false,
|
||||
downloadComments: false,
|
||||
},
|
||||
createdAt: now,
|
||||
};
|
||||
|
||||
|
||||
@@ -612,6 +612,10 @@ function enabledAuthenticationIntegrations(
|
||||
* @param user the User that we are updating
|
||||
*/
|
||||
function canUpdateLocalProfile(tenant: Tenant, user: User): boolean {
|
||||
if (!tenant.accountFeatures.changeUsername) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!hasLocalProfile(user)) {
|
||||
return false;
|
||||
}
|
||||
@@ -965,6 +969,9 @@ export async function requestCommentsDownload(
|
||||
user: User,
|
||||
now: Date
|
||||
) {
|
||||
if (!tenant.accountFeatures.downloadComments) {
|
||||
throw new Error("Downloading comments is not enabled");
|
||||
}
|
||||
// Check to see if the user is allowed to download this now.
|
||||
if (
|
||||
user.lastDownloadedAt &&
|
||||
|
||||
@@ -745,3 +745,19 @@ configure-general-reactions-sort-input =
|
||||
.placehodlder = E.g. Most Respected
|
||||
configure-general-reactions-preview = Preview
|
||||
configure-general-reaction-sortMenu-sortBy = Sort by
|
||||
|
||||
configure-account-features-title = Commenter account mangement features
|
||||
configure-account-features-explanation =
|
||||
You can enable and disable certain features for your commenters to use
|
||||
within their Profile. These features also assist towards GDPR
|
||||
compliance.
|
||||
configure-account-features-allow = Allow users to:
|
||||
configure-account-features-change-usernames = Change their usernames
|
||||
configure-account-features-change-usernames-details = Usernames can be changed once every 14 days.
|
||||
configure-account-features-yes = Yes
|
||||
configure-account-features-no = No
|
||||
configure-account-features-download-comments = Download their comments
|
||||
configure-account-features-download-comments-details = Commenters can download a csv of their comment history.
|
||||
configure-account-features-delete-account = Delete their account
|
||||
configure-account-features-delete-account-details =
|
||||
Removes all of their comment data, username, and email address from the site and the database.
|
||||
Reference in New Issue
Block a user