Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/.vuepress/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ module.exports = {
['/guide/graphql-get', 'Use GET for GraphQL Queries'],
['/guide/configuration', 'Configuration'],
['/guide/override-queries', 'Override queries'],
['/guide/recaptcha', 'ReCaptcha'],
['/guide/testing', 'Testing']
]
},
Expand Down
2 changes: 1 addition & 1 deletion docs/api-reference/magento-api.createproductreview.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@
<b>Signature:</b>

```typescript
_default: (context: Context, input: CreateProductReviewMutationVariables, customQuery?: CustomQuery) => Promise<FetchResult<CreateProductReviewMutation>>
_default: (context: Context, input: CreateProductReviewInput, customQuery?: CustomQuery) => Promise<FetchResult<CreateProductReviewMutation>>
```
52 changes: 52 additions & 0 deletions docs/guide/recaptcha.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# reCaptcha

You can activate the reCaptchta feature using this Guidelines.

## Activate reCaptcha module

Uncomment the line below in the `nuxt.config.js` file to activate the module.

```js
...
'@vue-storefront/middleware/nuxt',
'@nuxtjs/html-validator',
// '@nuxtjs/recaptcha',
],
recaptcha: {
...

```

## Configure the reCaptcha

On the `config` folder update the config file (`dev.json` for example) with your configurations.

```json5
{
...
"recaptchaEnabled": "{YOUR_RECAPTCHA_ENABLED}", // true or false, default value is false
"recaptchaHideBadge": "{YOUR_RECAPTCHA_HIDE_BADGE}", // true or false, default value is false
"recaptchaSize": "{YOUR_RECAPTCHA_SIZE}", // Size: 'compact', 'normal', 'invisible' (v2), default value is 'invisible'
"recaptchaSiteKey": "{YOUR_RECAPTCHA_SITE_KEY}", // Site key for requests, default value is ''
"recaptchaSecretkey": "{YOUR_RECAPTCHA_SECRET_KEY}", // Secret key for requests, default value is ''
"recaptchaVersion": "{YOUR_RECAPTCHA_VERSION}", // Version 2 or 3, default value is 3
"recaptchaMinScore": "{YOUR_RECAPTCHA_MIN_SCORE}" // The min score used for v3, default value is 0.5
...
}
```

### Sample configuration

```json5
{
...
"recaptchaEnabled": true,
"recaptchaHideBadge": false,
"recaptchaSize": "invisible",
"recaptchaSiteKey": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
"recaptchaSecretkey": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
"recaptchaVersion": 3,
"recaptchaMinScore": 0.5
...
}
```
2 changes: 1 addition & 1 deletion packages/api-client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,4 +57,4 @@
"engines": {
"node": ">=16.x"
}
}
}
22 changes: 21 additions & 1 deletion packages/api-client/src/api/createCustomer/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { FetchResult } from '@apollo/client/core';
import { CustomQuery } from '@vue-storefront/core';
import { GraphQLError } from 'graphql';
import recaptchaValidator from '../../helpers/recaptcha/recaptchaValidator';
import {
CreateCustomerMutation,
CreateCustomerMutationVariables,
Expand All @@ -14,12 +16,30 @@ export default async (
customQuery: CustomQuery = { createCustomer: 'createCustomer' },
): Promise<FetchResult<CreateCustomerMutation>> => {
try {
const {
recaptchaToken, ...variables
} = input;

if (context.config.recaptcha.isEnabled) {
/**
* recaptcha token verification
*/
const response = await recaptchaValidator(context, recaptchaToken);

if (!response.success) {
return {
errors: [new GraphQLError('Error during reCaptcha verification. Please try again.')],
data: null,
};
}
}

const { createCustomer: createCustomerGQL } = context.extendQuery(
customQuery,
{
createCustomer: {
query: createCustomer,
variables: { input },
variables: { input: variables },
},
},
);
Expand Down
28 changes: 24 additions & 4 deletions packages/api-client/src/api/createProductReview/index.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,45 @@
import { FetchResult } from '@apollo/client/core';
import { CustomQuery } from '@vue-storefront/core';
import { CreateProductReviewMutation, CreateProductReviewMutationVariables } from '../../types/GraphQL';
import { GraphQLError } from 'graphql';
import { CreateProductReviewMutation, CreateProductReviewInput } from '../../types/GraphQL';
import createProductReview from './createProductReview';
import { Context } from '../../types/context';
import recaptchaValidator from '../../helpers/recaptcha/recaptchaValidator';

export default async (
context: Context,
input: CreateProductReviewMutationVariables,
input: CreateProductReviewInput,
customQuery: CustomQuery = { createProductReview: 'createProductReview' },
): Promise<FetchResult<CreateProductReviewMutation>> => {
const {
recaptchaToken, ...variables
} = input;

if (context.config.recaptcha.isEnabled) {
/**
* recaptcha token verification
*/
const response = await recaptchaValidator(context, recaptchaToken);

if (!response.success) {
return {
errors: [new GraphQLError('Error during reCaptcha verification. Please try again.')],
data: null,
};
}
}

const { createProductReview: createProductReviewGQL } = context.extendQuery(
customQuery,
{
createProductReview: {
query: createProductReview,
variables: { input },
variables: { input: variables },
},
},
);

return context.client.mutate<CreateProductReviewMutation, { input: CreateProductReviewMutationVariables }>({
return context.client.mutate<CreateProductReviewMutation, { input: CreateProductReviewInput }>({
mutation: createProductReviewGQL.query,
variables: createProductReviewGQL.variables,
});
Expand Down
17 changes: 17 additions & 0 deletions packages/api-client/src/api/generateCustomerToken/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { FetchResult } from '@apollo/client/core';
import { CustomQuery } from '@vue-storefront/core';
import { GraphQLError } from 'graphql';
import recaptchaValidator from '../../helpers/recaptcha/recaptchaValidator';
import generateCustomerToken from './generateCustomerToken';
import {
GenerateCustomerTokenMutation,
Expand All @@ -12,10 +14,25 @@ export default async (
params: {
email: string;
password: string;
recaptchaToken: string;
},
customQuery: CustomQuery = { generateCustomerToken: 'generateCustomerToken' },
): Promise<FetchResult<GenerateCustomerTokenMutation>> => {
try {
if (context.config.recaptcha.isEnabled) {
/**
* recaptcha token verification
*/
const response = await recaptchaValidator(context, params.recaptchaToken);

if (!response.success) {
return {
errors: [new GraphQLError('Error during reCaptcha verification. Please try again.')],
data: null,
};
}
}

const { generateCustomerToken: generateCustomerTokenGQL } = context.extendQuery(
customQuery,
{
Expand Down
22 changes: 21 additions & 1 deletion packages/api-client/src/api/requestPasswordResetEmail/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { FetchResult } from '@apollo/client/core';
import { CustomQuery, Logger } from '@vue-storefront/core';
import { GraphQLError } from 'graphql';
import recaptchaValidator from '../../helpers/recaptcha/recaptchaValidator';
import requestPasswordResetEmailMutation from './requestPasswordResetEmail';
import {
RequestPasswordResetEmailMutation,
Expand All @@ -12,10 +14,28 @@ export default async (
input: RequestPasswordResetEmailMutationVariables,
customQuery: CustomQuery = { requestPasswordResetEmail: 'requestPasswordResetEmail' },
): Promise<FetchResult<RequestPasswordResetEmailMutation>> => {
const {
recaptchaToken, ...variables
} = input;

if (context.config.recaptcha.isEnabled) {
/**
* recaptcha token verification
*/
const response = await recaptchaValidator(context, recaptchaToken);

if (!response.success) {
return {
errors: [new GraphQLError('Error during reCaptcha verification. Please try again.')],
data: null,
};
}
}

const { requestPasswordResetEmail } = context.extendQuery(customQuery, {
requestPasswordResetEmail: {
query: requestPasswordResetEmailMutation,
variables: { ...input },
variables: { ...variables },
},
});

Expand Down
22 changes: 21 additions & 1 deletion packages/api-client/src/api/resetPassword/index.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,42 @@
import { FetchResult } from '@apollo/client/core';
import { CustomQuery, Logger } from '@vue-storefront/core';
import gql from 'graphql-tag';
import { GraphQLError } from 'graphql';
import resetPasswordMutation from './resetPassword';
import {
ResetPasswordMutation,
ResetPasswordMutationVariables,
} from '../../types/GraphQL';
import { Context } from '../../types/context';
import recaptchaValidator from '../../helpers/recaptcha/recaptchaValidator';

export default async (
context: Context,
input: ResetPasswordMutationVariables,
customQuery: CustomQuery = { resetPassword: 'resetPassword' },
): Promise<FetchResult<ResetPasswordMutation>> => {
const {
recaptchaToken, ...variables
} = input;

if (context.config.recaptcha.isEnabled) {
/**
* recaptcha token verification
*/
const response = await recaptchaValidator(context, recaptchaToken);

if (!response.success) {
return {
errors: [new GraphQLError('Error during reCaptcha verification. Please try again.')],
data: null,
};
}
}

const { resetPassword } = context.extendQuery(customQuery, {
resetPassword: {
query: resetPasswordMutation,
variables: { ...input },
variables: { ...variables },
},
});

Expand Down
26 changes: 26 additions & 0 deletions packages/api-client/src/helpers/recaptcha/recaptchaValidator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { Context } from '../../types/context';

interface RecaptchaApiResponse {
success: boolean,
challenge_ts: string,
hostname: string,
'error-codes'?: [any],
score?: number
}

export default async (
context: Context,
token: string,
): Promise<RecaptchaApiResponse> => {
try {
const { secretkey } = context.config.recaptcha;
const url = `https://www.google.com/recaptcha/api/siteverify?secret=${secretkey}&response=${token}`;

const result = await fetch(url);
const response = await result.json();

return response;
} catch (error) {
throw error.message || error;
}
};
2 changes: 1 addition & 1 deletion packages/api-client/src/types/API.ts
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,7 @@ export interface MagentoApiMethods {
): Promise<ExecutionResult<DeleteCustomerAddressMutation>>;

generateCustomerToken(
params: { email: string, password: string },
params: { email: string, password: string, recaptchaToken: string },
customQuery?: CustomQuery
): Promise<FetchResult<GenerateCustomerTokenMutation>>;

Expand Down
7 changes: 7 additions & 0 deletions packages/api-client/src/types/GraphQL.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2007,6 +2007,8 @@ export interface CreateProductReviewInput {
summary: Scalars['String'];
/** The review text. */
text: Scalars['String'];
/** The reCaptcha Token. */
recaptchaToken?: Scalars['String'];
}

export interface CreateProductReviewOutput {
Expand Down Expand Up @@ -2532,6 +2534,8 @@ export interface CustomerCreateInput {
suffix?: InputMaybe<Scalars['String']>;
/** The customer's Tax/VAT number (for corporate customers) */
taxvat?: InputMaybe<Scalars['String']>;
/** The reCaptcha Token */
recaptchaToken?: InputMaybe<Scalars['String']>;
}

export interface CustomerDownloadableProduct {
Expand Down Expand Up @@ -7312,6 +7316,7 @@ export type RemoveProductsFromWishlistMutation = { removeProductsFromWishlist?:

export type RequestPasswordResetEmailMutationVariables = Exact<{
email: Scalars['String'];
recaptchaToken?: Scalars['String'];
}>;


Expand All @@ -7321,6 +7326,7 @@ export type ResetPasswordMutationVariables = Exact<{
email: Scalars['String'];
newPassword: Scalars['String'];
resetPasswordToken: Scalars['String'];
recaptchaToken?: Scalars['String'];
}>;


Expand Down Expand Up @@ -7403,6 +7409,7 @@ export type UpdateCustomerAddressMutation = { updateCustomerAddress?: { id?: num
export type UpdateCustomerEmailMutationVariables = Exact<{
email: Scalars['String'];
password: Scalars['String'];
recaptchaToken?: Scalars['String'];
}>;


Expand Down
9 changes: 9 additions & 0 deletions packages/api-client/src/types/setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,12 +71,21 @@ export interface ClientConfig {
state: ConfigState;
}

export interface RecaptchaConfig {
isEnabled: boolean,
sitekey: string,
secretkey: string,
version: number,
score: number,
}

export interface Config<T = any> extends ClientConfig {
client?: ApolloClient<T>;
storage: Storage;
customOptions?: ApolloClientOptions<any>;
customApolloHttpLinkOptions?: HttpOptions;
overrides: MagentoApiMethods;
recaptcha: RecaptchaConfig;
}

export interface ClientInstance extends ApolloClient<any> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const factoryParams: UseForgotPasswordFactoryParams<any> = {
resetPassword: async (context: Context, params) => {
Logger.debug('[Magento]: Reset user password', { params });

const { data } = await context.$magento.api.requestPasswordResetEmail({ email: params.email });
const { data } = await context.$magento.api.requestPasswordResetEmail({ email: params.email, recaptchaToken: params.recaptchaToken });

Logger.debug('[Result]:', { data });

Expand All @@ -24,6 +24,7 @@ const factoryParams: UseForgotPasswordFactoryParams<any> = {
email: params.email,
newPassword: params.newPassword,
resetPasswordToken: params.tokenValue,
recaptchaToken: params.recaptchaToken,
});

Logger.debug('[Result]:', { data });
Expand Down
Loading