Contact Form 7 Recaptcha 3 headless site - wordpress

I am building a wordpress headless site (frontend using Nuxt.js) and am trying to use recaptcha v3 in contact form 7 . I have already setup the integration using the built in cf7 integration.
But, the problem is contact form 7 is marking all of my emails as spam. Therefore I installed Flamingo plugin just to see the error log. Apparently the error log says reCAPTCHA response token is empty. Which makes sense because the recaptcha is setup in WordPress while since my frontend is decoupled, it doesn't get the token.
I have read about using vue-recaptcha but that means setting up a new recaptcha that is entirely separate from the recaptcha I setup in WordPress. I can't find a way to link the recaptcha for WordPress and my frontend together.
Any advice would be very much appreciated, thanks!
A post that I found similar to mine:
https://www.reddit.com/r/Wordpress/comments/o48hd1/contact_form_7_plugin_endpoint_for_recaptcha/
, but no clear answers.
I am successful in implementing recaptcha at the frontend right now but I have no idea how to make use of the recaptcha token from WordPress for the backend and frontend to work together. The recaptcha certainly cannot be only at the frontend because else people would be able to use Postman to spam my endpoint. This is how I did it:
async function verifyCaptcha() {
try {
// #ts-ignore
recaptchaToken.value = await context.$recaptcha.execute();
const response = await axios.post(
`/captcha-api/siteverify?secret=${process.env.SECRET_KEY}&response=${recaptchaToken.value}`
);
// console.log(response)
return response;
} catch (error) {
return error;
}
}
async function onSubmit(e: any) {
const recaptchaResponse = await verifyCaptcha();
// Display error message if verification was not successful
if (!recaptchaResponse.data.success) {
// #ts-ignore
context.$recaptcha.reset()
return;
}
// If verification was successful, send the message
await submit(e);
}

I have found the solution. We simply need to pass some specific recaptcha 3 responses required by contact form 7 back to contact form 7.
const token = await context.$recaptcha.execute('login')
// console.log('ReCaptcha token:', token)
const emailBody = {
"your-name" : form.name,
"email" : form.email,
"enquiry" : form.enquiry,
"message" : form.message,
//recaptcha responses to pass back to CF7
"_wpcf7_recaptcha_response" : token,
"wpcf7_recaptcha_response" : token,
"recaptcha_response" : token,
"recaptcha" : token,
"token" : token
}
const body = new FormData()
for (const field in emailBody) {
// #ts-ignore
body.append(field, emailBody[field])
}
const headers = {
'Content-Type': 'multipart/form-data',
}
const response = await axios.post('https://yourdomain.com/wp-json/contact-form-7/v1/contact-forms/{id}/feedback', body, { headers: headers})
// Use responseData to check its status to do some logic
const responseData = response.data
onMounted(async () => {
await context.$recaptcha.init()
})
onBeforeUnmount(() => {
context.$recaptcha.destroy()
})

Related

Trying to implement shopify webhooks but getting 'InternalServerError: stream is not readable'

I'm building an app for shopify and need to add the GDPR webhooks. My back end is handled using next.js and I'm writing a webhook handler to verify them. The docs havent been very helpful because they dont show how to do it with node. This is my verification function.
export function verifiedShopifyWebhookHandler(
next: (req, res, body) => Promise
): NextApiHandler {
return async (req, res) => {
const hmacHeader = req.headers['x-shopify-hmac-sha256'];
const rawBody = await getRawBody(req);
const digest = crypto.createHmac('sha256', process.env.SHOPIFY_API_SECRET).update(rawBody).digest('base64');
if (digest === hmacHeader) {
return next(req, res, rawBody);
}
const webhookId = req.headers['x-shopify-webhook-id'];
return res.status(401).end();
};
}
But I get this Error: error - InternalServerError: stream is not readable
I think it has to do with now Next.js parses the incoming requests before they are sent to my api. Any ideas?
I discovered the answer. Next.js was pre parsing the body in the context which made it so that I couldn't use the raw body parser to parse it. By setting this:
export const config = {
api: {
bodyParser: false
}
};
above the api function in the api file it prevented next from parsing it and causing the issue. I found the answer because people had the same issue integrating swipe and using the bodyParser.

How to build a custom Email action handler in Flutter for Firebase Authentication

I am very new to web development and i am building a website with Flutter.
I am aware of the Routing / Navigator system.
I would like to know how can i create such page in Flutter to handle Firebase email actions :
https://example.com/usermgmt?mode=resetPassword&oobCode=ABC123&apiKey=AIzaSy...&lang=fr
I am following this documentation.
It is stated here that:
Firebase adds several query parameters to your action handler URL when it generates user management emails.
However i don't understand if it's up to me to build the full url in my Flutter Routes or simply provide the endpoint /usermgmt and Firebase is automatically adding the params ?
I have tried to build a page in my website that addresses : https://example.com/usermgmt and it's working, the page exists hosted in my website.
Now if i add the Firebase params, it returns a 404 not found.
What should i do ?
EDIT : I have made the UI working, it doesn't return a 404 not found anymore.
I have done this for those interrested :
onGenerateRoute: (settings) {
Widget? authPage;
if (settings.name != null) {
var uriData = Uri.parse(settings.name!);
//uriData.path will be the path and uriData.queryParameters will hold query-params values
print("Param in query : " + uriData.queryParameters.toString());
switch (uriData.path) {
case '/auth':
authPage = AuthHandlerPage();
break;
}
}
if (authPage != null) {
return MaterialPageRoute(
builder: (BuildContext context) => authPage!);
}
},
I simply need to handle the links according to the params now.
To complete the email action, you need to fetch the oobCode from URL parameters and use applyActionCode method (checkActionCode can be used before applyActionCode to make sure the oobCode is valid, usually not required as applyActionCode will throw an error on getting an invalid code as well):
var actionCode = "" // Get from URL
try {
await auth.checkActionCode(actionCode);
await auth.applyActionCode(actionCode);
// If successful, reload the user:
auth.currentUser.reload();
} on FirebaseAuthException catch (e) {
if (e.code == 'invalid-action-code') {
print('The code is invalid.');
}
}
If you are using dynamic links, you can get it this way:
//Get actionCode from the dynamicLink
final Uri deepLink = dynamicLink?.link;
var actionCode = deepLink.queryParameters['oobCode'];
You can refer the documentation for more information.
Alternatively, you can also use the Firebase REST API to verify the email links if you need to verify the oobCode from a Cloud function or server.

Posting Comments to WordPress with WPAPI

I'm using Netlify event-triggered webhooks to call a script that's designed to post a new comment to the WordPress API. I'm trying to implement wpapi to make the POST request but not sure if I'm hooked up properly.
exports.handler = async (event, context, callback) => {
let body = JSON.parse(event.body).payload
if (body.form_name == 'comment-form') {
// I assume I have to authenticate here
var wp = new WPAPI({
endpoint: 'https://example.com/wp-json',
username: 'username',
password: '123456'
});
...
I then form the data to pass in... From what I can tell from the WordPress REST API, I can pass in a name, comment, and a post id. I'm not sure if I'm missing a parameter as I can't find any documentation about required params.
// url encode - not sure if this is required
let comment = {
author_name: encodeURI(author_name),
author_comment: encodeURI(author_name),
post: body.data.postId
}
I then try calling wp.comments().create() passing in the object and setting up a callback:
wp.comments().create(comment, function(args) {
console.log(args) }
).then(function( response ) {
console.log( response );
}).catch(function (err) {
console.log(err);
});
I am using this function in a Gatsby project and am utitlizing gatsby-source-wordpress to pull data from a WordPress site, if that makes any difference.
When I run this function in Netlify, in the function log, there is no response or error message.
Thanks

Sharing cookies between sites on the same domain - Headless / Decoupled CMS

The context of my challenge
I'm building a headless WordPress / WooCommerce Store.
If you're not familiar with the concept of a headless CMS, I pull the store's content (Products, and their images, text) over the WordPress / WooCommerce REST API. This way, I have the benefit of a CMS dashboard for my client whilst I get to develop in a modern language / library, in my case - React!
If possible I'd like to keep the checkout in WordPress/WooCommerce/PHP. Depending on the project I apply this code / boilerplate to I suspect that I'll have to chop and change payment gateways, and making this secure and PCI compliant will be much easier in PHP/WordPress - there's a whole host of plugins for this.
This means the entire store / front-end will live in React, with the exception of the cart in which the user will be redirected to the CMS front-end (WordPress, PHP) when they wish to complete their order.
The Challenge
This makes managing cookies for the session rather unintuitive and unorthodox. When the user is redirected from the store (React site) to the checkout (WooCommerce/PHP site) the cart session has to persist between the two sites.
Additionally, requests to WooCommerce are routed through the Node/Express server which my React client sits ons. I do this because I want to keep the WordPress address obscured, and so I can apply GraphQL to clean up my requests & responses. This issue is that in this process, the cookies are lost because my client and my CMS are communicating through a middle man (my Node server) - I require extra logic to manually manage my cookies.
The Code
When I attempt to add something to a cart, from an action creator (I'm using Redux for state management) I hit the api corresponding endpoint on my Node/Express server:
export const addToCart = (productId, quantity) => async (dispatch) => {
dispatch({type: ADD_TO_CART});
try {
// Manually append cookies somewhere here
const payload = await axios.get(`${ROOT_API}/addtocart?productId=${productId}&quantity=${quantity}`, {
withCredentials: true
});
dispatch(addToSuccess(payload));
} catch (error) {
dispatch(addToCartFailure(error));
}
};
Then on the Node/Express server I make my request to WooCommerce:
app.get('/api/addtocart', async (req, res) => {
try {
// Manually retrieve & append cookies somewhere here
const productId = parseInt(req.query.productId);
const quantity = parseInt(req.query.quantity);
const response = await axios.post(`${WP_API}/wc/v2/cart/add`, {
product_id: productId,
quantity
});
return res.json(response.data);
} catch (error) {
// Handle error
return res.json(error);
}
});
With the clues given by #TarunLalwani (thanks a million!) in his comments, I've managed to formulate a solution.
Cookie Domain Setting
Since I was working with two seperate sites, in order for this to work I had to ensure they were both on the same domain, and that the domain was set in all cookies. This ensured cookies were included in my requests between the Node / Express server (sitting on eg. somedomain.com) and the WooCommerce CMS (sitting on eg. wp.somedomain.com), rather than being exclusive to the wp.somedomain subdomain. This was achieved by setting define( 'COOKIE_DOMAIN', 'somedomain.com' ); in my wp-config.php on the CMS.
Manually Getting and Setting Cookies
My code needed significant additional logic in order for cookies to be included whilst requests were routed through my Node / Express server through the client.
In React I had to check if the cookie existed, and if it did I had to send it through in the header of my GET request to the Node / Express server.
import Cookies from 'js-cookie';
export const getSessionData = () => {
// WooCommerce session cookies are appended with a random hash.
// Here I am tracking down the key of the session cookie.
const cookies = Cookies.get();
if (cookies) {
const cookieKeys = Object.keys(cookies);
for (const key of cookieKeys) {
if (key.includes('wp_woocommerce_session_')) {
return `${key}=${Cookies.get(key)};`;
}
}
}
return false;
};
export const addToCart = (productId, quantity) => async (dispatch) => {
dispatch({type: ADD_TO_CART});
const sessionData = getSessionData();
const config = {};
if (sessionData) config['session-data'] = sessionData;
console.log('config', config);
try {
const payload = await axios.get(`${ROOT_API}/addtocart?productId=${productId}&quantity=${quantity}`, {
withCredentials: true,
headers: config
});
dispatch(addToSuccess(payload));
} catch (error) {
dispatch(addToCartFailure(error));
}
};
On the Node / Express Server I had to check if I had included a cookie (saved in req.headers with the key session-data - it was illegal to use Cookie as a key here) from the client, and if I did, append that to the header of my request going to my CMS.
If I didn't find an appended cookie, it meant this was the first request in the session, so I had to manually grab the cookie from the response I got back from the CMS and save it to the client (setCookieFunc).
app.get('/api/addtocart', async (req, res) => {
try {
const productId = parseInt(req.query.productId);
const quantity = parseInt(req.query.quantity);
const sessionData = req.headers['session-data'];
const headers = {};
if (sessionData) headers.Cookie = sessionData;
const response = await axios.post(`${WP_API}/wc/v2/cart/add`, {
product_id: productId,
quantity
}, { headers });
if (!sessionData) {
const cookies = response.headers['set-cookie'];
const setCookieFunc = (cookie) => {
const [cookieKeyValue, ...cookieOptionsArr] = cookie.split('; ');
const cookieKey = cookieKeyValue.split('=')[0];
const cookieValue = decodeURIComponent(cookieKeyValue.split('=')[1]);
const cookieOptions = { };
cookieOptionsArr.forEach(option => (cookieOptions[option.split('=')[0]] = option.split('=')[1]));
if (cookieOptions.expires) {
const expires = new Date(cookieOptions.expires);
cookieOptions.expires = expires;
}
res.cookie(cookieKey, cookieValue, cookieOptions);
};
cookies.map(cookie => setCookieFunc(cookie));
}
return res.json(response.data);
} catch (error) {
// Handle error
return res.json(error);
}
});
I'm not sure if this is the most elegant solution to the problem, but it worked for me.
Notes
I used the js-cookie library for interacting with cookies on my React client.
Gotchas
If you're trying to make this work in your development environment (using localhost) there's some extra work to be done. See Cookies on localhost with explicit domain

MailChimp API 3.0 Subscribe

I am having trouble sorting out the new MailChimp API (V3.0). It does not seem like there is a way to call a subscribe method. It seems like I have to use their Sign Up Form. Am I correct?
If by "subscribe" you mean that your application will add someone to a mailing list, you may want to take a look at the List Members Collection portion of their documentation.
http://kb.mailchimp.com/api/resources/lists/members/lists-members-collection
Adding/editing a subscriber via MailChimp v3.0 REST API.
// node/javascript specific, but pretty basic PUT request to MailChimp API endpoint
// dependencies (npm)
var request = require('request'),
url = require('url'),
crypto = require('crypto');
// variables
var datacenter = "yourMailChimpDatacenter", // something like 'us11' (after '-' in api key)
listId = "yourMailChimpListId",
email = "subscriberEmailAddress",
apiKey = "yourMailChimpApiKey";
// mailchimp options
var options = {
url: url.parse('https://'+datacenter+'.api.mailchimp.com/3.0/lists/'+listId+'/members/'+crypto.createHash('md5').update(email).digest('hex')),
headers: {
'Authorization': 'authId '+apiKey // any string works for auth id
},
json: true,
body: {
email_address: email,
status_if_new: 'pending', // pending if new subscriber -> sends 'confirm your subscription' email
status: 'subscribed',
merge_fields: {
FNAME: "subscriberFirstName",
LNAME: "subscriberLastName"
},
interests: {
MailChimpListGroupId: true // if you're using groups within your list
}
}
};
// perform update
request.put(options, function(err, response, body) {
if (err) {
// handle error
} else {
console.log('subscriber added to mailchimp list');
}
});

Resources