From 921461008eefdc4e2fa3ba58985b11f19bfe22b5 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Wed, 18 Sep 2019 18:01:06 +0000 Subject: [PATCH] [CORL-621] Auth Fixes (#2569) * fix: resolve error with redirects - fixes #2529 * fix: apply validations to username for oidc * fix: converted components to function components * fix: snapshots --- .../Configure/sections/Auth/OIDCConfig.tsx | 22 +- .../sections/Auth/OIDCConfigContainer.tsx | 3 + src/core/client/auth/App/App.tsx | 7 +- .../auth/App/__snapshots__/App.spec.tsx.snap | 2 +- src/core/client/auth/dom/resizePopup.ts | 5 +- src/core/client/auth/hooks/useResizePopup.ts | 50 +++++ src/core/client/auth/index.tsx | 23 -- .../views/AddEmailAddress/AddEmailAddress.tsx | 181 ++++++++-------- .../views/AddEmailAddress/SetEmailMutation.ts | 8 +- .../views/CreatePassword/CreatePassword.tsx | 129 ++++++------ .../CreatePassword/SetPasswordMutation.ts | 11 +- .../views/CreateUsername/CreateUsername.tsx | 130 ++++++------ .../CreateUsername/SetUsernameMutation.ts | 11 +- .../auth/views/ForgotPassword/CheckEmail.tsx | 4 +- .../ForgotPassword/ForgotPasswordForm.tsx | 4 +- src/core/client/auth/views/SignIn/SignIn.tsx | 4 +- .../auth/views/SignIn/SignInContainer.tsx | 198 ++++++++++-------- .../auth/views/SignIn/SignInMutation.ts | 6 +- .../views/SignIn/SignInWithEmailContainer.tsx | 71 ++++--- .../SignIn/__snapshots__/SignIn.spec.tsx.snap | 4 +- src/core/client/auth/views/SignUp/SignUp.tsx | 4 +- .../auth/views/SignUp/SignUpContainer.tsx | 156 +++++++------- .../common/UserBox/UserBoxContainer.spec.tsx | 15 -- .../common/UserBox/UserBoxContainer.tsx | 37 +--- .../UserBoxContainer.spec.tsx.snap | 18 ++ src/core/client/ui/components/Popup/Popup.ts | 1 + .../passport/strategies/oidc/index.ts | 10 +- .../server/graph/tenant/schema/schema.graphql | 8 - 28 files changed, 567 insertions(+), 555 deletions(-) create mode 100644 src/core/client/auth/hooks/useResizePopup.ts diff --git a/src/core/client/admin/routes/Configure/sections/Auth/OIDCConfig.tsx b/src/core/client/admin/routes/Configure/sections/Auth/OIDCConfig.tsx index dac28c134..a25463e8f 100644 --- a/src/core/client/admin/routes/Configure/sections/Auth/OIDCConfig.tsx +++ b/src/core/client/admin/routes/Configure/sections/Auth/OIDCConfig.tsx @@ -53,13 +53,13 @@ const OIDCConfig: FunctionComponent = ({ }) => { return ( Login with OIDC } - name={`auth.integrations.oidc.enabled`} + name="auth.integrations.oidc.enabled" disabled={disabled} > {disabledInside => ( @@ -84,7 +84,7 @@ const OIDCConfig: FunctionComponent = ({ @@ -107,12 +107,12 @@ const OIDCConfig: FunctionComponent = ({ @@ -127,7 +127,7 @@ const OIDCConfig: FunctionComponent = ({ @@ -165,7 +165,7 @@ const OIDCConfig: FunctionComponent = ({ authorizationURL @@ -191,7 +191,7 @@ const OIDCConfig: FunctionComponent = ({ tokenURL @@ -217,7 +217,7 @@ const OIDCConfig: FunctionComponent = ({ jwksURI @@ -244,11 +244,11 @@ const OIDCConfig: FunctionComponent = ({ Use OIDC login on } - name={`auth.integrations.oidc.targetFilter`} + name="auth.integrations.oidc.targetFilter" disabled={disabledInside} /> diff --git a/src/core/client/admin/routes/Configure/sections/Auth/OIDCConfigContainer.tsx b/src/core/client/admin/routes/Configure/sections/Auth/OIDCConfigContainer.tsx index 738e1187b..bbc2f5dd4 100644 --- a/src/core/client/admin/routes/Configure/sections/Auth/OIDCConfigContainer.tsx +++ b/src/core/client/admin/routes/Configure/sections/Auth/OIDCConfigContainer.tsx @@ -42,6 +42,9 @@ class OIDCConfigContainer extends React.Component { issuer: form.getState().values.auth.integrations.oidc.issuer, }); if (config) { + if (config.issuer) { + form.change("auth.integrations.oidc.issuer", config.issuer); + } form.change( "auth.integrations.oidc.authorizationURL", config.authorizationURL diff --git a/src/core/client/auth/App/App.tsx b/src/core/client/auth/App/App.tsx index 7bf1c3243..cff05b8f6 100644 --- a/src/core/client/auth/App/App.tsx +++ b/src/core/client/auth/App/App.tsx @@ -1,9 +1,7 @@ import React, { FunctionComponent } from "react"; -import { useResizeObserver } from "coral-framework/hooks"; import { PropTypesOf } from "coral-framework/types"; -import resizePopup from "../dom/resizePopup"; import AddEmailAddress from "../views/AddEmailAddress"; import CreatePassword from "../views/CreatePassword"; import CreateUsername from "../views/CreateUsername"; @@ -50,11 +48,8 @@ const render = ({ view, auth, viewer }: AppProps) => { }; const App: FunctionComponent = props => { - const ref = useResizeObserver(entry => { - resizePopup(); - }); return ( -
+
{process.env.NODE_ENV !== "test" && }
{render(props)}
diff --git a/src/core/client/auth/App/__snapshots__/App.spec.tsx.snap b/src/core/client/auth/App/__snapshots__/App.spec.tsx.snap index 783e0f3f2..65427215a 100644 --- a/src/core/client/auth/App/__snapshots__/App.spec.tsx.snap +++ b/src/core/client/auth/App/__snapshots__/App.spec.tsx.snap @@ -3,7 +3,7 @@ exports[`renders sign in 1`] = `
-
diff --git a/src/core/client/auth/dom/resizePopup.ts b/src/core/client/auth/dom/resizePopup.ts index d9fc1ce6d..9fb0427cd 100644 --- a/src/core/client/auth/dom/resizePopup.ts +++ b/src/core/client/auth/dom/resizePopup.ts @@ -1,9 +1,6 @@ function resizePopup() { const innerHeight = window.document.body.offsetHeight; - window.resizeTo( - window.outerWidth, - innerHeight + window.outerHeight - window.innerHeight - ); + window.resizeTo(350, innerHeight + window.outerHeight - window.innerHeight); } let resizedAlready = false; diff --git a/src/core/client/auth/hooks/useResizePopup.ts b/src/core/client/auth/hooks/useResizePopup.ts new file mode 100644 index 000000000..80e6fabfc --- /dev/null +++ b/src/core/client/auth/hooks/useResizePopup.ts @@ -0,0 +1,50 @@ +import { useCallback, useEffect, useState } from "react"; + +import { useResizeObserver } from "coral-framework/hooks"; + +import resizePopup from "../dom/resizePopup"; + +export default function useResizePopup() { + const [polling, setPolling] = useState(true); + const [pollTimeout, setPollTimeout] = useState(null); + + const pollPopupHeight = useCallback( + (interval: number = 200) => { + if (!polling) { + return; + } + + // Save the reference to the browser timeout we create. + setPollTimeout( + // Create the timeout to fire after the interval. + setTimeout(() => { + // Using requestAnimationFrame, resize the popup, and reschedule the + // resize timeout again in another interval. + window.requestAnimationFrame(() => { + resizePopup(); + pollPopupHeight(interval); + }); + }, interval) + ); + }, + [pollTimeout, setPollTimeout, polling] + ); + + useEffect(() => { + // Poll for popup height changes. + pollPopupHeight(); + + return () => { + if (pollTimeout) { + clearTimeout(pollTimeout); + setPollTimeout(null); + setPolling(false); + } + }; + }, [setPollTimeout, setPolling]); + + const ref = useResizeObserver(() => { + resizePopup(); + }); + return ref; +} diff --git a/src/core/client/auth/index.tsx b/src/core/client/auth/index.tsx index 1ee19ff89..b8198709b 100644 --- a/src/core/client/auth/index.tsx +++ b/src/core/client/auth/index.tsx @@ -4,31 +4,12 @@ import ReactDOM from "react-dom"; import { createManaged } from "coral-framework/lib/bootstrap"; import App from "./App"; -import resizePopup from "./dom/resizePopup"; import { initLocalState } from "./local"; import localesData from "./locales"; // Import css variables. import "coral-ui/theme/variables.css"; -/** - * Adapt popup height to current content every 200ms. - * - * The goal is to smooth out height inconsistensies e.g. when fonts - * are switched out or other resources being loaded that React has no influence - * over. - * - * This works in addition to the ResizeObserver in App.tsx - */ -function pollPopupHeight(interval: number = 200) { - setTimeout(() => { - window.requestAnimationFrame(() => { - resizePopup(); - pollPopupHeight(interval); - }); - }, interval); -} - async function main() { const ManagedCoralContextProvider = await createManaged({ initLocalState, @@ -42,10 +23,6 @@ async function main() { ); ReactDOM.render(, document.getElementById("app")); - // Set width. - window.resizeTo(350, window.outerHeight); - // Poll height. - pollPopupHeight(); } main(); diff --git a/src/core/client/auth/views/AddEmailAddress/AddEmailAddress.tsx b/src/core/client/auth/views/AddEmailAddress/AddEmailAddress.tsx index a88ffb695..834de6cd2 100644 --- a/src/core/client/auth/views/AddEmailAddress/AddEmailAddress.tsx +++ b/src/core/client/auth/views/AddEmailAddress/AddEmailAddress.tsx @@ -1,13 +1,15 @@ import { FORM_ERROR } from "final-form"; import { Localized } from "fluent-react/compat"; -import React, { Component } from "react"; +import React, { FunctionComponent, useCallback } from "react"; import { Form } from "react-final-form"; import { Bar, Title } from "coral-auth/components//Header"; import ConfirmEmailField from "coral-auth/components/ConfirmEmailField"; import EmailField from "coral-auth/components/EmailField"; import Main from "coral-auth/components/Main"; +import useResizePopup from "coral-auth/hooks/useResizePopup"; import { OnSubmit } from "coral-framework/lib/form"; +import { useMutation } from "coral-framework/lib/relay"; import { Button, CallOut, @@ -16,101 +18,98 @@ import { Typography, } from "coral-ui/components"; -import { SetEmailMutation, withSetEmailMutation } from "./SetEmailMutation"; +import SetEmailMutation from "./SetEmailMutation"; import { ListItem, UnorderedList } from "./UnorderedList"; interface FormProps { email: string; } -interface Props { - setEmail: SetEmailMutation; -} +const AddEmailAddressContainer: FunctionComponent = () => { + const setEmail = useMutation(SetEmailMutation); + const onSubmit: OnSubmit = useCallback( + async (input, form) => { + try { + await setEmail({ email: input.email }); + return form.reset(); + } catch (error) { + return { [FORM_ERROR]: error.message }; + } + }, + [setEmail] + ); + const ref = useResizePopup(); -class AddEmailAddressContainer extends Component { - private handleSubmit: OnSubmit = async (input, form) => { - try { - await this.props.setEmail({ email: input.email }); - return form.reset(); - } catch (error) { - return { [FORM_ERROR]: error.message }; - } - }; + return ( +
+ + + Add Email Address + + +
+
+ {({ handleSubmit, submitting, submitError }) => ( + + + + + For your added security, we require users to add an email + address to their accounts. Your email address will be used + to: + + + + done}> + + + Receive updates regarding any changes to your account + (email address, username, password, etc.) + + + + done}> + + + Allow you to download your comments. + + + + done}> + + + Send comment notifications that you have chosen to + receive. + + + + + {submitError && ( + + {submitError} + + )} + + + + + + +
+ )} + +
+
+ ); +}; - public render() { - // tslint:disable-next-line:no-empty - return ( -
- - - Add Email Address - - -
-
- {({ handleSubmit, submitting, submitError }) => ( - - - - - For your added security, we require users to add an email - address to their accounts. Your email address will be used - to: - - - - done}> - - - Receive updates regarding any changes to your account - (email address, username, password, etc.) - - - - done}> - - - Allow you to download your comments. - - - - done}> - - - Send comment notifications that you have chosen to - receive. - - - - - {submitError && ( - - {submitError} - - )} - - - - - - -
- )} - -
-
- ); - } -} - -const enhanced = withSetEmailMutation(AddEmailAddressContainer); -export default enhanced; +export default AddEmailAddressContainer; diff --git a/src/core/client/auth/views/AddEmailAddress/SetEmailMutation.ts b/src/core/client/auth/views/AddEmailAddress/SetEmailMutation.ts index 711f1780f..e09574279 100644 --- a/src/core/client/auth/views/AddEmailAddress/SetEmailMutation.ts +++ b/src/core/client/auth/views/AddEmailAddress/SetEmailMutation.ts @@ -3,7 +3,7 @@ import { Environment } from "relay-runtime"; import { commitMutationPromiseNormalized, - createMutationContainer, + createMutation, } from "coral-framework/lib/relay"; import { Omit } from "coral-framework/types"; @@ -39,8 +39,6 @@ function commit(environment: Environment, input: SetEmailInput) { }); } -export const withSetEmailMutation = createMutationContainer("setEmail", commit); +const SetEmailMutation = createMutation("setEmail", commit); -export type SetEmailMutation = ( - input: SetEmailInput -) => Promise; +export default SetEmailMutation; diff --git a/src/core/client/auth/views/CreatePassword/CreatePassword.tsx b/src/core/client/auth/views/CreatePassword/CreatePassword.tsx index eabe1276d..5f1477be1 100644 --- a/src/core/client/auth/views/CreatePassword/CreatePassword.tsx +++ b/src/core/client/auth/views/CreatePassword/CreatePassword.tsx @@ -1,11 +1,12 @@ import { FORM_ERROR } from "final-form"; import { Localized } from "fluent-react/compat"; -import React, { Component } from "react"; +import React, { FunctionComponent, useCallback } from "react"; import { Form } from "react-final-form"; import { Bar, Title } from "coral-auth/components//Header"; import Main from "coral-auth/components/Main"; import SetPasswordField from "coral-auth/components/SetPasswordField"; +import useResizePopup from "coral-auth/hooks/useResizePopup"; import { OnSubmit } from "coral-framework/lib/form"; import { Button, @@ -14,75 +15,71 @@ import { Typography, } from "coral-ui/components"; -import { - SetPasswordMutation, - withSetPasswordMutation, -} from "./SetPasswordMutation"; +import { useMutation } from "coral-framework/lib/relay"; +import SetPasswordMutation from "./SetPasswordMutation"; interface FormProps { password: string; } -interface Props { - setPassword: SetPasswordMutation; -} +const CreatePasswordContainer: FunctionComponent = () => { + const setPassword = useMutation(SetPasswordMutation); + const onSubmit: OnSubmit = useCallback( + async (input, form) => { + try { + await setPassword({ password: input.password }); + return form.reset(); + } catch (error) { + return { [FORM_ERROR]: error.message }; + } + }, + [setPassword] + ); + const ref = useResizePopup(); -class CreatePasswordContainer extends Component { - private handleSubmit: OnSubmit = async (input, form) => { - try { - await this.props.setPassword({ password: input.password }); - return form.reset(); - } catch (error) { - return { [FORM_ERROR]: error.message }; - } - }; + return ( +
+ + + Create Password + + +
+
+ {({ handleSubmit, submitting, submitError }) => ( + + + + + To protect against unauthorized changes to your account, we + require users to create a password. + + + {submitError && ( + + {submitError} + + )} + + + + + +
+ )} + +
+
+ ); +}; - public render() { - return ( -
- - - Create Password - - -
-
- {({ handleSubmit, submitting, submitError }) => ( - - - - - To protect against unauthorized changes to your account, - we require users to create a password. - - - {submitError && ( - - {submitError} - - )} - - - - - -
- )} - -
-
- ); - } -} - -const enhanced = withSetPasswordMutation(CreatePasswordContainer); -export default enhanced; +export default CreatePasswordContainer; diff --git a/src/core/client/auth/views/CreatePassword/SetPasswordMutation.ts b/src/core/client/auth/views/CreatePassword/SetPasswordMutation.ts index c2c2750f9..071a64515 100644 --- a/src/core/client/auth/views/CreatePassword/SetPasswordMutation.ts +++ b/src/core/client/auth/views/CreatePassword/SetPasswordMutation.ts @@ -3,7 +3,7 @@ import { Environment } from "relay-runtime"; import { commitMutationPromiseNormalized, - createMutationContainer, + createMutation, } from "coral-framework/lib/relay"; import { Omit } from "coral-framework/types"; @@ -41,11 +41,6 @@ function commit(environment: Environment, input: SetPasswordInput) { }); } -export const withSetPasswordMutation = createMutationContainer( - "setPassword", - commit -); +const SetPasswordMutation = createMutation("setPassword", commit); -export type SetPasswordMutation = ( - input: SetPasswordInput -) => Promise; +export default SetPasswordMutation; diff --git a/src/core/client/auth/views/CreateUsername/CreateUsername.tsx b/src/core/client/auth/views/CreateUsername/CreateUsername.tsx index 9e4e46004..3f3395291 100644 --- a/src/core/client/auth/views/CreateUsername/CreateUsername.tsx +++ b/src/core/client/auth/views/CreateUsername/CreateUsername.tsx @@ -1,12 +1,14 @@ import { FORM_ERROR } from "final-form"; import { Localized } from "fluent-react/compat"; -import React, { Component } from "react"; +import React, { FunctionComponent, useCallback } from "react"; import { Form } from "react-final-form"; import { Bar, Title } from "coral-auth/components//Header"; import Main from "coral-auth/components/Main"; import UsernameField from "coral-auth/components/UsernameField"; +import useResizePopup from "coral-auth/hooks/useResizePopup"; import { OnSubmit } from "coral-framework/lib/form"; +import { useMutation } from "coral-framework/lib/relay"; import { Button, CallOut, @@ -14,76 +16,70 @@ import { Typography, } from "coral-ui/components"; -import { - SetUsernameMutation, - withSetUsernameMutation, -} from "./SetUsernameMutation"; +import SetUsernameMutation from "./SetUsernameMutation"; interface FormProps { username: string; } -interface Props { - setUsername: SetUsernameMutation; -} +const CreateUsernameContainer: FunctionComponent = () => { + const setUsername = useMutation(SetUsernameMutation); + const onSubmit: OnSubmit = useCallback( + async (input, form) => { + try { + await setUsername({ username: input.username }); + return form.reset(); + } catch (error) { + return { [FORM_ERROR]: error.message }; + } + }, + [setUsername] + ); + const ref = useResizePopup(); -class CreateUsernameContainer extends Component { - private handleSubmit: OnSubmit = async (input, form) => { - try { - await this.props.setUsername({ username: input.username }); - return form.reset(); - } catch (error) { - return { [FORM_ERROR]: error.message }; - } - }; + return ( +
+ + + Create Username + + +
+
+ {({ handleSubmit, submitting, submitError }) => ( + + + + + Your username is an identifier that will appear on all of + your comments. + + + {submitError && ( + + {submitError} + + )} + + + + + +
+ )} + +
+
+ ); +}; - public render() { - // tslint:disable-next-line:no-empty - return ( -
- - - Create Username - - -
-
- {({ handleSubmit, submitting, submitError }) => ( - - - - - Your username is an identifier that will appear on all of - your comments. - - - {submitError && ( - - {submitError} - - )} - - - - - -
- )} - -
-
- ); - } -} - -const enhanced = withSetUsernameMutation(CreateUsernameContainer); -export default enhanced; +export default CreateUsernameContainer; diff --git a/src/core/client/auth/views/CreateUsername/SetUsernameMutation.ts b/src/core/client/auth/views/CreateUsername/SetUsernameMutation.ts index 85dc4911f..53252b589 100644 --- a/src/core/client/auth/views/CreateUsername/SetUsernameMutation.ts +++ b/src/core/client/auth/views/CreateUsername/SetUsernameMutation.ts @@ -3,7 +3,7 @@ import { Environment } from "relay-runtime"; import { commitMutationPromiseNormalized, - createMutationContainer, + createMutation, } from "coral-framework/lib/relay"; import { Omit } from "coral-framework/types"; @@ -39,11 +39,6 @@ function commit(environment: Environment, input: SetUsernameInput) { }); } -export const withSetUsernameMutation = createMutationContainer( - "setUsername", - commit -); +const SetUsernameMutation = createMutation("setUsername", commit); -export type SetUsernameMutation = ( - input: SetUsernameInput -) => Promise; +export default SetUsernameMutation; diff --git a/src/core/client/auth/views/ForgotPassword/CheckEmail.tsx b/src/core/client/auth/views/ForgotPassword/CheckEmail.tsx index a7e9f5d45..640f7f6a5 100644 --- a/src/core/client/auth/views/ForgotPassword/CheckEmail.tsx +++ b/src/core/client/auth/views/ForgotPassword/CheckEmail.tsx @@ -3,6 +3,7 @@ import React, { FunctionComponent, useCallback } from "react"; import { Bar, Title } from "coral-auth/components/Header"; import Main from "coral-auth/components/Main"; +import useResizePopup from "coral-auth/hooks/useResizePopup"; import { Button, HorizontalGutter, Typography } from "coral-ui/components"; interface Props { @@ -10,12 +11,13 @@ interface Props { } const CheckEmail: FunctionComponent = ({ email }) => { + const ref = useResizePopup(); const closeWindow = useCallback(() => { window.close(); }, []); const UserEmail = () => {email}; return ( -
+
Check Your Email diff --git a/src/core/client/auth/views/ForgotPassword/ForgotPasswordForm.tsx b/src/core/client/auth/views/ForgotPassword/ForgotPasswordForm.tsx index ca95897f2..e0e0ddc66 100644 --- a/src/core/client/auth/views/ForgotPassword/ForgotPasswordForm.tsx +++ b/src/core/client/auth/views/ForgotPassword/ForgotPasswordForm.tsx @@ -6,6 +6,7 @@ import { Field, Form } from "react-final-form"; import { Bar, SubBar, Title } from "coral-auth/components/Header"; import Main from "coral-auth/components/Main"; import { getViewURL } from "coral-auth/helpers"; +import useResizePopup from "coral-auth/hooks/useResizePopup"; import { SetViewMutation } from "coral-auth/mutations"; import { InvalidRequestError } from "coral-framework/lib/errors"; import { colorFromMeta, ValidationMessage } from "coral-framework/lib/form"; @@ -42,6 +43,7 @@ const ForgotPasswordForm: FunctionComponent = ({ email, onCheckEmail, }) => { + const ref = useResizePopup(); const signInHref = getViewURL("SIGN_IN"); const forgotPassword = useMutation(ForgotPasswordMutation); const setView = useMutation(SetViewMutation); @@ -71,7 +73,7 @@ const ForgotPasswordForm: FunctionComponent = ({ ); return ( -
+
Forgot Password? diff --git a/src/core/client/auth/views/SignIn/SignIn.tsx b/src/core/client/auth/views/SignIn/SignIn.tsx index fcd6acd97..82cf36ea0 100644 --- a/src/core/client/auth/views/SignIn/SignIn.tsx +++ b/src/core/client/auth/views/SignIn/SignIn.tsx @@ -4,6 +4,7 @@ import React, { FunctionComponent } from "react"; import { Bar, SubBar, Subtitle, Title } from "coral-auth/components/Header"; import Main from "coral-auth/components/Main"; import OrSeparator from "coral-auth/components/OrSeparator"; +import useResizePopup from "coral-auth/hooks/useResizePopup"; import { PropTypesOf } from "coral-framework/types"; import { CallOut, @@ -41,10 +42,11 @@ const SignIn: FunctionComponent = ({ auth, error, }) => { + const ref = useResizePopup(); const oneClickIntegrationEnabled = facebookEnabled || googleEnabled || oidcEnabled; return ( -
+
} diff --git a/src/core/client/auth/views/SignIn/SignInContainer.tsx b/src/core/client/auth/views/SignIn/SignInContainer.tsx index aff90f18f..12aca2c23 100644 --- a/src/core/client/auth/views/SignIn/SignInContainer.tsx +++ b/src/core/client/auth/views/SignIn/SignInContainer.tsx @@ -1,15 +1,15 @@ -import React, { Component } from "react"; +import React, { FunctionComponent, useCallback, useEffect } from "react"; import { SignInContainer_auth as AuthData } from "coral-auth/__generated__/SignInContainer_auth.graphql"; import { SignInContainerLocal as LocalData } from "coral-auth/__generated__/SignInContainerLocal.graphql"; import { getViewURL } from "coral-auth/helpers"; import { SetViewMutation } from "coral-auth/mutations"; +import { redirectOAuth2 } from "coral-framework/helpers"; import { graphql, - MutationProp, + useMutation, withFragmentContainer, withLocalStateContainer, - withMutation, } from "coral-framework/lib/relay"; import { @@ -17,101 +17,129 @@ import { withClearErrorMutation, } from "./ClearErrorMutation"; import SignIn from "./SignIn"; -import { SignInMutation, withSignInMutation } from "./SignInMutation"; interface Props { local: LocalData; auth: AuthData; - signIn: SignInMutation; - setView: MutationProp; clearError: ClearErrorMutation; } -class SignInContainer extends Component { - private goToSignUp = (e: React.MouseEvent) => { - this.props.setView({ view: "SIGN_UP", history: "push" }); - if (e.preventDefault) { - e.preventDefault(); - } - }; +const SignInContainer: FunctionComponent = ({ + auth, + local, + clearError, +}) => { + const setView = useMutation(SetViewMutation); + const goToSignUp = useCallback( + (e: React.MouseEvent) => { + setView({ view: "SIGN_UP", history: "push" }); + if (e.preventDefault) { + e.preventDefault(); + } + }, + [setView] + ); - public componentWillUnmount() { - this.props.clearError(); - } + useEffect(() => { + return () => { + // Clear the error when we unmount. + clearError(); + }; + }, [clearError, auth]); - public render() { - const integrations = this.props.auth.integrations; - return ( - + // If there's only one enabled auth integration, we should just perform + // the redirect now. + if ( + !auth.integrations.local.enabled || + !auth.integrations.local.targetFilter.stream + ) { + // Local isn't enabled, so we can look into the rest of the integrations + // now. + const { facebook, google, oidc } = auth.integrations; + const enabledIntegrations = [facebook, google, oidc].filter( + ({ enabled, targetFilter: { stream } }) => enabled && stream ); - } -} + if ( + enabledIntegrations.length === 1 && + enabledIntegrations[0].redirectURL + ) { + redirectOAuth2(enabledIntegrations[0].redirectURL); -const enhanced = withMutation(SetViewMutation)( - withClearErrorMutation( - withSignInMutation( - withLocalStateContainer( - graphql` - fragment SignInContainerLocal on Local { - error - } - ` - )( - withFragmentContainer({ - auth: graphql` - fragment SignInContainer_auth on Auth { - ...SignInWithOIDCContainer_auth - ...SignInWithGoogleContainer_auth - ...SignInWithFacebookContainer_auth - integrations { - local { - enabled - targetFilter { - stream - } - } - facebook { - enabled - targetFilter { - stream - } - } - google { - enabled - targetFilter { - stream - } - } - oidc { - enabled - targetFilter { - stream - } - } + return null; + } + } + + const integrations = auth.integrations; + return ( + + ); +}; + +const enhanced = withClearErrorMutation( + withLocalStateContainer( + graphql` + fragment SignInContainerLocal on Local { + error + } + ` + )( + withFragmentContainer({ + auth: graphql` + fragment SignInContainer_auth on Auth { + ...SignInWithOIDCContainer_auth + ...SignInWithGoogleContainer_auth + ...SignInWithFacebookContainer_auth + integrations { + local { + enabled + targetFilter { + stream } } - `, - })(SignInContainer) - ) - ) + facebook { + enabled + redirectURL + targetFilter { + stream + } + } + google { + enabled + redirectURL + targetFilter { + stream + } + } + oidc { + enabled + redirectURL + targetFilter { + stream + } + } + } + } + `, + })(SignInContainer) ) ); + export default enhanced; diff --git a/src/core/client/auth/views/SignIn/SignInMutation.ts b/src/core/client/auth/views/SignIn/SignInMutation.ts index 1e436c79d..97da9e741 100644 --- a/src/core/client/auth/views/SignIn/SignInMutation.ts +++ b/src/core/client/auth/views/SignIn/SignInMutation.ts @@ -2,7 +2,7 @@ import { pick } from "lodash"; import { Environment } from "relay-runtime"; import { CoralContext } from "coral-framework/lib/bootstrap"; -import { createMutationContainer } from "coral-framework/lib/relay"; +import { createMutation } from "coral-framework/lib/relay"; import { signIn, SignInInput } from "coral-framework/rest"; export type SignInMutation = (input: SignInInput) => Promise; @@ -16,4 +16,6 @@ export async function commit( await clearSession(result.token); } -export const withSignInMutation = createMutationContainer("signIn", commit); +const SignInMutation = createMutation("signIn", commit); + +export default SignInMutation; diff --git a/src/core/client/auth/views/SignIn/SignInWithEmailContainer.tsx b/src/core/client/auth/views/SignIn/SignInWithEmailContainer.tsx index 36ff37b31..14eb15047 100644 --- a/src/core/client/auth/views/SignIn/SignInWithEmailContainer.tsx +++ b/src/core/client/auth/views/SignIn/SignInWithEmailContainer.tsx @@ -1,46 +1,45 @@ import { FORM_ERROR } from "final-form"; -import React, { Component } from "react"; +import React, { FunctionComponent, useCallback } from "react"; import { SetViewMutation } from "coral-auth/mutations"; -import { MutationProp, withMutation } from "coral-framework/lib/relay"; +import { useMutation } from "coral-framework/lib/relay"; import { getViewURL } from "coral-auth/helpers"; -import { SignInMutation, withSignInMutation } from "./SignInMutation"; +import SignInMutation from "./SignInMutation"; import SignInWithEmail, { SignInWithEmailForm } from "./SignInWithEmail"; -interface SignInContainerProps { - signIn: SignInMutation; - setView: MutationProp; -} +const SignInContainer: FunctionComponent = () => { + const signIn = useMutation(SignInMutation); + const setView = useMutation(SetViewMutation); + const onSubmit: SignInWithEmailForm["onSubmit"] = useCallback( + async (input, form) => { + try { + await signIn({ email: input.email, password: input.password }); + return form.reset(); + } catch (error) { + return { [FORM_ERROR]: error.message }; + } + }, + [signIn] + ); + const goToForgotPassword = useCallback( + (e: React.MouseEvent) => { + setView({ view: "FORGOT_PASSWORD", history: "push" }); + if (e.preventDefault) { + e.preventDefault(); + } + }, + [setView] + ); -class SignInContainer extends Component { - private onSubmit: SignInWithEmailForm["onSubmit"] = async (input, form) => { - try { - await this.props.signIn({ email: input.email, password: input.password }); - return form.reset(); - } catch (error) { - return { [FORM_ERROR]: error.message }; - } - }; - private goToForgotPassword = (e: React.MouseEvent) => { - this.props.setView({ view: "FORGOT_PASSWORD", history: "push" }); - if (e.preventDefault) { - e.preventDefault(); - } - }; - public render() { - return ( - - ); - } -} + return ( + + ); +}; -const enhanced = withMutation(SetViewMutation)( - withSignInMutation(SignInContainer) -); -export default enhanced; +export default SignInContainer; diff --git a/src/core/client/auth/views/SignIn/__snapshots__/SignIn.spec.tsx.snap b/src/core/client/auth/views/SignIn/__snapshots__/SignIn.spec.tsx.snap index 7b7800ce1..a6aec8be5 100644 --- a/src/core/client/auth/views/SignIn/__snapshots__/SignIn.spec.tsx.snap +++ b/src/core/client/auth/views/SignIn/__snapshots__/SignIn.spec.tsx.snap @@ -42,7 +42,7 @@ exports[`renders correctly 1`] = ` - + Server Error - + = ({ signInHref, auth, }) => { + const ref = useResizePopup(); const oneClickUptegrationEnabled = facebookEnabled || googleEnabled || oidcEnabled; return ( -
+
} diff --git a/src/core/client/auth/views/SignUp/SignUpContainer.tsx b/src/core/client/auth/views/SignUp/SignUpContainer.tsx index ef2913ae1..7a2920527 100644 --- a/src/core/client/auth/views/SignUp/SignUpContainer.tsx +++ b/src/core/client/auth/views/SignUp/SignUpContainer.tsx @@ -1,100 +1,100 @@ -import React, { Component } from "react"; +import React, { FunctionComponent, useCallback } from "react"; import { SignUpContainer_auth as AuthData } from "coral-auth/__generated__/SignUpContainer_auth.graphql"; +import { getViewURL } from "coral-auth/helpers"; import { SetViewMutation } from "coral-auth/mutations"; import { graphql, - MutationProp, + useMutation, withFragmentContainer, - withMutation, } from "coral-framework/lib/relay"; -import { getViewURL } from "coral-auth/helpers"; import SignUp from "./SignUp"; interface Props { auth: AuthData; - setView: MutationProp; } -class SignUpContainer extends Component { - private goToSignIn = (e: React.MouseEvent) => { - this.props.setView({ view: "SIGN_IN", history: "push" }); - if (e.preventDefault) { - e.preventDefault(); - } - }; - public render() { - const integrations = this.props.auth.integrations; - return ( - - ); - } -} +const SignUpContainer: FunctionComponent = ({ auth }) => { + const setView = useMutation(SetViewMutation); + const goToSignIn = useCallback( + (e: React.MouseEvent) => { + setView({ view: "SIGN_IN", history: "push" }); + if (e.preventDefault) { + e.preventDefault(); + } + }, + [setView] + ); -const enhanced = withMutation(SetViewMutation)( - withFragmentContainer({ - auth: graphql` - fragment SignUpContainer_auth on Auth { - ...SignUpWithOIDCContainer_auth - ...SignUpWithGoogleContainer_auth - ...SignUpWithFacebookContainer_auth - integrations { - local { - enabled - targetFilter { - stream - } - allowRegistration + const integrations = auth.integrations; + return ( + + ); +}; + +const enhanced = withFragmentContainer({ + auth: graphql` + fragment SignUpContainer_auth on Auth { + ...SignUpWithOIDCContainer_auth + ...SignUpWithGoogleContainer_auth + ...SignUpWithFacebookContainer_auth + integrations { + local { + enabled + targetFilter { + stream } - facebook { - enabled - targetFilter { - stream - } - allowRegistration + allowRegistration + } + facebook { + enabled + targetFilter { + stream } - google { - enabled - targetFilter { - stream - } - allowRegistration + allowRegistration + } + google { + enabled + targetFilter { + stream } - oidc { - enabled - targetFilter { - stream - } - allowRegistration + allowRegistration + } + oidc { + enabled + targetFilter { + stream } + allowRegistration } } - `, - })(SignUpContainer) -); + } + `, +})(SignUpContainer); + export default enhanced; diff --git a/src/core/client/stream/common/UserBox/UserBoxContainer.spec.tsx b/src/core/client/stream/common/UserBox/UserBoxContainer.spec.tsx index 1dbe6c647..9b73eac3e 100644 --- a/src/core/client/stream/common/UserBox/UserBoxContainer.spec.tsx +++ b/src/core/client/stream/common/UserBox/UserBoxContainer.spec.tsx @@ -28,7 +28,6 @@ it("renders fully", () => { facebook: { enabled: true, allowRegistration: true, - redirectURL: "http://localhost/facebook", targetFilter: { stream: true, }, @@ -36,7 +35,6 @@ it("renders fully", () => { google: { enabled: false, allowRegistration: true, - redirectURL: "http://localhost/google", targetFilter: { stream: true, }, @@ -44,7 +42,6 @@ it("renders fully", () => { oidc: { enabled: false, allowRegistration: true, - redirectURL: "http://localhost/oidc", targetFilter: { stream: true, }, @@ -90,7 +87,6 @@ it("renders without logout button", () => { facebook: { enabled: true, allowRegistration: true, - redirectURL: "http://localhost/facebook", targetFilter: { stream: true, }, @@ -98,7 +94,6 @@ it("renders without logout button", () => { google: { enabled: false, allowRegistration: true, - redirectURL: "http://localhost/google", targetFilter: { stream: true, }, @@ -106,7 +101,6 @@ it("renders without logout button", () => { oidc: { enabled: false, allowRegistration: true, - redirectURL: "http://localhost/oidc", targetFilter: { stream: true, }, @@ -152,7 +146,6 @@ it("renders sso only", () => { facebook: { enabled: false, allowRegistration: true, - redirectURL: "http://localhost/facebook", targetFilter: { stream: true, }, @@ -160,7 +153,6 @@ it("renders sso only", () => { google: { enabled: true, allowRegistration: true, - redirectURL: "http://localhost/google", targetFilter: { stream: false, }, @@ -168,7 +160,6 @@ it("renders sso only", () => { oidc: { enabled: false, allowRegistration: true, - redirectURL: "http://localhost/oidc", targetFilter: { stream: true, }, @@ -214,7 +205,6 @@ it("renders sso only without logout button", () => { facebook: { enabled: false, allowRegistration: true, - redirectURL: "http://localhost/facebook", targetFilter: { stream: true, }, @@ -222,7 +212,6 @@ it("renders sso only without logout button", () => { google: { enabled: false, allowRegistration: true, - redirectURL: "http://localhost/google", targetFilter: { stream: true, }, @@ -230,7 +219,6 @@ it("renders sso only without logout button", () => { oidc: { enabled: false, allowRegistration: true, - redirectURL: "http://localhost/oidc", targetFilter: { stream: true, }, @@ -276,7 +264,6 @@ it("renders without register button", () => { facebook: { enabled: true, allowRegistration: false, - redirectURL: "http://localhost/facebook", targetFilter: { stream: true, }, @@ -284,7 +271,6 @@ it("renders without register button", () => { google: { enabled: false, allowRegistration: true, - redirectURL: "http://localhost/google", targetFilter: { stream: false, }, @@ -292,7 +278,6 @@ it("renders without register button", () => { oidc: { enabled: false, allowRegistration: true, - redirectURL: "http://localhost/oidc", targetFilter: { stream: true, }, diff --git a/src/core/client/stream/common/UserBox/UserBoxContainer.tsx b/src/core/client/stream/common/UserBox/UserBoxContainer.tsx index b90716408..01799d423 100644 --- a/src/core/client/stream/common/UserBox/UserBoxContainer.tsx +++ b/src/core/client/stream/common/UserBox/UserBoxContainer.tsx @@ -71,39 +71,10 @@ export class UserBoxContainer extends Component { ].some(i => i.enabled && i.targetFilter.stream); } - private get authUrl(): string { - const { - facebook, - google, - local, - oidc, - } = this.props.settings.auth.integrations; - - const defaultAuthUrl = `${urls.embed.auth}?view=${ - this.props.local.authPopup.view - }`; - - if (local.enabled && local.targetFilter.stream) { - return defaultAuthUrl; - } - - // For each of these integrations, if only one is enabled for the stream, - // then return the redirectURL for that one only. - const integrations = [facebook, google, oidc]; - const enabled = integrations.filter( - integration => integration.enabled && integration.targetFilter.stream - ); - if (enabled.length === 1 && enabled[0].redirectURL) { - return enabled[0].redirectURL; - } - - return defaultAuthUrl; - } - public render() { const { local: { - authPopup: { open, focus }, + authPopup: { open, focus, view }, }, viewer, } = this.props; @@ -125,13 +96,14 @@ export class UserBoxContainer extends Component { return ( <>