[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
This commit is contained in:
Wyatt Johnson
2019-09-18 14:01:06 -04:00
committed by Kim Gardner
parent 64f102e6d4
commit 921461008e
28 changed files with 567 additions and 555 deletions
@@ -53,13 +53,13 @@ const OIDCConfig: FunctionComponent<Props> = ({
}) => {
return (
<ConfigBoxWithToggleField
data-testid={`configure-auth-oidc-container`}
data-testid="configure-auth-oidc-container"
title={
<Localized id="configure-auth-oidc-loginWith">
<span>Login with OIDC</span>
</Localized>
}
name={`auth.integrations.oidc.enabled`}
name="auth.integrations.oidc.enabled"
disabled={disabled}
>
{disabledInside => (
@@ -84,7 +84,7 @@ const OIDCConfig: FunctionComponent<Props> = ({
</InputDescription>
</Localized>
<Field
name={`auth.integrations.oidc.name`}
name="auth.integrations.oidc.name"
validate={composeValidatorsWhen(isEnabled, required)}
parse={identity}
>
@@ -107,12 +107,12 @@ const OIDCConfig: FunctionComponent<Props> = ({
</FormField>
<ClientIDField
validate={composeValidatorsWhen(isEnabled, required)}
name={`auth.integrations.oidc.clientID`}
name="auth.integrations.oidc.clientID"
disabled={disabledInside}
/>
<ClientSecretField
validate={composeValidatorsWhen(isEnabled, required)}
name={`auth.integrations.oidc.clientSecret`}
name="auth.integrations.oidc.clientSecret"
disabled={disabledInside}
/>
<FormField>
@@ -127,7 +127,7 @@ const OIDCConfig: FunctionComponent<Props> = ({
</InputDescription>
</Localized>
<Field
name={`auth.integrations.oidc.issuer`}
name="auth.integrations.oidc.issuer"
validate={composeValidatorsWhen(isEnabled, required, validateURL)}
parse={identity}
>
@@ -165,7 +165,7 @@ const OIDCConfig: FunctionComponent<Props> = ({
<InputLabel>authorizationURL</InputLabel>
</Localized>
<Field
name={`auth.integrations.oidc.authorizationURL`}
name="auth.integrations.oidc.authorizationURL"
validate={composeValidatorsWhen(isEnabled, required, validateURL)}
parse={identity}
>
@@ -191,7 +191,7 @@ const OIDCConfig: FunctionComponent<Props> = ({
<InputLabel>tokenURL</InputLabel>
</Localized>
<Field
name={`auth.integrations.oidc.tokenURL`}
name="auth.integrations.oidc.tokenURL"
validate={composeValidatorsWhen(isEnabled, required, validateURL)}
parse={identity}
>
@@ -217,7 +217,7 @@ const OIDCConfig: FunctionComponent<Props> = ({
<InputLabel>jwksURI</InputLabel>
</Localized>
<Field
name={`auth.integrations.oidc.jwksURI`}
name="auth.integrations.oidc.jwksURI"
validate={composeValidatorsWhen(isEnabled, required, validateURL)}
parse={identity}
>
@@ -244,11 +244,11 @@ const OIDCConfig: FunctionComponent<Props> = ({
<span>Use OIDC login on</span>
</Localized>
}
name={`auth.integrations.oidc.targetFilter`}
name="auth.integrations.oidc.targetFilter"
disabled={disabledInside}
/>
<RegistrationField
name={`auth.integrations.oidc.allowRegistration`}
name="auth.integrations.oidc.allowRegistration"
disabled={disabledInside}
/>
</HorizontalGutter>
@@ -42,6 +42,9 @@ class OIDCConfigContainer extends React.Component<Props, State> {
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
+1 -6
View File
@@ -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<AppProps> = props => {
const ref = useResizeObserver(entry => {
resizePopup();
});
return (
<div ref={ref}>
<div>
{process.env.NODE_ENV !== "test" && <ViewRouter />}
<div>{render(props)}</div>
</div>
@@ -3,7 +3,7 @@
exports[`renders sign in 1`] = `
<div>
<div>
<withContext(withMutation(withContext(createMutationContainer(withContext(createMutationContainer(withContext(withLocalStateContainer(Relay(SignInContainer)))))))))
<withContext(createMutationContainer(withContext(withLocalStateContainer(Relay(SignInContainer)))))
auth={Object {}}
/>
</div>
+1 -4
View File
@@ -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;
@@ -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<NodeJS.Timer | null>(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;
}
-23
View File
@@ -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(<Index />, document.getElementById("app"));
// Set width.
window.resizeTo(350, window.outerHeight);
// Poll height.
pollPopupHeight();
}
main();
@@ -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<FormProps> = 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<Props> {
private handleSubmit: OnSubmit<FormProps> = async (input, form) => {
try {
await this.props.setEmail({ email: input.email });
return form.reset();
} catch (error) {
return { [FORM_ERROR]: error.message };
}
};
return (
<div ref={ref} data-testid="addEmailAddress-container">
<Bar>
<Localized id="addEmailAddress-addEmailAddressHeader">
<Title>Add Email Address</Title>
</Localized>
</Bar>
<Main data-testid="addEmailAddress-main">
<Form onSubmit={onSubmit}>
{({ handleSubmit, submitting, submitError }) => (
<form autoComplete="off" onSubmit={handleSubmit}>
<HorizontalGutter size="oneAndAHalf">
<Localized id="addEmailAddress-whatItIs">
<Typography variant="bodyCopy">
For your added security, we require users to add an email
address to their accounts. Your email address will be used
to:
</Typography>
</Localized>
<UnorderedList>
<ListItem icon={<Icon>done</Icon>}>
<Localized id="addEmailAddress-receiveUpdates">
<Typography container="div">
Receive updates regarding any changes to your account
(email address, username, password, etc.)
</Typography>
</Localized>
</ListItem>
<ListItem icon={<Icon>done</Icon>}>
<Localized id="addEmailAddress-allowDownload">
<Typography container="div">
Allow you to download your comments.
</Typography>
</Localized>
</ListItem>
<ListItem icon={<Icon>done</Icon>}>
<Localized id="addEmailAddress-sendNotifications">
<Typography container="div">
Send comment notifications that you have chosen to
receive.
</Typography>
</Localized>
</ListItem>
</UnorderedList>
{submitError && (
<CallOut color="error" fullWidth>
{submitError}
</CallOut>
)}
<EmailField disabled={submitting} />
<ConfirmEmailField disabled={submitting} />
<Localized id="addEmailAddress-addEmailAddressButton">
<Button
variant="filled"
color="primary"
size="large"
type="submit"
fullWidth
disabled={submitting}
>
Add Email Address
</Button>
</Localized>
</HorizontalGutter>
</form>
)}
</Form>
</Main>
</div>
);
};
public render() {
// tslint:disable-next-line:no-empty
return (
<div data-testid="addEmailAddress-container">
<Bar>
<Localized id="addEmailAddress-addEmailAddressHeader">
<Title>Add Email Address</Title>
</Localized>
</Bar>
<Main data-testid="addEmailAddress-main">
<Form onSubmit={this.handleSubmit}>
{({ handleSubmit, submitting, submitError }) => (
<form autoComplete="off" onSubmit={handleSubmit}>
<HorizontalGutter size="oneAndAHalf">
<Localized id="addEmailAddress-whatItIs">
<Typography variant="bodyCopy">
For your added security, we require users to add an email
address to their accounts. Your email address will be used
to:
</Typography>
</Localized>
<UnorderedList>
<ListItem icon={<Icon>done</Icon>}>
<Localized id="addEmailAddress-receiveUpdates">
<Typography container="div">
Receive updates regarding any changes to your account
(email address, username, password, etc.)
</Typography>
</Localized>
</ListItem>
<ListItem icon={<Icon>done</Icon>}>
<Localized id="addEmailAddress-allowDownload">
<Typography container="div">
Allow you to download your comments.
</Typography>
</Localized>
</ListItem>
<ListItem icon={<Icon>done</Icon>}>
<Localized id="addEmailAddress-sendNotifications">
<Typography container="div">
Send comment notifications that you have chosen to
receive.
</Typography>
</Localized>
</ListItem>
</UnorderedList>
{submitError && (
<CallOut color="error" fullWidth>
{submitError}
</CallOut>
)}
<EmailField disabled={submitting} />
<ConfirmEmailField disabled={submitting} />
<Localized id="addEmailAddress-addEmailAddressButton">
<Button
variant="filled"
color="primary"
size="large"
type="submit"
fullWidth
disabled={submitting}
>
Add Email Address
</Button>
</Localized>
</HorizontalGutter>
</form>
)}
</Form>
</Main>
</div>
);
}
}
const enhanced = withSetEmailMutation(AddEmailAddressContainer);
export default enhanced;
export default AddEmailAddressContainer;
@@ -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<MutationTypes["response"]["setEmail"]>;
export default SetEmailMutation;
@@ -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<FormProps> = 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<Props> {
private handleSubmit: OnSubmit<FormProps> = async (input, form) => {
try {
await this.props.setPassword({ password: input.password });
return form.reset();
} catch (error) {
return { [FORM_ERROR]: error.message };
}
};
return (
<div ref={ref} data-testid="createPassword-container">
<Bar>
<Localized id="createPassword-createPasswordHeader">
<Title>Create Password</Title>
</Localized>
</Bar>
<Main data-testid="createPassword-main">
<Form onSubmit={onSubmit}>
{({ handleSubmit, submitting, submitError }) => (
<form autoComplete="off" onSubmit={handleSubmit}>
<HorizontalGutter size="oneAndAHalf">
<Localized id="createPassword-whatItIs">
<Typography variant="bodyCopy">
To protect against unauthorized changes to your account, we
require users to create a password.
</Typography>
</Localized>
{submitError && (
<CallOut color="error" fullWidth>
{submitError}
</CallOut>
)}
<SetPasswordField disabled={submitting} />
<Localized id="createPassword-createPasswordButton">
<Button
variant="filled"
color="primary"
size="large"
type="submit"
fullWidth
disabled={submitting}
>
Create Password
</Button>
</Localized>
</HorizontalGutter>
</form>
)}
</Form>
</Main>
</div>
);
};
public render() {
return (
<div data-testid="createPassword-container">
<Bar>
<Localized id="createPassword-createPasswordHeader">
<Title>Create Password</Title>
</Localized>
</Bar>
<Main data-testid="createPassword-main">
<Form onSubmit={this.handleSubmit}>
{({ handleSubmit, submitting, submitError }) => (
<form autoComplete="off" onSubmit={handleSubmit}>
<HorizontalGutter size="oneAndAHalf">
<Localized id="createPassword-whatItIs">
<Typography variant="bodyCopy">
To protect against unauthorized changes to your account,
we require users to create a password.
</Typography>
</Localized>
{submitError && (
<CallOut color="error" fullWidth>
{submitError}
</CallOut>
)}
<SetPasswordField disabled={submitting} />
<Localized id="createPassword-createPasswordButton">
<Button
variant="filled"
color="primary"
size="large"
type="submit"
fullWidth
disabled={submitting}
>
Create Password
</Button>
</Localized>
</HorizontalGutter>
</form>
)}
</Form>
</Main>
</div>
);
}
}
const enhanced = withSetPasswordMutation(CreatePasswordContainer);
export default enhanced;
export default CreatePasswordContainer;
@@ -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<MutationTypes["response"]["setPassword"]>;
export default SetPasswordMutation;
@@ -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<FormProps> = 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<Props> {
private handleSubmit: OnSubmit<FormProps> = async (input, form) => {
try {
await this.props.setUsername({ username: input.username });
return form.reset();
} catch (error) {
return { [FORM_ERROR]: error.message };
}
};
return (
<div ref={ref} data-testid="createUsername-container">
<Bar>
<Localized id="createUsername-createUsernameHeader">
<Title>Create Username</Title>
</Localized>
</Bar>
<Main data-testid="createUsername-main">
<Form onSubmit={onSubmit}>
{({ handleSubmit, submitting, submitError }) => (
<form autoComplete="off" onSubmit={handleSubmit}>
<HorizontalGutter size="oneAndAHalf">
<Localized id="createUsername-whatItIs">
<Typography variant="bodyCopy">
Your username is an identifier that will appear on all of
your comments.
</Typography>
</Localized>
{submitError && (
<CallOut color="error" fullWidth>
{submitError}
</CallOut>
)}
<UsernameField disabled={submitting} />
<Localized id="createUsername-createUsernameButton">
<Button
variant="filled"
color="primary"
size="large"
type="submit"
fullWidth
disabled={submitting}
>
Create Username
</Button>
</Localized>
</HorizontalGutter>
</form>
)}
</Form>
</Main>
</div>
);
};
public render() {
// tslint:disable-next-line:no-empty
return (
<div data-testid="createUsername-container">
<Bar>
<Localized id="createUsername-createUsernameHeader">
<Title>Create Username</Title>
</Localized>
</Bar>
<Main data-testid="createUsername-main">
<Form onSubmit={this.handleSubmit}>
{({ handleSubmit, submitting, submitError }) => (
<form autoComplete="off" onSubmit={handleSubmit}>
<HorizontalGutter size="oneAndAHalf">
<Localized id="createUsername-whatItIs">
<Typography variant="bodyCopy">
Your username is an identifier that will appear on all of
your comments.
</Typography>
</Localized>
{submitError && (
<CallOut color="error" fullWidth>
{submitError}
</CallOut>
)}
<UsernameField disabled={submitting} />
<Localized id="createUsername-createUsernameButton">
<Button
variant="filled"
color="primary"
size="large"
type="submit"
fullWidth
disabled={submitting}
>
Create Username
</Button>
</Localized>
</HorizontalGutter>
</form>
)}
</Form>
</Main>
</div>
);
}
}
const enhanced = withSetUsernameMutation(CreateUsernameContainer);
export default enhanced;
export default CreateUsernameContainer;
@@ -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<MutationTypes["response"]["setUsername"]>;
export default SetUsernameMutation;
@@ -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<Props> = ({ email }) => {
const ref = useResizePopup();
const closeWindow = useCallback(() => {
window.close();
}, []);
const UserEmail = () => <strong>{email}</strong>;
return (
<div data-testid="forgotPassword-checkEmail-container">
<div ref={ref} data-testid="forgotPassword-checkEmail-container">
<Bar>
<Localized id="forgotPassword-checkEmail-checkEmailHeader">
<Title>Check Your Email</Title>
@@ -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<Props> = ({
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<Props> = ({
);
return (
<div data-testid="forgotPassword-container">
<div ref={ref} data-testid="forgotPassword-container">
<Bar>
<Localized id="forgotPassword-forgotPasswordHeader">
<Title>Forgot Password?</Title>
+3 -1
View File
@@ -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<SignInForm> = ({
auth,
error,
}) => {
const ref = useResizePopup();
const oneClickIntegrationEnabled =
facebookEnabled || googleEnabled || oidcEnabled;
return (
<div data-testid="signIn-container">
<div ref={ref} data-testid="signIn-container">
<Localized
id="signIn-signInToJoinHeader"
title={<Title />}
@@ -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<typeof SetViewMutation>;
clearError: ClearErrorMutation;
}
class SignInContainer extends Component<Props> {
private goToSignUp = (e: React.MouseEvent) => {
this.props.setView({ view: "SIGN_UP", history: "push" });
if (e.preventDefault) {
e.preventDefault();
}
};
const SignInContainer: FunctionComponent<Props> = ({
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 (
<SignIn
error={this.props.local.error}
auth={this.props.auth}
onGotoSignUp={this.goToSignUp}
emailEnabled={
integrations.local.enabled && integrations.local.targetFilter.stream
}
facebookEnabled={
integrations.facebook.enabled &&
integrations.facebook.targetFilter.stream
}
googleEnabled={
integrations.google.enabled && integrations.google.targetFilter.stream
}
oidcEnabled={
integrations.oidc.enabled && integrations.oidc.targetFilter.stream
}
signUpHref={getViewURL("SIGN_UP")}
/>
// 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<Props>({
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 (
<SignIn
error={local.error}
auth={auth}
onGotoSignUp={goToSignUp}
emailEnabled={
integrations.local.enabled && integrations.local.targetFilter.stream
}
facebookEnabled={
integrations.facebook.enabled &&
integrations.facebook.targetFilter.stream
}
googleEnabled={
integrations.google.enabled && integrations.google.targetFilter.stream
}
oidcEnabled={
integrations.oidc.enabled && integrations.oidc.targetFilter.stream
}
signUpHref={getViewURL("SIGN_UP")}
/>
);
};
const enhanced = withClearErrorMutation(
withLocalStateContainer(
graphql`
fragment SignInContainerLocal on Local {
error
}
`
)(
withFragmentContainer<Props>({
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;
@@ -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<void>;
@@ -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;
@@ -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<typeof SetViewMutation>;
}
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<SignInContainerProps> {
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 (
<SignInWithEmail
onSubmit={this.onSubmit}
onGotoForgotPassword={this.goToForgotPassword}
forgotPasswordHref={getViewURL("FORGOT_PASSWORD")}
/>
);
}
}
return (
<SignInWithEmail
onSubmit={onSubmit}
onGotoForgotPassword={goToForgotPassword}
forgotPasswordHref={getViewURL("FORGOT_PASSWORD")}
/>
);
};
const enhanced = withMutation(SetViewMutation)(
withSignInMutation(SignInContainer)
);
export default enhanced;
export default SignInContainer;
@@ -42,7 +42,7 @@ exports[`renders correctly 1`] = `
<ForwardRef(forwardRef)
size="oneAndAHalf"
>
<withContext(withMutation(withContext(createMutationContainer(SignInContainer)))) />
<SignInContainer />
<OrSeparator />
<ForwardRef(forwardRef)>
<Relay(SignInWithFacebookContainer)
@@ -108,7 +108,7 @@ exports[`renders error 1`] = `
>
Server Error
</withPropsOnChange(CallOut)>
<withContext(withMutation(withContext(createMutationContainer(SignInContainer)))) />
<SignInContainer />
<OrSeparator />
<ForwardRef(forwardRef)>
<Relay(SignInWithFacebookContainer)
+3 -1
View File
@@ -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 {
Flex,
@@ -38,10 +39,11 @@ const SignUp: FunctionComponent<Props> = ({
signInHref,
auth,
}) => {
const ref = useResizePopup();
const oneClickUptegrationEnabled =
facebookEnabled || googleEnabled || oidcEnabled;
return (
<div data-testid="signUp-container">
<div ref={ref} data-testid="signUp-container">
<Localized
id="signUp-signUpToJoinHeader"
title={<Title />}
@@ -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<typeof SetViewMutation>;
}
class SignUpContainer extends Component<Props> {
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 (
<SignUp
signInHref={getViewURL("SIGN_IN")}
auth={this.props.auth}
onGotoSignIn={this.goToSignIn}
emailEnabled={
integrations.local.enabled &&
integrations.local.targetFilter.stream &&
integrations.local.allowRegistration
}
facebookEnabled={
integrations.facebook.enabled &&
integrations.facebook.targetFilter.stream &&
integrations.facebook.allowRegistration
}
googleEnabled={
integrations.google.enabled &&
integrations.google.targetFilter.stream &&
integrations.google.allowRegistration
}
oidcEnabled={
integrations.oidc.enabled &&
integrations.oidc.targetFilter.stream &&
integrations.oidc.allowRegistration
}
/>
);
}
}
const SignUpContainer: FunctionComponent<Props> = ({ 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<Props>({
auth: graphql`
fragment SignUpContainer_auth on Auth {
...SignUpWithOIDCContainer_auth
...SignUpWithGoogleContainer_auth
...SignUpWithFacebookContainer_auth
integrations {
local {
enabled
targetFilter {
stream
}
allowRegistration
const integrations = auth.integrations;
return (
<SignUp
signInHref={getViewURL("SIGN_IN")}
auth={auth}
onGotoSignIn={goToSignIn}
emailEnabled={
integrations.local.enabled &&
integrations.local.targetFilter.stream &&
integrations.local.allowRegistration
}
facebookEnabled={
integrations.facebook.enabled &&
integrations.facebook.targetFilter.stream &&
integrations.facebook.allowRegistration
}
googleEnabled={
integrations.google.enabled &&
integrations.google.targetFilter.stream &&
integrations.google.allowRegistration
}
oidcEnabled={
integrations.oidc.enabled &&
integrations.oidc.targetFilter.stream &&
integrations.oidc.allowRegistration
}
/>
);
};
const enhanced = withFragmentContainer<Props>({
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;
@@ -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,
},
@@ -71,39 +71,10 @@ export class UserBoxContainer extends Component<Props> {
].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<Props> {
return (
<>
<Popup
href={this.authUrl}
href={`${urls.embed.auth}?view=${view}`}
title="Coral Auth"
open={open}
focus={focus}
onFocus={this.handleFocus}
onBlur={this.handleBlur}
onClose={this.handleClose}
features={{ width: 350, innerWidth: 350 }}
/>
<UserBoxUnauthenticated
onSignIn={this.handleSignIn}
@@ -182,7 +154,6 @@ const enhanced = withSignOutMutation(
oidc {
enabled
allowRegistration
redirectURL
targetFilter {
stream
}
@@ -190,7 +161,6 @@ const enhanced = withSignOutMutation(
google {
enabled
allowRegistration
redirectURL
targetFilter {
stream
}
@@ -198,7 +168,6 @@ const enhanced = withSignOutMutation(
facebook {
enabled
allowRegistration
redirectURL
targetFilter {
stream
}
@@ -3,6 +3,12 @@
exports[`renders fully 1`] = `
<React.Fragment>
<Popup
features={
Object {
"innerWidth": 350,
"width": 350,
}
}
focus={false}
href="/embed/auth?view=SIGN_IN"
onBlur={[Function]}
@@ -26,6 +32,12 @@ exports[`renders sso only without logout button 1`] = `null`;
exports[`renders without logout button 1`] = `
<React.Fragment>
<Popup
features={
Object {
"innerWidth": 350,
"width": 350,
}
}
focus={false}
href="/embed/auth?view=SIGN_IN"
onBlur={[Function]}
@@ -45,6 +57,12 @@ exports[`renders without logout button 1`] = `
exports[`renders without register button 1`] = `
<React.Fragment>
<Popup
features={
Object {
"innerWidth": 350,
"width": 350,
}
}
focus={false}
href="/embed/auth?view=SIGN_IN"
onBlur={[Function]}
@@ -6,6 +6,7 @@ interface WindowFeatures {
width: number;
height: number;
centered: boolean;
innerWidth?: number;
}
interface PopupProps {