mirror of
https://github.com/wassname/talk.git
synced 2026-07-03 18:41:21 +08:00
[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:
committed by
Kim Gardner
parent
64f102e6d4
commit
921461008e
@@ -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,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,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;
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -22,6 +22,7 @@ import { AsymmetricSigningAlgorithm } from "coral-server/services/jwt";
|
||||
import TenantCache from "coral-server/services/tenant/cache";
|
||||
import { TenantCacheAdapter } from "coral-server/services/tenant/cache/adapter";
|
||||
import { insert } from "coral-server/services/users";
|
||||
import { validateUsername } from "coral-server/services/users/helpers";
|
||||
import { Request } from "coral-server/types/express";
|
||||
|
||||
export interface Params {
|
||||
@@ -170,7 +171,14 @@ export async function findOrCreateOIDCUser(
|
||||
// FIXME: implement rules.
|
||||
|
||||
// Try to extract the username from the following chain:
|
||||
const username = preferred_username || nickname || name;
|
||||
let username = preferred_username || nickname || name;
|
||||
if (username) {
|
||||
try {
|
||||
validateUsername(username);
|
||||
} catch (err) {
|
||||
username = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
// Create the new user, as one didn't exist before!
|
||||
user = await insert(
|
||||
|
||||
@@ -422,7 +422,6 @@ type LocalAuthIntegration {
|
||||
integration should be displayed in all targets.
|
||||
"""
|
||||
targetFilter: AuthenticationTargetFilter!
|
||||
|
||||
}
|
||||
|
||||
##########################
|
||||
@@ -443,13 +442,6 @@ type SSOAuthIntegration {
|
||||
"""
|
||||
allowRegistration: Boolean!
|
||||
|
||||
"""
|
||||
redirectURL is the URL that the user should be redirected to in order to start
|
||||
an authentication flow with the given integration. This field is not stored,
|
||||
and is instead computed from the Tenant.
|
||||
"""
|
||||
redirectURL: String
|
||||
|
||||
"""
|
||||
targetFilter will restrict where the authentication integration should be
|
||||
displayed. If the value of targetFilter is null, then the authentication
|
||||
|
||||
Reference in New Issue
Block a user