From 2f46899a594ddab6091672be248219f98909b43e Mon Sep 17 00:00:00 2001 From: Bill John Tran Date: Thu, 29 Aug 2024 18:49:57 -0700 Subject: [PATCH] Feaute/asub 8252 Apple OIDC (#2193) * add apple oidc button * add oidc logic * update apple sign on to use OIDC if clientId is added * add logic for when apple v1 is not available and OIDC is * add tests for apple sign on * update apple to use oidc sign in * update identity and sales SDK versions * add 1.89.0 sdk version --- .../identity-block/components/login/index.jsx | 14 +++- .../social-sign-on/_children/AppleSignIn.jsx | 28 ++++++- .../_children/AppleSignIn.test.jsx | 39 ++++++++++ .../components/social-sign-on/index.jsx | 51 +++++++++++-- .../components/social-sign-on/index.test.jsx | 76 ++++++++++++++----- .../social-sign-on/utils/useSocialSignIn.js | 1 + .../features/social-sign-on/default.jsx | 16 +++- blocks/identity-block/package.json | 4 +- blocks/subscriptions-block/package.json | 4 +- package-lock.json | 12 +-- package.json | 4 +- 11 files changed, 206 insertions(+), 43 deletions(-) diff --git a/blocks/identity-block/components/login/index.jsx b/blocks/identity-block/components/login/index.jsx index 1fc61c6ac..6eab30487 100644 --- a/blocks/identity-block/components/login/index.jsx +++ b/blocks/identity-block/components/login/index.jsx @@ -11,6 +11,7 @@ const useLogin = ({ loggedInPageLocation, isOIDC, appleCode, + appleState, }) => { const { Identity } = useIdentity(); @@ -32,8 +33,13 @@ const useLogin = ({ }; useEffect(() => { - const askForloginWithApple = async (code) => { - await Identity.appleSignOn(code); + const askForloginWithApple = async (code, state) => { + if (state) { + await Identity.signInWithOIDC(code, state); + } else { + await Identity.appleSignOn(code); + } + const isLoggedIn = await Identity.isLoggedIn(); if (isLoggedIn) { @@ -42,9 +48,9 @@ const useLogin = ({ }; if (Identity && appleCode) { - askForloginWithApple(appleCode); + askForloginWithApple(appleCode, appleState); } - }, [appleCode, Identity]); + }, [appleCode, Identity, appleState]); useEffect(() => { const searchParams = new URLSearchParams(window.location.search.substring(1)); diff --git a/blocks/identity-block/components/social-sign-on/_children/AppleSignIn.jsx b/blocks/identity-block/components/social-sign-on/_children/AppleSignIn.jsx index f03e23747..7e5742aad 100644 --- a/blocks/identity-block/components/social-sign-on/_children/AppleSignIn.jsx +++ b/blocks/identity-block/components/social-sign-on/_children/AppleSignIn.jsx @@ -5,15 +5,38 @@ import { SIGN_UP } from "../constants"; const AppleIcon = ; -function AppleSignIn({ customButtons, socialSignOnIn, className }) { +function AppleSignIn({ customButtons, socialSignOnIn, className, oidcClients = [], appleClientId }) { const phrases = usePhrases(); const { Identity } = useIdentity(); + const appleOIDCClient = oidcClients.find((oidcClient) => { + const parsedClientId = oidcClient.clientId.split(';')[0]; + + return oidcClient.protocol === 'Apple' && parsedClientId === appleClientId; + }); + + const handleClick = () => { + if (appleOIDCClient?.clientId) { + Identity.initiateOIDC( + appleOIDCClient.clientId, + ['name', 'email'], + true, + true + ); + } else { + Identity.initAppleSignOn(); + } + }; + return ( +
+
); } diff --git a/blocks/identity-block/components/social-sign-on/_children/AppleSignIn.test.jsx b/blocks/identity-block/components/social-sign-on/_children/AppleSignIn.test.jsx index a65b73950..d07488ecf 100644 --- a/blocks/identity-block/components/social-sign-on/_children/AppleSignIn.test.jsx +++ b/blocks/identity-block/components/social-sign-on/_children/AppleSignIn.test.jsx @@ -19,6 +19,21 @@ jest.mock("@wpmedia/arc-themes-components", () => ({ Icon: () =>
, })); +const mockInitAppleSignOn = jest.fn(); +const mockInitiateOIDCMock = jest.fn(); + +jest.mock("@wpmedia/arc-themes-components", () => ({ + ...jest.requireActual("@wpmedia/arc-themes-components"), + Icon: () =>
, + useIdentity: () => { + const Identity = { + initiateOIDC: mockInitiateOIDCMock, + initAppleSignOn: mockInitAppleSignOn, + }; + return { Identity }; + }, +})); + describe("Identity Social Login Component", () => { beforeEach(() => { jest.clearAllMocks(); @@ -37,4 +52,28 @@ describe("Identity Social Login Component", () => { render(); expect(screen.getByText("identity-block.social-signOn-apple-signUp")).not.toBeNull(); }); + + it("triggers Identity.initiateOIDC when using Apple OIDC is used", () => { + render(); + screen.getByText("identity-block.social-signOn-apple-login").click(); + expect(mockInitiateOIDCMock).toHaveBeenCalled(); + }); + + it("triggers Identity.initAppleSignOn when using Apple v1 sign on is used", () => { + render(); + screen.getByText("identity-block.social-signOn-apple-login").click(); + expect(mockInitAppleSignOn).toHaveBeenCalled(); + }); + + it("triggers Identity.initAppleSignOn when clientId is not available in OIDC client list", () => { + render(); + screen.getByText("identity-block.social-signOn-apple-login").click(); + expect(mockInitAppleSignOn).toHaveBeenCalled(); + }); + + it("triggers Identity.initAppleSignOn when clientId is not a protocol: 'Apple'", () => { + render(); + screen.getByText("identity-block.social-signOn-apple-login").click(); + expect(mockInitAppleSignOn).toHaveBeenCalled(); + }); }); diff --git a/blocks/identity-block/components/social-sign-on/index.jsx b/blocks/identity-block/components/social-sign-on/index.jsx index 833231b31..b717577ee 100644 --- a/blocks/identity-block/components/social-sign-on/index.jsx +++ b/blocks/identity-block/components/social-sign-on/index.jsx @@ -5,19 +5,60 @@ import GoogleSignIn from "./_children/GoogleSignIn"; import AppleSignIn from "./_children/AppleSignIn"; import useSocialSignIn from "./utils/useSocialSignIn"; -const SocialSignOn = ({ className, onError, redirectURL, isOIDC, socialSignOnIn, customButtons }) => { - const { facebookAppId, googleClientId, appleTeamId, appleKeyId, appleUrlToReceiveAuthToken} = useSocialSignIn(redirectURL, isOIDC, socialSignOnIn, onError, customButtons); +const SocialSignOn = ({ + className, + onError, + redirectURL, + isOIDC, + socialSignOnIn, + customButtons, + appleClientId, +}) => { + const { + facebookAppId, + googleClientId, + appleTeamId, + appleKeyId, + appleUrlToReceiveAuthToken, + oidcClients, + } = useSocialSignIn(redirectURL, isOIDC, socialSignOnIn, onError, customButtons); + + const hasAppleClient = () => { + const hasOIDCAppleClient = oidcClients && oidcClients.find((oidcClient) => ( + oidcClient.protocol === "Apple" && oidcClient.clientId === appleClientId + )); + + if (hasOIDCAppleClient) { + return true; + } + + if (appleTeamId && appleKeyId && appleUrlToReceiveAuthToken) { + return true; + } + + return false; + } + return (
- {googleClientId ? : null} - {facebookAppId ? : null} - {appleTeamId && appleKeyId && appleUrlToReceiveAuthToken ? : null} + {googleClientId && } + {facebookAppId && } + {hasAppleClient() && ( + + )}
); }; SocialSignOn.propTypes = { redirectURL: PropTypes.string.isRequired, + appleClientId: PropTypes.string, onError: PropTypes.func.isRequired, }; diff --git a/blocks/identity-block/components/social-sign-on/index.test.jsx b/blocks/identity-block/components/social-sign-on/index.test.jsx index 9451a0d1d..c8c30bb6a 100644 --- a/blocks/identity-block/components/social-sign-on/index.test.jsx +++ b/blocks/identity-block/components/social-sign-on/index.test.jsx @@ -12,7 +12,7 @@ jest.mock('@wpmedia/arc-themes-components', () => ({ Button: (props) => mockButton(props), useIdentity: jest.fn(), usePhrases: jest.fn() - })); +})); describe("Identity Social Login Component", () => { it("renders nothing if config settings are false", () => { @@ -24,8 +24,8 @@ describe("Identity Social Login Component", () => { googleClientId: false, facebookAppId: false, }, - initFacebookLogin: () => {}, - initializeFacebook: () => {}, + initFacebookLogin: () => { }, + initializeFacebook: () => { }, }, })); usePhrases.mockImplementation(() => ({ @@ -61,8 +61,8 @@ describe("Identity Social Login Component", () => { recaptchaSiteKey: "6LdXKVQcAAAAAO2tv3GdUbSK-1vcgujX6cP0IgF_", }), ), - initFacebookLogin: () => {}, - initializeFacebook: () => {}, + initFacebookLogin: () => { }, + initializeFacebook: () => { }, isLoggedIn: jest.fn(() => false), }, })); @@ -82,8 +82,8 @@ describe("Identity Social Login Component", () => { useIdentity.mockImplementation(() => ({ Identity: { getConfig: getConfigMock, - initFacebookLogin: () => {}, - initializeFacebook: () => {}, + initFacebookLogin: () => { }, + initializeFacebook: () => { }, isLoggedIn: jest.fn(() => false), }, isInitialized: true, @@ -110,9 +110,9 @@ describe("Identity Social Login Component", () => { facebookAppId: true, }, facebookSignOn: facebookSignOnMock, - getConfig: () => {}, - initFacebookLogin: () => {}, - initializeFacebook: () => {}, + getConfig: () => { }, + initFacebookLogin: () => { }, + initializeFacebook: () => { }, isLoggedIn: jest.fn(() => false), }, })); @@ -140,9 +140,9 @@ describe("Identity Social Login Component", () => { facebookAppId: true, }, facebookSignOn: facebookSignOnMock, - getConfig: () => {}, - initFacebookLogin: () => {}, - initializeFacebook: () => {}, + getConfig: () => { }, + initFacebookLogin: () => { }, + initializeFacebook: () => { }, isLoggedIn: jest.fn(() => false), }, })); @@ -162,6 +162,48 @@ describe("Identity Social Login Component", () => { window.onFacebookSignOn(); expect(onErrorMock).toHaveBeenCalled(); }); + + it("renders Apple Sign in", () => { + useIdentity.mockImplementation(() => ({ + isInitialized: true, + isLoggedIn: () => true, + Identity: { + configOptions: { + googleClientId: true, + facebookAppId: true, + teamId: "teamId", + keyId: "keyId", + urlToReceiveAuthToken: "urlToReceiveAuthToken", + oidcClients: [], + }, + getConfig: jest.fn(() => + Promise.resolve({ + signinRecaptcha: false, + recaptchaSiteKey: "6LdXKVQcAAAAAO2tv3GdUbSK-1vcgujX6cP0IgF_", + }), + ), + initFacebookLogin: () => { }, + initializeFacebook: () => { }, + isLoggedIn: jest.fn(() => false), + }, + })); + + usePhrases.mockImplementation(() => ({ + t: jest + .fn() + .mockReturnValue( + "Sign-in prompt was suppressed by the user or dismissed. Please try again later or use another sign-in method.", + ), + })); + + render( + + null} redirectURL="#" /> + , + ); + + expect(screen.getByTestId("apple-sign-in-button")).toBeInTheDocument(); + }); }); describe("Identity Social Login Component - Google Button", () => { @@ -174,8 +216,8 @@ describe("Identity Social Login Component - Google Button", () => { googleClientId: true, facebookAppId: false, }, - initFacebookLogin: () => {}, - initializeFacebook: () => {}, + initFacebookLogin: () => { }, + initializeFacebook: () => { }, }, })); }); @@ -215,8 +257,8 @@ describe("Identity Social Login Component - Facebook Button", () => { googleClientId: false, facebookAppId: true, }, - initFacebookLogin: () => {}, - initializeFacebook: () => {}, + initFacebookLogin: () => { }, + initializeFacebook: () => { }, }, })); }); diff --git a/blocks/identity-block/components/social-sign-on/utils/useSocialSignIn.js b/blocks/identity-block/components/social-sign-on/utils/useSocialSignIn.js index 4bf7692ed..37f024e53 100644 --- a/blocks/identity-block/components/social-sign-on/utils/useSocialSignIn.js +++ b/blocks/identity-block/components/social-sign-on/utils/useSocialSignIn.js @@ -182,6 +182,7 @@ function useSocialSignIn( appleTeamId: config.teamId, appleKeyId: config.keyId, appleUrlToReceiveAuthToken: config.urlToReceiveAuthToken, + oidcClients: config.oidcClients, updateIdentities, }; } diff --git a/blocks/identity-block/features/social-sign-on/default.jsx b/blocks/identity-block/features/social-sign-on/default.jsx index 735608290..439de9293 100644 --- a/blocks/identity-block/features/social-sign-on/default.jsx +++ b/blocks/identity-block/features/social-sign-on/default.jsx @@ -18,7 +18,8 @@ const SocialSignOnBlock = ({ customFields }) => { OIDC, socialSignOnIn, hideDiv, - customButtons + customButtons, + appleClientId, } = customFields; const checkAppleCodeExists = (url) => { @@ -27,7 +28,8 @@ const SocialSignOnBlock = ({ customFields }) => { const charsAfterLastQuestionMark = urlQueryParams[urlQueryParams.length - 1]; const queryParams = new URLSearchParams(charsAfterLastQuestionMark); const appleCode = queryParams.get("code"); - return appleCode; + const appleState = queryParams.get("state"); + return { appleCode, appleState }; } return null; }; @@ -37,7 +39,7 @@ const SocialSignOnBlock = ({ customFields }) => { const isOIDC = OIDC && url.searchParams.get("client_id") && url.searchParams.get("response_type") === "code"; - const appleCode = checkAppleCodeExists(urlString); + const { appleCode, appleState } = checkAppleCodeExists(urlString); const { isAdmin, arcSite } = useFusionContext(); const { locale } = getProperties(arcSite); const phrases = getTranslatedPhrases(locale); @@ -53,6 +55,7 @@ const SocialSignOnBlock = ({ customFields }) => { loggedInPageLocation, isOIDC, appleCode, + appleState, }); if (!isInitialized) { @@ -72,6 +75,7 @@ const SocialSignOnBlock = ({ customFields }) => { isOIDC={isOIDC} socialSignOnIn={socialSignOnIn} customButtons={customButtons} + appleClientId={appleClientId} /> {error ? ( @@ -91,6 +95,12 @@ SocialSignOnBlock.propTypes = { name: "Redirect URL", defaultValue: "/account/", }), + appleClientId: PropTypes.string.tag({ + name: "Apple OIDC Client ID", + defaultValue: "", + description: + 'Client ID for Apple OIDC authentication provider. This is the "Provider key" in your "Authentication Providers" settings', + }), redirectToPreviousPage: PropTypes.bool.tag({ name: "Redirect to previous page", defaultValue: true, diff --git a/blocks/identity-block/package.json b/blocks/identity-block/package.json index 128438508..d06868e8c 100644 --- a/blocks/identity-block/package.json +++ b/blocks/identity-block/package.json @@ -30,7 +30,7 @@ }, "dependencies": { "@arc-fusion/prop-types": "^0.1.5", - "@arc-publishing/sdk-identity": "^1.88.0", + "@arc-publishing/sdk-identity": "^1.89.0", "react-google-recaptcha": "^3.1.0", "react-google-recaptcha-v3": "^1.10.1" }, @@ -38,4 +38,4 @@ "@testing-library/jest-dom": "^6.4.6", "@testing-library/react": "^16.0.0" } -} +} \ No newline at end of file diff --git a/blocks/subscriptions-block/package.json b/blocks/subscriptions-block/package.json index 4a3d08263..7f004645f 100644 --- a/blocks/subscriptions-block/package.json +++ b/blocks/subscriptions-block/package.json @@ -31,9 +31,9 @@ "dependencies": { "@arc-fusion/prop-types": "^0.1.5", "@arc-publishing/sdk-sales": "^1.55.0", - "@arc-publishing/sdk-identity": "^1.88.0", + "@arc-publishing/sdk-identity": "^1.89.0", "@stripe/react-stripe-js": "^2.4.0", "@stripe/stripe-js": "^2.3.0", "is-url": "^1.2.4" } -} +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index fe7dc801d..c9f8ed09a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -83,7 +83,7 @@ }, "devDependencies": { "@arc-fusion/prop-types": "^0.1.5", - "@arc-publishing/sdk-identity": "^1.88.0", + "@arc-publishing/sdk-identity": "^1.89.0", "@babel/core": "^7.23.2", "@babel/eslint-parser": "^7.23.3", "@babel/plugin-proposal-class-properties": "^7.16.7", @@ -405,7 +405,7 @@ "license": "CC-BY-NC-ND-4.0", "dependencies": { "@arc-fusion/prop-types": "^0.1.5", - "@arc-publishing/sdk-identity": "^1.88.0", + "@arc-publishing/sdk-identity": "^1.89.0", "react-google-recaptcha": "^3.1.0", "react-google-recaptcha-v3": "^1.10.1" }, @@ -927,7 +927,7 @@ "license": "CC-BY-NC-ND-4.0", "dependencies": { "@arc-fusion/prop-types": "^0.1.5", - "@arc-publishing/sdk-identity": "^1.55.0", + "@arc-publishing/sdk-identity": "^1.89.0", "@arc-publishing/sdk-sales": "^1.55.0", "@stripe/react-stripe-js": "^2.4.0", "@stripe/stripe-js": "^2.3.0", @@ -1188,9 +1188,9 @@ } }, "node_modules/@arc-publishing/sdk-identity": { - "version": "1.88.0", - "resolved": "https://registry.npmjs.org/@arc-publishing/sdk-identity/-/sdk-identity-1.88.0.tgz", - "integrity": "sha512-CSq/eAsE3tppnDnxRTHnTbO9HAo+4eS3byn+xHSPvDbDU/E+tPngWi0Hu9skfJxC1kBb/X3I97NBPcNxGs9fXQ==", + "version": "1.89.0", + "resolved": "https://registry.npmjs.org/@arc-publishing/sdk-identity/-/sdk-identity-1.89.0.tgz", + "integrity": "sha512-fgHgaL6qtcOLpXasRhjSJrNlu5+O17rd+LBgALdlViRiaStuXra4trOL2kstCnb2JSE1O87m6hYygkoa4uEjzg==", "engines": { "node": ">=8" } diff --git a/package.json b/package.json index e281a1f4e..2ea7271b5 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,7 @@ }, "devDependencies": { "@arc-fusion/prop-types": "^0.1.5", - "@arc-publishing/sdk-identity": "^1.88.0", + "@arc-publishing/sdk-identity": "^1.89.0", "@babel/core": "^7.23.2", "@babel/eslint-parser": "^7.23.3", "@babel/plugin-proposal-class-properties": "^7.16.7", @@ -199,4 +199,4 @@ "stylelint": "$stylelint" } } -} +} \ No newline at end of file