Skip to content

Commit

Permalink
ASUB-8476 Redeem ota link (#2129)
Browse files Browse the repository at this point in the history
redeem ota link
  • Loading branch information
LauraPinilla authored May 30, 2024
1 parent 97794d5 commit 45bbb13
Show file tree
Hide file tree
Showing 30 changed files with 571 additions and 223 deletions.
5 changes: 5 additions & 0 deletions blocks/identity-block/_index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,11 @@
@include scss.block-properties("ota-sub-headline");
}

&__ota-form-error {
@include scss.block-components("ota-form-error");
@include scss.block-properties("ota-form-error");
}

&__recaptcha {
@include scss.block-components("ota-recaptcha");
@include scss.block-properties("ota-recaptcha");
Expand Down
126 changes: 99 additions & 27 deletions blocks/identity-block/features/one-time-password/default.jsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,34 @@
import React, { useState } from "react";
import React, { useEffect, useState } from "react";
import PropTypes from "@arc-fusion/prop-types";
import { useFusionContext } from "fusion:context";
import getProperties from "fusion:properties";
import getTranslatedPhrases from "fusion:intl";
import { Input, useIdentity, BotChallengeProtection, Heading, HeadingSection } from "@wpmedia/arc-themes-components";
import {
Button,
Input,
useIdentity,
BotChallengeProtection,
Heading,
Paragraph,
HeadingSection,
} from "@wpmedia/arc-themes-components";

import HeadlinedSubmitForm from "../../components/headlined-submit-form";
import validateURL from "../../utils/validate-redirect-url";

const BLOCK_CLASS_NAME = "b-one-time-login-form";
const OTA_NONCE = 'ota_nonce';

const errorCodes = {
100015: "identity-block.login-form-error.account-is-disabled",
130001: "identity-block.login-form-error.captcha-token-invalid",
130051: "identity-block.login-form-error.unverified-email-address",
100013: "identity-block.login-form-error.max-devices",
0: "identity-block.login-form-error.invalid-email-password",
0: "identity-block.login-form-error.something-went-wrong",
};

const definedMessageByCode = (code) => errorCodes[code] || errorCodes["0"];

const OneTimePasswordLogin = () => {
const OneTimePasswordLogin = ({ customFields }) => {
const { loggedInPageLocation } = customFields;

const { arcSite } = useFusionContext();
const { locale } = getProperties(arcSite);
const phrases = getTranslatedPhrases(locale);
Expand All @@ -27,12 +38,56 @@ const OneTimePasswordLogin = () => {
const [error, setError] = useState();
const [captchaError, setCaptchaError] = useState();
const [success, setSuccess] = useState(false);
const [userEmail, setUserEmail] = useState('');
const [successRedeem, setSuccessRedeem] = useState(true);
const [userEmail, setUserEmail] = useState("");

const url = new URL(window.location.href);
const params = new URLSearchParams(url.search);
const otaNonce = params.get(OTA_NONCE);

useEffect(() => {
if (otaNonce) {
Identity.redeemOTALink(otaNonce)
.then(() => {
const validatedURL = validateURL(loggedInPageLocation);
window.location.href = validatedURL;
})
.catch(() => {
setSuccessRedeem(false);
});
}
}, [otaNonce, Identity, loggedInPageLocation, phrases]);

if (!isInitialized) {
return null;
}

if (!successRedeem) {
return (
<div className={BLOCK_CLASS_NAME}>
<HeadingSection>
<Heading>{phrases.t("identity-block.ota-ivalid-login-link")}</Heading>
</HeadingSection>
<p className={`${BLOCK_CLASS_NAME}__ota-sub-headline`}>
{phrases.t("identity-block.ota-ivalid-login-link-subheadline")}
</p>
<Button
size="large"
variant="primary"
fullWidth
type="button"
onClick={() => {
params.delete(OTA_NONCE, otaNonce);
const newUrl = `${url.origin}${url.pathname}?${params.toString()}`;
window.location.href = newUrl;
}}
>
<span>{phrases.t("identity-block.ota-get-new-link")}</span>
</Button>
</div>
);
}

if (success) {
return (
<div className={BLOCK_CLASS_NAME}>
Expand All @@ -41,40 +96,46 @@ const OneTimePasswordLogin = () => {
</HeadingSection>
<p
className={`${BLOCK_CLASS_NAME}__ota-sub-headline`}
dangerouslySetInnerHTML={{__html: phrases.t("identity-block.ota-success-body", { userEmail })}}
dangerouslySetInnerHTML={{
__html: phrases.t("identity-block.ota-success-body", { userEmail }),
}}
/>
</div>
)
);
}

return (
<div>
<HeadlinedSubmitForm
buttonLabel={phrases.t("identity-block.ota-form-button")}
className={BLOCK_CLASS_NAME}
formErrorText={error}
headline={phrases.t("identity-block.ota-headline")}
onSubmit={({ email }) => {
setError(null);
setCaptchaError(null);
return Identity.requestOTALink(
email,
captchaToken,
).then(() => {
setUserEmail(email);
setSuccess(true);
})
.catch((e) => {
setResetRecaptcha(!resetRecaptcha);
if (e?.code === "130001") {
setCaptchaError(phrases.t(definedMessageByCode(e.code)));
} else {
setError(phrases.t(definedMessageByCode(e.code)));
}
});
return Identity.requestOTALink(email, captchaToken)
.then(() => {
setUserEmail(email);
setSuccess(true);
})
.catch((e) => {
setResetRecaptcha(!resetRecaptcha);
if (e?.code === "130001") {
setCaptchaError(phrases.t(definedMessageByCode(e.code)));
} else {
setError(phrases.t(definedMessageByCode(e.code)));
}
});
}}
>
<p className={`${BLOCK_CLASS_NAME}__ota-sub-headline`}>{phrases.t("identity-block.ota-subheadline")}</p>
<p className={`${BLOCK_CLASS_NAME}__ota-sub-headline`}>
{phrases.t("identity-block.ota-subheadline")}
</p>
{error ? (
<div className={`${BLOCK_CLASS_NAME}__ota-form-error`}>
<Paragraph>{error}</Paragraph>
</div>
) : null}
<Input
autoComplete="email"
label={phrases.t("identity-block.email-label")}
Expand Down Expand Up @@ -103,4 +164,15 @@ const OneTimePasswordLogin = () => {

OneTimePasswordLogin.label = "Identity One Time Password Request Form - Arc Block";

OneTimePasswordLogin.propTypes = {
customFields: PropTypes.shape({
loggedInPageLocation: PropTypes.string.tag({
name: "Logged In URL",
defaultValue: "/account/",
description:
"The URL to which a user would be redirected to if visiting a login page when already logged in.",
}),
}),
};

export default OneTimePasswordLogin;
75 changes: 63 additions & 12 deletions blocks/identity-block/features/one-time-password/default.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,16 @@ jest.mock("@wpmedia/arc-themes-components", () => ({
...mockIdentity,
},
})),
BotChallengeProtection: ({ challengeIn= 'magicLink' }) => <div data-testid={`reCapctha-${challengeIn}`} />
BotChallengeProtection: ({ challengeIn = "magicLink" }) => (
<div data-testid={`reCapctha-${challengeIn}`} />
),
}));
jest.mock("fusion:properties", () => jest.fn(() => ({})));

const customFields = {
loggedInPageLocation: "/account/",
};

describe("Identity One Time Password Request Form - Arc Block", () => {
beforeEach(() => {
useIdentity.mockImplementation(() => ({
Expand All @@ -42,21 +48,21 @@ describe("Identity One Time Password Request Form - Arc Block", () => {
},
}));

render(<OneTimePassword />);
render(<OneTimePassword customFields={customFields} />);

expect(screen.queryAllByRole("form").length).toEqual(0);
});

it("Does not render if Identity isn't initialized", () => {
render(<OneTimePassword />);
render(<OneTimePassword customFields={customFields} />);

expect(screen.queryAllByRole("form").length).toEqual(1);
expect(screen.getByTestId('reCapctha-magicLink')).toBeTruthy();
expect(screen.getByTestId("reCapctha-magicLink")).toBeTruthy();
});

it("Should be able submit form", async () => {
render(<OneTimePassword />);
render(<OneTimePassword customFields={customFields} />);

await waitFor(() => expect(screen.getByLabelText("identity-block.email-label")));
fireEvent.change(screen.getByLabelText("identity-block.email-label"), {
target: { value: "[email protected]" },
Expand Down Expand Up @@ -84,8 +90,8 @@ describe("Identity One Time Password Request Form - Arc Block", () => {
},
}));

render(<OneTimePassword />);
render(<OneTimePassword customFields={customFields} />);

await waitFor(() => expect(screen.getByLabelText("identity-block.email-label")));
fireEvent.change(screen.getByLabelText("identity-block.email-label"), {
target: { value: "[email protected]" },
Expand All @@ -111,8 +117,8 @@ describe("Identity One Time Password Request Form - Arc Block", () => {
},
}));

render(<OneTimePassword />);
render(<OneTimePassword customFields={customFields} />);

await waitFor(() => expect(screen.getByLabelText("identity-block.email-label")));
fireEvent.change(screen.getByLabelText("identity-block.email-label"), {
target: { value: "[email protected]" },
Expand All @@ -123,4 +129,49 @@ describe("Identity One Time Password Request Form - Arc Block", () => {

await waitFor(() => expect(errorMessage).toHaveBeenCalled());
});

it("Should redeem the nonce", async () => {
delete window.location;
window.location = {
href: "http://localhost/onetimeaccess/?ota_nonce=123",
};

useIdentity.mockImplementation(() => ({
isInitialized: true,
Identity: {
...mockIdentity,
redeemOTALink: jest.fn(() => Promise.resolve()),
},
}));

render(<OneTimePassword customFields={customFields} />);
expect(window.location.href).toBe('http://localhost/onetimeaccess/?ota_nonce=123');

await waitFor(() => expect(window.location.href).toBe(`${customFields.loggedInPageLocation}`));
});

it("Should fail when trying to redeem the nonce", async () => {
delete window.location;
window.location = {
href: "http://localhost/onetimeaccess/?ota_nonce=123",
};

const error = new Error("Fake error");
error.code = "300040";

const errorMessage = jest.fn(() => Promise.reject(error));

useIdentity.mockImplementation(() => ({
isInitialized: true,
Identity: {
...mockIdentity,
redeemOTALink: errorMessage,
},
}));

render(<OneTimePassword customFields={customFields} />);

expect(await screen.findByText("identity-block.ota-ivalid-login-link")).not.toBeNull();
expect(await screen.findByText("identity-block.ota-ivalid-login-link-subheadline")).not.toBeNull();
});
});
Loading

0 comments on commit 45bbb13

Please sign in to comment.