I'm trying to setup a lambda function to authenticate, validate, and handle sending of a contact form..
I'm new to lambda functions so my code may be flawed, but no matter what I do I can't seem to modify the response that is sent back to my vue.js app.
I keep getting "response undefined" ...
Can some explain what I'm doing wrong, and maybe a better way to change the data returned based on what's going on in my function?
const axios = require('axios');
const FormData = require('form-data');
const AUTH_API_ENDPOINT = 'https://www.mywebsite.com/wp-json/jwt-auth/v1/token/'
const FORM_API_ENDPOINT = 'https://www.mywebsite.com/wp-json/contact-form-7/v1/contact-forms/1217/feedback'
const captchaThreshhold = 0.5
exports.handler = async function(event, context) {
const eventBody = JSON.parse(event.body)
const captchaSecret = process.env.CAPTCHA_SECRET
const captchaToken = eventBody.token
const stringFormData = eventBody.formData
let parsedFormData = JSON.parse(stringFormData)
let formData = new FormData()
var response;
//build a new FormData object
for ( var key in parsedFormData ) {
formData.append(key, parsedFormData[key])
}
// first step is to validate the captcha..
//let response_captcha
try {
response = await axios.post(`https://www.google.com/recaptcha/api/siteverify?secret=${captchaSecret}&response=${captchaToken}`,{})
} catch(err) {
return {
statusCode: 200,
body: JSON.stringify({
status: 'error',
message: 'Opps! The server tried to run an AI algorithm to determine if you are a spam robot, but the server did not respond properly so we are unable to continue contact form security verification... Please try again later or contact via phone instead. We appologize for the inconvenience.',
error: err.message
})
}
}
// if we're over the threshold we continue and get a fresh JWT
if (response.data.score >= captchaThreshhold) {
// let response_jwt
try {
response = await axios.post(AUTH_API_ENDPOINT,{
username: process.env.AUTH_USERNAME,
password: process.env.AUTH_PASSWORD,
}).then(res => {
// JWT token returned something.. lets try to submit our form data with authentication code..
axios.post(FORM_API_ENDPOINT, formData, {
headers: {
'Authorization': `Bearer ${res.data.token}`,
'Content-Type': 'multipart/form-data; charset="utf-8"',
...formData.getHeaders()
}
})
.then( res => {
console.log('>> response came back from the Form endpoint : ',res.data.status, res.data.message)
return {
statusCode: 200,
body: {
status: res.data.status,
message: res.data.message
}
}
})
.catch( err => {
console.log('>> something went wrong while trying to submit formData to form endpoint ',err.response.data);
return {
statusCode: 200,
body: JSON.stringify({
status: 'error',
error: err.message,
message: 'Yikes! The form data was processed and sent to our email server but there was no response back. Our developer will look into this shortly. In the meantime, please try again later or contact via phone. We appologize for the inconvenience.'
})
}
})
}).catch( err => {
console.log('>> something went wrong while trying to fetch JWT from endpoint ',err.response.data);
return {
statusCode: 200,
body: JSON.stringify({
status: 'error',
error: err.message,
message: 'Yikes! The form data was processed and sent to our email server for authentication but got no response back.. This is a server issue so our developer will look into this shortly. In the meantime, please try again later or contact via phone. We appologize for the inconvenience.'
})
}
})
} catch(err) {
return {
statusCode: 200,
body: JSON.stringify({
status: 'error',
error: err.message,
message: 'Yikes! The form data was processed and sent to our email server but the server was unable to authenticate the request. This is a server issue so our developer will look into this shortly. In the meantime, please try again later or contact via phone. We appologize for the inconvenience.'
})
}
}
} else {
// user failed the captcha test.. is probably a robot..
return {
statusCode: 200,
body: JSON.stringify({
status: 'error',
message: "Error! Captcha Failed: our AI algorithms tried to determine if you are a robot or a human, it seems they couldn't decide, therefor for security reasons your form submission was blocked. Perhaps try again later, or contact via phone. We appologize for any inconvenience. :("
})
}
}
//send back the response..
return response
}
Maybe something more like:
exports.handler = function(event, context) {
return new Promise((resolve, reject) => {
axios.post(...args).then(response => {
if(bool){
axios.post(...moreArgs).then(response => {
resolve('ok!')
}).catch(reject)
} else {
reject('not bool!')
}
}).catch(reject)
})
}
I feel like async / await makes this more complicated than without it.
Related
I am using netlify functions to send an email from the frontend and it works fine... as in it does send the email.
However on the clientside (browser) I can't get any response. I need a basic response that would allow me to do a if (status==="success") displayMessage() but I can't seem to get any response on the browser.
I get this message Uncaught (in promise) SyntaxError: JSON.parse: unexpected character at line 1 column 1 of the JSON data However on sending the request via POSTMAN I get a response 'Email Sent Succesfully' which is the body part of the callback response.
Here's the function that I am using at .netlify/functions/sendMail
const nodemailer = require("nodemailer");
exports.handler = function (event, context, callback) {
const mailConfig = {
host: "smtp.mailgun.org",
port: 465,
secure: true,
auth: {
user: process.env.MAILGUN_USER,
pass: process.env.MAILGUN_PASSWORD,
},
};
const transporter = nodemailer.createTransport(mailConfig);
transporter.verify((error, success) => {
if (error) {
console.log(error);
} else {
console.log("Ready to send emails");
}
});
const messageData = JSON.parse(event.body);
const { email, name, mobile, message, subject, recipient } = messageData;
console.log(messageData);
const mailOptions = {
from: email,
to: recipient,
subject: subject,
text: message,
};
transporter.sendMail(mailOptions, (error, success) => {
if (error) {
console.log(error);
callback(error);
} else {
console.log("email sent");
callback(null, {
statusCode: 200,
body: "Email sent successfully",
});
}
});
};
and on the client side I have this
const form = document.querySelector("#message");
const submitMessage = (event) => {
event.preventDefault();
const formData = new FormData(form);
formData.append("recipient", "testing#gmail.com");
formData.append("subject", "Submission from website");
const messageData = Object.fromEntries(formData);
console.log(messageData);
const url = ".netlify/functions/sendMail";
const options = {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(messageData),
};
fetch(url, options)
.then((response) => response.json())
.then((data) => console.log(data));
};
form.addEventListener("submit", submitMessage);
in the fetch request, I expected data to give me a response so I could trigger some action to display a success message..
Can someone advise me what I am doing wrong here?
I realised what I was doing wrong and here's the solution in case someone else faces the same issues.
In the callback from the netlify function I was sending the body as text
callback(null, {
statusCode: 200,
body: "Email sent successfully",
});
but in the clientside fetch request I was treating it as json
fetch(url, options)
.then((response) => response.json())
.then((data) => console.log(data));
So basically I could either use a response.text() instead of response.json() for the fetch request or use JSON.stringify and return a JSON object from the callback. I preferred the JSON.stringify option as below for the callback
callback(null, {
statusCode: 200,
body: JSON.stringify({
status: "success",
message: "Email sent successfully",
}),
});
I have the following React Native client code:
confirmMatchRecord(userId, matchedUserData['userId'], matchRecordData['matchRecord']['matchWinner'], matchRecordData['matchType'], currentUserRating, matchedUserRating, matchData['_id'], matchRecordData['matchRecord']['_id'], airbnbRatingValue, true, new Date())
.then((results) => {
// Do stuff
})
.catch((error) => {
Alert.alert('Error', 'There was an issue with confirming the record. Please check your connection and/or try again later.');
});
And the following code in my confirmMatchRecord function:
export async function confirmMatchRecord(userId, matchedUserId, matchWinner, matchType, currentUserRating, matchedUserRating, matchId, matchRecordId, matchRating, matchConfirmed, timestamp) {
console.log('Attempting to record match');
info = { userId, matchedUserId, matchWinner, matchType, currentUserRating, matchedUserRating, matchId, matchRecordId, matchRating, matchConfirmed, timestamp }
const firebaseIdToken = await AsyncStorage.getItem('#firebaseIdToken')
const requestOptions = {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + firebaseIdToken },
body: JSON.stringify(info)
};
const response = await fetch(ngrokOrLocalhost + '/confirmmatchrecord', requestOptions)
if (response['Status']==='Failure') {
// throw new Error(`HTTP error! status: ${response.status}`);
throw new Error(400);
} else if (response['Status']==='Success') {
const data = await response.json()
return data
}
}
Server code:
router.post('/confirmmatchrecord', async (req, res) => {
// Do a lot of stuff
if (response==='Success') {
return res.status(200).json({'Status': 'Success'})
} else {
return res.status(400).json({'Status': 'Failure'})
console.log('Match record was not confirmed successfully');
}
When response['Status']==='Failure (sent by server) it throws an error 400 as you can see, I was hoping to trigger the .catch in the client code then. But that does not happen, because the client code continues to run on the .then part.
How should I do this instead? Not sure if using .catch here is even correct or if I should do this type of work another way.
You seem to be aware of the bit of a footgun in the fetch API (I write about it here) where fetch only rejects its promise on network errors, not HTTP errors, but your check is incorrect in a couple of ways:
It's status, not Status (capitalization matters), and
It's the HTTP code (400 for instance), not a string
The Response object provides a convenient ok flag that's true for any successful response and false otherwise, so:
const response = await fetch(ngrokOrLocalhost + '/confirmmatchrecord', requestOptions)
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`); // This will be "HTTP error! status: 400" if the HTTP error code is 400
}
const data = await response.json();
return data;
In a comment you've said:
My response['Status'] was checking for a custom server message I had sent (res.status(400).json({'Status': 'Failure'}), I updated the post with it. Not sure why it didn't catch that
Ah! Okay. The reason it didn't catch it is that you're looking for it on the Response object, but your JSON is in the response body.
I suspect you don't want to use your own Status anymore since you know about response.ok and response.status now, but if you ever do want to include your own information in an error response as JSON, you can do that. You'd do it like this:
const response = await fetch(ngrokOrLocalhost + '/confirmmatchrecord', requestOptions)
const data = await response.json(); // Expects JSON in *both* the success response and the error response
if (data.Status === "Failure") {
throw new Error(`HTTP error! status: ${response.status}`); // This will be "HTTP error! status: 400" if the HTTP error code is 400
}
return data;
But I'd stick with just the built-in ok and status for pure success/failure information. This could be handy if you wanted to provide more details of the failure, though.
I am following a MailChimp API tutorial
When I test the API, I get a 401 response saying my API key is invalid.
Error -
Status: 401
"Your API key may be invalid, or you've attempted to access the wrong datacenter."
I have yet to register a domain yet, this is being testing using a local server. Could this be error be caused by MailChimp's refusing the request for another reason, perhaps CORS?
app.post('/signup', (req, res) => {
// Get form data
const { email } = req.body;
// Make sure field is filled
if(!email) {
res.redirect('/html/fail.html');
return;
}
// Construct req data
const data = {
members: [
{
email_address: email,
status: 'subscribed'
}
]
}
// Convert to JSON
const postData = JSON.stringify(data);
const options = {
url: 'https://us19.api.mailchimp.com/3.0/lists/listID',
method: 'POST',
headers: {
Authorization: 'auth xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx-us19'
},
body: postData
};
request(options, (err, response, body) => {
if(err) {
console.log(err);
res.redirect('/html/fail.html');
} else {
if(response.statusCode === 200) {
res.redirect('/html/success.html');
} else {
console.log(response.body);
res.redirect('/html/fail.html');
}
}
});
})
I tried running the same code in request in PostMan and I got back a 200 response.
I was initially importing the API key from a config file, that I had not destructured...
I made an authentification system in express using firebase auth.
I created an endpoint in express like this :
app.post('/users/login', (req, res) => {
console.log('logare...');
firebase.auth().signInWithEmailAndPassword(req.body.email, req.body.parola)
.then(user => {
res.json({ succes: true, msg: "te-ai autentificat cu succes" });
})
.catch(err => {
console.log('date invalide');
res.json({ succes: false, msg: 'date invalide', errCode: err.code });
})
})
then I created a function in my font-end js file for send request to server :
const autentificare = async (email, parola) => {
return await fetch('https://cm-api-1.herokuapp.com/users/login', {
method: 'POST',
headers: {
'content-type': "application/json"
},
body: JSON.stringify({
email: email,
pasword: parola
})
})
.then(res => {
console.log(res.status)
return res.json();
});
};
In my app.js file I created submit event for verfy email and pasword:
form.addEventListener('submit', async e => {
e.preventDefault();
const email = emailDom.value;
const parola = parolaDom.value;
await autentificare(email, parola)
.then(data => {
if (data.succes) {
console.log('ok datele sunt valide')
} else {
console.log('date invalide')
}
})
});
When I submited the form in console it shows me this errors: POST https://cm-api-1.herokuapp.com/users/login 500 (Internal Server Error)and Uncaught (in promise) SyntaxError: Unexpected token < in JSON at position 0 My api it's deployed on heroku.
I don't know what I missed
From my experience writing REST APIs, this error usually occur when there's some syntax error or an exception being caught in the API itself. The code looks fine but the HTML error 500 indicates it's not on the client end of things.
I have no experience with firebase and express, i've mostly written REST APIs with Swagger and php.
It is probably due to some issue with the JSON body content. Something like unescaped double quotes. Inspect what is being sent and received.
Read more about it here - https://www.kevinleary.net/syntax-error-unexpected-token-json-position-0/
I can't seem to get the correct response headers when my code enters bcrypt.compare. I thought it was a cors issue at first but I still get the correct response if I entered the wrong and "user does not exist" is displayed.
Here's my api server side code in express
router.post("/api/signIn", (req, res) => {
const { user, password } = req.body;
const queryString = "SELECT * FROM users WHERE user_id = ?";
db.query(queryString, [user])
.then(result => {
if (result.length > 0) {
const hash = result[0].password;
//here bcrypt.compare works server side or with cURL but won't set the response headers in the browser
bcrypt
.compare(password, hash)
.then(same => {
if (same === true) {
console.log("correct password");
res.status(200).json({
code: 200,
message: "correct password"
});
} else {
res.status(401).json({
code: 401,
message: "incorrect password"
});
}
})
.catch(err => console.log(err));
} else {
//this one works though and i can get the response in the browser so it can't be a cors issue
console.log("user does not exist");
res.status(200).json({
code: 401,
message: "User does not exist"
});
}
})
.catch(err => {
console.log("error" + err.message);
});
});
and this is the test function i use in react
const signIn = () => {
fetch("http://localhost:5000/api/signIn", {
method: "POST",
body: JSON.stringify({
user: userName,
password: password
}),
headers: {
"Content-Type": "application/json"
},
})
.then(res => res.json())
.then(response => alert(response.code + response.message))
.catch(err => alert(err));
};
so if i entered the wrong username that is not in the database, the alert function would show (code401User does not exist) but if i entered the correct user bcrypt.compare() doesn't seem to set the response for both correct and incorrect passwords and i would get (TypeError: failed to fetch). testing the api in cURL works though.
Got it, I forgot to put event.preventDefault() on the fetch function.