GCP Google cloud App engine socket.io won't connect by wss - google-app-engine

I'm using nodejs and run a very simple socketserver based on the examples of Google. I've basically set everything according to how they explain, my environment is flex etc. But yet everytime my application client (angular based) produces the following error in the developer console
WebSocket connection to 'wss://' failed: and falls back to https polling
It works perfectly fine when I run my application locally.
My socket app.js
'use strict';
const express = require('express');
const app = express();
const userSocketIdMap = new Map();
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.set('view engine', 'pug');
console.log("clienturl is:", process.env.clienturl);
const server = require('http').Server(app);
const io = require("socket.io")(server, {
cors: {
origin: process.env.clienturl, //this is filled with correct origin url
credentials: true,
},
allowEIO3: true
});
app.get('/', (req, res) => {
io.emit('message', { message: `GET call` });
res.status(200).send()
});
app.post('/', (req, res) => {
let message = ''
if (req.body.message.hasOwnProperty('data')) {
message = Buffer.from(req.body.message.data, 'base64').toString(
'utf-8'
);
}
let sender = '';
let uid = '';
if (req.body.message.hasOwnProperty('attributes')) {
if (req.body.message.attributes.hasOwnProperty('sender')) {
sender = req.body.message.attributes.sender;
}
if (req.body.message.attributes.hasOwnProperty('uid')) {
uid = req.body.message.attributes.uid;
}
}
if (uid) {
let recipientSocketIds = userSocketIdMap.get(uid);
if (recipientSocketIds) {
for (let socketId of recipientSocketIds) {
io.to(socketId).emit('message', { message: message, sender: sender, uid: uid });
}
}
} else {
io.emit('message', { message: message, sender: sender, uid: uid });
}
console.log({ message: message, sender: sender, uid: uid });
res.status(200).send()
});
io.on('connection', socket => {
let uid = socket.handshake.query.uid;
if (!userSocketIdMap.has(uid)) {
//when user is joining first time
userSocketIdMap.set(uid, new Set([socket.id]));
} else {
//user had already joined from one client and now joining using another
userSocketIdMap.delete(uid);
userSocketIdMap.set(uid, new Set([socket.id]));
}
console.log('client connected', process.env.clienturl,userSocketIdMap)
});
io.on('disconnect', socket => {
console.log('Got disconnect!');
});
if (module === require.main) {
const PORT = process.env.PORT || 8080;
server.listen(PORT, () => {
console.log(`App listening on port ${PORT}`);
console.log('Press Ctrl+C to quit.');
});
}
module.exports = server;
And my app.yaml file
runtime: nodejs
env: flex
manual_scaling:
instances: 1
network:
session_affinity: true

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');
}
});

Body Parser issue with Express, React. Posting Null values in database

Middleware issue with body-parser:
The data gets stored as an object which later calls /api/new to post in SQL database.
No Issues with the query itself but data has null values in the database.
My thoughts:
app.use(express.urlencoded({
extended: true
}));
is set to true, as some values are INT,
I tried JSON.Stringify the data before it gets sent to the POST request. Did not work
Also played around with the header
// const config = {
// headers: {
// accept: "application/json",
// "accept-language": "en_US",
// "content-type": "application/x-www-form-urlencoded"
// }
// };
axios.post("/api/new",{newEntry}, config ...
I think the issue may be due to the fact that its router.post in my api-routes instead of app.post
```
var newEntry = {
First_Name: FirstName.toUpperCase(),
Last_Name: LastName.toUpperCase(),
Employ_ID: parseInt(EmployID),
Problem: checkedValue,
PhoneNumber: phoneNumber,
Provider: domain
};
var stringNewEntry = JSON.stringify(newEntry);
console.log("Right before Post");
console.log(newEntry);
axios
.post(
"/api/new",
{newEntry},
{
timeout: 10000
}
)
.catch(error => {
if (error.code === "ECONNABORTED") return "timeout";
})
.then(res => {
console.log(newEntry);
if (res === "timeout") {
console.log("Please Try Again");
} else {
console.log("SUCCESS!");
}
});
}
````
Server File
const express = require("express");
const app = express();
var bodyParser = require('body-parser')
const PORT = process.env.PORT || 3001;
var routes = require("./routes/api-routes");
// Define middleware here
app.use(express.json());
app.use(bodyParser.json());
app.use(express.urlencoded({
extended: true
}));
// Serve up static assets (usually on heroku)
if (process.env.NODE_ENV === "production") {
app.use(express.static("client/build"));
}
// Add routes, both API and view
app.use(routes);
// Start the API server
app.listen(PORT, function() {
console.log(`🌎 ==> API Server now listening on PORT ${PORT}!`);
});
api-routes
router.route("/api/new").post(function(req, res) {
console.log("Data:");
console.log(req.body.First_Name);
var dbQuery =
"INSERT INTO KioskInfo (First_Name,Last_name,Employ_ID,Problem,PhoneNumber,Provider) VALUES (?,?,?,?,?,?)";
var dbQueryPermanent =
"INSERT INTO KioskData (First_Name,Last_name,Employ_ID,Problem) VALUES (?,?,?,?)";
var IgnoreQuery =
"INSERT IGNORE INTO KioskInfo (First_Name,Last_name,Employ_ID,Problem,PhoneNumber,Provider) VALUES (?,?,?,?,?,?)";
connection.query(
dbQuery,
[
req.body.First_Name,
req.body.Last_Name,
req.body.Employ_ID,
req.body.Problem,
req.body.PhoneNumber,
req.body.Provider
],
function(err, result) {
if (err) {
connection.query(
IgnoreQuery,
[
req.body.First_Name,
req.body.Last_Name,
req.body.Employ_ID,
req.body.Problem,
req.body.PhoneNumber,
req.body.Provider
],
function(IgnErr, IgnResult) {
if (IgnErr) {
throw IgnErr;
} else {
console.log("Duplicate Entry Ignored");
}
}
);
}
//Learn to specify DUP ERR;
console.log("Successfully Saved Into KioskInfo");
}
);
connection.query(
dbQueryPermanent,
[
req.body.First_Name,
req.body.Last_Name,
req.body.Employ_ID,
req.body.Problem
],
function(err, result) {
if (err) throw err;
console.log("Successfully Saved Into KioskData");
res.end();
}
);
});

Async Post with "await axios"

in my React-App, I want to send an email with axios and nodemailer to the user after registration. So, when the button "Register" is pushed, it will use the api "/api/form" for sending the data to the server.
My problem is that the data will not be sent to the server in normal using. If I use the developertools with breackpoints for debugging this function, it works! So it can not be a problem with the server side, I think the problem is at the front end, but I don´t see why.
Is it the way I call axios.post?
Here is the code for the submit function (front end):
async onSubmit(e) {
e.preventDefault
var token = Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15);
const { firstName, email, authToken } = this.state;
const form = await axios.post('/api/form', {
firstName,
email,
authToken: token
}, console.log("form"),this.props.history.push(`/login`));
return form;
}
This is the code in index.js (server):
/*Double-Opt-In*/
app.use(express.static('public'))
app.use(bodyParser.json())
app.use(bodyParser.urlencoded({ extended: false }))
/*Double-Opt-In*/
app.post('/api/form', (req, res) => {
console.log(req.body);
nodemailer.createTestAccount((err, account) => {
token= req.body.authToken;
link="http://localhost:3000/verify"+"?token="+token;
console.log("createTestAccount");
const htmlEmail = `
<h3>WebFit Registrierung</h3>
`
let transporter = nodemailer.createTransport({
host: 'smtp.gmail.com',
port: 465,
secure: true,
auth: {
user: 'email',
pass: 'password'
}
})
let mailOptions = {
from: 'WebFit', // sender address
to: req.body.email, // list of receivers
replyTo: 'app#webfit.app',
subject: 'Welcome to Webfit <3', // Subject line
text: req.body.message, // plain text body
html: htmlEmail // html body
};
// send mail with defined transport object
transporter.sendMail(mailOptions, (error, info) => {
if (error) {
return console.log(error);
}
console.log('Message sent: %s', info.messageId);
// Preview only available when sending through an Ethereal account
console.log('Preview URL: %s', nodemailer.getTestMessageUrl(info));
// Message sent: <b658f8ca-6296-ccf4-8306-87d57a0b4321#example.com>
// Preview URL: https://ethereal.email/message/WaQKMgKddxQDoou...
});
});
});
const PORT = process.env.PORT || 3001
app.listen(PORT, () => {
console.log(`Server listening on port ${PORT}`);
})
/*End Double-Opt-In*/
axios runs fine with async/await, so catch the throw/reject and check if axios or other. Depending on your api you can extract further info like shown:
async function login(token) {
/* post token and return jwt */
try {
const d = await api.post("/login", token);
return res.data["jwt"];
} catch (e) {
if(e.name == 'AxiosError'){
// map axios error to an app-friendly display message
// your api error handling dictates what you pull out,
// expressjs: return res.status(401).send("expired.");
// results in: '401 expired.'
return new Error(`${e.response.status} ${e.response.data}`);
} else {
return new Error(`${e.name} ${e.message}`);
}
}
}
If your tired of handling on every api call, use interceptors which can do the api error 2 local app error mapping service.js wide.

No data returned on MongoDB using Express router

I have a Mongo database set up on a Bitnami Lightsail MEAN stack instance that I am trying to connect to with an Angular/Node/Express application that I am building. I have followed the instructions on how to connect and create SSH Port Forwarding from my local machine (https://docs.bitnami.com/aws/infrastructure/mean/).
I am able access localhost:8888, which gives me access to RockMongo that was set up on the MEAN Lightsail instance with my Mongo database. That being said, I think the configuration for the connection to the server from my local machine is fine.
When I run node server and navigate to the URL (http://localhost:3000/api/numbers) for my api GET method, I am not receiving an error on connecting to the database. Instead, I get the following response, which is basically an empty array of data:
{"status":200,"data":[],"message":null}
Here is the code for my api.js file:
const express = require('express');
const router = express.Router();
const MongoClient = require('mongodb').MongoClient;
const ObjectID = require('mongodb').ObjectID;
// Connect
const connection = (closure) => {
return MongoClient.connect('mongodb://localhost:27017/sakDB', (err, db) => {
if (err) {
return console.log(err);
}
closure(db);
});
};
// Error handling
const sendError = (err, res) => {
response.status = 501;
response.message = typeof err == 'object' ? err.message : err;
res.status(501).json(response);
};
// Response handling
let response = {
status: 200,
data: [],
message: null
};
// Get numbers
router.get('/numbers', (req, res) => {
connection((db) => {
db.collection('numbers')
.find()
.toArray()
.then((numbers) => {
response.data = numbers;
res.json(response);
})
.catch((err) => {
sendError(err, res);
});
});
});
module.exports = router;
And the code for my router.js file:
const express = require('express');
const bodyParser = require('body-parser');
const path = require('path');
const http = require('http');
const app = express();
const api = require('./server/routes/api');
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(express.static(path.join(__dirname, 'dist')));
app.use('/api', api);
app.get('*', (req, res) => {
res.sendFile(path.join(__dirname, 'dist/index.html'));
});
const port = process.env.PORT || '3000';
app.set('port', port);
const server = http.createServer(app);
server.listen(port, () => console.log(`Running on localhost:${port}`));
I am beginning to think that this is some configuration issue with MongoDB on the MEAN Lightsail instance. If I try to run db.numbers.find() in MongoDB shell, I get the following error:
MongoDB server version: 3.4.7
> db.numbers.find()
Error: error: {
"ok" : 0,
"errmsg" : "not authorized on sakDB to execute command { find: \"numbers\", filter: {} }",
"code" : 13,
"codeName" : "Unauthorized"
}
I have to log in as a user mongo sakDB -u admin -p that I created to in order to find data on the collection.
When I try adding those credentials to the connection string mongodb://admin:PASSWORD#localhost:27017/sakDB, I receive a different authentication error:
name: 'MongoError',
message: 'Authentication failed.',
ok: 0,
errmsg: 'Authentication failed.',
code: 18,
codeName: 'AuthenticationFailed' }
It looks like this issue was related to Bitnami's server configuration with my Express application. It has been resolved.

MEAN: TypeError: ... is not a constructor

I'm very new to development and working my way through this MEAN tutorial:
MEAN Stack Front To Back [Part 3] - User Model & Register
I've read a few questions on here but can't find a relevant answer.
Here is the primary error code:
Server started on port 3000
Database Error MongoError: failed to connect to server [localhost:27017]
on first connect
TypeError: User is not a constructor
at router.post (/Users/user/Desktop/Development/meanauthapp/routes/users.js:12:16)
Which is odd, because my mongod terminal states this:
2017-03-15T09:52:49.306-0700 I NETWORK
[thread1] waiting for connections on port 27017
2017-03-15T09:52:54.514-0700 I NETWORK
[thread1] connection accepted from 127.0.0.1:49188 #1 (1 connection now open)
2017-03-15T09:52:54.515-0700 I NETWORK
[conn1] received client metadata from 127.0.0.1:49188 conn1:
{ application: { name: "MongoDB Shell" },
driver: { name: "MongoDB Internal Client", version: "3.4.1" },
os: { type: "Darwin", name: "Mac OS X", architecture: "x86_64", version: "15.6.0" } }
/routes/users.js
/*---------------Dependencies-------------*/
const express = require('express');
const router = express.Router();
const User = require('../config/database');
const passport = require('passport')
const jwt = require('jsonwebtoken');
/*----------------------------------------*/
/*---------------Register-----------------*/
router.post('/register', (req, res, next) => {
let newUser = new User({
name: req.body.name,
email: req.body.email,
username: req.body.username,
password: req.body.password
});
User.addUser(newUser, (err, user) => {
if(err) {
res.json({success: false, msg:'Failed to register user'});
} else {
res.json({success: true, msg: 'User Registered'})
}
});
});
/*----------------------------------------*/
/*---------------Authenticate---------------*/
router.post('/authenticate', (req, res, next) => {
res.send('AUTHENTICATE')
});
/*----------------------------------------*/
/*-----------------Profile------------------*/
router.get('/profile', (req, res, next) => {
res.send('PROFILE')
});
/*----------------------------------------*/
module.exports = router;
The line with error is : let newUser = new User({
/config/database.js
module.exports = { database: "mongodb://localhost:27017/famjam",
secret : "yoursecret" }
/models/users.js
const mongoose = require('mongoose');
const bcrypt = require('bcryptjs');
const config = require('../models/database');
/*User Scheme*/
const UserScheme = mongoose.Scheme({
name: {
type: String
},
email: {
type: String,
required: true
},
username: {
type: String,
required: true
},
password: {
type: String,
required: true
}
});
const User = module.exports = mongoose.model('User', UserScheme);
module.exports.getUserById = function(id, callback) {
User.findById(id, callback);
}
module.exports.getUserbyUsername = function(username, callback) {
const query = {username: username}
User.findOne(query, callback);
}
module.exports.addUser = function(newUser, callback) {
bcrypt.genSalt(10, (err, salt) => {
bcrypt.hash(newUser.password, salt, (err, hash) => {
if(err) throw err;
newUser.password = hash;
newUser.save(callback);
})
});
}
app.js
/*---------------Dependencies-------------*/
const express = require('express');
const path = require('path');
const bodyParser = require('body-parser');
const cors = require('cors');
const passport = require('passport');
const mongoose = require('mongoose');
const users = require('./routes/users')
const config = require('./config/database')
/*----------------------------------------*/
/*---------------Database-------------*/
mongoose.connect(config.database);
mongoose.connection.on('connected', () => {
console.log('connected to database ' +config.database)
});
mongoose.connection.on('error', (err) => {
console.log('Database Error '+err)
});
/*----------------------------------------*/
/*------------------App-------------------*/
const app = express();
// Port Number
const port = 3000;
app.listen(port, () => {
console.log('Server started on port '+port)
});
//CORS Middleware
app.use(cors());
// Body Parser Middelware
app.use(bodyParser.json())
// Set Static Folder
app.use(express.static(path.join(__dirname, 'public')));
app.use('/users', users)
/*----------------------------------------*/
/*---------------Index Route--------------*/
app.get('/', (req, res) => {
res.send('Invalid Endpoint')
});
app.get('/myaccount', (req,res) =>{
res.render('myaccount')
})
If your req.body is an object having name, email, username and password set, you should be able to create a new User simply doing:
let newUser = new User(req.body);
You get this error since you are passing to the constructor an object having 4 different attributes, while it expect to receive a request body.

Resources