Skip to content

Commit

Permalink
ReCaptcha now using the recaptchaV3Config query and add a fallback …
Browse files Browse the repository at this point in the history
…for Magento 2.4.6 and earlier to still use the configuration. Magento 2.4.7 doesn't need that configuration anymore.
  • Loading branch information
paales committed Feb 10, 2025
1 parent 9a777ca commit a261149
Show file tree
Hide file tree
Showing 13 changed files with 104 additions and 32 deletions.
6 changes: 6 additions & 0 deletions .changeset/gentle-rabbits-relax.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@graphcommerce/googlerecaptcha': patch
'@graphcommerce/graphql': patch
---

ReCaptcha now using the `recaptchaV3Config` query and add a fallback for Magento 2.4.6 and earlier to still use the configuration. Magento 2.4.7 doesn't need that configuration anymore.
4 changes: 2 additions & 2 deletions packages/googlerecaptcha/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ page so it isn't initialized on all pages.

## Configuration

1. Configure the following ([configuration values](./Config.graphqls)) in your
graphcommerce.config.js
1. Only Magento 2.4.6 and earlier, 2.4.7: Configure the following
([configuration values](./Config.graphqls)) in your graphcommerce.config.js

2. Add `X-Recaptcha` header to your `.meshrc.yml` if it isn't there.
[example](../../examples/magento-graphcms/.meshrc.yml)
Expand Down
5 changes: 5 additions & 0 deletions packages/googlerecaptcha/graphql/RecaptchaV3Config.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
query RecaptchaV3Config {
recaptchaV3Config {
website_key
}
}
8 changes: 0 additions & 8 deletions packages/googlerecaptcha/hooks/useGoogleRecaptchaSiteKey.ts

This file was deleted.

9 changes: 6 additions & 3 deletions packages/googlerecaptcha/link/recaptchaLink.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { GraphQLRequest } from '@graphcommerce/graphql'
import { setContext } from '@graphcommerce/graphql/apollo'
import { Kind, OperationTypeNode } from 'graphql'
import { RecaptchaV3ConfigDocument } from '../graphql/RecaptchaV3Config.gql'

const isMutation = (operation: GraphQLRequest) =>
operation.query.definitions.some(
Expand All @@ -11,8 +12,10 @@ const isMutation = (operation: GraphQLRequest) =>

/** Apollo link that adds the Google reCAPTCHA token to the request context. */
export const recaptchaLink = setContext(async (operation, context) => {
const recaptchaKey = import.meta.graphCommerce.googleRecaptchaKey
if (!recaptchaKey || !globalThis.grecaptcha || !isMutation(operation)) return context
const siteKey = context.cache?.readQuery({ query: RecaptchaV3ConfigDocument })?.recaptchaV3Config
?.website_key

if (!siteKey || !globalThis.grecaptcha || !isMutation(operation)) return context

await new Promise<void>((resolve) => {
globalThis.grecaptcha?.ready(resolve)
Expand All @@ -23,7 +26,7 @@ export const recaptchaLink = setContext(async (operation, context) => {
while (failure < 5) {
try {
// eslint-disable-next-line no-await-in-loop
token = await globalThis.grecaptcha.execute(recaptchaKey, { action: 'submit' })
token = await globalThis.grecaptcha.execute(siteKey, { action: 'submit' })
break
} catch {
failure++
Expand Down
31 changes: 31 additions & 0 deletions packages/googlerecaptcha/mesh/resolvers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { storefrontFromContext, type Resolvers } from '@graphcommerce/graphql-mesh'

export const resolvers: Resolvers = {
Query: {
recaptchaV3Config: (root, args, context, info) => {
const config = storefrontFromContext(context)
const key = config?.googleRecaptchaKey ?? import.meta.graphCommerce.googleRecaptchaKey

if (!key) return null
return {
website_key: key,
minimum_score: 0,
badge_position: '',
failure_message: '',
is_enabled: !!key,
forms: [
'PLACE_ORDER',
'CONTACT',
'CUSTOMER_LOGIN',
'CUSTOMER_FORGOT_PASSWORD',
'CUSTOMER_CREATE',
'CUSTOMER_EDIT',
'NEWSLETTER',
'PRODUCT_REVIEW',
'SENDFRIEND',
'BRAINTREE',
],
}
},
},
}
4 changes: 4 additions & 0 deletions packages/googlerecaptcha/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"peerDependencies": {
"@graphcommerce/eslint-config-pwa": "^9.1.0-canary.16",
"@graphcommerce/graphql": "^9.1.0-canary.16",
"@graphcommerce/graphql-mesh": "^9.1.0-canary.16",
"@graphcommerce/next-ui": "^9.1.0-canary.16",
"@graphcommerce/prettier-config-pwa": "^9.1.0-canary.16",
"@graphcommerce/typescript-config-pwa": "^9.1.0-canary.16",
Expand All @@ -25,5 +26,8 @@
"next": "*",
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"dependencies": {
"@types/react-recaptcha-v3": "^1.1.5"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { useGoogleRecaptcha } from '../hooks/useGoogleRecaptcha'
export const config: PluginConfig = {
type: 'component',
module: '@graphcommerce/ecommerce-ui',
ifConfig: 'googleRecaptchaKey',
}

export function ApolloErrorAlert(props: PluginProps<ApolloErrorAlertProps>) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { useGoogleRecaptcha } from '../hooks/useGoogleRecaptcha'
export const config: PluginConfig = {
type: 'component',
module: '@graphcommerce/ecommerce-ui',
ifConfig: 'googleRecaptchaKey',
}

export function ApolloErrorFullPage(props: PluginProps<ApolloErrorFullPageProps>) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { useGoogleRecaptcha } from '../hooks/useGoogleRecaptcha'
export const config: PluginConfig = {
type: 'component',
module: '@graphcommerce/ecommerce-ui',
ifConfig: 'googleRecaptchaKey',
}

export function ApolloErrorSnackbar(props: PluginProps<ApolloErrorSnackbarProps>) {
Expand Down
39 changes: 23 additions & 16 deletions packages/googlerecaptcha/plugins/GrecaptchaGraphQLProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,37 @@
import type { GraphQLProviderProps } from '@graphcommerce/graphql'
import { useQuery, type GraphQLProviderProps } from '@graphcommerce/graphql'
import type { PluginConfig, PluginProps } from '@graphcommerce/next-config'
import { GlobalStyles } from '@mui/material'
import Script from 'next/script'
import { useMemo, useState } from 'react'
import type { RecaptchaContext } from '../context/recaptchaContext'
import { recaptchaContext } from '../context/recaptchaContext'
import { useGoogleRecaptchaSiteKey } from '../hooks/useGoogleRecaptchaSiteKey'
import { RecaptchaV3ConfigDocument } from '../graphql/RecaptchaV3Config.gql'
import { recaptchaLink } from '../link/recaptchaLink'

export const config: PluginConfig = {
type: 'component',
module: '@graphcommerce/graphql',
ifConfig: 'googleRecaptchaKey',
}

function ReCaptchaScript() {
const siteKey = useQuery(RecaptchaV3ConfigDocument).data?.recaptchaV3Config?.website_key

console.log(siteKey)

if (!siteKey) return null
return (
<Script
key='@graphcommerce/grecaptcha'
strategy='lazyOnload'
src={`https://www.google.com/recaptcha/api.js?render=${siteKey}`}
/>
)
}

export function GraphQLProvider(props: PluginProps<GraphQLProviderProps>) {
const { Prev, links = [], ...prev } = props
const { Prev, links = [], children, ...prev } = props

const [enabled, setEnabled] = useState(false)
const siteKey = useGoogleRecaptchaSiteKey()

const context: RecaptchaContext = useMemo(
() => ({ enabled, enable: () => setEnabled(true) }),
Expand All @@ -27,21 +40,15 @@ export function GraphQLProvider(props: PluginProps<GraphQLProviderProps>) {

return (
<recaptchaContext.Provider value={context}>
{enabled && (
<>
<Script
key='@graphcommerce/grecaptcha'
strategy='lazyOnload'
src={`https://www.google.com/recaptcha/api.js?render=${siteKey}`}
/>
<GlobalStyles styles={{ '.grecaptcha-badge': { visibility: 'hidden' } }} />
</>
)}
<Prev
{...prev}
// Add recaptcha headers to the request.
links={[...links, recaptchaLink]}
/>
>
{children}
{enabled && <ReCaptchaScript />}
{enabled && <GlobalStyles styles={{ '.grecaptcha-badge': { visibility: 'hidden' } }} />}
</Prev>
</recaptchaContext.Provider>
)
}
26 changes: 26 additions & 0 deletions packages/googlerecaptcha/plugins/meshRecaptcha.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import type { meshConfig as meshConfigBase } from '@graphcommerce/graphql-mesh/meshConfig'
import type { FunctionPlugin, PluginConfig } from '@graphcommerce/next-config'

export const config: PluginConfig = {
module: '@graphcommerce/graphql-mesh/meshConfig',
type: 'function',
}

export const meshConfig: FunctionPlugin<typeof meshConfigBase> = (
prev,
baseConfig,
graphCommerceConfig,
) => {
if (graphCommerceConfig.magentoVersion <= 247 || !graphCommerceConfig.googleRecaptchaKey)
return prev(baseConfig, graphCommerceConfig)
return prev(
{
...baseConfig,
additionalResolvers: [
...(baseConfig.additionalResolvers ?? []),
'@graphcommerce/googlerecaptcha/mesh/resolvers.ts',
],
},
graphCommerceConfig,
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export const persistenceMapper = (data: string): Promise<string> => {
'ROOT_QUERY.guestOrder',
'ROOT_QUERY.__type*',
'*Product:{"uid":"*"}.crosssell_products',
'ROOT_QUERY.recaptchaV3Config',
])

const cache = new InMemoryCache({})
Expand Down

0 comments on commit a261149

Please sign in to comment.