[CORL-845] Account Linking (#2818)

* feat: added new linking backend

* feat: added duplicateEmail to hash

* fix: stored the duplicate email on the user

* feat: initial implmentation of account linking in auth

* test: fix unit tests

* fix+test: translations and tests added

* chore+test: rename view to LINK_ACCOUNT + more tests

* feat+test: account linking admin + more tests

* feat: Handle incomplete accounts

* chore: add some comments

* feat: expose duplicateEmail through graphql and impl for stream

* feat: admin to use duplicateEmail from graphql

* fix: no need to validate password for account linking

* fix: dont validate password

* fix: no need to render error message when account was incomplete

* chore: log to console when encountering incomplete account

* chore: adjust comment

* chore: simplify + add comments

* chore: wording

* chore: comments

Co-authored-by: Vinh <vinh@vinh.tech>
Co-authored-by: Kim Gardner <kgardnr@gmail.com>
This commit is contained in:
Wyatt Johnson
2020-02-25 15:46:32 -05:00
committed by GitHub
co-authored by Vinh Kim Gardner
parent 7497e33046
commit dcb2a10e72
80 changed files with 2414 additions and 710 deletions
@@ -0,0 +1,34 @@
import { useCallback } from "react";
import { ERROR_CODES } from "coral-common/errors";
import { InvalidRequestError } from "coral-framework/lib/errors";
import { useMutation } from "coral-framework/lib/relay";
import { SignOutMutation } from "coral-stream/mutations";
/**
* useHandleIncompleteAccount logs out a user when it
* encounters a `USER_NOT_ENTITLED` during a query. It accepts
* `data` from a `QueryRenderer`. Should only be used on Queries
* that normally does not fail except when the account
* is incomplete. Returns `true` if account was incomplete.
*/
const useHandleIncompleteAccount = () => {
const signOut = useMutation(SignOutMutation);
return useCallback(
(data: { error: Error | null }) => {
if (
data.error instanceof InvalidRequestError &&
data.error.code === ERROR_CODES.USER_NOT_ENTITLED
) {
// eslint-disable-next-line
console.log("Coral: User account is incomplete. Perform logout.")
signOut();
return true;
}
return false;
},
[signOut]
);
};
export default useHandleIncompleteAccount;
@@ -8,6 +8,7 @@ import {
withLocalStateContainer,
} from "coral-framework/lib/relay";
import Spinner from "coral-stream/common/Spinner";
import useHandleIncompleteAccount from "coral-stream/common/useHandleIncompleteAccount";
import { Delay } from "coral-ui/components";
import { PermalinkViewQuery as QueryTypes } from "coral-stream/__generated__/PermalinkViewQuery.graphql";
@@ -50,6 +51,7 @@ export const render = ({ error, props }: QueryRenderData<QueryTypes>) => {
const PermalinkViewQuery: FunctionComponent<Props> = ({
local: { commentID, storyID, storyURL },
}) => {
const handleIncompleteAccount = useHandleIncompleteAccount();
return (
<QueryRenderer<QueryTypes>
query={graphql`
@@ -77,7 +79,12 @@ const PermalinkViewQuery: FunctionComponent<Props> = ({
storyID,
storyURL,
}}
render={render}
render={data => {
if (handleIncompleteAccount(data)) {
return null;
}
return render(data);
}}
/>
);
};
@@ -8,6 +8,7 @@ import {
withLocalStateContainer,
} from "coral-framework/lib/relay";
import Spinner from "coral-stream/common/Spinner";
import useHandleIncompleteAccount from "coral-stream/common/useHandleIncompleteAccount";
import { Delay, Flex } from "coral-ui/components";
import { COMMENTS_TAB } from "coral-stream/__generated__/StreamContainerLocal.graphql";
@@ -67,6 +68,7 @@ const StreamQuery: FunctionComponent<Props> = props => {
const {
local: { storyID, storyURL, commentsTab },
} = props;
const handleIncompleteAccount = useHandleIncompleteAccount();
return (
<>
<QueryRenderer<QueryTypes>
@@ -88,6 +90,9 @@ const StreamQuery: FunctionComponent<Props> = props => {
storyURL,
}}
render={data => {
if (handleIncompleteAccount(data)) {
return null;
}
return render(data, commentsTab);
}}
/>
@@ -10,6 +10,7 @@ import {
withLocalStateContainer,
} from "coral-framework/lib/relay";
import Spinner from "coral-stream/common/Spinner";
import useHandleIncompleteAccount from "coral-stream/common/useHandleIncompleteAccount";
import { CallOut, Delay } from "coral-ui/components";
import { ProfileQuery as QueryTypes } from "coral-stream/__generated__/ProfileQuery.graphql";
@@ -79,28 +80,36 @@ export const render = ({ error, props }: QueryRenderData<QueryTypes>) => {
const ProfileQuery: FunctionComponent<Props> = ({
local: { storyID, storyURL },
}) => (
<QueryRenderer<QueryTypes>
query={graphql`
query ProfileQuery($storyID: ID, $storyURL: String) {
story: stream(id: $storyID, url: $storyURL) {
...ProfileContainer_story
}) => {
const handleIncompleteAccount = useHandleIncompleteAccount();
return (
<QueryRenderer<QueryTypes>
query={graphql`
query ProfileQuery($storyID: ID, $storyURL: String) {
story: stream(id: $storyID, url: $storyURL) {
...ProfileContainer_story
}
viewer {
...ProfileContainer_viewer
}
settings {
...ProfileContainer_settings
}
}
viewer {
...ProfileContainer_viewer
`}
variables={{
storyID,
storyURL,
}}
render={data => {
if (handleIncompleteAccount(data)) {
return null;
}
settings {
...ProfileContainer_settings
}
}
`}
variables={{
storyID,
storyURL,
}}
render={render}
/>
);
return render(data);
}}
/>
);
};
const enhanced = withLocalStateContainer(
graphql`
@@ -89,6 +89,9 @@ beforeEach(() => {
});
it("renders permalink view", async () => {
// axe checking takes a bit of time.
jest.setTimeout(10000);
const tabPane = await waitForElement(() =>
within(testRenderer.root).getByTestID("current-tab-pane")
);