How to return a promise from Lambda - reactjs

I'm trying to connect my NextJS application via AWS Amplify to the Google Calendar API. I added an API with "Amplify Add API". Selected "Rest" and chose "Serverless Express Function". So far so good. The test function works. I am utilizing Proxy integration to let lambda decide what to do with the code. Here is my client side code:
import React from "react";
import { API } from "aws-amplify";
const getCalendar = () => {
React.useEffect(() => {
getCal();
async function getCal() {
const apiName = 'gcal'; // api name.
const path = '/events'; // API path
await API
.get(apiName, path)
.then(response => {
console.log(response)
}).then(() => console.log(` the resopnse returned
successfully as: ${JSON.stringify(response.body)
}`))
.catch(error => {
console.log(`there was an error returning the
response from the lambda function: ${error}`);
});
}
},[])
}
export default getCalendar;
Inside the Lambda function I started with the Proxy Integration sample and replaced with the code below. The issue is, the response is visible and correct from Google API but not received by the client from Lambda. I tried a callback, async/await, and res.send(). I know I'm missing something in this function below regarding the async nature of the request. Getting 500 or 502 errors from Cloud Watch Logs. Lambda index.js code(with problem):
const awsServerlessExpress = require("aws-serverless-express");
require("dotenv").config();
var express = require("express");
var bodyParser = require("body-parser");
var awsServerlessExpressMiddleware = require("aws-serverless-
express/middleware");
const { google } = require("googleapis");
const calendar = google.calendar("v3");
var app = express();
const server = awsServerlessExpress.createServer(app, null);
app.use(bodyParser.json());
app.use(awsServerlessExpressMiddleware.eventContext());
app.use(function (req, res, next) {
res.header("Access-Control-Allow-Origin", "*");
res.header("Access-Control-Allow-Headers", "*");
res.header("Content-Type", "*");
next();
});
exports.handler = (event, context) => {
return awsServerlessExpress.proxy(server, event, context,
"PROMISE").promise;
};
app.post("/events", function () {
main();
async function main() {
const auth = new google.auth.GoogleAuth({
keyFilename: "./<my-secret-file>.json",
scopes: [
"https://www.googleapis.com/auth/calendar",
"https://www.googleapis.com/auth/calendar.readonly",
],
});
const now = new Date().toISOString();
// Acquire an auth client, and bind it to all future calls
const authClient = await auth.getClient();
google.options({ auth: authClient });
// Do the magic
let response = await calendar.events.list({
calendarId: process.env.API_CALENDAR_ID,
showHiddenInvitations: false,
timeMin: now,
showDeleted: false,
});
return await response.json();
}
return res.send(response);
});
app.listen(3000, function () {
console.log("Google Calendar Function Is Running");
});
Here is the working response from Google API to Lambda in Dev Tools:
I am receiving {"message": "Internal server error"} after completion.
Here is the failed attempted response to the client from Lambda:
inside Cloud Watch Logs I get "no help", and a succeeded response:
Any help or suggestions would be greatly appreciated.

I found out the answer was in both mixing promises with async await and also with res.send(). After returning the data from Google API I still needed to format the response and set the correct headers. Here is the working code.
const awsServerlessExpress = require("aws-serverless-express");
require("dotenv").config();
var express = require("express");
var bodyParser = require("body-parser");
var awsServerlessExpressMiddleware = require("aws-serverless-
express/middleware");
var app = express();
const server = awsServerlessExpress.createServer(app, null);
app.use(awsServerlessExpressMiddleware.eventContext());
app.use(function (req, res, next) {
res.header("Access-Control-Allow-Origin", "*");
res.header("Access-Control-Allow-Headers", "*");
res.header("Content-Type", "application/json");
next();
});
app.use(bodyParser.json());
const { google } = require("googleapis");
const calendar = google.calendar("v3");
exports.handler = (event, context) => {
return awsServerlessExpress.proxy(server, event, context,
"PROMISE").promise;
};
app.get("/events", function( req, res ) {
async function main() {
try {
const auth = new google.auth.GoogleAuth({
keyFilename: "./my-secret-file.json",
// Scopes can be specified either as an array or as a single,
space-delimited string.
scopes: [
"https://www.googleapis.com/auth/calendar",
"https://www.googleapis.com/auth/calendar.readonly",
],
});
const now = new Date().toISOString();
// Acquire an auth client, and bind it to all future calls
const authClient = await auth.getClient();
google.options({ auth: authClient });
let responseBody = await calendar.events.list({
calendarId: process.env.API_CALENDAR_ID,
showHiddenInvitations: false,
timeMin: now,
showDeleted: false,
});
// Do the magic
let response =
{
"isBase64Encoded": "false",
"statusCode": "200",
"headers": {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Headers": "*",
"Content-Type": "application/json",
"Access-Control-Allow-Methods": "*"
},
"body": responseBody,
};
return res.send(JSON.stringify(response))
} catch (error) {
console.log(error);
}}
main();
});
app.listen(3000, function () {
console.log("The Google Calendar Function Is Running");
});

Related

React profile page, how to avoid 'GET http://localhost:3001/users/profile 401 (Unauthorized)' when trying to get JSON data from back end

For this application, I am using React & Express. I have React running on PORT 3000, and Express running on PORT 3001. On the Express side, I have authentication working that uses JWT.
First, here is my auth.js service file:
const jwt = require('jsonwebtoken');
const models = require('../models');
const bcrypt = require('bcryptjs');
var authService = {
signUser: function (user) {
const token = jwt.sign({
Username: user.Username,
UserId: user.UserId
},
'secretkey',
{
expiresIn: '1h'
}
);
return token;
},
verifyUser: function (token) {
try {
let decoded = jwt.verify(token, 'secretkey');
return models.users.findByPk(decoded.UserId);
} catch (err) {
console.log(err);
return null;
}
},
hashPassword: function (plainTextPassword) {
let salt = bcrypt.genSaltSync(10);
let hash = bcrypt.hashSync(plainTextPassword, salt);
return hash;
},
comparePasswords: function (plainTextPassword, hashedPassword) {
return bcrypt.compareSync(plainTextPassword, hashedPassword);
}
}
module.exports = authService;
When a user makes a POST request to the signup route, it works:
router.post('/signup', function (req, res, next) {
models.users.findOrCreate({
where: {
Username: req.body.username
},
defaults: {
FirstName: req.body.firstName,
LastName: req.body.lastName,
Email: req.body.email,
Password: authService.hashPassword(req.body.password)
}
})
.spread(function (result, created) {
if (created) {
res.redirect("http://localhost:3000/login");
} else {
res.send('This user already exist')
}
});
});
Signup works in both Postman and React.
When a user makes a POST request to the login route, it works:
router.post('/login', function (req, res, next) {
models.users.findOne({
where: {
Username: req.body.username
}
}).then(user => {
if (!user) {
console.log('User not found')
return res.status(401).json({
message: "Login Failed"
});
} else {
let passwordMatch = authService.comparePasswords(req.body.password, user.Password);
if (passwordMatch) {
let token = authService.signUser(user);
res.cookie('jwt', token);
res.redirect('http://localhost:3001/users/profile');
} else {
console.log('Wrong Password');
}
}
});
});
Login works in both Postman and React.
When a user makes a GET request to the profile route, it semi-works:
router.get('/profile', function (req, res, next) {
let token = req.cookies.jwt;
if (token) {
authService.verifyUser(token).then(user => {
if (user) {
res.setHeader('Content-Type', 'application/json');
res.send(JSON.stringify(user));
} else {
res.status(401);
res.send('Invalid authentication token');
}
});
} else {
res.status(401);
res.send('Invalid authentication token');
}
});
This works only in Postman, I can see the data that I want using Postman. In React, it will not get the profile route that I request. This is where the error comes in: Console Error
On the React side, this is profile GET component:
import React from 'react';
import axios from 'axios';
class UserProfile extends React.Component {
constructor(props) {
super(props);
this.state = {
profileData: []
}
};
fetchProfileData = () => {
var encodedURI = window.encodeURI(this.props.uri);
return axios.get(encodedURI).then(response => {
this.setState(() => {
return {
profileData: response.data
};
});
});
};
componentDidMount() {
this.fetchProfileData();
}
render() {
console.log(this.state.profileData);
if (this.state.profileData.length === 0) {
return <div>Failed to fetch data from server</div>
}
const profile = this.state.profileData.map(user => (
<div key={user.UserId}>Hello world</div>
));
return <div>{profile}</div>
}
}
export default UserProfile;
Then when I go to render this component, I just:
<UserProfile uri="http://localhost:3001/users/profile" />
Which then will render 'Failed to fetch data from server', then the console will log the '401 (Unauthorized)' error. I just can't get it to render in React.
And if anyone wants my Express app.js file for some extra information:
var createError = require('http-errors');
var express = require('express');
var path = require('path');
var cookieParser = require('cookie-parser');
var logger = require('morgan');
var models = require('./models');
var cors = require('cors');
var indexRouter = require('./routes/index');
var usersRouter = require('./routes/users');
var app = express();
// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'hbs');
app.use(function (req, res, next) {
res.header('Access-Control-Allow-Origin', 'http://localhost:3000');
res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept');
next();
});
app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
app.use(cors());
app.use('/', indexRouter);
app.use('/users', usersRouter);
// catch 404 and forward to error handler
app.use(function (req, res, next) {
next(createError(404));
});
// error handler
app.use(function (err, req, res, next) {
// set locals, only providing error in development
res.locals.message = err.message;
res.locals.error = req.app.get('env') === 'development' ? err : {};
// render the error page
res.status(err.status || 500);
res.render('error');
});
models.sequelize.sync().then(function () {
console.log("DB Synced Up");
});
module.exports = app;
Thank you in advanced. I have been struggling to figure this out.
I have tried toying with my UserProfile component. And I've tried toying with my /profile route in Express. The only 2 errors I've gotten is the 401 (Unauthorized) and something about the Headers. I know that my JWT key gets passed onto reacts side, because when I do 'localhost:3000/profile' (react side), I can see that I have the cookie stored. I'm not sure on how to approach authorization on React side. At this point, I am very clueless on what to do. This is the first time I've tried setting up authentication with React. I have always used Express and the .hbs files to render my profile pages. But I've been told that you shouldn't render a profile page in the back-end. So, here I am trying to do it with React.
I have rendered things from the back-end to the front-end, but that's without the use of JWT. I strongly believe that it has something to do with the JWT cookie. I just don't know how to authenticate it in React. Thanks again in advanced.
I fixed it by adding this into my React project:
I added this into my fetchProfileData()
{ withCredentials: true }
fetchProfileData = () => {
var encodedURI = window.encodeURI(this.props.uri);
return axios.get(encodedURI, { withCredentials: true }).then(response => {
this.setState(() => {
return {
profileData: response.data
};
});
});
};
Then in Express, I toyed with my Profile route. Put the data into an array, and sent it on its way:
router.get('/profile', function (req, res, next) {
var userData = [];
let token = req.cookies.jwt;
if (token) {
authService.verifyUser(token).then(user => {
userData.push(user);
res.send(userData);
});
} else {
res.status(401);
res.send('Invalid authentication token');
}
});

JWT gives invalid token

Working locally, my jwt token is invalid but in jwt.io it shows verified signature. Not sure what i am missing. I am having invalid signature whenever i tried to make a call to a api whithin the app.
Link.js
const { Router } = require("express");
const Link = require("../models/Link");
const auth = require("../middleware/auth.middleware");
const router = Router();
router.get("/", auth, async (req, res) => {
try {
const links = await Link.find({ owner: req.user.userId });
res.json(links);
} catch (error) {
res.status(500).json({ message: "Something went wrong, try again" });
}
});
auth.middleware.js
const jwt = require("jsonwebtoken");
const config = require("config");
module.exports = (req, res, next) => {
if (req.method === "OPTIONS") {
return next();
}
try {
const token = req.headers.authorization; // Token
if (!token) {
return res.status(401).json({ message: "No Authorization" });
}
const decoded = jwt.verify(token, config.get("secret"));
req.user = decoded;
next();
} catch (error) {
res.status(401).json({ message: "No Authorization" });
}
};
Links.tsx
const LinksPage: React.FC = () => {
const [links, setLinks] = useState([]);
const fetchLinks = useCallback(async () => {
try {
const fetched = await request("http://localhost:5000/api/link/", "GET", null, {
Authorization: Token,
});
setLinks(fetched);
} catch (error) {
alert(error);
}
}, []);
};
Maybe the "req.headers.authorization" was not what you looking for.
Try to console.log(req.headers.authorization) F12 in chrome, firefox.
I suggest you also POSTMAN (free software). It help me a lot for debugging the back end (server side).
I solved the problem. I had to json.parse(token) which stored in the client in order to jwt.verify(token, secret), but instead i was verifying string that contains object of token and userId.

req.body.something returns undefined

I have been trying to post data to my express server using axios, and when I console.log(req.body.something) it returns undefined, and when I console.log(req.body) only it logs this message to the console:
[Object: null prototype] { '{"nameVal":"Usef","nickNameVal":"US"}': '' }
Any Help Will Be Appreciated.
// This My Server.js Code
const express = require("express");
const bodyParser = require("body-parser");
const cors = require("cors");
const app = express();
app.use(bodyParser.json());
// create application/x-www-form-urlencoded parser
const urlencodedparser = bodyParser.urlencoded({ extended: false });
// Use Cors As MiddleWhere
app.use(cors());
// Get The Post Request
app.post("/user", urlencodedparser, (req, res) => {
console.log(req.body.name); // returns undefined
});
app.listen(5000);
// and this the react component state along with the axios post request
state = {
nameVal: "Usef",
nickNameVal: "US"
};
handleSubmit = event => {
event.preventDefault();
const { nameVal, nickNameVal } = this.state;
axios.post("http://localhost:5000/user", { nameVal, nickNameVal },
{ headers: { "Content-Type": "application/x-www-form-urlencoded" } }
).then(res => {console.log(res)});
};
If you remove your custom Content-Type header from the axios request, axios will send your data as JSON by default, and it will be parsed by your express JSON parser middleware.
axios.post("http://localhost:5000/user", { nameVal, nickNameVal })
.then(res => console.log(res));
The data you send to the server is nameVal and nickNameVal, so trying to access req.body.name will still give undefined. Try logging nameVal and nickNameVal instead.
app.post("/user", (req, res) => {
console.log(req.body.nameVal, req.body.nickNameVal);
});
According to axios documentation you need to pass an instance of URLSearchParams (or a query string of the parameters) as the second argument.
const params = new URLSearchParams();
params.append('nameVal', state.nameVal);
params.append('nickNameVal', state.nickNameVal);
axios.post('/user', params);

data is not being sent by POST, reads OPTIONS in console

I have a react redux app where I am posting data to my node (express) server. In my action creator the data is being sent to the server but it isn't responding to the file. Here's my action creator.
// action creator
export function addItem(product) {
return dispatch => {
dispatch(request(product));
axios.post(api + '/api/addtoinventory', { product })
.then(res => {
dispatch(success(product));
})
.catch(err => {
dispatch(failure(err.toString()));
});
}
function request(product) { return { type: ADDING_ITEM, product } }
function success(product) { return { type: ITEM_ADDED, product } }
function failure(error) { return { type: ADD_TOAST, payload: error} }
}
Then in my express file I have code like this..
// server.js
var express = require('express');
var router = express.Router();
var multer = require('multer');
var uuidv4 = require('uuid/v4');
var path = require('path');
var database = require('./database');
const storage = multer.diskStorage({
destination: (req, file, cb) => {
cb(null, '../../../adminpanel/src/0000001');
},
filename: (req, file, cb) => {
const newFilename = `${uuidv4()}${path.extname(file.originalname)}`;
cb(null, newFilename);
}
});
const upload = multer({ storage });
router.post('/', function(req, res) {
var title = req.body.product.title;
var price = req.body.product.price;
var description = req.body.product.description;
database.query("INSERT INTO `Items` (`ID`, `Title`, `Price`, `Description`, `CreateDate`) VALUES (NULL, ?, ?, ?, CURRENT_TIMESTAMP)", [title, price, description], function(err, result) {
if(err) {
console.log(err);
} else {
var id = result.insertId;
console.log(id);
}
});
});
module.exports = router;
Then when i check for the console log I just get this in return
OPTIONS /api/addtoinventory 200 10.300 ms - 4
Shouldn't that say POST instead of OPTIONS ?
You need to create a middleware which will allow CORS for your registered req origins
var allowCrossDomain = function(req, res, next) {
res.header('Access-Control-Allow-Origin', 'your domain here');
res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE');
res.header('Access-Control-Allow-Headers', 'Content-Type');
next();
}
Then in your startup file include this middleware
app.use(allowCrossDomain);
If you want to read about it more
https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#Preflighted_requests

How to Get Param Id from URL in express/mongo/mongoose on server side, axios/react/redux on client side

I am having some trouble with get the param from the url. I use Express(4.16.3) on the server side, and using Axios to make the request. But I couldn't seem to get the param from the url in Express.
Here is my code:
on my Route.js in Express
app.get('/api/surveys/:surveyId', (req, res, next) => {
var id = req.params.surveyId;
console.log(req.params);
// it gets params {surveyId: ':surverId'}
res.send('Hello World');
});
so instead of getting the actual id, it logs params: {surveyId: ':surveyId'}. I have been researching, but seems this is the correct way to do it. I also use axios to make the request:
in actions/index.js (I use react):
export const fetchOneSurvey = () => async dispatch => {
const res = await axios.get('/api/surveys/:surveyId');
dispatch({ type: FETCH_ONE_SURVEY, payload: res.data });};
Not sure if this is relevant:
On the view page, instead of having http://localhost:3000/api/surveys/:surveyId, I have http://localhost:3000/surveys/:surveyId route set in React. When I go to http://localhost:3000/surveys/:surveyId, it does console log (req.params) like I write in express, but I only get a string ':surveyId' is the params, not the actual id on the url.
Please anyone can help me? I have tried many different ways, but nothing seem working. I thank you all very much in advance.
===== Extra section ======
Here is my index.js:
const express = require('express');
const mongoose = require('mongoose');
const cookieSession = require('cookie-session');
const passport = require('passport');
const bodyParser = require('body-parser');
const keys = require('./config/keys');
require('./models/User');
require('./models/Survey');
require('./services/passport');
mongoose.connect(keys.mongoURI);
const app = express();
app.use(bodyParser.json());
app.use(
cookieSession({
maxAge: 30 * 24 * 60 * 60 * 1000,
keys: [keys.cookieKey]
})
);
app.use(passport.initialize());
app.use(passport.session());
require('./routes/authRoutes')(app);
require('./routes/billingRoutes')(app);
require('./routes/surveyRoutes')(app);
if (process.env.NODE_ENV === 'production') {
app.use(express.static('client/build'));
const path = require('path');
app.get('*', (req, res) => {
res.sendFile(path.resolve(__dirname, 'client', 'build', 'index.html'));
});
}
My survey model route js:
const _ = require('lodash');
const Path = require('path-parser');
const { URL } = require('url');
const mongoose = require('mongoose');
const requireLogin = require('../middlewares/requireLogin');
const requireCredits = require('../middlewares/requireCredits');
const Mailer = require('../services/Mailer');
const surveyTemplate = require('../services/emailTemplates/surveyTemplate');
const Survey = mongoose.model('surveys');
module.exports = app => {
app.get('/api/surveys', requireLogin, async (req, res) => {
const surveys = await Survey.find({ _user: req.user.id }).select({
recipients: false
});
res.send(surveys);
});
app.get('/api/surveys/:surveyId/:choice', (req, res) => {
res.send('thanks for voting');
});
app.get('/api/surveys/:surveyId', (req, res, next) => {
var id = req.params.surveyId;
console.log(id);
// it gets params {surveyId: ':surverId'}
res.send('Hello World');
});
app.post('/api/surveys/webhooks', (req, res) => {
// console.log(req.body);
// res.send({});
const p = new Path('/api/surveys/:surveyId/:choice');
const test = _.chain(req.body)
.map(({ email, url }) => {
const match = p.test(new URL(url).pathname);
if (match) {
return {
email,
surveyId: match.surveyId,
choice: match.choice
};
}
})
.compact()
.uniqBy('email', 'surveyId')
.each(({ surveyId, email, choice }) => {
Survey.updateOne(
{
// have to add _ to keys as mongoDB rule, mongoose doensn't need.
_id: surveyId,
recipients: {
$elemMatch: { email: email, responded: false }
}
},
{
$inc: { [choice]: 1 },
$set: { 'recipients.$.responded': true },
lastResponded: new Date()
}
).exec();
})
.value();
console.log(test);
res.send({});
});
app.post('/api/surveys', requireLogin, requireCredits, async (req, res) => {
const { title, subject, body, recipients } = req.body;
const survey = new Survey({
// map(email => ({ email }) === map(email =>{ return {email: email}})
title,
body,
subject,
recipients: recipients
.split(',')
.map(email => ({ email: email.trim() })),
_user: req.user.id,
dateSent: Date.now()
});
// send an email
const mailer = new Mailer(survey, surveyTemplate(survey));
try {
await mailer.send();
await survey.save();
req.user.credits -= 1;
const user = await req.user.save();
res.send(user);
} catch (err) {
res.status(422).send(err);
}
});
};
Posting below details for debugging the issue
Note: if you are using Windows OS, use command prompt for node project development. i have seen people using git bash for doing node project developments and it causes unnecessary issues
Below are the steps for debugging
1.Create a new directoryforexample test and initialize it using npm init
2.Install express npm install --save express
3.Create a new file for example index.js and use below code
test/index.js
var express= require("express");
var app = express();
app.get("/api/surveys/:surveyId",(req,res,next)=>{
console.log(req.params.surveyId);
res.send('Hello World');
});
var server= app.listen(3000,()=>{
console.log("port started at ",server.address().port);
})
4.Start the program node index.js
5.Trigger http request from browser http://localhost:3000/api/surveys/llads . The value llads can be accessed using the path param surveyId in the route
6.if you can see the below output in node console then the program is working as it should. And this has to work as described here.
if above steps yields expected output then i don't see any problem in your route code.
Let me know your feedback.

Resources